view io.c @ 1483:001120e91fed nuklear_ui

Skip loading menu ROM if Nuklear UI is enabled. Allow disabling Nuklear UI in favor of old menu ROM both at compile time and in config. Fall back to ROM UI if GL is unavailable
author Michael Pavone <pavone@retrodev.com>
date Sat, 25 Nov 2017 20:43:20 -0800
parents da1dce39e846
children f29bd94ffa76
line wrap: on
line source

/*
 Copyright 2013 Michael Pavone
 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.
*/
#ifndef _WIN32
#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>
#endif
#include <string.h>
#include <stdlib.h>

#include "serialize.h"
#include "io.h"
#include "blastem.h"
#include "genesis.h"
#include "sms.h"
#include "render.h"
#include "util.h"
#include "menu.h"
#include "saves.h"
#ifndef DISABLE_NUKLEAR
#include "nuklear_ui/blastem_nuklear.h"
#endif

#define CYCLE_NEVER 0xFFFFFFFF
#define MIN_POLL_INTERVAL 6840

const char * device_type_names[] = {
	"SMS gamepad",
	"3-button gamepad",
	"6-button gamepad",
	"Mega Mouse",
	"Saturn Keyboard",
	"XBAND Keyboard",
	"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_GAMEPAD3,
	BIND_GAMEPAD4,
	BIND_GAMEPAD5,
	BIND_GAMEPAD6,
	BIND_GAMEPAD7,
	BIND_GAMEPAD8,
	BIND_MOUSE1,
	BIND_MOUSE2,
	BIND_MOUSE3,
	BIND_MOUSE4,
	BIND_MOUSE5,
	BIND_MOUSE6,
	BIND_MOUSE7,
	BIND_MOUSE8
};

typedef enum {
	UI_DEBUG_MODE_INC,
	UI_DEBUG_PAL_INC,
	UI_ENTER_DEBUGGER,
	UI_SAVE_STATE,
	UI_SET_SPEED,
	UI_NEXT_SPEED,
	UI_PREV_SPEED,
	UI_RELEASE_MOUSE,
	UI_TOGGLE_KEYBOARD_CAPTURE,
	UI_TOGGLE_FULLSCREEN,
	UI_SOFT_RESET,
	UI_RELOAD,
	UI_SMS_PAUSE,
	UI_SCREENSHOT,
	UI_EXIT
} ui_action;

typedef enum {
	MOUSE_NONE,     //mouse is ignored
	MOUSE_ABSOLUTE, //really only useful for menu ROM
	MOUSE_RELATIVE, //for full screen
	MOUSE_CAPTURE   //for windowed mode
} mouse_modes;


typedef struct {
	io_port *port;
	uint8_t bind_type;
	uint8_t subtype_a;
	uint8_t subtype_b;
	uint8_t value;
} keybinding;

typedef struct {
	keybinding bindings[4];
	uint8_t    state;
} joydpad;

typedef struct {
	keybinding positive;
	keybinding negative;
	int16_t    value;
} joyaxis;

typedef struct {
	keybinding *buttons;
	joydpad    *dpads;
	joyaxis    *axes;
	uint32_t   num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device
	uint32_t   num_dpads;   //number of entries in the dpads array, not necessarily the number of dpads on the device
	uint32_t   num_axes;    //number of entries in the axes array, not necessarily the number of dpads on the device
} joystick;

typedef struct {
	io_port    *motion_port;
	keybinding buttons[MAX_MOUSE_BUTTONS];
	uint8_t    bind_type;
} mousebinding;

#define DEFAULT_JOYBUTTON_ALLOC 12

static sega_io *current_io;
static keybinding *bindings[0x10000];
static joystick joysticks[MAX_JOYSTICKS];
static mousebinding mice[MAX_MICE];
static io_port *keyboard_port;
const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};

static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
{
	binding->bind_type = bind_type;
	binding->subtype_a = subtype_a;
	binding->subtype_b = subtype_b;
	binding->value = value;
}

void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
{
	int bucket = keycode >> 15 & 0xFFFF;
	if (!bindings[bucket]) {
		bindings[bucket] = malloc(sizeof(keybinding) * 0x8000);
		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
	}
	int idx = keycode & 0x7FFF;
	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value);
}

void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
{
	if (joystick >= MAX_JOYSTICKS) {
		return;
	}
	if (!joysticks[joystick].buttons) {
		joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1;
		joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding));
	} else if (joysticks[joystick].num_buttons <= button) {
		uint32_t old_capacity = joysticks[joystick].num_buttons;
		joysticks[joystick].num_buttons *= 2;
		joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons);
		memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity);
	}
	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value);
}

void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
{
	if (joystick >= MAX_JOYSTICKS) {
		return;
	}
	if (!joysticks[joystick].dpads) {
		//multiple D-pads/hats are not common, so don't allocate any extra space
		joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad));
		joysticks[joystick].num_dpads = dpad+1;
	} else if (joysticks[joystick].num_dpads <= dpad) {
		uint32_t old_capacity = joysticks[joystick].num_dpads;
		joysticks[joystick].num_dpads *= 2;
		joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads);
		memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad));
	}
	for (int i = 0; i < 4; i ++) {
		if (dpadbits[i] & direction) {
			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value);
			break;
		}
	}
}

void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
{
	if (joystick >= MAX_JOYSTICKS) {
		return;
	}
	if (!joysticks[joystick].axes) {
		//typical gamepad has 4 axes
		joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4;
		joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis));
	} else if (joysticks[joystick].num_axes <= axis) {
		uint32_t old_capacity = joysticks[joystick].num_axes;
		joysticks[joystick].num_axes *= 2;
		joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
		memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
	}
	if (positive) {
		do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b, value);
	} else {
		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b, value);
	}
}

void reset_joystick_bindings(int joystick)
{
	if (joystick >= MAX_JOYSTICKS) {
		return;
	}
	if (joysticks[joystick].buttons) {
		for (int i = 0; i < joysticks[joystick].num_buttons; i++)
		{
			joysticks[joystick].buttons[i].bind_type = BIND_NONE;
		}
	}
	if (joysticks[joystick].dpads) {
		for (int i = 0; i < joysticks[joystick].num_dpads; i++)
		{
			for (int dir = 0; dir < 4; dir++)
			{
				joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE;
			}
		}
	}
	if (joysticks[joystick].axes) {
		for (int i = 0; i < joysticks[joystick].num_axes; i++)
		{
			joysticks[joystick].axes[i].positive.bind_type = BIND_NONE;
			joysticks[joystick].axes[i].negative.bind_type = BIND_NONE;
		}
	}
}

#define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE)  (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE)

#define DPAD_UP      GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x01)
#define BUTTON_Z     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x01)
#define DPAD_DOWN    GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x02)
#define BUTTON_Y     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x02)
#define DPAD_LEFT    GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x04)
#define BUTTON_X     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x04)
#define DPAD_RIGHT   GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x08)
#define BUTTON_MODE  GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x08)
#define BUTTON_A     GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x10)
#define BUTTON_B     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x10)
#define BUTTON_START GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x20)
#define BUTTON_C     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x20)

#define PSEUDO_BUTTON_MOTION 0xFFFF
#define MOUSE_LEFT           1
#define MOUSE_RIGHT          2
#define MOUSE_MIDDLE         4
#define MOUSE_START          8

void bind_gamepad(int keycode, int gamepadnum, int button)
{

	if (gamepadnum < 1 || gamepadnum > 8) {
		return;
	}
	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
	bind_key(keycode, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
}

void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbutton)
{
	if (gamepadnum < 1 || gamepadnum > 8) {
		return;
	}
	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
	bind_button(joystick, joybutton, bind_type, padbutton >> 12, padbutton >> 8 & 0xF, padbutton & 0xFF);
}

