#include #include "../comdevice.h" #include "../common.h" #include "../key_config.h" #include "jvs.h" #include "mx.h" BOOL JVS_SENSE = false; BOOL coin_solenoid = false; BOOL test_btn = false; #define PLAYER_COUNT 2 #define COIN_COUNTERS 2 #define SWITCH_BYTES 2 // const char JVS_837_14572_ID[] = "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-14572 ;Ver1.00;98/10"; const char JVS_837_14572_ID[] = "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; typedef struct _jvs_board { int num; unsigned short coin_counts[COIN_COUNTERS]; unsigned char (*handler)(struct _jvs_board* board, unsigned char* inData, short inCount, unsigned char* outData, unsigned char* outCount); const char* id; unsigned char last_sysbuttons; unsigned short last_buttons[PLAYER_COUNT]; } jvs_board_t; #define JVS_FLAG_NONE 0 #define JVS_FLAG_NC 1 #define JVS_FLAG_INVERT 2 jvs_board_t jvs_boards[JVS_IO_MAX] = { 0 }; short jvs_unpad(unsigned char* paddedData, short length, unsigned char* unpaddedData) { short index = 0; bool escape = false; for (short i = 0; i < length; i++) { if (escape) { unpaddedData[index++] = paddedData[i] + 1; escape = false; } else if (paddedData[i] == JVS_MARK) escape = true; else unpaddedData[index++] = paddedData[i]; } return index; } short jvs_pad(unsigned char* unpaddedData, short length, unsigned char* paddedData, short paddedMax) { short index = 0; for (short i = 0; i < length; i++) { if (i > paddedMax) return -1; if (i != 0 && (unpaddedData[i] == JVS_MARK || unpaddedData[i] == JVS_SYNC)) { paddedData[index++] = JVS_MARK; paddedData[index++] = unpaddedData[i] - 1; } else { paddedData[index++] = unpaddedData[i]; } } return index; } void update_jvs_buttons(jvs_board_t* board) { unsigned char sysButtons = 0x00; if (GetAsyncKeyState(jvsKeybindings[board->num].test) < 0) sysButtons |= 0x80; board->last_sysbuttons = sysButtons; for (int player = 0; player < PLAYER_COUNT; player++) { unsigned short buttons = 0x0000; for (int i = 0; i < SWITCH_BYTES * 8; i++) { buttons <<= 1; if (i < 14) { int scancode = jvsKeybindings[board->num].buttons[(i + 1) * 2 + player]; bool invert = jvsKeybindings[board->num].invert[(i + 1) * 2 + player]; if (scancode == 0) continue; if (invert) buttons |= (GetAsyncKeyState(scancode) >= 0); // << i; else buttons |= (GetAsyncKeyState(scancode) < 0); // << i; } // 0x80 == push 3 // buttons |= 1; // buttons |= 0x80; } board->last_buttons[player] = buttons; } } unsigned char jvs_exchange(jvs_board_t* board, unsigned char* inData, short inCount, unsigned char* response, unsigned char* outCount) { short jvsIndex = 0; unsigned char respIndex = 0; #define jvs_read(x) \ if (jvsIndex >= inCount) { \ return JVS_STATUS_OVERFLOW; \ } else { \ (x) = inData[jvsIndex++]; \ } #define jvs_write(x) response[respIndex++] = (x); static bool flipflop = false; while (jvsIndex < inCount) { unsigned char cmd; jvs_read(cmd); // log_info(plfMxJvs, "CMD: %02x", cmd); switch (cmd) { case JVS_CMD_RESET: unsigned char reset_assert; jvs_read(reset_assert); if (reset_assert != JVS_CMD_RESET_ASSERT) return JVS_STATUS_UKCOM; JVS_SENSE = true; // Special case *outCount = 0; return JVS_STATUS_OK; case JVS_CMD_CHANGE_COMMS: // Special case *outCount = 0; return JVS_STATUS_OK; case JVS_CMD_ASSIGN_ADDR: jvs_write(JVS_REPORT_OK); JVS_SENSE = false; // we don't bother remembering the address because at the moment we only simulate // a single board in the JVS chain. unsigned char _; jvs_read(_); break; case JVS_CMD_READ_ID: jvs_write(JVS_REPORT_OK); for (size_t i = 0; i < strlen(board->id); i++) jvs_write(board->id[i]); break; case JVS_CMD_GET_CMD_VERSION: jvs_write(JVS_REPORT_OK); jvs_write(JVS_VERSION_CMD); break; case JVS_CMD_GET_JVS_VERSION: jvs_write(JVS_REPORT_OK); jvs_write(JVS_VERSION_JVS); break; case JVS_CMD_GET_COMM_VERSION: jvs_write(JVS_REPORT_OK); jvs_write(JVS_VERSION_COMM); break; case JVS_CMD_GET_FEATURES: jvs_write(JVS_REPORT_OK); jvs_write(JVS_FEATURE_PLAYERS); jvs_write(PLAYER_COUNT); jvs_write(13); // bits per player jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_COINS); jvs_write(COIN_COUNTERS); jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_ANALOG); jvs_write(8); // 8 ADC channels jvs_write(JVS_FEATURE_PAD); // ?? (was "10" prior) jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_GPIO); jvs_write(6); // 6 ports jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_PAD); jvs_write(JVS_FEATURE_EOF); break; case JVS_CMD_RECEIVE_MAIN_ID: unsigned char tempRead = 0xff; while (tempRead != 0) jvs_read(tempRead); // TODO: Do we need to report here? break; case JVS_CMD_READ_SW: unsigned char players; unsigned char switch_bytes; jvs_read(players); jvs_read(switch_bytes); if (players > PLAYER_COUNT || switch_bytes != 2) { jvs_write(JVS_REPORT_PARAM_INVALID); break; } update_jvs_buttons(board); jvs_write(JVS_REPORT_OK); jvs_write(board->last_sysbuttons); for (int player = 0; player < players; player++) { for (int i = switch_bytes - 1; i >= 0; i--) { jvs_write((board->last_buttons[player] >> (i * 8)) & 0xff); } } break; case JVS_CMD_READ_COIN: jvs_write(JVS_REPORT_OK); unsigned char coin_count; jvs_read(coin_count); int coin1 = jvsKeybindings[board->num].buttons[0]; int coin2 = jvsKeybindings[board->num].buttons[1]; if (coin1 && GetAsyncKeyState(coin1) & 1) board->coin_counts[0]++; if (coin2 && GetAsyncKeyState(coin2) & 1) board->coin_counts[1]++; for (unsigned char slot = 0; slot < coin_count; slot++) { jvs_write(board->coin_counts[slot] >> 8); jvs_write(board->coin_counts[slot] & 0xff); } break; case JVS_CMD_READ_ANALOGS: jvs_write(JVS_REPORT_OK); // TODO: Actually emulate these (super low priority) unsigned char analog_count; jvs_read(analog_count); for (int i = analog_count; i > 0; i--) { jvs_write(0xde); jvs_write(0xad); } break; case JVS_CMD_WRITE_GPIO1: jvs_write(JVS_REPORT_OK); unsigned char gpio_bytes; jvs_read(gpio_bytes); for (int i = 0; i < gpio_bytes; i++) { unsigned char gpio_value; jvs_read(gpio_value); if (i == 0) { if (!!(gpio_value & 0x80) != coin_solenoid) { coin_solenoid = !!(gpio_value & 0x80); log_info(plfMxJvs, "Coin solenoid: %s", coin_solenoid ? "Locked" : "Unlocked"); } } // log_warning(plfMxJvs, "Unhandled GPIO write: *(%d) = %02x", i, gpio_value); } break; case JVS_CMD_COIN_DECREASE: jvs_write(JVS_REPORT_OK); unsigned char slot; jvs_read(slot); unsigned char cAmount[2]; jvs_read(cAmount[0]); jvs_read(cAmount[1]); unsigned short sAmount = cAmount[0] << 8 | cAmount[1]; board->coin_counts[slot] -= sAmount; break; default: log_error(plfMxJvs, "Unknown command: 0x%02x", cmd); return JVS_STATUS_UKCOM; } } #undef jvs_read #undef jvs_write *outCount = respIndex; return JVS_STATUS_OK; } void jvs_send_status(unsigned char status, unsigned char* outData, LPDWORD outCount) { short respIndex = 4; outData[0] = JVS_SYNC; outData[1] = JVS_NODE_MASTER; outData[2] = 2; if (status == JVS_MARK || status == JVS_SYNC) { outData[3] = JVS_MARK; outData[4] = status - 1; outData[5] = JVS_NODE_MASTER + 2 + status; *outCount = 6; } else { outData[3] = status; outData[4] = JVS_NODE_MASTER + 2 + status; *outCount = 5; } } void mxjvs_handle(unsigned char* paddedIn, short inCount, unsigned char* outData, short maxOut, LPDWORD outCount) { *outCount = 0; unsigned char* inData = malloc(inCount); inCount = jvs_unpad(paddedIn, inCount, inData); // JVS frame is 4 bytes in total if (inCount < 4) { log_error(plfMxJvs, "inCount impossibly small: %d", inCount); jvs_send_status(JVS_STATUS_UNKNOWN, outData, outCount); free(inData); return; } // This isn't a JVS packet if (inData[0] != JVS_SYNC) { log_error(plfMxJvs, "SYNC missing. Saw 0x%02x", inData[0]); jvs_send_status(JVS_STATUS_UNKNOWN, outData, outCount); free(inData); return; } // Validate the checksum before proceeding unsigned char sum = 0; bool escape = false; for (int i = 1; i < inCount - 1; i++) sum += inData[i]; if (sum != inData[inCount - 1]) { log_error(plfMxJvs, "Checksum failed. Computed 0x%02x, expected 0x%02x", sum, inData[inCount - 1]); jvs_send_status(JVS_STATUS_SUM, outData, outCount); return; } unsigned char destination = inData[1]; // Not broadcast, not master-bound, and within bounds if (destination != 0xff && (destination == 0 || destination > MiceConfig.keys.board_count)) return; unsigned char* response = malloc(maxOut); unsigned char packetSize = 0; unsigned char status = JVS_STATUS_UNKNOWN; if (destination == 0xff) { for (int i = 0; i < MiceConfig.keys.board_count; i++) { jvs_board_t* board = &jvs_boards[i]; status = board->handler(board, inData + 3, inData[2] - 1, response + 4, &packetSize); if (packetSize != 0) break; } } else { jvs_board_t* board = &jvs_boards[MiceConfig.keys.board_count - destination]; status = board->handler(board, inData + 3, inData[2] - 1, response + 4, &packetSize); } free(inData); if (status == JVS_STATUS_OK) { if (packetSize == 0) return; response[0] = JVS_SYNC; response[1] = JVS_NODE_MASTER; response[2] = packetSize + 2; response[3] = status; sum = 0; for (int i = 1; i < packetSize + 4; i++) sum += response[i]; response[packetSize + 4] = sum; short paddedLength = jvs_pad(response, packetSize + 5, outData, maxOut); *outCount = (paddedLength == -1) ? 0 : paddedLength; free(response); } else { log_error(plfMxJvs, "JVS board %d returned status %d", destination, status); free(response); jvs_send_status(status, outData, outCount); } } BOOL WINAPI mxjvs_DeviceIoControl(file_context_t* ctx, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) { switch (dwIoControlCode) { case IOCTL_MXJVS_EXCHANGE: log_trace(plfMxJvs, "DeviceIoControl(, , 0x%p, 0x%x, -, " "0x%x, -, -)", lpInBuffer, nInBufferSize, nOutBufferSize); mxjvs_handle(lpInBuffer, nInBufferSize & 0xffff, lpOutBuffer, nOutBufferSize & 0xFFFF, lpBytesReturned); break; default: log_warning(plfMxJvs, "unhandled 0x%08x", dwIoControlCode); return FALSE; } return TRUE; } BOOL mxjvs_SetupComm(void* com, DWORD dwInQueue, DWORD dwOutQueue) { return TRUE; } BOOL mxjvs_PurgeComm(void* com, DWORD dwFlags) { return TRUE; } BOOL mxjvs_GetCommState(void* com, LPDCB lpDCB) { return TRUE; } BOOL mxjvs_SetCommTimeouts(void* com, LPCOMMTIMEOUTS lpDCB) { return TRUE; } BOOL mxjvs_GetCommModemStatus(void* com, LPDWORD lpModelStat) { *lpModelStat = !JVS_SENSE ? MS_DSR_ON : 0; return TRUE; } BOOL mxjvs_SetCommState(void* com, LPDCB lpDCB) { char PARITY[] = { 'N', 'O', 'E', 'M', 'S' }; char* STOP[] = { "1", "1.5", "2" }; log_misc(plfMxJvs, "Switching to %d baud (%d%c%s)", lpDCB->BaudRate, lpDCB->ByteSize, PARITY[lpDCB->Parity], STOP[lpDCB->StopBits]); return TRUE; } void init_jvs_boards() { for (int i = 0; i < _countof(jvs_boards); i++) { jvs_boards[i].num = i; jvs_boards[i].id = JVS_837_14572_ID; jvs_boards[i].handler = &jvs_exchange; } } DWORD __stdcall mxjvs_comdev_thread(com_device_t* com) { BYTE inBuffer[512]; BYTE outBuffer[512]; DWORD bytesReturned; while (1) { do { comdev_read_blocking(com, inBuffer, 1); } while (inBuffer[0] != 0xE0); comdev_read_blocking(com, inBuffer + 1, 2); BYTE nbytes = inBuffer[2]; BYTE* bufferPtr = inBuffer + 3; if (nbytes == 0xD0) { comdev_read_blocking(com, bufferPtr++, 1); nbytes = *bufferPtr; } while (nbytes) { comdev_read_blocking(com, bufferPtr++, 1); if (bufferPtr[-1] == 0xD0) comdev_read_blocking(com, bufferPtr++, 1); nbytes--; } short packetSize = bufferPtr - inBuffer; mxjvs_handle(inBuffer, packetSize, outBuffer, _countof(outBuffer), &bytesReturned); com->modemStatus = !JVS_SENSE ? MS_DSR_ON : 0; comdev_write(com, outBuffer, bytesReturned & 0xFFFF); } } void setup_mxjvs() { init_jvs_boards(); file_hook_t* mxjvs = new_file_hook(L"\\\\.\\mxjvs"); mxjvs->DeviceIoControl = &mxjvs_DeviceIoControl; hook_file(mxjvs); com_hook_t* jvscom = new_com_hook(4); wcscpy_s(jvscom->wName, sizeof jvscom->wName, L"\\\\.\\mxjvs"); wcscpy_s(jvscom->wDosName, sizeof jvscom->wDosName, L"\\\\.\\mxjvs"); jvscom->GetCommState = mxjvs_GetCommState; jvscom->SetCommState = mxjvs_SetCommState; jvscom->SetCommTimeouts = mxjvs_SetCommTimeouts; jvscom->SetupComm = mxjvs_SetupComm; jvscom->PurgeComm = mxjvs_PurgeComm; jvscom->GetCommModemStatus = mxjvs_GetCommModemStatus; mxjvs->com_hook = jvscom; // TODO: Looks like some things might use JVS on COM4. We should setup a comdevice for this! attach_com_device(4, mxjvs_comdev_thread); }