chuniio-brokenithm/src/chuniio.c

668 lines
20 KiB
C

//
// Created by beerpsi on 12/30/2023.
//
#include "chuniio.h"
#include <stdio.h>
#include <inttypes.h>
#include <signal.h>
#include <time.h>
#include <process.h>
#include <assert.h>
#include "config.h"
#include "struct.h"
#include "util/dprintf.h"
//region Brokenithm
struct IPCMemoryInfo* chuni_io_file_mapping;
const char *memFileName = "Local\\BROKENITHM_SHARED_BUFFER";
char remote_address[BUFSIZ];
uint16_t remote_port = 52468;
uint16_t server_port = 52468;
bool tcp_mode = false;
volatile sig_atomic_t EXIT_FLAG = false, CONNECTED = false;
uint32_t last_input_packet_id = 0;
enum {
CARD_AIME,
CARD_FELICA,
};
enum {
FUNCTION_COIN = 1,
FUNCTION_CARD
};
typedef struct {
SOCKET sHost;
struct IPCMemoryInfo* memory;
} thread_args;
void socket_set_timeout(SOCKET sHost, int timeout) {
setsockopt(sHost, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(int));
setsockopt(sHost, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int));
}
int socket_bind(SOCKET sHost, unsigned long addr, uint16_t port) {
struct sockaddr_in srcaddr = {};
memset(&srcaddr, 0, sizeof(srcaddr));
srcaddr.sin_family = AF_INET;
srcaddr.sin_addr.s_addr = addr;
srcaddr.sin_port = htons(port);
return bind(sHost, (struct sockaddr*)&srcaddr, sizeof(srcaddr));
}
int socket_send_to(SOCKET sHost, const struct sockaddr_in* addr, const char* buf, int len) {
return sendto(sHost, buf, len, 0, (struct sockaddr*)&addr, sizeof(&addr));
}
void print_err(const char* fmt, ...) {
time_t lt = time(NULL);
struct tm *local = localtime(&lt);
char tmpbuf[32];
strftime(tmpbuf, 32, "%Y-%m-%d %H:%M:%S", local);
dprintf("brokenithm_server: [%s] ", tmpbuf);
va_list ap;
va_start(ap, fmt);
dprintfv(fmt, ap);
va_end(ap);
}
void get_socks_address(const struct PacketConnect* pkt, char* address, int address_len, uint16_t *port) {
if (!pkt || !address || !port) {
return;
}
*port = ntohs(pkt->port);
switch (pkt->addrType) {
case 1:
inet_ntop(AF_INET, pkt->addr.addr4.addr, address, address_len);
break;
case 2:
inet_ntop(AF_INET6, pkt->addr.addr6, address, address_len);
break;
default:
return;
}
}
void update_packet_id(uint32_t new_packet_id)
{
if (last_input_packet_id > new_packet_id) {
print_err("[WARN] Packet #%" PRIu32 " came too late\n", new_packet_id);
} else if (new_packet_id > last_input_packet_id + 1) {
print_err("[WARN] Packets between #%" PRIu32 " and #%" PRIu32 " total %" PRIu32 " packet(s) are missing, probably too late or dropped\n",
last_input_packet_id, new_packet_id, new_packet_id - last_input_packet_id - 1);
} else if (new_packet_id == last_input_packet_id) {
print_err("[WARN] Packet #%" PRIu32 " duplicated\n", new_packet_id);
}
last_input_packet_id = new_packet_id;
}
void dump_bytes(const void *ptr, size_t nbytes, bool hex_string)
{
const uint8_t *bytes;
uint8_t c;
size_t i;
size_t j;
if (nbytes == 0) {
dprintf("\t--- Empty ---\n");
}
bytes = (const unsigned char*)ptr;
if (hex_string) {
for (i = 0 ; i < nbytes ; i++) {
dprintf("%02x", bytes[i]);
}
dprintf("\n");
return;
}
for (i = 0 ; i < nbytes ; i += 16) {
dprintf(" %08x:", (int) i);
for (j = 0 ; i + j < nbytes && j < 16 ; j++) {
dprintf(" %02x", bytes[i + j]);
}
while (j < 16) {
dprintf(" ");
j++;
}
dprintf(" ");
for (j = 0 ; i + j < nbytes && j < 16 ; j++) {
c = bytes[i + j];
if (c < 0x20 || c >= 0x7F) {
c = '.';
}
dprintf("%c", c);
}
dprintf("\n");
}
dprintf("\n");
}
void print_card_info(uint8_t card_type, uint8_t *card_id) {
switch (card_type) {
case CARD_AIME:
print_err("[INFO] Card type: AiMe, ID: ");
dump_bytes(card_id, 10, true);
break;
case CARD_FELICA:
print_err("[INFO] Card type: FeliCa, ID: ");
dump_bytes(card_id, 8, true);
break;
default:
break;
}
}
int make_ipv4_address(struct sockaddr_in* addr, char* host, uint16_t port) {
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
return inet_pton(AF_INET, host, (struct in_addr *)&addr->sin_addr.s_addr);
}
uint8_t previous_led_status[3 * 32];
bool has_previous_led_status = false;
int skip_count = 0;
unsigned int __stdcall thread_led_broadcast(void *ctx) {
thread_args *args = (thread_args*)ctx;
SOCKET sHost = args->sHost;
struct IPCMemoryInfo* memory = args->memory;
struct sockaddr_in addr = {};
make_ipv4_address(&addr, remote_address, remote_port);
char send_buffer[4 + 3 * 32];
send_buffer[0] = 100;
send_buffer[1] = 'L';
send_buffer[2] = 'E';
send_buffer[3] = 'D';
uint8_t current_led_status[3 * 32];
while (!EXIT_FLAG) {
if (!CONNECTED) {
Sleep(50);
continue;
}
memcpy(current_led_status, memory->ledRgbData, 3 * 32);
bool same;
if (!has_previous_led_status) {
same = (memcmp(previous_led_status, current_led_status, 3 * 32) == 0);
} else {
same = false;
}
memcpy(previous_led_status, current_led_status, 3 * 32);
has_previous_led_status = true;
if (!same || ++skip_count > 50) {
memcpy(send_buffer + 4, current_led_status, 3 * 32);
if (socket_send_to(sHost, &addr, send_buffer, 100) < 0) {
print_err("[ERROR] Cannot send packet: error %lu\n", GetLastError());
if (tcp_mode) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
} else {
print_err("[INFO] Device disconnected!\n");
CONNECTED = false;
EXIT_FLAG = true;
break;
}
}
}
skip_count = 0;
}
Sleep(10);
}
return 0;
}
uint8_t last_card_id[10];
unsigned int __stdcall thread_input_recv(void *ctx) {
thread_args *args = (thread_args*)ctx;
SOCKET sHost = args->sHost;
struct IPCMemoryInfo* memory = args->memory;
char buffer[BUFSIZ];
struct sockaddr_in addr = {};
make_ipv4_address(&addr, remote_address, remote_port);
int recv_len, packet_len;
uint8_t real_len;
while (!EXIT_FLAG) {
if (!tcp_mode) {
/**
on UDP mode data is sent as packets, so just receive into a buffer big enough for 1 packet
each recvfrom call will only get 1 packet of data, the remaining data is discarded
**/
if ((recv_len = recvfrom(sHost, buffer, BUFSIZ - 1, 0, NULL, NULL)) == -1) {
continue;
}
real_len = (unsigned char)buffer[0];
if (real_len > recv_len) {
continue;
}
packet_len = real_len + 1;
} else {
/**
on TCP mode packets are length-prefixed, so we read in the first 4 bytes to
figure out how much we need to read, then read in the full data.
**/
recv_len = 0;
while (recv_len < 4) {
int read = recv(sHost, buffer + recv_len, 4 - recv_len, 0);
if (read == -1) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
} else {
print_err("[INFO] Device disconnected!\n");
CONNECTED = false;
EXIT_FLAG = true;
break;
}
}
recv_len = recv_len + read;
}
real_len = buffer[0];
packet_len = real_len + 1;
while (recv_len < packet_len) {
int read = recv(sHost, buffer + recv_len, packet_len - recv_len, 0);
if (read == -1) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
} else {
print_err("[INFO] Device disconnected!\n");
CONNECTED = false;
EXIT_FLAG = true;
break;
}
}
recv_len = recv_len + read;
}
}
if (packet_len >= sizeof(struct PacketInput) && buffer[1] == 'I' && buffer[2] == 'N' && buffer[3] == 'P') {
struct PacketInput* pkt = (struct PacketInput*)buffer;
memcpy(memory->airIoStatus, pkt->airIoStatus, sizeof(pkt->airIoStatus));
memcpy(memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus));
memory->testBtn = pkt->testBtn;
memory->serviceBtn = pkt->serviceBtn;
update_packet_id(ntohl(pkt->packetId));
} else if (packet_len >= sizeof(struct PacketInputNoAir) && buffer[1] == 'I' && buffer[2] == 'P' && buffer[3] == 'T') { // without air
struct PacketInputNoAir* pkt = (struct PacketInputNoAir*)buffer;
memcpy(memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus));
memory->testBtn = pkt->testBtn;
memory->serviceBtn = pkt->serviceBtn;
update_packet_id(ntohl(pkt->packetId));
} else if (packet_len >= sizeof(struct PacketFunction) && buffer[1] == 'F' && buffer[2] == 'N' && buffer[3] == 'C') {
struct PacketFunction* pkt = (struct PacketFunction*)buffer;
switch (pkt->funcBtn) {
case FUNCTION_COIN:
memory->coinInsertion = 1;
break;
case FUNCTION_CARD:
memory->cardRead = 1;
break;
}
} else if (packet_len >= sizeof(struct PacketConnect) && buffer[1] == 'C' && buffer[2] == 'O' && buffer[3] == 'N') {
struct PacketConnect* pkt = (struct PacketConnect*)buffer;
get_socks_address(pkt, remote_address, BUFSIZ - 1, &remote_port);
print_err("[INFO] Device %s:%d connected.\n", remote_address, remote_port);
last_input_packet_id = 0;
CONNECTED = true;
} else if (packet_len >= 4 && buffer[1] == 'D' && buffer[2] == 'I' && buffer[3] == 'S') {
CONNECTED = false;
if (tcp_mode) {
EXIT_FLAG = 1;
print_err("[INFO] Device disconnected!\n");
break;
}
if (strlen(remote_address)) {
print_err("[INFO] Device %s:%d disconnected.\n", remote_address, remote_port);
memset(remote_address, 0, BUFSIZ);
}
} else if (packet_len >= sizeof(struct PacketPing) && buffer[1] == 'P' && buffer[2] == 'I' && buffer[3] == 'N') {
if (!CONNECTED) {
continue;
}
char response[13];
memcpy(response, buffer, 12);
response[2] = 'O';
socket_send_to(sHost, &addr, response, 13);
} else if (packet_len >= sizeof(struct PacketCard) && buffer[1] == 'C' && buffer[2] == 'R' && buffer[3] == 'D') {
struct PacketCard* pkt = (struct PacketCard*)buffer;
if (pkt->remoteCardRead) {
if (memcmp(last_card_id, pkt->remoteCardId, 10) != 0) {
print_err("[INFO] Got remote card.\n");
print_card_info(pkt->remoteCardType, pkt->remoteCardId);
memcpy(last_card_id, pkt->remoteCardId, 10);
}
} else if (memory->remoteCardRead) {
print_err("[INFO] Remote card removed.\n");
memset(last_card_id, 0, 10);
}
memory->remoteCardRead = pkt->remoteCardRead;
memory->remoteCardType = pkt->remoteCardType;
memcpy(memory->remoteCardId, pkt->remoteCardId, 10);
}
}
return 0;
}
unsigned int __stdcall server_thread_proc(void* ctx) {
struct IPCMemoryInfo* memory = (struct IPCMemoryInfo*)ctx;
if (!tcp_mode) {
print_err("[INFO] Mode: UDP\n");
SOCKET sHost = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
socket_set_timeout(sHost, 2000);
socket_bind(sHost, htonl(INADDR_ANY), server_port);
print_err("[INFO] Waiting for device on port %d...\n", server_port);
thread_args args = { .memory = memory, .sHost = sHost };
HANDLE led_thread = (HANDLE)_beginthreadex(NULL, 0, thread_led_broadcast, &args, 0, NULL);
HANDLE input_thread = (HANDLE)_beginthreadex(NULL, 0, thread_input_recv, &args, 0, NULL);
WaitForSingleObject(led_thread, INFINITE);
WaitForSingleObject(input_thread, INFINITE);
CloseHandle(led_thread);
CloseHandle(input_thread);
} else {
print_err("[INFO] Mode: TCP\n");
SOCKET sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
socket_set_timeout(sHost, 50);
socket_bind(sHost, htonl(INADDR_ANY), server_port);
listen(sHost, 10);
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
for (;;) {
print_err("[INFO] Waiting for device on port %d...\n", server_port);
struct sockaddr_in user_socket = {};
socklen_t sock_size = sizeof(struct sockaddr_in);
SOCKET acc_socket = accept(sHost, (struct sockaddr *)&user_socket, &sock_size);
char buffer[20] = {};
const char* user_address = inet_ntop(AF_INET, &user_socket.sin_addr, buffer, 20);
if (user_address != NULL) {
print_err("[INFO] Device %s:%d connected.\n", user_address, user_socket.sin_port);
}
CONNECTED = true;
EXIT_FLAG = false;
thread_args args = { .memory = memory, .sHost = acc_socket };
HANDLE led_thread = (HANDLE)_beginthreadex(NULL, 0, thread_led_broadcast, &args, 0, NULL);
HANDLE input_thread = (HANDLE)_beginthreadex(NULL, 0, thread_input_recv, &args, 0, NULL);
WaitForSingleObject(led_thread, INFINITE);
WaitForSingleObject(input_thread, INFINITE);
CloseHandle(led_thread);
CloseHandle(input_thread);
print_err("[INFO] Exiting gracefully...\n");
last_input_packet_id = 0;
EXIT_FLAG = true;
CONNECTED = false;
}
#pragma clang diagnostic pop
}
return 0;
}
HRESULT server_start() {
tcp_mode = GetPrivateProfileIntW(L"brokenithm", L"tcp", 0, L".\\segatools.ini") == 1;
server_port = GetPrivateProfileIntW(L"brokenithm", L"port", 52468, L".\\segatools.ini");
struct WSAData wsaData = {};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
print_err("[ERROR] WSA startup failed!\n");
return E_FAIL;
}
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, memFileName);
if (hMapFile == NULL) {
hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, memFileName);
if (hMapFile == NULL) {
print_err("[ERROR] CreateFileMapping failed! error: %lu\n", GetLastError());
return E_FAIL;
}
}
struct IPCMemoryInfo* memory = (struct IPCMemoryInfo*)MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1024, NULL);
chuni_io_file_mapping = memory;
if (memory == NULL) {
print_err("[ERROR] Cannot get view of memory map! error: %lu\n", GetLastError());
return E_FAIL;
}
_beginthreadex(NULL, 0, server_thread_proc, memory, 0, NULL);
return S_OK;
}
//endregion
//region ChuniIO stuff
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx);
static bool chuni_io_coin;
static uint16_t chuni_io_coins;
static uint8_t chuni_io_hand_pos;
static HANDLE chuni_io_slider_thread;
static bool chuni_io_slider_stop_flag;
static struct chuni_io_config chuni_io_cfg;
uint16_t chuni_io_get_api_version() {
return 0x0102;
}
HRESULT chuni_io_jvs_init() {
chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini");
HRESULT result = server_start();
if (result != S_OK) {
return result;
}
return S_OK;
}
void chuni_io_jvs_read_coin_counter(uint16_t *out) {
if (out == NULL) {
return;
}
if (chuni_io_file_mapping && chuni_io_file_mapping->coinInsertion) {
chuni_io_coins++;
chuni_io_file_mapping->coinInsertion = 0;
} else {
if (GetAsyncKeyState(chuni_io_cfg.vk_coin)) {
if (!chuni_io_coin) {
chuni_io_coin = true;
chuni_io_coins++;
}
} else {
chuni_io_coin = false;
}
}
*out = chuni_io_coins;
}
void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) {
size_t i;
if ((chuni_io_file_mapping && chuni_io_file_mapping->testBtn) || GetAsyncKeyState(chuni_io_cfg.vk_test)) {
*opbtn |= CHUNI_IO_OPBTN_TEST; /* Test */
}
if ((chuni_io_file_mapping && chuni_io_file_mapping->serviceBtn) || GetAsyncKeyState(chuni_io_cfg.vk_service)) {
*opbtn |= CHUNI_IO_OPBTN_SERVICE; /* Service */
}
if (GetAsyncKeyState(chuni_io_cfg.vk_ir_emu)) {
if (chuni_io_hand_pos < 6) {
chuni_io_hand_pos++;
}
} else {
if (chuni_io_hand_pos > 0) {
chuni_io_hand_pos--;
}
}
for (i = 0 ; i < 6 ; i++) {
if (chuni_io_hand_pos > i) {
*beams |= (1 << i);
}
}
// IR format is beams[5:0] = {b5,b6,b3,b4,b1,b2};
for (i = 0 ; i < 3 ; i++) {
if (chuni_io_file_mapping && chuni_io_file_mapping->airIoStatus[i*2])
*beams |= (1 << (i*2+1));
if (chuni_io_file_mapping && chuni_io_file_mapping->airIoStatus[i*2+1])
*beams |= (1 << (i*2));
}
}
HRESULT chuni_io_slider_init() {
return S_OK;
}
void chuni_io_slider_start(void* callback) {
if (chuni_io_slider_thread != NULL) {
return;
}
chuni_io_slider_thread = (HANDLE) _beginthreadex(
NULL,
0,
chuni_io_slider_thread_proc,
callback,
0,
NULL);
}
void chuni_io_slider_stop(void) {
if (chuni_io_slider_thread == NULL) {
return;
}
chuni_io_slider_stop_flag = true;
WaitForSingleObject(chuni_io_slider_thread, INFINITE);
CloseHandle(chuni_io_slider_thread);
chuni_io_slider_thread = NULL;
chuni_io_slider_stop_flag = false;
}
void chuni_io_slider_set_leds(const uint8_t *rgb) {
if (chuni_io_file_mapping) {
memcpy(chuni_io_file_mapping->ledRgbData, rgb, 32 * 3);
}
}
HRESULT chuni_io_led_init(void) {
return S_OK;
}
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb) {}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
{
chuni_io_slider_callback_t callback;
uint8_t pressure[32];
callback = (chuni_io_slider_callback_t)ctx;
#pragma clang diagnostic push
#pragma ide diagnostic ignored "LoopDoesntUseConditionVariableInspection"
while (!chuni_io_slider_stop_flag) {
if (chuni_io_file_mapping) {
memcpy(pressure, chuni_io_file_mapping->sliderIoStatus, 32);
}
callback(pressure);
Sleep(1);
}
#pragma clang diagnostic pop
return 0;
}
//endregion
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
return TRUE;
}