void bind_dpad_gamepad(int joystick, int dpad, uint8_t direction, int gamepadnum, int button)
{
	if (gamepadnum < 1 || gamepadnum > 8) {
		return;
	}
	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
	bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
}

void bind_axis_gamepad(int joystick, int axis, uint8_t positive, int gamepadnum, int button)
{
	if (gamepadnum < 1 || gamepadnum > 8) {
		return;
	}
	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
	bind_axis(joystick, axis, positive, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
}

void bind_ui(int keycode, ui_action action, uint8_t param)
{
	bind_key(keycode, BIND_UI, action, 0, param);
}

void bind_button_ui(int joystick, int joybutton, ui_action action, uint8_t param)
{
	bind_button(joystick, joybutton, BIND_UI, action, 0, param);
}

void bind_dpad_ui(int joystick, int dpad, uint8_t direction, ui_action action, uint8_t param)
{
	bind_dpad(joystick, dpad, direction, BIND_UI, action, 0, param);
}

void bind_axis_ui(int joystick, int axis, uint8_t positive, ui_action action, uint8_t param)
{
	bind_axis(joystick, axis, positive, BIND_UI, action, 0, param);
}

void handle_binding_down(keybinding * binding)
{
	if (binding->bind_type >= BIND_GAMEPAD1 && binding->bind_type <= BIND_GAMEPAD8)
	{
		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
			binding->port->input[binding->subtype_a] |= binding->value;
		}
		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
			binding->port->input[binding->subtype_b] |= binding->value;
		}
	}
	else if (binding->bind_type >= BIND_MOUSE1 && binding->bind_type <= BIND_MOUSE8)
	{
		if (binding->port) {
			binding->port->input[0] |= binding->value;
		}
	}
}

void store_key_event(uint16_t code)
{
	if (keyboard_port && keyboard_port->device.keyboard.write_pos != keyboard_port->device.keyboard.read_pos) {
		//there's room in the buffer, record this event
		keyboard_port->device.keyboard.events[keyboard_port->device.keyboard.write_pos] = code;
		if (keyboard_port->device.keyboard.read_pos == 0xFF) {
			//ring buffer was empty, update read_pos to indicate there is now data
			keyboard_port->device.keyboard.read_pos = keyboard_port->device.keyboard.write_pos;
		}
		keyboard_port->device.keyboard.write_pos = (keyboard_port->device.keyboard.write_pos + 1) & 7;
	}
}

void handle_keydown(int keycode, uint8_t scancode)
{
	int bucket = keycode >> 15 & 0xFFFF;
	int idx = keycode & 0x7FFF;
	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
		handle_binding_down(binding);
	} else if (current_io->keyboard_captured) {
		store_key_event(scancode);
	}
}

void handle_joydown(int joystick, int button)
{
	if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
		return;
	}
	keybinding * binding = joysticks[joystick].buttons + button;
	handle_binding_down(binding);
}

void handle_mousedown(int mouse, int button)
{
	if (!current_io) {
		return;
	}
	if (current_io->mouse_mode == MOUSE_CAPTURE && !current_io->mouse_captured) {
		current_io->mouse_captured = 1;
		render_relative_mouse(1);
		return;
	}
	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
		return;
	}
	keybinding * binding = mice[mouse].buttons + button - 1;
	handle_binding_down(binding);
}

uint8_t ui_debug_mode = 0;
uint8_t ui_debug_pal = 0;

int current_speed = 0;
int num_speeds = 1;
uint32_t * speeds = NULL;

uint8_t is_keyboard(io_port *port)
{
	return port->device_type == IO_SATURN_KEYBOARD || port->device_type == IO_XBAND_KEYBOARD;
}

uint8_t keyboard_connected(sega_io *io)
{
	return is_keyboard(io->ports) || is_keyboard(io->ports+1) || is_keyboard(io->ports+2);
}

#ifdef _WIN32
#define localtime_r(a,b) localtime(a)
#endif

void handle_binding_up(keybinding * binding)
{
	switch(binding->bind_type)
	{
	case BIND_GAMEPAD1:
	case BIND_GAMEPAD2:
	case BIND_GAMEPAD3:
	case BIND_GAMEPAD4:
	case BIND_GAMEPAD5:
	case BIND_GAMEPAD6:
	case BIND_GAMEPAD7:
	case BIND_GAMEPAD8:
		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
			binding->port->input[binding->subtype_a] &= ~binding->value;
		}
		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
			binding->port->input[binding->subtype_b] &= ~binding->value;
		}
		break;
	case BIND_MOUSE1:
	case BIND_MOUSE2:
	case BIND_MOUSE3:
	case BIND_MOUSE4:
	case BIND_MOUSE5:
	case BIND_MOUSE6:
	case BIND_MOUSE7:
	case BIND_MOUSE8:
		if (binding->port) {
			binding->port->input[0] &= ~binding->value;
		}
		break;
	case BIND_UI:
		switch (binding->subtype_a)
		{
		case UI_DEBUG_MODE_INC:
			current_system->inc_debug_mode(current_system);
			break;
		case UI_DEBUG_PAL_INC:
			current_system->inc_debug_pal(current_system);
			break;
		case UI_ENTER_DEBUGGER:
			current_system->enter_debugger = 1;
			break;
		case UI_SAVE_STATE:
			current_system->save_state = QUICK_SAVE_SLOT+1;
			break;
		case UI_NEXT_SPEED:
			current_speed++;
			if (current_speed >= num_speeds) {
				current_speed = 0;
			}
			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
			current_system->set_speed_percent(current_system, speeds[current_speed]);
			break;
		case UI_PREV_SPEED:
			current_speed--;
			if (current_speed < 0) {
				current_speed = num_speeds - 1;
			}
			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
			current_system->set_speed_percent(current_system, speeds[current_speed]);
			break;
		case UI_SET_SPEED:
			if (binding->value < num_speeds) {
				current_speed = binding->value;
				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
				current_system->set_speed_percent(current_system, speeds[current_speed]);
			} else {
				printf("Setting speed to %d\n", speeds[current_speed]);
				current_system->set_speed_percent(current_system, speeds[current_speed]);
			}
			break;
		case UI_RELEASE_MOUSE:
			if (current_io->mouse_captured) {
				current_io->mouse_captured = 0;
				render_relative_mouse(0);
			}
			break;
		case UI_TOGGLE_KEYBOARD_CAPTURE:
			if (keyboard_connected(current_io)) {
				current_io->keyboard_captured = !current_io->keyboard_captured;
			}
			break;
		case UI_TOGGLE_FULLSCREEN:
			render_toggle_fullscreen();
			break;
		case UI_SOFT_RESET:
			current_system->soft_reset(current_system);
			break;
		case UI_RELOAD:
			reload_media();
			break;
		case UI_SMS_PAUSE:
			if (current_system->type == SYSTEM_SMS) {
				sms_context *sms = (sms_context *)current_system;
				vdp_pbc_pause(sms->vdp);
			}
			break;
		case UI_SCREENSHOT: {
			char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
			if (!screenshot_base) {
				screenshot_base = "$HOME";
			}
			tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
			vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
			screenshot_base = replace_vars(screenshot_base, vars, 1);
			tern_free(vars);
			time_t now = time(NULL);
			struct tm local_store;
			char fname_part[256];
			char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
			if (!template) {
				template = "blastem_%c.ppm";
			}
			strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
			char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
			char *path = alloc_concat_m(3, parts);
			free(screenshot_base);
			render_save_screenshot(path);
			break;
		}
		case UI_EXIT:
#ifndef DISABLE_NUKLEAR
			if (is_nuklear_active()) {
				show_pause_menu();
			} else {
#endif
			current_system->request_exit(current_system);
			if (current_system->type == SYSTEM_GENESIS) {
				genesis_context *gen = (genesis_context *)current_system;
				if (gen->extra) {
					//TODO: More robust mechanism for detecting menu
					menu_context *menu = gen->extra;
					menu->external_game_load = 1;
				}
			}
#ifndef DISABLE_NUKLEAR
			}
#endif
			break;
		}
		break;
	}
}

