diff --git a/src/chuniio.c b/src/chuniio.c index be754e3..eeb122a 100644 --- a/src/chuniio.c +++ b/src/chuniio.c @@ -1,702 +1,702 @@ -// -// Created by beerpsi on 12/30/2023. -// - -#include "chuniio.h" - -#include -#include - -#include "config.h" -#include "struct.h" -#include "util/dprintf.h" - -#ifdef ENV32BIT -#include -#include -#include - -#include "socket.h" -#endif - -//region Brokenithm -struct IPCMemoryInfo* chuni_io_file_mapping; - -const char *memFileName = "Local\\BROKENITHM_SHARED_BUFFER"; - -#ifdef ENV32BIT -uint16_t server_port = 52468; -bool tcp_mode = false; - -enum { - CARD_AIME, - CARD_FELICA, -}; - -enum { - FUNCTION_COIN = 1, - FUNCTION_CARD -}; - -typedef struct { - SOCKET sock; - char remote_address[BUFSIZ]; - uint16_t remote_port; - atomic_bool exit_flag; - atomic_bool connected; - uint32_t last_input_packet_id; - struct IPCMemoryInfo* memory; -} thread_ctx; - -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(<); - 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(thread_ctx *ctx, uint32_t new_packet_id) -{ - if (ctx->last_input_packet_id > new_packet_id) { - print_err("[WARN] Packet #%" PRIu32 " came too late\n", new_packet_id); - } else if (new_packet_id > ctx->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", - ctx->last_input_packet_id, new_packet_id, new_packet_id - ctx->last_input_packet_id - 1); - } else if (new_packet_id == ctx->last_input_packet_id) { - print_err("[WARN] Packet #%" PRIu32 " duplicated\n", new_packet_id); - } - ctx->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 *v) { - thread_ctx *ctx = (thread_ctx*)v; - - SOCKET sHost = ctx->sock; - struct IPCMemoryInfo* memory = ctx->memory; - - struct sockaddr_in addr = {}; - make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port); - - char send_buffer[4 + 3 * 32]; - send_buffer[0] = 99; - send_buffer[1] = 'L'; - send_buffer[2] = 'E'; - send_buffer[3] = 'D'; - - uint8_t current_led_status[3 * 32]; - - - while (!atomic_load(&ctx->exit_flag)) { - if (!atomic_load(&ctx->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"); - atomic_store(&ctx->connected, false); - atomic_store(&ctx->exit_flag, true); - break; - } - } - } - - skip_count = 0; - } - - Sleep(10); - } - - return 0; -} - -uint8_t last_card_id[10]; - -unsigned int __stdcall thread_input_recv(void *v) { - thread_ctx *ctx = (thread_ctx*)v; - - SOCKET sHost = ctx->sock; - struct IPCMemoryInfo* memory = ctx->memory; - - char buffer[BUFSIZ]; - - struct sockaddr_in addr = {}; - make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port); - - int recv_len, packet_len; - uint8_t real_len; - - while (!atomic_load(&ctx->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"); - atomic_store(&ctx->connected, false); - atomic_store(&ctx->exit_flag, true); - break; - } - } - - recv_len = recv_len + read; - } - - real_len = buffer[0]; - packet_len = real_len + 1; // 1 for the packetSize - - 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"); - atomic_store(&ctx->connected, false); - atomic_store(&ctx->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(ctx, 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(ctx, 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, ctx->remote_address, BUFSIZ - 1, &ctx->remote_port); - print_err("[INFO] Device %s:%d connected.\n", ctx->remote_address, ctx->remote_port); - - ctx->last_input_packet_id = 0; - atomic_store(&ctx->connected, true); - } else if (packet_len >= 4 && buffer[1] == 'D' && buffer[2] == 'I' && buffer[3] == 'S') { - atomic_store(&ctx->connected, false); - - if (tcp_mode) { - atomic_store(&ctx->exit_flag, true); - print_err("[INFO] Device disconnected!\n"); - break; - } - - if (strlen(ctx->remote_address)) { - print_err("[INFO] Device %s:%d disconnected.\n", ctx->remote_address, ctx->remote_port); - memset(ctx->remote_address, 0, BUFSIZ); - } - } else if (packet_len >= sizeof(struct PacketPing) && buffer[1] == 'P' && buffer[2] == 'I' && buffer[3] == 'N') { - if (!atomic_load(&ctx->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_ctx args = { - .sock = sHost, - .exit_flag = ATOMIC_VAR_INIT(false), - .connected = ATOMIC_VAR_INIT(true), - .last_input_packet_id = 0, - .memory = memory - }; - - 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); - - print_err("[INFO] Waiting for device on port %d...\n", server_port); - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "EndlessLoop" - for (;;) { - 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); - } - - thread_ctx args = { - .sock = acc_socket, - .exit_flag = ATOMIC_VAR_INIT(false), - .connected = ATOMIC_VAR_INIT(true), - .last_input_packet_id = 0, - .memory = memory - }; - - _beginthreadex(NULL, 0, thread_led_broadcast, &args, 0, NULL); - _beginthreadex(NULL, 0, thread_input_recv, &args, 0, NULL); - } -#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; -} -#endif -//endregion - -//region ChuniIO stuff -#ifdef ENV64BIT -static HANDLE chuni_io_file_mapping_handle; - -void chuni_io_init_shared_memory() { - if (chuni_io_file_mapping) { - dprintf("chuni_io_init_shared_memory: shared memory already exists\n"); - return; - } - - if ((chuni_io_file_mapping_handle = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, sizeof(struct IPCMemoryInfo), memFileName)) == 0) { - dprintf("chuni_io_init_shared_memory: could not create file mapping: %ld\n", GetLastError()); - return; - } - - if ((chuni_io_file_mapping = (struct IPCMemoryInfo*)MapViewOfFile(chuni_io_file_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct IPCMemoryInfo))) == 0) { - dprintf("chuni_io_init_shared_memory: could not get view of file: %ld\n", GetLastError()); - return; - } - - memset(chuni_io_file_mapping, 0, sizeof(struct IPCMemoryInfo)); - SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); -} -#endif - - -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"); - -#ifdef ENV32BIT - HRESULT result = server_start(); - if (result != S_OK) { - return result; - } -#endif -#ifdef ENV64BIT - chuni_io_init_shared_memory(); -#endif - - 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; -} +// +// Created by beerpsi on 12/30/2023. +// + +#include "chuniio.h" + +#include +#include + +#include "config.h" +#include "struct.h" +#include "util/dprintf.h" + +#ifdef ENV32BIT +#include +#include +#include + +#include "socket.h" +#endif + +//region Brokenithm +struct IPCMemoryInfo* chuni_io_file_mapping; + +const char *memFileName = "Local\\BROKENITHM_SHARED_BUFFER"; + +#ifdef ENV32BIT +uint16_t server_port = 52468; +bool tcp_mode = false; + +enum { + CARD_AIME, + CARD_FELICA, +}; + +enum { + FUNCTION_COIN = 1, + FUNCTION_CARD +}; + +typedef struct { + SOCKET sock; + char remote_address[BUFSIZ]; + uint16_t remote_port; + atomic_bool exit_flag; + atomic_bool connected; + uint32_t last_input_packet_id; + struct IPCMemoryInfo* memory; +} thread_ctx; + +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(<); + 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(thread_ctx *ctx, uint32_t new_packet_id) +{ + if (ctx->last_input_packet_id > new_packet_id) { + print_err("[WARN] Packet #%" PRIu32 " came too late\n", new_packet_id); + } else if (new_packet_id > ctx->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", + ctx->last_input_packet_id, new_packet_id, new_packet_id - ctx->last_input_packet_id - 1); + } else if (new_packet_id == ctx->last_input_packet_id) { + print_err("[WARN] Packet #%" PRIu32 " duplicated\n", new_packet_id); + } + ctx->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 *v) { + thread_ctx *ctx = (thread_ctx*)v; + + SOCKET sHost = ctx->sock; + struct IPCMemoryInfo* memory = ctx->memory; + + struct sockaddr_in addr = {}; + make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port); + + char send_buffer[4 + 3 * 32]; + send_buffer[0] = 99; + send_buffer[1] = 'L'; + send_buffer[2] = 'E'; + send_buffer[3] = 'D'; + + uint8_t current_led_status[3 * 32]; + + + while (!atomic_load(&ctx->exit_flag)) { + if (!atomic_load(&ctx->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"); + atomic_store(&ctx->connected, false); + atomic_store(&ctx->exit_flag, true); + break; + } + } + } + + skip_count = 0; + } + + Sleep(10); + } + + return 0; +} + +uint8_t last_card_id[10]; + +unsigned int __stdcall thread_input_recv(void *v) { + thread_ctx *ctx = (thread_ctx*)v; + + SOCKET sHost = ctx->sock; + struct IPCMemoryInfo* memory = ctx->memory; + + char buffer[BUFSIZ]; + + struct sockaddr_in addr = {}; + make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port); + + int recv_len, packet_len; + uint8_t real_len; + + while (!atomic_load(&ctx->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"); + atomic_store(&ctx->connected, false); + atomic_store(&ctx->exit_flag, true); + break; + } + } + + recv_len = recv_len + read; + } + + real_len = buffer[0]; + packet_len = real_len + 1; // 1 for the packetSize + + 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"); + atomic_store(&ctx->connected, false); + atomic_store(&ctx->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(ctx, 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(ctx, 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, ctx->remote_address, BUFSIZ - 1, &ctx->remote_port); + print_err("[INFO] Device %s:%d connected.\n", ctx->remote_address, ctx->remote_port); + + ctx->last_input_packet_id = 0; + atomic_store(&ctx->connected, true); + } else if (packet_len >= 4 && buffer[1] == 'D' && buffer[2] == 'I' && buffer[3] == 'S') { + atomic_store(&ctx->connected, false); + + if (tcp_mode) { + atomic_store(&ctx->exit_flag, true); + print_err("[INFO] Device disconnected!\n"); + break; + } + + if (strlen(ctx->remote_address)) { + print_err("[INFO] Device %s:%d disconnected.\n", ctx->remote_address, ctx->remote_port); + memset(ctx->remote_address, 0, BUFSIZ); + } + } else if (packet_len >= sizeof(struct PacketPing) && buffer[1] == 'P' && buffer[2] == 'I' && buffer[3] == 'N') { + if (!atomic_load(&ctx->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_ctx args = { + .sock = sHost, + .exit_flag = ATOMIC_VAR_INIT(false), + .connected = ATOMIC_VAR_INIT(false), + .last_input_packet_id = 0, + .memory = memory + }; + + 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); + + print_err("[INFO] Waiting for device on port %d...\n", server_port); + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "EndlessLoop" + for (;;) { + 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); + } + + thread_ctx args = { + .sock = acc_socket, + .exit_flag = ATOMIC_VAR_INIT(false), + .connected = ATOMIC_VAR_INIT(true), + .last_input_packet_id = 0, + .memory = memory + }; + + _beginthreadex(NULL, 0, thread_led_broadcast, &args, 0, NULL); + _beginthreadex(NULL, 0, thread_input_recv, &args, 0, NULL); + } +#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; +} +#endif +//endregion + +//region ChuniIO stuff +#ifdef ENV64BIT +static HANDLE chuni_io_file_mapping_handle; + +void chuni_io_init_shared_memory() { + if (chuni_io_file_mapping) { + dprintf("chuni_io_init_shared_memory: shared memory already exists\n"); + return; + } + + if ((chuni_io_file_mapping_handle = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, sizeof(struct IPCMemoryInfo), memFileName)) == 0) { + dprintf("chuni_io_init_shared_memory: could not create file mapping: %ld\n", GetLastError()); + return; + } + + if ((chuni_io_file_mapping = (struct IPCMemoryInfo*)MapViewOfFile(chuni_io_file_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct IPCMemoryInfo))) == 0) { + dprintf("chuni_io_init_shared_memory: could not get view of file: %ld\n", GetLastError()); + return; + } + + memset(chuni_io_file_mapping, 0, sizeof(struct IPCMemoryInfo)); + SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); +} +#endif + + +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"); + +#ifdef ENV32BIT + HRESULT result = server_start(); + if (result != S_OK) { + return result; + } +#endif +#ifdef ENV64BIT + chuni_io_init_shared_memory(); +#endif + + 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; +}