# HG changeset patch # User Michael Pavone # Date 1663124906 25200 # Node ID 93918a6a8ab73959bd8be9e0ff951edee53d7f90 # Parent b6fdedd3b070d72ae5b7e88728f665ecd3d65e06 Initial support for Sega multi-tap diff -r b6fdedd3b070 -r 93918a6a8ab7 config.c --- a/config.c Sun Sep 11 15:04:42 2022 -0700 +++ b/config.c Tue Sep 13 20:08:26 2022 -0700 @@ -285,7 +285,7 @@ *pads = tern_insert_node(*pads, key, dupe_tree(val.ptrval)); } -#define CONFIG_VERSION 3 +#define CONFIG_VERSION 4 static tern_node *migrate_config(tern_node *config, int from_version) { tern_node *def_config = parse_bundled_config("default.cfg"); @@ -341,6 +341,16 @@ sms = tern_insert_path(sms, "io\0devices\0""2\0", (tern_val){.ptrval = strdup(io2)}, TVAL_PTR); config = tern_insert_node(config, "sms", sms); } + case 3: { + char *tap11 = tern_find_path_default(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = "gamepad6.2"}, TVAL_PTR).ptrval; + char *tap12 = tern_find_path_default(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = "gamepad6.3"}, TVAL_PTR).ptrval; + char *tap13 = tern_find_path_default(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = "gamepad6.4"}, TVAL_PTR).ptrval; + char *tap14 = tern_find_path_default(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = "gamepad6.5"}, TVAL_PTR).ptrval; + config = tern_insert_path(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = strdup(tap11)}, TVAL_PTR); + config = tern_insert_path(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = strdup(tap12)}, TVAL_PTR); + config = tern_insert_path(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = strdup(tap13)}, TVAL_PTR); + config = tern_insert_path(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = strdup(tap14)}, TVAL_PTR); + } } char buffer[16]; sprintf(buffer, "%d", CONFIG_VERSION); diff -r b6fdedd3b070 -r 93918a6a8ab7 default.cfg --- a/default.cfg Sun Sep 11 15:04:42 2022 -0700 +++ b/default.cfg Tue Sep 13 20:08:26 2022 -0700 @@ -276,6 +276,12 @@ 1 gamepad6.1 2 gamepad6.2 } + sega_multitap.1 { + 1 gamepad6.2 + 2 gamepad6.3 + 3 gamepad6.4 + 4 gamepad6.5 + } } video { diff -r b6fdedd3b070 -r 93918a6a8ab7 io.c --- a/io.c Sun Sep 11 15:04:42 2022 -0700 +++ b/io.c Tue Sep 13 20:08:26 2022 -0700 @@ -35,7 +35,7 @@ "XBAND Keyboard", "Menacer", "Justifier", - "Sega multi-tap", + "Sega Multi-tap", "EA 4-way Play cable A", "EA 4-way Play cable B", "Sega Parallel Transfer Board", @@ -97,7 +97,16 @@ } if (port->device_type == IO_HEARTBEAT_TRAINER && port->device.heartbeat_trainer.device_num == gamepad_num) { return port; - } + } + if (port->device_type == IO_SEGA_MULTI) { + for (int j = 0; j < 4; j++) + { + io_port *tap_port = port->device.multitap.ports + j; + if (tap_port->device_type < IO_MOUSE && tap_port->device.pad.gamepad_num == gamepad_num) { + return tap_port; + } + } + } } return NULL; } @@ -241,6 +250,10 @@ { return; } + io_port *old_ports = NULL; + if (port->device_type == IO_SEGA_MULTI) { + old_ports = port->device.multitap.ports; + } const int gamepad_len = strlen("gamepad"); if (startswith(device_type, "gamepad")) @@ -306,7 +319,20 @@ port->device.stream.data_fd = -1; port->device.stream.listen_fd = -1; } + } else if(startswith(device_type, "sega_multitap.")) { + if (port->device_type != IO_SEGA_MULTI) { + port->device_type = IO_SEGA_MULTI; + port->device.multitap.ports = old_ports ? old_ports : calloc(4, sizeof(io_port)); + port->device.multitap.tap_num = device_type[strlen("sega_multitap.")] - '0'; + if (!old_ports) { + port->device.multitap.tr_counter = 0; + port->device.multitap.ready_cycle = CYCLE_NEVER; + port->input[0] = 0x13; + } + old_ports = NULL; + } } + free(old_ports); } char * io_name(int i) @@ -450,6 +476,23 @@ memset(ports[i].device.heartbeat_trainer.nv_memory, 0xFF, bufsize); } ports[i].device.heartbeat_trainer.state = HBPT_NEED_INIT; + } else if (ports[i].device_type == IO_SEGA_MULTI) { + char path[] = "io\0sega_multitap.1\0"; + path[17] = '0' + ports[i].device.multitap.tap_num; + tern_node *port_defs = tern_find_path(config, path, TVAL_NODE).ptrval; + debug_message("IO port %s connected to Sega multitap %d\n", io_name(i), ports[i].device.multitap.tap_num); + for (int j = 0; j < 4; j++) + { + char port_num[] = {'1' + j, 0, 0}; + char *dev_type = tern_find_ptr(port_defs, port_num); + process_device(dev_type, ports[i].device.multitap.ports + j); + debug_message("\tTap port %d connected to device '%s'\n", j + 1, device_type_names[ports[i].device.multitap.ports[j].device_type]); + if (ports[i].control & ports[i].output & 0x40) { + io_control_write(ports[i].device.multitap.ports + j, 0x40, 0); + io_data_write(ports[i].device.multitap.ports + j, 0x40, 0); + } + } + } else { debug_message("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]); } @@ -459,7 +502,28 @@ #define TH 0x40 #define TR 0x20 +#define TL 0x10 #define TH_TIMEOUT 56000 +#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; +} void mouse_check_ready(io_port *port, uint32_t current_cycle) { @@ -485,6 +549,145 @@ } } +void multitap_check_ready(io_port *port, uint32_t current_cycle) +{ + if (current_cycle >= port->device.multitap.ready_cycle) { + if (port->device.multitap.reset_state) { + uint8_t output = get_output_value(port, current_cycle, SLOW_RISE_DEVICE); + if (output & TR) { + port->input[0] |= TL; + port->device.multitap.reset_state = 0; + } else { + port->input[0] &= TR; + } + port->device.multitap.ready_cycle = CYCLE_NEVER; + return; + } + port->device.multitap.tr_counter++; + port->device.multitap.ready_cycle = CYCLE_NEVER; + switch (port->device.multitap.tr_counter) + { + case 1: + for (int i = 0; i < 4; i++) + { + uint8_t id = io_data_read(port->device.multitap.ports + i, current_cycle); + io_data_write(port->device.multitap.ports + i, 0, current_cycle); + uint8_t value = io_data_read(port->device.multitap.ports + i, current_cycle); + uint8_t pad_data = (id & 0x3F) | (value << 2 & 0xC0); + id = (id & 0xA) | (id << 1 & 0xA); + id |= (value & 0x5) | (value >> 1 & 0x5); + id = (id & 0x9) | (id << 1 & 0x4) | (id >> 1 & 0x2); + if (id == 0xD || id == 0xC) { + port->device.multitap.data[i] = pad_data; + io_data_write(port->device.multitap.ports + i, 0x40, current_cycle); + } else if (id == 0x3) { + //set TR to output for mouse + io_control_write(port->device.multitap.ports + i, 0x60, current_cycle); + } + port->device.multitap.device_ids[i] = id; + } + port->input[0] = 0; + break; + case 2: + for (int i = 0; i < 4; i++) + { + if (port->device.multitap.device_ids[i] == 0xC || port->device.multitap.device_ids[i] == 0xD) { + io_data_write(port->device.multitap.ports + i, 0, current_cycle); + } else if (port->device.multitap.device_ids[i] == 0x3) { + //TODO: Fix delays so mouse has enough time to respond + io_data_write(port->device.multitap.ports + i, 0x20, current_cycle); + } + } + port->input[0] = 0x10; + break; + case 3: + for (int i = 0; i < 4; i++) + { + if (port->device.multitap.device_ids[i] == 0xC || port->device.multitap.device_ids[i] == 0xD) { + io_data_write(port->device.multitap.ports + i, 0x40, current_cycle); + io_data_write(port->device.multitap.ports + i, 0, current_cycle); + uint8_t value = io_data_read(port->device.multitap.ports + i, current_cycle); + if (value & 0xF) { + //3 button + port->device.multitap.device_ids[i] = 0; + } else { + port->device.multitap.device_ids[i] = 1; + } + } else if (port->device.multitap.device_ids[i] == 0xC) { + //TODO: Fix delays so mouse has enough time to respond + io_data_write(port->device.multitap.ports + i, 0, current_cycle); + port->device.multitap.device_ids[i] = 2; + } else { + port->device.multitap.device_ids[i] = 0xF; + } + } + port->input[0] = port->device.multitap.device_ids[0]; + break; + case 4: + port->input[0] = 0x10 | port->device.multitap.device_ids[1]; + break; + case 5: + port->input[0] = port->device.multitap.device_ids[2]; + break; + case 6: + port->input[0] = 0x10 | port->device.multitap.device_ids[3]; + port->device.multitap.cur_port = 0; + port->device.multitap.port_start = 7; + break; + default: { + port->input[0] = (port->input[0] & ~TL) | ((~port->input[0]) & TL); + uint8_t tr_diff = port->device.multitap.tr_counter - port->device.multitap.port_start; + for (;;) + { + if (port->device.multitap.cur_port > 3) { + return; + } + switch (port->device.multitap.device_ids[port->device.multitap.cur_port]) + { + case 0: + if (tr_diff) { + port->input[0] = (port->input[0] & 0xF0) | (port->device.multitap.data[port->device.multitap.cur_port] >> 4); + port->device.multitap.cur_port++; + port->device.multitap.port_start = port->device.multitap.tr_counter + 1; + } else { + port->input[0] = (port->input[0] & 0xF0) | (port->device.multitap.data[port->device.multitap.cur_port] & 0xF); + } + return; + case 1: + if (tr_diff == 2) { + uint8_t value = io_data_read(port->device.multitap.ports + port->device.multitap.cur_port, current_cycle); + port->input[0] = (port->input[0] & 0xF0) | (value & 0xF); + //finish 6-button cycle + io_data_write(port->device.multitap.ports + port->device.multitap.cur_port, 0, current_cycle); + port->device.multitap.cur_port++; + port->device.multitap.port_start = port->device.multitap.tr_counter + 1; + } else if (tr_diff) { + io_data_write(port->device.multitap.ports + port->device.multitap.cur_port, 0x40, current_cycle); + port->input[0] = (port->input[0] & 0xF0) | (port->device.multitap.data[port->device.multitap.cur_port] >> 4); + } else { + port->input[0] = (port->input[0] & 0xF0) | (port->device.multitap.data[port->device.multitap.cur_port] & 0xF); + } + return; + case 2: { + io_port *dst_port = port->device.multitap.ports + port->device.multitap.cur_port; + uint8_t value = io_data_read(dst_port, current_cycle); + io_data_write(dst_port, (~dst_port->output) & TR, current_cycle); + if (tr_diff == 5) { + port->device.multitap.cur_port++; + port->device.multitap.port_start = port->device.multitap.tr_counter + 1; + } + return; + } + default: + port->device.multitap.cur_port++; + port->device.multitap.port_start = port->device.multitap.tr_counter; + } + } + } + } + } +} + void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction) { /*uint8_t control = pad->control | 0x80; @@ -505,6 +708,15 @@ if (port->device.mouse.ready_cycle != CYCLE_NEVER) { port->device.mouse.ready_cycle -= deduction; } + } else if (port->device_type == IO_SEGA_MULTI) { + multitap_check_ready(port, current_cycle); + if (port->device.multitap.ready_cycle != CYCLE_NEVER) { + port->device.multitap.ready_cycle -= deduction; + } + for (int i = 0; i < 4; i++) + { + io_adjust_cycles(port->device.multitap.ports + i, current_cycle, deduction); + } } for (int i = 0; i < 8; i++) { @@ -1021,8 +1233,9 @@ 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); + uint8_t old_output = get_output_value(port, current_cycle, SLOW_RISE_DEVICE); + port->output = value; + uint8_t output = get_output_value(port, current_cycle, SLOW_RISE_DEVICE); switch (port->device_type) { case IO_GAMEPAD6: @@ -1135,6 +1348,33 @@ } } break; + case IO_SEGA_MULTI: + multitap_check_ready(port, current_cycle); + if (output & TH) { + //request is over + port->device.multitap.tr_counter = 0; + if ((output & TR) != (old_output & TR)) { + port->device.multitap.ready_cycle = current_cycle + 16 * 7; + port->device.multitap.reset_state = 1; + } else if (!port->device.multitap.reset_state) { + port->device.multitap.ready_cycle = CYCLE_NEVER; + port->input[0] = 0x13; + } + for (int i = 0; i < 4; i++) + { + io_control_write(port->device.multitap.ports + i, 0x40, current_cycle); + io_data_write(port->device.multitap.ports + i, 0x40, current_cycle); + } + } else { + if (old_output & TH) { + port->input[0] = 0x1F; + } + if ((output & TR) != (old_output & TR)) { + //TODO: measure actual delays + port->device.multitap.ready_cycle = current_cycle + 16 * 7; + } + } + break; #ifndef _WIN32 case IO_GENERIC: wait_for_connection(port); @@ -1176,27 +1416,6 @@ 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); @@ -1450,6 +1669,11 @@ device_driven = 0x1F; break; } + case IO_SEGA_MULTI: + multitap_check_ready(port, current_cycle); + device_driven = 0x1F; + input = port->input[0]; + break; #ifndef _WIN32 case IO_SEGA_PARALLEL: if (!th) diff -r b6fdedd3b070 -r 93918a6a8ab7 io.h --- a/io.h Sun Sep 11 15:04:42 2022 -0700 +++ b/io.h Tue Sep 13 20:08:26 2022 -0700 @@ -29,7 +29,8 @@ IO_HEARTBEAT_TRAINER }; -typedef struct { +typedef struct io_port io_port; +struct io_port { union { struct { uint32_t timeout_cycle; @@ -76,6 +77,17 @@ uint8_t cmd; uint8_t remaining_bytes; } heartbeat_trainer; + struct { + io_port *ports; + uint32_t ready_cycle; + uint8_t tr_counter; + uint8_t tap_num; + uint8_t device_ids[4]; + uint8_t data[4]; + uint8_t cur_port; + uint8_t port_start; + uint8_t reset_state; + } multitap; } device; uint8_t output; uint8_t control; @@ -92,7 +104,7 @@ uint8_t serial_receiving; uint8_t serial_ctrl; uint8_t device_type; -} io_port; +}; typedef struct { io_port ports[3]; diff -r b6fdedd3b070 -r 93918a6a8ab7 nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Sun Sep 11 15:04:42 2022 -0700 +++ b/nuklear_ui/blastem_nuklear.c Tue Sep 13 20:08:26 2022 -0700 @@ -2035,7 +2035,7 @@ } } - const char *formats[] = { + static const char *formats[] = { "native", "gst" }; @@ -2044,7 +2044,7 @@ if (selected_format < 0) { selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native"); } - const char *ram_inits[] = { + static const char *ram_inits[] = { "zero", "random" }; @@ -2053,28 +2053,39 @@ if (selected_init < 0) { selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero"); } - const char *io_opts_1[] = { + static const char *io_opts_1[] = { "none", "gamepad2.1", "gamepad3.1", "gamepad6.1", + "sega_multitap.1", "mouse.1", "saturn keyboard", "xband keyboard" }; - const char *io_opts_2[] = { + static const char *io_opts_2[] = { "none", "gamepad2.2", "gamepad3.2", "gamepad6.2", + "sega_multitap.1", "mouse.1", "saturn keyboard", "xband keyboard" }; + static const char *type_names[sizeof(io_opts_1)/sizeof(*io_opts_1)]; static int32_t selected_io_1 = -1; static int32_t selected_io_2 = -1; const uint32_t num_io = sizeof(io_opts_1)/sizeof(*io_opts_1); if (selected_io_1 < 0 || selected_io_2 < 0 || show_sms != old_show_sms) { + type_names[0] = device_type_names[IO_NONE]; + type_names[1] = device_type_names[IO_GAMEPAD2]; + type_names[2] = device_type_names[IO_GAMEPAD3]; + type_names[3] = device_type_names[IO_GAMEPAD6]; + type_names[4] = device_type_names[IO_SEGA_MULTI]; + type_names[5] = device_type_names[IO_MOUSE]; + type_names[6] = device_type_names[IO_SATURN_KEYBOARD]; + type_names[7] = device_type_names[IO_XBAND_KEYBOARD]; if (show_sms) { selected_io_1 = find_match(io_opts_1, num_io, "sms\0io\0devices\0""1\0", "gamepad2.1"); selected_io_2 = find_match(io_opts_2, num_io, "sms\0io\0devices\0""2\0", "gamepad2.2"); @@ -2096,8 +2107,8 @@ } else { selected_model = settings_dropdown_ex(context, "Model", model_opts, model_names, num_models, selected_model, "system\0model\0"); } - selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, device_type_names, num_io, selected_io_1, show_sms ? "sms\0io\0devices\0""1\0" : "io\0devices\0""1\0"); - selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, device_type_names, num_io, selected_io_2, show_sms ? "sms\0io\0devices\0""2\0" : "io\0devices\0""2\0"); + selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, type_names, num_io, selected_io_1, show_sms ? "sms\0io\0devices\0""1\0" : "io\0devices\0""1\0"); + selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, type_names, num_io, selected_io_2, show_sms ? "sms\0io\0devices\0""2\0" : "io\0devices\0""2\0"); selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0"); selected_sync = settings_dropdown(context, "Sync Source", sync_opts, num_sync_opts, selected_sync, "system\0sync_source\0"); if (!show_sms) {