void handle_keyup(int keycode, uint8_t scancode)
{
	int bucket = keycode >> 15 & 0xFFFF;
	int idx = keycode & 0x7FFF;
	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
		handle_binding_up(binding);
	} else if (current_io->keyboard_captured) {
		store_key_event(0xF000 | scancode);
	}
}

void handle_joyup(int joystick, int button)
{
	if (joystick >= MAX_JOYSTICKS  || button >= joysticks[joystick].num_buttons) {
		return;
	}
	keybinding * binding = joysticks[joystick].buttons + button;
	handle_binding_up(binding);
}

void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
{
	if (joystick >= MAX_JOYSTICKS  || dpadnum >= joysticks[joystick].num_dpads) {
		return;
	}
	joydpad * dpad = joysticks[joystick].dpads + dpadnum;
	uint8_t newdown = (value ^ dpad->state) & value;
	uint8_t newup = ((~value) ^ (~dpad->state)) & (~value);
	dpad->state = value;
	for (int i = 0; i < 4; i++) {
		if (newdown & dpadbits[i]) {
			handle_binding_down(dpad->bindings + i);
		} else if(newup & dpadbits[i]) {
			handle_binding_up(dpad->bindings + i);
		}
	}
}

#define JOY_AXIS_THRESHOLD 2000

void handle_joy_axis(int joystick, int axis, int16_t value)
{
	if (joystick >= MAX_JOYSTICKS  || axis >= joysticks[joystick].num_axes) {
		return;
	}
	joyaxis *jaxis = joysticks[joystick].axes + axis;
	int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
	int new_active = abs(value) > JOY_AXIS_THRESHOLD;
	int old_pos = jaxis->value > 0;
	int new_pos = value > 0;
	jaxis->value = value;
	if (old_active && (!new_active || old_pos != new_pos)) {
		//previously activated direction is no longer active
		handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative);
	}
	if (new_active && (!old_active || old_pos != new_pos)) {
		//previously unactivated direction is now active
		handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative);
	}
}

void handle_mouseup(int mouse, int button)
{
	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
		return;
	}
	keybinding * binding = mice[mouse].buttons + button - 1;
	handle_binding_up(binding);
}

void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
{
	if (mouse >= MAX_MICE || !mice[mouse].motion_port) {
		return;
	}
	switch(current_io->mouse_mode)
	{
	case MOUSE_NONE:
		break;
	case MOUSE_ABSOLUTE: {
		float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width());
		float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
		int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
		int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
		if (adj_x >= 0 && adj_y >= 0) {
			mice[mouse].motion_port->device.mouse.cur_x = adj_x;
			mice[mouse].motion_port->device.mouse.cur_y = adj_y;
		}
		break;
	}
	case MOUSE_RELATIVE: {
		mice[mouse].motion_port->device.mouse.cur_x += deltax;
		mice[mouse].motion_port->device.mouse.cur_y += deltay;
		break;
	}
	case MOUSE_CAPTURE: {
		if (current_io->mouse_captured) {
			mice[mouse].motion_port->device.mouse.cur_x += deltax;
			mice[mouse].motion_port->device.mouse.cur_y += deltay;
		}
		break;
	}
	}
}

int parse_binding_target(char * target, tern_node * padbuttons, tern_node *mousebuttons, int * ui_out, int * padnum_out, int * padbutton_out)
{
	const int gpadslen = strlen("gamepads.");
	const int mouselen = strlen("mouse.");
	if (!strncmp(target, "gamepads.", gpadslen)) {
		if (target[gpadslen] >= '1' && target[gpadslen] <= '8') {
			int padnum = target[gpadslen] - '0';
			int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
			if (button) {
				*padnum_out = padnum;
				*padbutton_out = button;
				return BIND_GAMEPAD1;
			} else {
				if (target[gpadslen+1]) {
					warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1);
				} else {
					warning("Gamepad mapping string '%s' has no button component\n", target);
				}
			}
		} else {
			warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
		}
	} else if(!strncmp(target, "mouse.", mouselen)) {
		if (target[mouselen] >= '1' && target[mouselen] <= '8') {
			int mousenum = target[mouselen] - '0';
			int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
			if (button) {
				*padnum_out = mousenum;
				*padbutton_out = button;
				return BIND_MOUSE1;
			} else {
				if (target[mouselen+1]) {
					warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1);
				} else {
					warning("Mouse mapping string '%s' has no button component\n", target);
				}
			}
		} else {
			warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
		}
	} else if(!strncmp(target, "ui.", strlen("ui."))) {
		*padbutton_out = 0;
		if (!strcmp(target + 3, "vdp_debug_mode")) {
			*ui_out = UI_DEBUG_MODE_INC;
		} else if(!strcmp(target + 3, "vdp_debug_pal")) {
			*ui_out = UI_DEBUG_PAL_INC;
		} else if(!strcmp(target + 3, "enter_debugger")) {
			*ui_out = UI_ENTER_DEBUGGER;
		} else if(!strcmp(target + 3, "save_state")) {
			*ui_out = UI_SAVE_STATE;
		} else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) {
			*ui_out = UI_SET_SPEED;
			*padbutton_out = atoi(target + 3 + strlen("set_speed."));
		} else if(!strcmp(target + 3, "next_speed")) {
			*ui_out = UI_NEXT_SPEED;
		} else if(!strcmp(target + 3, "prev_speed")) {
			*ui_out = UI_PREV_SPEED;
		} else if(!strcmp(target + 3, "release_mouse")) {
			*ui_out = UI_RELEASE_MOUSE;
		} else if(!strcmp(target + 3, "toggle_keyboard_captured")) {
			*ui_out = UI_TOGGLE_KEYBOARD_CAPTURE;
		} else if (!strcmp(target + 3, "toggle_fullscreen")) {
			*ui_out = UI_TOGGLE_FULLSCREEN;
		} else if (!strcmp(target + 3, "soft_reset")) {
			*ui_out = UI_SOFT_RESET;
		} else if (!strcmp(target + 3, "reload")) {
			*ui_out = UI_RELOAD;
		} else if (!strcmp(target + 3, "sms_pause")) {
			*ui_out = UI_SMS_PAUSE;
		} else if (!strcmp(target + 3, "screenshot")) {
			*ui_out = UI_SCREENSHOT;
		} else if(!strcmp(target + 3, "exit")) {
			*ui_out = UI_EXIT;
		} else {
			warning("Unreconized UI binding type %s\n", target);
			return 0;
		}
		return BIND_UI;
	} else {
		warning("Unrecognized binding type %s\n", target);
	}
	return 0;
}

void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix)
{
	char * curstr = NULL;
	int len;
	if (!cur) {
		return;
	}
	char onec[2];
	if (prefix) {
		len = strlen(prefix);
		curstr = malloc(len + 2);
		memcpy(curstr, prefix, len);
	} else {
		curstr = onec;
		len = 0;
	}
	curstr[len] = cur->el;
	curstr[len+1] = 0;
	if (cur->el) {
		process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr);
	} else {
		int keycode = tern_find_int(special, curstr, 0);
		if (!keycode) {
			keycode = curstr[0];
			if (curstr[1] != 0) {
				warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]);
			}
		}
		char * target = cur->straight.value.ptrval;
		int ui_func, padnum, button;
		int bindtype = parse_binding_target(target, padbuttons, mousebuttons, &ui_func, &padnum, &button);
		if (bindtype == BIND_GAMEPAD1) {
			bind_gamepad(keycode, padnum, button);
		} else if(bindtype == BIND_UI) {
			bind_ui(keycode, ui_func, button);
		}
	}
	process_keys(cur->left, special, padbuttons, mousebuttons, prefix);
	process_keys(cur->right, special, padbuttons, mousebuttons, prefix);
	if (curstr && len) {
		free(curstr);
	}
}

