/* Elo Touchsystems 2701 (Sega 838-14772) touchscreen controller emulator This board only supports single-touch. References: Elo 'SmartSet' protocol https://archive.org/details/manualzilla-id-5973842 Credits: emihiok */ #include #include #include #include #include #include #include "board/elo-cmd.h" #include "board/elo-frame.h" #include "divahook/elo.h" #include "divahook/diva-dll.h" #include "hook/iobuf.h" #include "hook/iohook.h" #include "hook/table.h" #include "hooklib/uart.h" #include "util/dprintf.h" #include "util/dump.h" static HRESULT elo_handle_irp(struct irp *irp); static HRESULT elo_handle_irp_locked(struct irp *irp); static HRESULT elo_req_dispatch(const union elo_packet_any *req); static HRESULT elo_req_acknowledge(void); static HRESULT elo_req_reset(const struct elo_packet_reset *req); static HRESULT elo_query_parameter(const struct elo_packet_parameter *req); static HRESULT elo_set_parameter(const struct elo_packet_parameter *req); static HRESULT elo_send_acknowledge(void); static void elo_send_touch( const uint8_t status, const uint16_t x, const uint16_t y, const uint8_t id); static void elo_set_error(uint8_t error_code); static void elo_clear_error(void); static CRITICAL_SECTION elo_lock; static struct uart elo_uart; static uint8_t elo_written_bytes[520]; static uint8_t elo_readable_bytes[520]; static uint8_t elo_last_error[4]; static bool elo_acknowledge_required; static bool elo_error_report_required; static bool elo_touch_started; /* Cursor specific API hooks */ static HCURSOR hook_SetCursor(HCURSOR hCursor); static HCURSOR (*next_SetCursor)(HCURSOR hCursor); static const struct hook_symbol cursor_syms[] = { { .name = "SetCursor", .patch = hook_SetCursor, .link = (void **) &next_SetCursor }, }; HRESULT elo_hook_init( const struct elo_config *cfg, unsigned int port_no) { assert(cfg != NULL); if (!cfg->enable) { return S_FALSE; } if (cfg->port_no != 0) { port_no = cfg->port_no; } hook_table_apply( NULL, "user32.dll", cursor_syms, _countof(cursor_syms)); InitializeCriticalSection(&elo_lock); uart_init(&elo_uart, port_no); elo_uart.baud.BaudRate = 9600; elo_uart.written.bytes = elo_written_bytes; elo_uart.written.nbytes = sizeof(elo_written_bytes); elo_uart.readable.bytes = elo_readable_bytes; elo_uart.readable.nbytes = sizeof(elo_readable_bytes); memset(elo_last_error, ELO_ERR_NONE, sizeof(elo_last_error)); elo_acknowledge_required = false; elo_error_report_required = false; elo_touch_started = false; return iohook_push_handler(elo_handle_irp); } static HCURSOR hook_SetCursor(HCURSOR hCursor) { HCURSOR fake_cursor; if (hCursor) return next_SetCursor(hCursor); fake_cursor = LoadCursorA(NULL, IDC_CROSS); return next_SetCursor(fake_cursor); } static HRESULT elo_handle_irp(struct irp *irp) { HRESULT hr; assert(irp != NULL); if (!uart_match_irp(&elo_uart, irp)) { return iohook_invoke_next(irp); } EnterCriticalSection(&elo_lock); hr = elo_handle_irp_locked(irp); LeaveCriticalSection(&elo_lock); return hr; } static HRESULT elo_handle_irp_locked(struct irp *irp) { union elo_packet_any req; struct iobuf req_iobuf; HRESULT hr; assert(diva_dll.touch_init != NULL); if (irp->op == IRP_OP_OPEN) { dprintf("Elo Touch: Starting DIVA Touch backend\n"); hr = diva_dll.touch_init(); if (FAILED(hr)) { dprintf("Elo Touch: Backend error, touchscreen disconnected: %x\n", (int) hr); return hr; } dprintf("Elo Touch: Start touch thread\n"); diva_dll.touch_start(elo_send_touch); elo_touch_started = true; } hr = uart_handle_irp(&elo_uart, irp); if (FAILED(hr) || irp->op != IRP_OP_WRITE) { return hr; } for (;;) { if (elo_acknowledge_required) { hr = elo_send_acknowledge(); if (FAILED(hr)) { dprintf("Elo Touch: Acknowledge failed: %x\n", (int) hr); } } #if 0 dprintf("Elo Touch DEBUG: TX Buffer:\n"); dump_iobuf(&elo_uart.written); #endif req_iobuf.bytes = req.bytes; req_iobuf.nbytes = sizeof(req.bytes); req_iobuf.pos = 0; hr = elo_frame_decode(&req_iobuf, &elo_uart.written); if (hr != S_OK) { if (hr == HRESULT_FROM_WIN32(ERROR_CRC)) { elo_set_error(ELO_ERR_BAD_INPUT_CHECKSUM); } if (FAILED(hr)) { dprintf("Elo Touch: Deframe error: %x\n", (int) hr); } return hr; } #if 0 dprintf("Elo Touch DEBUG: Deframe Buffer:\n"); dump_iobuf(&req_iobuf); #endif hr = elo_req_dispatch(&req); if (FAILED(hr)) { dprintf("Elo Touch: Processing error: %x\n", (int) hr); } if (SUCCEEDED(hr)) { if (!elo_touch_started) { dprintf("Elo Touch: Restart touch reports\n"); diva_dll.touch_start(elo_send_touch); elo_touch_started = true; } } } } static HRESULT elo_req_dispatch(const union elo_packet_any *req) { switch (req->hdr.cmd) { case ELO_CMD_QUERY_ACKNOWLEDGE: return elo_req_acknowledge(); case ELO_CMD_SET_RESET: return elo_req_reset(&req->reset); case ELO_CMD_QUERY_PARAMETER: return elo_query_parameter(&req->param); case ELO_CMD_SET_PARAMETER: return elo_set_parameter(&req->param); case ELO_CMD_ACKNOWLEDGE: dprintf("Elo Touch: Invalid command, ACK cannot be set\n"); elo_set_error(ELO_ERR_SET_UNAVAILABLE); return E_INVALIDARG; case 'r': dprintf("Elo Touch: Invalid command, Reset cannot be queried\n"); elo_set_error(ELO_ERR_QUERY_UNAVAILABLE); return E_INVALIDARG; default: dprintf("Elo Touch: Unhandled command %02x\n", req->hdr.cmd); elo_set_error(ELO_ERR_ILLEGAL_COMMAND); return S_OK; } } static HRESULT elo_req_acknowledge(void) { dprintf("Elo Touch: Query acknowledge\n"); elo_acknowledge_required = true; return S_OK; } static HRESULT elo_req_reset(const struct elo_packet_reset *req) { if (req->r_type == '0') { dprintf("Elo Touch: Hard reset\n"); elo_clear_error(); /* IO DLL worker thread might attempt to invoke the callback (which needs to take elo_lock, which we are currently holding) before noticing that it needs to shut down. Unlock here so that we don't deadlock in that situation. */ LeaveCriticalSection(&elo_lock); dprintf("Elo Touch: Stop touch reports\n"); diva_dll.touch_stop(); elo_touch_started = false; EnterCriticalSection(&elo_lock); /* No response or ack whatsoever as controller fully reboots. Game will wait approx. 5 seconds before sending the next command */ return S_OK; } else if (req->r_type == '1') { dprintf("Elo Touch: Soft reset\n"); } else { /* Type '2' (reset to NVRAM defaults) is only available on COACh IV controllers, which the 2701 does not have. */ dprintf("Elo Touch: Warning -- Unknown reset type %02x\n", req->r_type); } union elo_packet_any resp; memset(&resp, 0, sizeof(resp)); resp.hdr.lead = ELO_FRAME_LEAD; resp.hdr.cmd = ELO_CMD_SET_RESET; /* Response only, message is not acknowledged */ return elo_frame_encode(&elo_uart.readable, &resp, sizeof(resp)); } static HRESULT elo_query_parameter(const struct elo_packet_parameter *req) { dprintf("Elo Touch: Query parameters\n"); struct elo_packet_parameter resp; memset(&resp, 0, sizeof(resp)); resp.hdr.lead = ELO_FRAME_LEAD; resp.hdr.cmd = ELO_CMD_SET_PARAMETER; resp.io = '0'; // Serial resp.ser1 = 0b00000110; // 9600 bps, 8-N-1 resp.ser2 = 0b00000100; // Hardware handshaking enabled elo_acknowledge_required = true; return elo_frame_encode(&elo_uart.readable, &resp, sizeof(resp)); } static HRESULT elo_set_parameter(const struct elo_packet_parameter *req) { dprintf("Elo Touch: Set parameters\n"); dprintf("Elo Touch: --- Begin Parameter Receive ---\n"); bool bad_bit_flag = false; bool unimpl_flag = false; if (req->io == '0') { dprintf("Elo Touch: Param: Serial interface\n"); } else { dprintf("Elo Touch: Param: Unknown IO preset: %02x\n", req->io); unimpl_flag = true; goto end; } switch (req->ser1 & 0b111) { case 0b101: dprintf("Elo Touch: Param: Baudrate 4800bps\n"); elo_uart.baud.BaudRate = 4800; break; case 0b110: dprintf("Elo Touch: Param: Baudrate 9600bps\n"); elo_uart.baud.BaudRate = 9600; break; case 0b111: dprintf("Elo Touch: Param: Baudrate 19200bps\n"); elo_uart.baud.BaudRate = 19200; break; default: dprintf("Elo Touch: Param: Unknown Serial1 preset: %02x\n", req->ser1); unimpl_flag = true; goto end; } for (int i = 0 ; i < 8 ; i++) { bool set = (req->ser2) & (1 << i); switch (i) { case 0: if (set) dprintf("Elo Touch: Param: Checksum required\n"); break; case 1: if (set) dprintf("Elo Touch: Param: Software handshaking " "enabled\n"); break; case 2: if (set) { dprintf("Elo Touch: Param: Hardware handshaking enabled\n"); elo_uart.handflow.ControlHandShake = SERIAL_DTR_CONTROL | SERIAL_DTR_HANDSHAKE; elo_uart.handflow.FlowReplace = SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE; } break; case 3: if (set) dprintf("Elo Touch: Param: Invert hardware " "handshaking\n"); break; /* Reserved bits should not be set */ case 4: case 5: case 6: if (set) bad_bit_flag = true; break; case 7: if (set) dprintf("Elo Touch: Param: Full duplex\n"); break; default: break; } } end: dprintf("Elo Touch: --- End Parameter Receive ---\n"); elo_acknowledge_required = true; if (bad_bit_flag) { elo_set_error(ELO_ERR_BAD_INPUT_PACKET); return E_INVALIDARG; } else if (unimpl_flag) { elo_set_error(ELO_ERR_BAD_SERIAL_SETUP); return E_NOTIMPL; } return S_OK; } static HRESULT elo_send_acknowledge(void) { dprintf("Elo Touch: Acknowledge\n"); struct elo_packet_acknowledge resp; HRESULT hr; memset(&resp, 0, sizeof(resp)); resp.hdr.lead = ELO_FRAME_LEAD; resp.hdr.cmd = ELO_CMD_ACKNOWLEDGE; for (int i = 0 ; i < sizeof(resp.error_code) ; i++) { resp.error_code[i] = elo_last_error[i]; } hr = elo_frame_encode(&elo_uart.readable, &resp, sizeof(resp)); elo_acknowledge_required = false; return hr; } static void elo_send_touch( const uint8_t status, const uint16_t x, const uint16_t y, const uint8_t id) { if (!elo_touch_started || !status || !id) return; if (id > 1) return; /* Generate "IntelliTouch" packet */ struct elo_packet_touch resp; memset(&resp, 0, sizeof(resp)); resp.hdr.lead = ELO_FRAME_LEAD; resp.hdr.cmd = ELO_CMD_TOUCH; /* IntelliTouch touch events always have MSB (Z-Axis Valid) set */ switch (status) { case DIVA_IO_TOUCH_DOWN: resp.status |= (1 << 7) | 1; break; case DIVA_IO_TOUCH_STREAM: resp.status |= (1 << 7) | (1 << 1); break; case DIVA_IO_TOUCH_LIFTOFF: resp.status |= (1 << 7) | (1 << 2); break; default: /* No touch events occurred..? */ return; } /* Touch event detected, report coordinates */ if ((resp.status & 0b111) != 0) { /* Max resolution: 4095 x 4095 */ uint16_t x_restrict = x & 0x0FFF; uint16_t y_restrict = y & 0x0FFF; resp.x_low = x_restrict & UINT8_MAX; resp.x_high = x_restrict >> 8; resp.y_low = y_restrict & UINT8_MAX; resp.y_high = y_restrict >> 8; resp.z_low = UINT8_MAX; // Ignore pressure for now resp.z_high = 0; // Unused } if (elo_error_report_required) { resp.status |= 1 << 4; } #if 0 dprintf("Elo Touch DEBUG: STATUS:%02x, X LOW:%02x, X HIGH:%02x, " "Y LOW:%02x, Y HIGH:%02x, Z LOW:%02x, Z HIGH:%02x\n", resp.status, resp.x_low, resp.x_high, resp.y_low, resp.y_high, resp.z_low, resp.z_high); #endif EnterCriticalSection(&elo_lock); elo_frame_encode(&elo_uart.readable, &resp, sizeof(resp)); LeaveCriticalSection(&elo_lock); elo_error_report_required = false; } static void elo_set_error(uint8_t error_code) { bool empty_slot_found = false; for (int i = 0 ; i < 4 && empty_slot_found == false ; i++) { if (elo_last_error[i] == ELO_ERR_NONE) { elo_last_error[i] = error_code; empty_slot_found = true; } } /* All error slots full, overwrite first slot */ if (!empty_slot_found) { elo_last_error[0] = error_code; } elo_error_report_required = true; } static void elo_clear_error(void) { memset(&elo_last_error, ELO_ERR_NONE, sizeof(elo_last_error)); }