Mercurial > repos > blastem
view io.c @ 1579:f66290afae65
Add some basic scaling to rest of UI
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 01 May 2018 20:19:31 -0700 |
parents | 2b132d894d76 |
children | 430dd12e4010 |
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) { if (!current_io) { return; } 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) { if (!current_io) { return; } 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; } } } void io_release_capture(sega_io *io) { if (io->mouse_mode == MOUSE_RELATIVE || (io->mouse_mode == MOUSE_CAPTURE && io->mouse_captured)) { render_relative_mouse(0); } current_io->keyboard_captured = 0; } void io_reacquire_capture(sega_io *io) { if (io->mouse_mode == MOUSE_RELATIVE || (io->mouse_mode == MOUSE_CAPTURE && io->mouse_captured)) { render_relative_mouse(1); } } 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); special = tern_insert_int(special, "np0", RENDERKEY_NP0); special = tern_insert_int(special, "np1", RENDERKEY_NP1); special = tern_insert_int(special, "np2", RENDERKEY_NP2); special = tern_insert_int(special, "np3", RENDERKEY_NP3); special = tern_insert_int(special, "np4", RENDERKEY_NP4); special = tern_insert_int(special, "np5", RENDERKEY_NP5); special = tern_insert_int(special, "np6", RENDERKEY_NP6); special = tern_insert_int(special, "np7", RENDERKEY_NP7); special = tern_insert_int(special, "np8", RENDERKEY_NP8); special = tern_insert_int(special, "np9", RENDERKEY_NP9); special = tern_insert_int(special, "np/", RENDERKEY_NP_DIV); special = tern_insert_int(special, "np*", RENDERKEY_NP_MUL); special = tern_insert_int(special, "np-", RENDERKEY_NP_MIN); special = tern_insert_int(special, "np+", RENDERKEY_NP_PLUS); special = tern_insert_int(special, "npenter", RENDERKEY_NP_ENTER); special = tern_insert_int(special, "np.", RENDERKEY_NP_STOP); 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; } }