void process_speeds(tern_node * cur, char * prefix)
{
	char * curstr = NULL;
	int len;
	if (!cur) {
		return;
	}
	char onec[2];
	if (prefix) {
		len = strlen(prefix);
		curstr = malloc(len + 2);
		memcpy(curstr, prefix, len);
	} else {
		curstr = onec;
		len = 0;
	}
	curstr[len] = cur->el;
	curstr[len+1] = 0;
	if (cur->el) {
		process_speeds(cur->straight.next, curstr);
	} else {
		char *end;
		long speed_index = strtol(curstr, &end, 10);
		if (speed_index < 0 || end == curstr || *end) {
			warning("%s is not a valid speed index", curstr);
		} else {
			if (speed_index >= num_speeds) {
				speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1));
				for(; num_speeds < speed_index + 1; num_speeds++) {
					speeds[num_speeds] = 0;
				}
			}
			speeds[speed_index] = atoi(cur->straight.value.ptrval);
			if (speeds[speed_index] < 1) {
				warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index);
				speeds[speed_index] = 100;
			}
		}
	}
	process_speeds(cur->left, prefix);
	process_speeds(cur->right, prefix);
	if (curstr && len) {
		free(curstr);
	}
}

void process_device(char * device_type, io_port * port)
{
	port->device_type = IO_NONE;
	if (!device_type)
	{
		return;
	}

	const int gamepad_len = strlen("gamepad");
	const int mouse_len = strlen("mouse");
	if (!strncmp(device_type, "gamepad", gamepad_len))
	{
		if (
			(device_type[gamepad_len] != '3' && device_type[gamepad_len] != '6' && device_type[gamepad_len] != '2')
			|| device_type[gamepad_len+1] != '.' || device_type[gamepad_len+2] < '1'
			|| device_type[gamepad_len+2] > '8' || device_type[gamepad_len+3] != 0
		) {
			warning("%s is not a valid gamepad type\n", device_type);
		} else if (device_type[gamepad_len] == '3') {
			port->device_type = IO_GAMEPAD3;
		} else if (device_type[gamepad_len] == '2') {
			port->device_type = IO_GAMEPAD2;
		} else {
			port->device_type = IO_GAMEPAD6;
		}
		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '1';
	} else if(!strncmp(device_type, "mouse", mouse_len)) {
		port->device_type = IO_MOUSE;
		port->device.mouse.mouse_num = device_type[mouse_len+1] - '1';
		port->device.mouse.last_read_x = 0;
		port->device.mouse.last_read_y = 0;
		port->device.mouse.cur_x = 0;
		port->device.mouse.cur_y = 0;
		port->device.mouse.latched_x = 0;
		port->device.mouse.latched_y = 0;
		port->device.mouse.ready_cycle = CYCLE_NEVER;
		port->device.mouse.tr_counter = 0;
	} else if(!strcmp(device_type, "saturn keyboard")) {
		port->device_type = IO_SATURN_KEYBOARD;
		port->device.keyboard.read_pos = 0xFF;
		port->device.keyboard.write_pos = 0;
	} else if(!strcmp(device_type, "xband keyboard")) {
		port->device_type = IO_XBAND_KEYBOARD;
		port->device.keyboard.read_pos = 0xFF;
		port->device.keyboard.write_pos = 0;
	} 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, rom_info *rom, sega_io *io)
{
	current_io = io;
	io_port * ports = current_io->ports;
	tern_node *io_nodes = tern_find_path(config, "io\0devices\0", TVAL_NODE).ptrval;
	char * io_1 = rom->port1_override ? rom->port1_override : io_nodes ? tern_find_ptr(io_nodes, "1") : NULL;
	char * io_2 = rom->port2_override ? rom->port2_override : io_nodes ? tern_find_ptr(io_nodes, "2") : NULL;
	char * io_ext = rom->ext_override ? rom->ext_override : io_nodes ? tern_find_ptr(io_nodes, "ext") : NULL;

	process_device(io_1, ports);
	process_device(io_2, ports+1);
	process_device(io_ext, ports+2);

	if (ports[0].device_type == IO_MOUSE || ports[1].device_type == IO_MOUSE || ports[2].device_type == IO_MOUSE) {
		if (render_fullscreen()) {
				current_io->mouse_mode = MOUSE_RELATIVE;
				render_relative_mouse(1);
		} else {
			if (rom->mouse_mode && !strcmp(rom->mouse_mode, "absolute")) {
				current_io->mouse_mode = MOUSE_ABSOLUTE;
			} else {
				current_io->mouse_mode = MOUSE_CAPTURE;
			}
		}
	} else {
		current_io->mouse_mode = MOUSE_NONE;
	}

	for (int i = 0; i < 3; i++)
	{
#ifndef _WIN32
		if (ports[i].device_type == IO_SEGA_PARALLEL)
		{
			char *pipe_name = tern_find_path(config, "io\0parallel_pipe\0", TVAL_PTR).ptrval;
			if (!pipe_name)
			{
				warning("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)
					{
						warning("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)
						{
							warning("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_path(config, "io\0socket\0", TVAL_PTR).ptrval;
			if (!sock_name)
			{
				warning("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))
				{
					warning("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))
				{
					warning("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
#endif
		if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6 || ports[i].device_type == IO_GAMEPAD2) {
			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 && bindings[i].bind_type <= BIND_GAMEPAD8)
		{
			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_type == IO_GAMEPAD2)
					 && ports[j].device.pad.gamepad_num == num
				)
				{
					memset(ports[j].input, 0, sizeof(ports[j].input));
					bindings[i].port = ports + j;
					break;
				}
			}
		}
		else if (bindings[i].bind_type >= BIND_MOUSE1 && bindings[i].bind_type <= BIND_MOUSE8)
		{
			int num = bindings[i].bind_type - BIND_MOUSE1;
			for (int j = 0; j < 3; j++)
			{
				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
				{
					memset(ports[j].input, 0, sizeof(ports[j].input));
					bindings[i].port = ports + j;
					break;
				}
			}
		}
	}
}

typedef struct {
	tern_node *padbuttons;
	tern_node *mousebuttons;
	int       mouseidx;
} pmb_state;

void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data)
{
	pmb_state *state = data;
	int buttonnum = atoi(buttonstr);
	if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) {
		warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr);
		return;
	}
	if (valtype != TVAL_PTR) {
		warning("Mouse button %s is not a scalar value!\n", buttonstr);
		return;
	}
	buttonnum--;
	int ui_func, devicenum, button;
	int bindtype = parse_binding_target(value.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &devicenum, &button);
	switch (bindtype)
	{
	case BIND_UI:
		mice[state->mouseidx].buttons[buttonnum].subtype_a = ui_func;
		break;
	case BIND_GAMEPAD1:
		mice[state->mouseidx].buttons[buttonnum].subtype_a = button >> 12;
		mice[state->mouseidx].buttons[buttonnum].subtype_b = button >> 8 & 0xF;
		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
		break;
	case BIND_MOUSE1:
		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
		break;
	}
	if (bindtype != BIND_UI) {
		bindtype += devicenum-1;
	}
	mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype;

}

void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data)
{
	tern_node **buttonmaps = data;
	if (valtype != TVAL_NODE) {
		warning("Binding for mouse %s is a scalar!\n", mousenum);
		return;
	}
	tern_node *mousedef = value.ptrval;
	tern_node *padbuttons = buttonmaps[0];
	tern_node *mousebuttons = buttonmaps[1];

	int mouseidx = atoi(mousenum);
	if (mouseidx < 0 || mouseidx >= MAX_MICE) {
		warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx);
		return;
	}
	char *motion = tern_find_ptr(mousedef, "motion");
	if (motion) {
		int ui_func,devicenum,button;
		int bindtype = parse_binding_target(motion, padbuttons, mousebuttons, &ui_func, &devicenum, &button);
		if (bindtype != BIND_UI) {
			bindtype += devicenum-1;
		}
		if (button == PSEUDO_BUTTON_MOTION) {
			mice[mouseidx].bind_type = bindtype;
		} else {
			warning("Mouse motion can't be bound to target %s\n", motion);
		}
	}
	tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval;
	if (buttons) {
		pmb_state state = {padbuttons, mousebuttons, mouseidx};
		tern_foreach(buttons, process_mouse_button, &state);
	}
}

typedef struct {
	int       padnum;
	tern_node *padbuttons;
	tern_node *mousebuttons;
} pad_button_state;


static long map_warning_pad = -1;
void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
{
	pad_button_state *state = data;
	int hostpadnum = state->padnum;
	int ui_func, padnum, button;
	if (valtype != TVAL_PTR) {
		warning("Pad button %s has a non-scalar value\n", key);
		return;
	}
	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
	char *end;
	long hostbutton = strtol(key, &end, 10);
	if (*end) {
		//key is not a valid base 10 integer
		hostbutton = render_translate_input_name(hostpadnum, key, 0);
		if (hostbutton < 0) {
			if (hostbutton == RENDER_INVALID_NAME) {
				warning("%s is not a valid gamepad input name\n", key);
			} else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
				map_warning_pad = hostpadnum;
			}
			return;
		}
		if (hostbutton & RENDER_DPAD_BIT) {
			if (bindtype == BIND_GAMEPAD1) {
				bind_dpad_gamepad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), padnum, button);
			} else {
				bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button);
			}
			return;
		} else if (hostbutton & RENDER_AXIS_BIT) {
			if (bindtype == BIND_GAMEPAD1) {
				bind_axis_gamepad(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
			} else {
				bind_axis_ui(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
			}
			return;
		}
	}
	if (bindtype == BIND_GAMEPAD1) {
		bind_button_gamepad(hostpadnum, hostbutton, padnum, button);
	} else if (bindtype == BIND_UI) {
		bind_button_ui(hostpadnum, hostbutton, ui_func, button);
	}
}

void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
{
	key = strdup(key);
	pad_button_state *state = data;
	int hostpadnum = state->padnum;
	int ui_func, padnum, button;
	if (valtype != TVAL_PTR) {
		warning("Mapping for axis %s has a non-scalar value", key);
		return;
	}
	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
	char *modifier = strchr(key, '.');
	int positive = 1;
	if (modifier) {
		*modifier = 0;
		modifier++;
		if (!strcmp("negative", modifier)) {
			positive = 0;
		} else if(strcmp("positive", modifier)) {
			warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum);
		}
	}
	char *end;
	long axis = strtol(key, &end, 10);
	if (*end) {
		//key is not a valid base 10 integer
		axis = render_translate_input_name(hostpadnum, key, 1);
		if (axis < 0) {
			if (axis == RENDER_INVALID_NAME) {
				warning("%s is not a valid gamepad input name\n", key);
			} else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
				map_warning_pad = hostpadnum;
			}
			goto done;
		}
		if (axis & RENDER_DPAD_BIT) {
			if (bindtype == BIND_GAMEPAD1) {
				bind_dpad_gamepad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), padnum, button);
			} else {
				bind_dpad_ui(hostpadnum, render_dpad_part(axis), render_direction_part(axis), ui_func, button);
			}
			goto done;
		} else if (axis & RENDER_AXIS_BIT) {
			axis = render_axis_part(axis);
		} else {
			if (bindtype == BIND_GAMEPAD1) {
				bind_button_gamepad(hostpadnum, axis, padnum, button);
			} else if (bindtype == BIND_UI) {
				bind_button_ui(hostpadnum, axis, ui_func, button);
			}
			goto done;
		}
	}
	if (bindtype == BIND_GAMEPAD1) {
		bind_axis_gamepad(hostpadnum, axis, positive, padnum, button);
	} else {
		bind_axis_ui(hostpadnum, axis, positive, ui_func, button);
	}
