micetools/src/micetools/dll/drivers/mxjvs.c

475 lines
16 KiB
C

#include <Windows.h>
#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(<mxjvs>, <exchange>, 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);
}