done:
	free(key);
	return;
}

static tern_node *get_pad_buttons()
{
	static tern_node *padbuttons;
	if (!padbuttons) {
		padbuttons = tern_insert_int(NULL, ".up", DPAD_UP);
		padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN);
		padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT);
		padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT);
		padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A);
		padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B);
		padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C);
		padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X);
		padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y);
		padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z);
		padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
		padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
	}
	return padbuttons;
}

static tern_node *get_mouse_buttons()
{
	static tern_node *mousebuttons;
	if (!mousebuttons) {
		mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT);
		mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE);
		mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT);
		mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
		mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
	}
	return mousebuttons;
}

void handle_joy_added(int joystick)
{
	if (joystick > MAX_JOYSTICKS) {
		return;
	}
	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
	if (pads) {
		char numstr[11];
		sprintf(numstr, "%d", joystick);
		tern_node * pad = tern_find_node(pads, numstr);
		if (pad) {
			tern_node * dpad_node = tern_find_node(pad, "dpads");
			if (dpad_node) {
				for (int dpad = 0; dpad < 10; dpad++)
				{
					numstr[0] = dpad + '0';
					numstr[1] = 0;
					tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
					char * dirs[] = {"up", "down", "left", "right"};
					int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
					for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
						char * target = tern_find_ptr(pad_dpad, dirs[dir]);
						if (target) {
							int ui_func, padnum, button;
							int bindtype = parse_binding_target(target, get_pad_buttons(), get_mouse_buttons(), &ui_func, &padnum, &button);
							if (bindtype == BIND_GAMEPAD1) {
								bind_dpad_gamepad(joystick, dpad, dirnums[dir], padnum, button);
							} else if (bindtype == BIND_UI) {
								bind_dpad_ui(joystick, dpad, dirnums[dir], ui_func, button);
							}
						}
					}
				}
			}
			tern_node *button_node = tern_find_node(pad, "buttons");
			if (button_node) {
				pad_button_state state = {
					.padnum = joystick,
					.padbuttons = get_pad_buttons(),
					.mousebuttons = get_mouse_buttons()
				};
				tern_foreach(button_node, process_pad_button, &state);
			}
			tern_node *axes_node = tern_find_node(pad, "axes");
			if (axes_node) {
				pad_button_state state = {
					.padnum = joystick,
					.padbuttons = get_pad_buttons(),
					.mousebuttons = get_mouse_buttons()
				};
				tern_foreach(axes_node, process_pad_axis, &state);
			}
			if (current_io) {
				if (joysticks[joystick].buttons) {
					map_bindings(current_io->ports, joysticks[joystick].buttons, joysticks[joystick].num_buttons);
				}
				if (joysticks[joystick].dpads)
				{
					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++)
					{
						map_bindings(current_io->ports, joysticks[joystick].dpads[i].bindings, 4);
					}
				}
				if (joysticks[joystick].axes) {
					for (uint32_t i = 0; i < joysticks[joystick].num_axes; i++)
					{
						map_bindings(current_io->ports, &joysticks[joystick].axes[i].positive, 1);
						map_bindings(current_io->ports, &joysticks[joystick].axes[i].negative, 1);
					}
				}
			}
		}
	}
	
}

void set_keybindings(sega_io *io)
{
	static uint8_t already_done;
	if (already_done) {
		map_all_bindings(io);
		return;
	}
	already_done = 1;
	io_port *ports = io->ports;
	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
	special = tern_insert_int(special, "left", RENDERKEY_LEFT);
	special = tern_insert_int(special, "right", RENDERKEY_RIGHT);
	special = tern_insert_int(special, "enter", '\r');
	special = tern_insert_int(special, "space", ' ');
	special = tern_insert_int(special, "tab", '\t');
	special = tern_insert_int(special, "backspace", '\b');
	special = tern_insert_int(special, "esc", RENDERKEY_ESC);
	special = tern_insert_int(special, "delete", RENDERKEY_DEL);
	special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT);
	special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT);
	special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL);
	special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL);
	special = tern_insert_int(special, "lalt", RENDERKEY_LALT);
	special = tern_insert_int(special, "ralt", RENDERKEY_RALT);
	special = tern_insert_int(special, "home", RENDERKEY_HOME);
	special = tern_insert_int(special, "end", RENDERKEY_END);
	special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP);
	special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN);
	special = tern_insert_int(special, "f1", RENDERKEY_F1);
	special = tern_insert_int(special, "f2", RENDERKEY_F2);
	special = tern_insert_int(special, "f3", RENDERKEY_F3);
	special = tern_insert_int(special, "f4", RENDERKEY_F4);
	special = tern_insert_int(special, "f5", RENDERKEY_F5);
	special = tern_insert_int(special, "f6", RENDERKEY_F6);
	special = tern_insert_int(special, "f7", RENDERKEY_F7);
	special = tern_insert_int(special, "f8", RENDERKEY_F8);
	special = tern_insert_int(special, "f9", RENDERKEY_F9);
	special = tern_insert_int(special, "f10", RENDERKEY_F10);
	special = tern_insert_int(special, "f11", RENDERKEY_F11);
	special = tern_insert_int(special, "f12", RENDERKEY_F12);
	special = tern_insert_int(special, "select", RENDERKEY_SELECT);
	special = tern_insert_int(special, "play", RENDERKEY_PLAY);
	special = tern_insert_int(special, "search", RENDERKEY_SEARCH);
	special = tern_insert_int(special, "back", RENDERKEY_BACK);

	tern_node *padbuttons = get_pad_buttons();

	tern_node *mousebuttons = get_mouse_buttons();
	
	tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
	process_keys(keys, special, padbuttons, mousebuttons, NULL);
	char numstr[] = "00";
	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
	if (pads) {
		for (int i = 0; i < MAX_JOYSTICKS; i++)
		{

			if (i < 10) {
				numstr[0] = i + '0';
				numstr[1] = 0;
			} else {
				numstr[0] = i/10 + '0';
				numstr[1] = i%10 + '0';
			}
			
		}
	}
	memset(mice, 0, sizeof(mice));
	tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
	if (mice) {
		tern_node *buttonmaps[2] = {padbuttons, mousebuttons};
		tern_foreach(mice, process_mouse, buttonmaps);
	}
	tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
	speeds = malloc(sizeof(uint32_t));
	speeds[0] = 100;
	process_speeds(speed_nodes, NULL);
	for (int i = 0; i < num_speeds; i++)
	{
		if (!speeds[i]) {
			warning("Speed index %d was not set to a valid percentage!", i);
			speeds[i] = 100;
		}
	}
	map_all_bindings(io);
}

void map_all_bindings(sega_io *io)
{
	current_io = io;
	io_port *ports = io->ports;
	
	for (int bucket = 0; bucket < 0x10000; bucket++)
	{
		if (bindings[bucket])
		{
			map_bindings(ports, bindings[bucket], 0x8000);
		}
	}
	for (int stick = 0; stick < MAX_JOYSTICKS; stick++)
	{
		if (joysticks[stick].buttons) {
			map_bindings(ports, joysticks[stick].buttons, joysticks[stick].num_buttons);
		}
		if (joysticks[stick].dpads)
		{
			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++)
			{
				map_bindings(ports, joysticks[stick].dpads[i].bindings, 4);
			}
		}
		for (uint32_t i = 0; i < joysticks[stick].num_axes; i++)
		{
			map_bindings(current_io->ports, &joysticks[stick].axes[i].positive, 1);
			map_bindings(current_io->ports, &joysticks[stick].axes[i].negative, 1);
		}
	}
	for (int mouse = 0; mouse < MAX_MICE; mouse++)
	{
		if (mice[mouse].bind_type >= BIND_MOUSE1 && mice[mouse].bind_type <= BIND_MOUSE8) {
			int num = mice[mouse].bind_type - BIND_MOUSE1;
			for (int j = 0; j < 3; j++)
			{
				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
				{
					memset(ports[j].input, 0, sizeof(ports[j].input));
					mice[mouse].motion_port = ports + j;
					break;
				}
			}
		}
		map_bindings(ports, mice[mouse].buttons, MAX_MOUSE_BUTTONS);
	}
	keyboard_port = NULL;
	for (int i = 0; i < 3; i++)
	{
		if (ports[i].device_type == IO_SATURN_KEYBOARD || ports[i].device_type == IO_XBAND_KEYBOARD) {
			keyboard_port = ports + i;
			break;
		}
	}
	//not really related to the intention of this function, but the best place to do this currently
	if (speeds[0] != 100) {
		current_system->set_speed_percent(current_system, speeds[0]);
	}
}

#define TH 0x40
#define TR 0x20
#define TH_TIMEOUT 56000

void mouse_check_ready(io_port *port, uint32_t current_cycle)
{
	if (current_cycle >= port->device.mouse.ready_cycle) {
		port->device.mouse.tr_counter++;
		port->device.mouse.ready_cycle = CYCLE_NEVER;
		if (port->device.mouse.tr_counter == 3) {
			port->device.mouse.latched_x = port->device.mouse.cur_x;
			port->device.mouse.latched_y = port->device.mouse.cur_y;
			if (current_io->mouse_mode == MOUSE_ABSOLUTE) {
				//avoid overflow in absolute mode
				int deltax = port->device.mouse.latched_x - port->device.mouse.last_read_x;
				if (abs(deltax) > 255) {
					port->device.mouse.latched_x = port->device.mouse.last_read_x + (deltax > 0 ? 255 : -255);
				}
				int deltay = port->device.mouse.latched_y - port->device.mouse.last_read_y;
				if (abs(deltay) > 255) {
					port->device.mouse.latched_y = port->device.mouse.last_read_y + (deltay > 0 ? 255 : -255);
				}
			}
		}
	}
}

uint32_t last_poll_cycle;
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 (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;
		}
	} else if (port->device_type == IO_MOUSE) {
		mouse_check_ready(port, current_cycle);
		if (port->device.mouse.ready_cycle != CYCLE_NEVER) {
			port->device.mouse.ready_cycle -= deduction;
		}
	}
	for (int i = 0; i < 8; i++)
	{
		if (port->slow_rise_start[i] != CYCLE_NEVER) {
			if (port->slow_rise_start[i] >= deduction) {
				port->slow_rise_start[i] -= deduction;
			} else {
				port->slow_rise_start[i] = CYCLE_NEVER;
			}
		}
	}
	if (last_poll_cycle >= deduction) {
		last_poll_cycle -= deduction;
	} else {
		last_poll_cycle = 0;
	}
}

#ifndef _WIN32
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);
	}
}

static void service_pipe(io_port * port)
{
	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) {
		warning("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;
			}
		} else if (numRead == 0) {
			port->device.stream.data_fd = -1;
			wait_for_connection(port);
		} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
			warning("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) {
				warning("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;
				}
			}
		}
	}
}
#endif

const int mouse_delays[] = {112*7, 120*7, 96*7, 132*7, 104*7, 96*7, 112*7, 96*7};

enum {
	KB_SETUP,
	KB_READ,
	KB_WRITE
};

void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle)
{
	uint8_t changes = value ^ port->control;
	if (changes) {
		for (int i = 0; i < 8; i++)
		{
			if (!(value & 1 << i) && !(port->output & 1 << i)) {
				//port switched from output to input and the output value was 0
				//since there is a weak pull-up on input pins, this will lead
				//to a slow rise from 0 to 1 if the pin isn't being externally driven
				port->slow_rise_start[i] = current_cycle;
			} else {
				port->slow_rise_start[i] = CYCLE_NEVER;
			}
		}
		port->control = value;
	}
}

void io_data_write(io_port * port, uint8_t value, uint32_t current_cycle)
{
	uint8_t old_output = (port->control & port->output) | (~port->control & 0xFF);
	uint8_t output = (port->control & value) | (~port->control & 0xFF);
	switch (port->device_type)
	{
	case IO_GAMEPAD6:
		//check if TH has changed
		if ((old_output & TH) ^ (output & TH)) {
			if (current_cycle >= port->device.pad.timeout_cycle) {
				port->device.pad.th_counter = 0;
			}
			if ((output & TH)) {
				port->device.pad.th_counter++;
			}
			port->device.pad.timeout_cycle = current_cycle + TH_TIMEOUT;
		}
		break;
	case IO_MOUSE:
		mouse_check_ready(port, current_cycle);
		if (output & TH) {
			//request is over or mouse is being reset
			if (port->device.mouse.tr_counter) {
				//request is over
				port->device.mouse.last_read_x = port->device.mouse.latched_x;
				port->device.mouse.last_read_y = port->device.mouse.latched_y;
			}
			port->device.mouse.tr_counter = 0;
			port->device.mouse.ready_cycle = CYCLE_NEVER;
		} else {
			if ((output & TR) != (old_output & TR)) {
				int delay_index = port->device.mouse.tr_counter >= sizeof(mouse_delays) ? sizeof(mouse_delays)-1 : port->device.mouse.tr_counter;
				port->device.mouse.ready_cycle = current_cycle + mouse_delays[delay_index];
			}
		}
		break;
	case IO_SATURN_KEYBOARD:
		if (output & TH) {
			//request is over
			if (port->device.keyboard.tr_counter >= 10 && port->device.keyboard.read_pos != 0xFF) {
				//remove scan code from buffer
				port->device.keyboard.read_pos++;
				port->device.keyboard.read_pos &= 7;
				if (port->device.keyboard.read_pos == port->device.keyboard.write_pos) {
					port->device.keyboard.read_pos = 0xFF;
				}
			}
			port->device.keyboard.tr_counter = 0;
		} else {
			if ((output & TR) != (old_output & TR)) {
				port->device.keyboard.tr_counter++;
			}
		}
		break;
	case IO_XBAND_KEYBOARD:
		if (output & TH) {
			//request is over
			if (
				port->device.keyboard.mode == KB_READ && port->device.keyboard.tr_counter > 6
				&& (port->device.keyboard.tr_counter & 1)
			) {
				if (port->device.keyboard.events[port->device.keyboard.read_pos] & 0xFF00) {
					port->device.keyboard.events[port->device.keyboard.read_pos] &= 0xFF;
				} else {
					port->device.keyboard.read_pos++;
					port->device.keyboard.read_pos &= 7;
					if (port->device.keyboard.read_pos == port->device.keyboard.write_pos) {
						port->device.keyboard.read_pos = 0xFF;
					}
				}
			}
			port->device.keyboard.tr_counter = 0;
			port->device.keyboard.mode = KB_SETUP;
		} else {
			if ((output & TR) != (old_output & TR)) {
				port->device.keyboard.tr_counter++;
				if (port->device.keyboard.tr_counter == 2) {
					port->device.keyboard.mode = (output & 0xF) ? KB_READ : KB_WRITE;
				} else if (port->device.keyboard.mode == KB_WRITE) {
					switch (port->device.keyboard.tr_counter)
					{
					case 3:
						//host writes 0b0001
						break;
					case 4:
						//host writes 0b0000
						break;
					case 5:
						//host writes 0b0000
						break;
					case 6:
						port->device.keyboard.cmd = output << 4;
						break;
					case 7:
						port->device.keyboard.cmd |= output & 0xF;
						//TODO: actually do something with the command
						break;
					}
				} else if (
					port->device.keyboard.mode == KB_READ && port->device.keyboard.tr_counter > 7
					&& !(port->device.keyboard.tr_counter & 1)
				) {
					
					if (port->device.keyboard.events[port->device.keyboard.read_pos] & 0xFF00) {
						port->device.keyboard.events[port->device.keyboard.read_pos] &= 0xFF;
					} else {
						port->device.keyboard.read_pos++;
						port->device.keyboard.read_pos &= 7;
						if (port->device.keyboard.read_pos == port->device.keyboard.write_pos) {
							port->device.keyboard.read_pos = 0xFF;
						}
					}
				}
			}
		}
		break;
#ifndef _WIN32
	case IO_GENERIC:
		wait_for_connection(port);
		port->input[IO_STATE] = IO_WRITE_PENDING;
		service_socket(port);
		break;
#endif
	}
	port->output = value;

}

uint8_t get_scancode_bytes(io_port *port)
{
	if (port->device.keyboard.read_pos == 0xFF) {
		return 0;
	}
	uint8_t bytes = 0, read_pos = port->device.keyboard.read_pos;
	do {
		bytes += port->device.keyboard.events[read_pos] & 0xFF00 ? 2 : 1;
		read_pos++;
		read_pos &= 7;
	} while (read_pos != port->device.keyboard.write_pos);
	
	return bytes;
}

#define SLOW_RISE_DEVICE (30*7)
#define SLOW_RISE_INPUT (12*7)

static uint8_t get_output_value(io_port *port, uint32_t current_cycle, uint32_t slow_rise_delay)
{
	uint8_t output = (port->control | 0x80) & port->output;
	for (int i = 0; i < 8; i++)
	{
		if (!(port->control & 1 << i)) {
			if (port->slow_rise_start[i] != CYCLE_NEVER) {
				if (current_cycle - port->slow_rise_start[i] >= slow_rise_delay) {
					output |= 1 << i;
				}
			} else {
				output |= 1 << i;
			}
		}
	}
	return output;
}

uint8_t io_data_read(io_port * port, uint32_t current_cycle)
{
	uint8_t output = get_output_value(port, current_cycle, SLOW_RISE_DEVICE);
	uint8_t control = port->control | 0x80;
	uint8_t th = output & 0x40;
	uint8_t input;
	uint8_t device_driven;
	if (current_cycle - last_poll_cycle > MIN_POLL_INTERVAL) {
		process_events();
		last_poll_cycle = current_cycle;
	}
	switch (port->device_type)
	{
	case IO_GAMEPAD2:
		input = ~port->input[GAMEPAD_TH1];
		device_driven = 0x3F;
		break;
	case IO_GAMEPAD3:
	{
		input = port->input[th ? GAMEPAD_TH1 : GAMEPAD_TH0];
		if (!th) {
			input |= 0xC;
		}
		//controller output is logically inverted
		input = ~input;
		device_driven = 0x3F;
		break;
	}
	case IO_GAMEPAD6:
	{
		if (current_cycle >= port->device.pad.timeout_cycle) {
			port->device.pad.th_counter = 0;
		}
		/*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 {
			if (port->device.pad.th_counter == 2) {
				input = port->input[GAMEPAD_TH0] | 0xF;
			} else if(port->device.pad.th_counter == 3) {
				input = port->input[GAMEPAD_TH0]  & 0x30;
			} else {
				input = port->input[GAMEPAD_TH0] | 0xC;
			}
		}
		//controller output is logically inverted
		input = ~input;
		device_driven = 0x3F;
		break;
	}
	case IO_MOUSE:
	{
		mouse_check_ready(port, current_cycle);
		uint8_t tr = output & TR;
		if (th) {
			if (tr) {
				input = 0x10;
			} else {
				input = 0;
			}
		} else {

			int16_t delta_x = port->device.mouse.latched_x - port->device.mouse.last_read_x;
			int16_t delta_y = port->device.mouse.last_read_y - port->device.mouse.latched_y;
			switch (port->device.mouse.tr_counter)
			{
			case 0:
				input = 0xB;
				break;
			case 1:
			case 2:
				input = 0xF;
				break;
			case 3:
				input = 0;
				if (delta_y > 255 || delta_y < -255) {
					input |= 8;
				}
				if (delta_x > 255 || delta_x < -255) {
					input |= 4;
				}
				if (delta_y < 0) {
					input |= 2;
				}
				if (delta_x < 0) {
					input |= 1;
				}
				break;
			case 4:
				input = port->input[0];
				break;
			case 5:
				input = delta_x >> 4 & 0xF;
				break;
			case 6:
				input = delta_x & 0xF;
				break;
			case 7:
				input = delta_y >> 4 & 0xF;
				break;
			case 8:
			default:
				input = delta_y & 0xF;
				break;
			}
			input |= ((port->device.mouse.tr_counter & 1) == 0) << 4;
		}
		device_driven = 0x1F;
		break;
	}
	case IO_SATURN_KEYBOARD:
	{
		if (th) {
			input = 0x11;
		} else {
			uint8_t tr = output & TR;
			uint16_t code = port->device.keyboard.read_pos == 0xFF ? 0 
				: port->device.keyboard.events[port->device.keyboard.read_pos];
			switch (port->device.keyboard.tr_counter)
			{
			case 0:
				input = 1;
				break;
			case 1:
				//Saturn peripheral ID
				input = 3;
				break;
			case 2:
				//data size
				input = 4;
				break;
			case 3:
				//d-pad
				//TODO: set these based on keyboard state
				input = 0xF;
				break;
			case 4:
				//Start ABC
				//TODO: set these based on keyboard state
				input = 0xF;
				break;
			case 5:
				//R XYZ
				//TODO: set these based on keyboard state
				input = 0xF;
				break;
			case 6:
				//L and KBID
				//TODO: set L based on keyboard state
				input = 0x8;
				break;
			case 7:
				//Capslock, Numlock, Scrolllock
				//TODO: set these based on keyboard state
				input = 0;
				break;
			case 8:
				input = 6;
				if (code & 0xFF00) {
					//break
					input |= 1;
				} else if (code) {
					input |= 8;
				}
				break;
			case 9:
				input = code >> 4 & 0xF;
				break;
			case 10:
				input = code & 0xF;
				break;
			case 11:
				input = 0;
				break;
			default:
				input = 1;
				break;
			}
			input |= ((port->device.keyboard.tr_counter & 1) == 0) << 4;
		}
		device_driven = 0x1F;
		break;
	}
	case IO_XBAND_KEYBOARD:
	{
		if (th) {
			input = 0x1C;
		} else {
			uint8_t size;
			if (port->device.keyboard.mode == KB_SETUP || port->device.keyboard.mode == KB_READ) {
				switch (port->device.keyboard.tr_counter)
				{
				case 0:
					input = 0x3;
					break;
				case 1:
					input = 0x6;
					break;
				case 2:
					//This is where thoe host indicates a read or write
					//presumably, the keyboard only outputs this if the host
					//is not already driving the data bus low
					input = 0x9;
					break;
				case 3:
					size = get_scancode_bytes(port);
					if (size) {
						++size;
					}
					if (size > 15) {
						size = 15;
					}
					input = size;
					break;
				case 4:
				case 5:
					//always send packet type 0 for now
					input = 0;
					break;
				default:
					if (port->device.keyboard.read_pos == 0xFF) {
						//we've run out of bytes
						input = 0;
					} else if (port->device.keyboard.events[port->device.keyboard.read_pos] & 0xFF00) {
						if (port->device.keyboard.tr_counter & 1) {
							input = port->device.keyboard.events[port->device.keyboard.read_pos] >> 8 & 0xF;
						} else {
							input = port->device.keyboard.events[port->device.keyboard.read_pos] >> 12;
						}
					} else {
						if (port->device.keyboard.tr_counter & 1) {
							input = port->device.keyboard.events[port->device.keyboard.read_pos] & 0xF;
						} else {
							input = port->device.keyboard.events[port->device.keyboard.read_pos] >> 4;
						}
					}
					break;
				}
			} else {
				input = 0xF;
			}
			input |= ((port->device.keyboard.tr_counter & 1) == 0) << 4;
		}
		//this is not strictly correct at all times, but good enough for now
		device_driven = 0x1F;
		break;
	}
#ifndef _WIN32
	case IO_SEGA_PARALLEL:
		if (!th)
		{
			service_pipe(port);
		}
		input = port->input[th ? IO_TH1 : IO_TH0];
		device_driven = 0x3F;
		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];
		device_driven = 0x7F;
		break;
#endif
	default:
		input = 0;
		device_driven = 0;
		break;
	}
	uint8_t value = (input & (~control) & device_driven) | (port->output & control);
	//deal with pins that are configured as inputs, but not being actively driven by the device
	uint8_t floating = (~device_driven) & (~control);
	if (floating) {
		value |= get_output_value(port, current_cycle, SLOW_RISE_INPUT) & floating;
	}
	/*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
		printf ("value: %X\n", value);
	}*/
	return value;
}

void io_serialize(io_port *port, serialize_buffer *buf)
{
	save_int8(buf, port->output);
	save_int8(buf, port->control);
	save_int8(buf, port->serial_out);
	save_int8(buf, port->serial_in);
	save_int8(buf, port->serial_ctrl);
	save_int8(buf, port->device_type);
	save_buffer32(buf, port->slow_rise_start, 8);
	switch (port->device_type)
	{
	case IO_GAMEPAD6:
		save_int32(buf, port->device.pad.timeout_cycle);
		save_int16(buf, port->device.pad.th_counter);
		break;
	case IO_MOUSE:
		save_int32(buf, port->device.mouse.ready_cycle);
		save_int16(buf, port->device.mouse.last_read_x);
		save_int16(buf, port->device.mouse.last_read_y);
		save_int16(buf, port->device.mouse.latched_x);
		save_int16(buf, port->device.mouse.latched_y);
		save_int8(buf, port->device.mouse.tr_counter);
		break;
	case IO_SATURN_KEYBOARD:
	case IO_XBAND_KEYBOARD:
		save_int8(buf, port->device.keyboard.tr_counter);
		if (port->device_type == IO_XBAND_KEYBOARD) {
			save_int8(buf, port->device.keyboard.mode);
			save_int8(buf, port->device.keyboard.cmd);
		}
		break;
	}
}

void io_deserialize(deserialize_buffer *buf, void *vport)
{
	io_port *port = vport;
	port->output = load_int8(buf);
	port->control = load_int8(buf);
	port->serial_out = load_int8(buf);
	port->serial_in = load_int8(buf);
	port->serial_ctrl = load_int8(buf);
	uint8_t device_type = load_int8(buf);
	if (device_type != port->device_type) {
		warning("Loaded save state has a different device type from the current configuration");
		return;
	}
	switch (port->device_type)
	{
	case IO_GAMEPAD6:
		port->device.pad.timeout_cycle = load_int32(buf);
		port->device.pad.th_counter = load_int16(buf);
		break;
	case IO_MOUSE:
		port->device.mouse.ready_cycle = load_int32(buf);
		port->device.mouse.last_read_x = load_int16(buf);
		port->device.mouse.last_read_y = load_int16(buf);
		port->device.mouse.latched_x = load_int16(buf);
		port->device.mouse.latched_y = load_int16(buf);
		port->device.mouse.tr_counter = load_int8(buf);
		break;
	case IO_SATURN_KEYBOARD:
	case IO_XBAND_KEYBOARD:
		port->device.keyboard.tr_counter = load_int8(buf);
		if (port->device_type == IO_XBAND_KEYBOARD) {
			port->device.keyboard.mode = load_int8(buf);
			port->device.keyboard.cmd = load_int8(buf);
		}
		break;
	}
}