diff --git a/CMakeLists.txt b/CMakeLists.txt index 586743e..6d3422c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,33 +1,12 @@ cmake_minimum_required(VERSION 3.27) -project(chuniio_brokenithm) +project(brokenithm) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") include_directories("${CMAKE_SOURCE_DIR}/include/") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_C_STANDARD 11) -set(CMAKE_C_COMPILER "gcc") -set(CMAKE_CXX_COMPILER "g++") +include_directories(.) -include(CheckIPOSupported) -check_ipo_supported(RESULT supported OUTPUT error) - -if (supported) - message(STATUS "IPO / LTO enabled") - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) -else() - message(STATUS "IPO / LTO not supported: <${error}>") -endif() - -link_directories(src) -add_library(chuniio_brokenithm SHARED src/chuniio.c - src/chuniio.h - src/config.c - src/config.h - src/socket.h - src/struct.h - src/util/dprintf.c - src/util/dprintf.h) - -set_target_properties(chuniio_brokenithm PROPERTIES PREFIX "") -set_target_properties(chuniio_brokenithm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--allow-multiple-definition") -target_link_libraries(chuniio_brokenithm "-static-libgcc -Wl,-Bstatic -limobiledevice-1.0 -lssl -lcrypto -lplist-2.0 -lusbmuxd-2.0 -lpthread -Wl,-Bdynamic -lws2_32 -lcrypt32 -liphlpapi") +add_subdirectory(aimeio) +add_subdirectory(chuniio) +add_subdirectory(util) diff --git a/README.md b/README.md index d8c3458..4e17ded 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # chuniio-brokenithm -ChuniIO driver for [Brokenithm-Android](https://github.com/tindy2013/Brokenithm-Android) +ChuniIO/AimeIO driver for [Brokenithm-Android](https://github.com/tindy2013/Brokenithm-Android) and +[Brokenithm-iOS](https://github.com/esterTion/Brokenithm-iOS) without needing an external server. -It is recommended to use this with [Dniel97's segatools](https://gitea.tendokyu.moe/Dniel97/segatools/releases), -since it allows loading 32-bit chuniio DLLs without any messy hacks. - ## Configuration segatools.ini ```ini +[aimeio] +path64=aimeio_brokenithm.dll + [chuniio] -path=chuniio_brokenithm.dll +path32=chuniio_brokenithm_x86.dll +path64=chuniio_brokenithm_x64.dll [io3] ; Test button virtual-key code. Default is the 1 key. @@ -34,11 +36,21 @@ port=52468 ## Build instructions ```shell -mkdir cmake-build -cd cmake-build +# In MinGW32 +pacman -S mingw-w64-i686-libimobiledevice -cmake .. +mkdir -p build/build32 +cd build/build32 +cmake ../.. ninja -ls chuniio_brokenithm.dll +# In MinGW64 +mkdir -p build/build64 +cd build/build64 +cmake ../.. +ninja + +ls build/build32/chuniio/chuniio_brokenithm.dll +ls build/build64/chuniio/chuniio_brokenithm.dll +ls build/build64/aimeio/aimeio_brokenithm.dll ``` \ No newline at end of file diff --git a/aimeio/CMakeLists.txt b/aimeio/CMakeLists.txt new file mode 100644 index 0000000..fd16da8 --- /dev/null +++ b/aimeio/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.27) +project(aimeio_brokenithm) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +include_directories("${CMAKE_SOURCE_DIR}/include/") +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_C_COMPILER "gcc") +set(CMAKE_CXX_COMPILER "g++") + +link_directories(src) +add_library(aimeio_brokenithm SHARED src/aimeio.c + src/aimeio.h) + +target_link_libraries(aimeio_brokenithm util) + +set_target_properties(aimeio_brokenithm PROPERTIES PREFIX "") +target_include_directories(aimeio_brokenithm PRIVATE src) + +target_link_libraries(aimeio_brokenithm "-static-libgcc") diff --git a/aimeio/src/aimeio.c b/aimeio/src/aimeio.c new file mode 100644 index 0000000..eb5a0f4 --- /dev/null +++ b/aimeio/src/aimeio.c @@ -0,0 +1,347 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#include "aimeio.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "util/dprintf.h" + +struct aime_io_config { + wchar_t aime_path[MAX_PATH]; + wchar_t felica_path[MAX_PATH]; + bool felica_gen; + bool aime_gen; + uint8_t vk_scan; +}; + +static struct aime_io_config aime_io_cfg; +static uint8_t aime_io_aime_id[10]; +static uint8_t aime_io_felica_id[8]; +static bool aime_io_aime_id_present; +static bool aime_io_felica_id_present; + +struct aime_io_ipc_memory_info { + uint8_t airIoStatus[6]; + uint8_t sliderIoStatus[32]; + uint8_t ledRgbData[32 * 3]; + uint8_t testBtn; + uint8_t serviceBtn; + uint8_t coinInsertion; + uint8_t cardRead; + uint8_t remoteCardRead; + uint8_t remoteCardType; + uint8_t remoteCardId[10]; +}; +typedef struct aime_io_ipc_memory_info aime_io_ipc_memory_info; +static HANDLE aime_io_file_mapping_handle; +aime_io_ipc_memory_info *aime_io_file_mapping; + +void aime_io_init_shared_memory() { + if (aime_io_file_mapping) { + return; + } + if ((aime_io_file_mapping_handle = + CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, + sizeof(aime_io_ipc_memory_info), + "Local\\BROKENITHM_SHARED_BUFFER")) == 0) { + return; + } + + if ((aime_io_file_mapping = (aime_io_ipc_memory_info *)MapViewOfFile( + aime_io_file_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, + sizeof(aime_io_ipc_memory_info))) == 0) { + return; + } + + memset(aime_io_file_mapping, 0, sizeof(aime_io_ipc_memory_info)); + SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); +} + +static void aime_io_config_read(struct aime_io_config *cfg, const wchar_t *filename); + +static HRESULT aime_io_read_id_file(const wchar_t *path, uint8_t *bytes, size_t nbytes); + +static HRESULT aime_io_generate_felica(const wchar_t *path, uint8_t *bytes, + size_t nbytes); + +static void aime_io_config_read(struct aime_io_config *cfg, const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW(L"aime", L"aimePath", L"DEVICE\\aime.txt", cfg->aime_path, + _countof(cfg->aime_path), filename); + + GetPrivateProfileStringW(L"aime", L"felicaPath", L"DEVICE\\felica.txt", + cfg->felica_path, _countof(cfg->felica_path), filename); + dprintf("NFC: felicaPath GetLastError %lx\n", GetLastError()); + + cfg->felica_gen = GetPrivateProfileIntW(L"aime", L"felicaGen", 0, filename); + + cfg->aime_gen = GetPrivateProfileIntW(L"aime", L"aimeGen", 1, filename); + + cfg->vk_scan = GetPrivateProfileIntW(L"aime", L"scan", VK_RETURN, filename); +} + +static HRESULT aime_io_read_id_file(const wchar_t *path, uint8_t *bytes, + size_t nbytes) { + HRESULT hr; + FILE *f; + size_t i; + int byte; + int r; + + f = _wfopen(path, L"r"); + + if (f == NULL) { + return S_FALSE; + } + + memset(bytes, 0, nbytes); + + for (i = 0; i < nbytes; i++) { + r = fscanf(f, "%02x ", &byte); + + if (r != 1) { + hr = E_FAIL; + dprintf("AimeIO DLL: %S: fscanf[%i] failed: %i\n", path, (int)i, r); + + goto end; + } + + bytes[i] = byte; + } + + hr = S_OK; + +end: + if (f != NULL) { + fclose(f); + } + + return hr; +} + +static HRESULT aime_io_generate_felica(const wchar_t *path, uint8_t *bytes, + size_t nbytes) { + size_t i; + FILE *f; + + assert(path != NULL); + assert(bytes != NULL); + assert(nbytes > 0); + + srand(time(NULL)); + + for (i = 0; i < nbytes; i++) { + bytes[i] = rand(); + } + + /* FeliCa IDm values should have a 0 in their high nibble. I think. */ + bytes[0] &= 0x0F; + + f = _wfopen(path, L"w"); + + if (f == NULL) { + dprintf("AimeIO DLL: %S: fopen failed: %i\n", path, (int)errno); + + return E_FAIL; + } + + for (i = 0; i < nbytes; i++) { + fprintf(f, "%02X", bytes[i]); + } + + fprintf(f, "\n"); + fclose(f); + + dprintf("AimeIO DLL: Generated random FeliCa ID\n"); + + return S_OK; +} + +static HRESULT aime_io_generate_aime(const wchar_t *path, uint8_t *bytes, + size_t nbytes) { + size_t i; + FILE *f; + + assert(path != NULL); + assert(bytes != NULL); + assert(nbytes > 0); + + srand(time(NULL)); + + /* AiMe IDs should not start with 3, due to a missing check for BananaPass IDs */ + do { + for (i = 0; i < nbytes; i++) { + bytes[i] = rand() % 10 << 4 | rand() % 10; + } + } while (bytes[0] >> 4 == 3); + + f = _wfopen(path, L"w"); + + if (f == NULL) { + dprintf("AimeIO DLL: %S: fopen failed: %i\n", path, (int)errno); + + return E_FAIL; + } + + for (i = 0; i < nbytes; i++) { + fprintf(f, "%02x", bytes[i]); + } + + fprintf(f, "\n"); + fclose(f); + + dprintf("AimeIO DLL: Generated random AiMe ID\n"); + + return S_OK; +} + +uint16_t aime_io_get_api_version(void) { return 0x0100; } + +HRESULT aime_io_init(void) { + aime_io_config_read(&aime_io_cfg, L".\\segatools.ini"); + + aime_io_init_shared_memory(); + + return S_OK; +} + +HRESULT aime_io_nfc_poll(uint8_t unit_no) { + bool sense; + HRESULT hr; + + if (unit_no != 0) { + return S_OK; + } + + /* Reset presence flags */ + + aime_io_aime_id_present = false; + aime_io_felica_id_present = false; + + /* First check remote card status, if there is one report it */ + + if (aime_io_file_mapping && aime_io_file_mapping->remoteCardRead) { + switch (aime_io_file_mapping->remoteCardType) { + case 0: // Aime + memcpy(aime_io_aime_id, aime_io_file_mapping->remoteCardId, 10); + aime_io_aime_id_present = true; + break; + case 1: // FeliCa + memcpy(aime_io_felica_id, aime_io_file_mapping->remoteCardId, 8); + aime_io_felica_id_present = true; + break; + } + return S_OK; + } + + /* Don't do anything more if the scan key is not held */ + + if (aime_io_file_mapping && aime_io_file_mapping->cardRead) { + sense = true; + aime_io_file_mapping->cardRead = 0; + } else { + sense = GetAsyncKeyState(aime_io_cfg.vk_scan) & 0x8000; + } + + if (!sense) { + return S_OK; + } + + /* Try AiMe IC */ + + hr = aime_io_read_id_file(aime_io_cfg.aime_path, aime_io_aime_id, + sizeof(aime_io_aime_id)); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + aime_io_aime_id_present = true; + + return S_OK; + } + + /* Try generating AiMe IC (if enabled) */ + + if (aime_io_cfg.aime_gen) { + hr = aime_io_generate_aime(aime_io_cfg.aime_path, aime_io_aime_id, + sizeof(aime_io_aime_id)); + + if (FAILED(hr)) { + return hr; + } + + aime_io_aime_id_present = true; + return S_OK; + } + + /* Try FeliCa IC */ + + hr = aime_io_read_id_file(aime_io_cfg.felica_path, aime_io_felica_id, + sizeof(aime_io_felica_id)); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + aime_io_felica_id_present = true; + + return S_OK; + } + + /* Try generating FeliCa IC (if enabled) */ + + if (aime_io_cfg.felica_gen) { + hr = aime_io_generate_felica(aime_io_cfg.felica_path, aime_io_felica_id, + sizeof(aime_io_felica_id)); + + if (FAILED(hr)) { + return hr; + } + + aime_io_felica_id_present = true; + } + + return S_OK; +} + +HRESULT aime_io_nfc_get_aime_id(uint8_t unit_no, uint8_t *luid, size_t luid_size) { + assert(luid != NULL); + assert(luid_size == sizeof(aime_io_aime_id)); + + if (unit_no != 0 || !aime_io_aime_id_present) { + return S_FALSE; + } + + memcpy(luid, aime_io_aime_id, luid_size); + + return S_OK; +} + +HRESULT aime_io_nfc_get_felica_id(uint8_t unit_no, uint64_t *IDm) { + uint64_t val; + size_t i; + + assert(IDm != NULL); + + if (unit_no != 0 || !aime_io_felica_id_present) { + return S_FALSE; + } + + val = 0; + + for (i = 0; i < 8; i++) { + val = (val << 8) | aime_io_felica_id[i]; + } + + *IDm = val; + + return S_OK; +} + +void aime_io_led_set_color(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b) {} diff --git a/aimeio/src/aimeio.h b/aimeio/src/aimeio.h new file mode 100644 index 0000000..50a9bc2 --- /dev/null +++ b/aimeio/src/aimeio.h @@ -0,0 +1,94 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#ifndef BROKENITHM_AIMEIO_H +#define BROKENITHM_AIMEIO_H + +#include + +#include +#include + +/* + Get the version of the Aime IO API that this DLL supports. This function + should return a positive 16-bit integer, where the high byte is the major + version and the low byte is the minor version (as defined by the Semantic + Versioning standard). + + The latest API version as of this writing is 0x0100. + */ +uint16_t aime_io_get_api_version(void); + +/* + Initialize Aime IO provider DLL. Only called once, before any other + functions exported from this DLL are called (except for + aime_io_get_api_version). + + Minimum API version: 0x0100 + */ +HRESULT aime_io_init(void); + +/* + Poll for IC cards in the vicinity. + + - unit_no: Always 0 as of the current API version + + Minimum API version: 0x0100 + */ +HRESULT aime_io_nfc_poll(uint8_t unit_no); + +/* + Attempt to read out a classic Aime card ID + + - unit_no: Always 0 as of the current API version + - luid: Pointer to a ten-byte buffer that will receive the ID + - luid_size: Size of the buffer at *luid. Always 10. + + Returns: + + - S_OK if a classic Aime is present and was read successfully + - S_FALSE if no classic Aime card is present (*luid will be ignored) + - Any HRESULT error if an error occured. + + Minimum API version: 0x0100 +*/ +HRESULT aime_io_nfc_get_aime_id( + uint8_t unit_no, + uint8_t *luid, + size_t luid_size); + +/* + Attempt to read out a FeliCa card ID ("IDm"). The following are examples + of FeliCa cards: + + - Amuse IC (which includes new-style Aime-branded cards, among others) + - Smartphones with FeliCa NFC capability (uncommon outside Japan) + - Various Japanese e-cash cards and train passes + + Parameters: + + - unit_no: Always 0 as of the current API version + - IDm: Output parameter that will receive the card ID + + Returns: + + - S_OK if a FeliCa device is present and was read successfully + - S_FALSE if no FeliCa device is present (*IDm will be ignored) + - Any HRESULT error if an error occured. + + Minimum API version: 0x0100 +*/ +HRESULT aime_io_nfc_get_felica_id(uint8_t unit_no, uint64_t *IDm); + +/* + Change the color and brightness of the card reader's RGB lighting + + - unit_no: Always 0 as of the current API version + - r, g, b: Primary color intensity, from 0 to 255 inclusive. + + Minimum API version: 0x0100 +*/ +void aime_io_led_set_color(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b); + +#endif // BROKENITHM_AIMEIO_H diff --git a/chuniio/CMakeLists.txt b/chuniio/CMakeLists.txt new file mode 100644 index 0000000..820ec90 --- /dev/null +++ b/chuniio/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.27) +project(chuniio_brokenithm) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +include_directories("${CMAKE_SOURCE_DIR}/include/") +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_C_COMPILER "gcc") +set(CMAKE_CXX_COMPILER "g++") + +link_directories(src) +add_library(chuniio_brokenithm SHARED src/chuniio.c + src/chuniio.h + src/config.c + src/config.h + src/socket.h + src/struct.h + src/servers/android.c + src/servers/android.h + src/servers/common.h + src/servers/common.c + src/servers/ios.c + src/servers/ios.h) + +target_link_libraries(chuniio_brokenithm util) +set_target_properties(chuniio_brokenithm PROPERTIES PREFIX "") +target_include_directories(chuniio_brokenithm PRIVATE src) + +if (CMAKE_SIZEOF_VOID_P EQUAL 4) + # Ugly hack around libimobiledevice shipping a DllMain for some reason??? + set_target_properties(chuniio_brokenithm PROPERTIES LINK_FLAGS "-Wl,--allow-multiple-definition") + + target_link_libraries(chuniio_brokenithm "-static-libgcc -Wl,-Bstatic -limobiledevice-1.0 -lssl -lcrypto -lplist-2.0 -lusbmuxd-2.0 -lpthread -Wl,-Bdynamic -lws2_32 -lcrypt32 -liphlpapi") +elseif (CMAKE_SIZEOF_VOID_P EQUAL 8) + target_link_libraries(chuniio_brokenithm "-static-libgcc") +endif () diff --git a/chuniio/src/arch.h b/chuniio/src/arch.h new file mode 100644 index 0000000..697f2aa --- /dev/null +++ b/chuniio/src/arch.h @@ -0,0 +1,24 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#ifndef BROKENITHM_ARCH_H +#define BROKENITHM_ARCH_H + +#if _WIN32 || _WIN64 +#if _WIN64 +#define ENV64BIT +#else +#define ENV32BIT +#endif // _WIN64 +#endif // _WIN32 || _WIN64 + +#if __GNUC__ +#if __x86_64__ || __ppc64__ +#define ENV64BIT +#else +#define ENV32BIT +#endif // __x86_64__ || __ppc64__ +#endif + +#endif // BROKENITHM_ARCH_H diff --git a/chuniio/src/chuniio.c b/chuniio/src/chuniio.c new file mode 100644 index 0000000..4d35edf --- /dev/null +++ b/chuniio/src/chuniio.c @@ -0,0 +1,231 @@ +// +// Created by beerpsi on 12/30/2023. +// + +#include "chuniio.h" + +#include + +#include "arch.h" +#include "config.h" +#include "struct.h" + +#define MEM_FILE_NAME "Local\\BROKENITHM_SHARED_BUFFER" + +struct IPCMemoryInfo *chuni_io_file_mapping; + +#ifdef ENV32BIT +#include "servers/android.h" +#include "servers/common.h" +#include "servers/ios.h" + +HRESULT server_start() { + HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, MEM_FILE_NAME); + + if (hMapFile == NULL) { + hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, + 1024, MEM_FILE_NAME); + if (hMapFile == NULL) { + print_err("[ERROR] CreateFileMapping failed! error: %lu\n", GetLastError()); + return E_FAIL; + } + } + + struct IPCMemoryInfo *memory = + MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1024, NULL); + chuni_io_file_mapping = memory; + + HRESULT result; + + if ((result = android_init_server(memory))) { + print_err("[ERROR] Android server intialization failed: %ld", result); + + return result; + } + + if ((result = ios_init_server(memory))) { + print_err("[ERROR] iOS server initialization failed: %ld", result); + + return result; + } + + return S_OK; +} +#endif + +#ifdef ENV64BIT +#include "util/dprintf.h" + +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), MEM_FILE_NAME)) == 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 *total) { + if (total == 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; + } + } + + *total = 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) { + const chuni_io_slider_callback_t callback = ctx; + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "LoopDoesntUseConditionVariableInspection" + // ReSharper disable once CppDFALoopConditionNotUpdated + while (!chuni_io_slider_stop_flag) { + uint8_t pressure[32]; + if (chuni_io_file_mapping) { + memcpy(pressure, chuni_io_file_mapping->sliderIoStatus, 32); + } + + callback(pressure); + Sleep(1); + } +#pragma clang diagnostic pop + + return 0; +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + return TRUE; +} diff --git a/src/chuniio.h b/chuniio/src/chuniio.h similarity index 97% rename from src/chuniio.h rename to chuniio/src/chuniio.h index f15ae96..b5aa536 100644 --- a/src/chuniio.h +++ b/chuniio/src/chuniio.h @@ -1,172 +1,172 @@ -// -// Created by beerpsi on 12/30/2023. -// - -#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H -#define CHUNIIO_BROKENITHM_CHUNIIO_H - -#include "socket.h" -#include - -#include -#include - -enum { - CHUNI_IO_OPBTN_TEST = 0x01, - CHUNI_IO_OPBTN_SERVICE = 0x02, - CHUNI_IO_OPBTN_COIN = 0x04, -}; - -/* Get the version of the Chunithm IO API that this DLL supports. This - function should return a positive 16-bit integer, where the high byte is - the major version and the low byte is the minor version (as defined by the - Semantic Versioning standard). - - The latest API version as of this writing is 0x0101. */ - -uint16_t __declspec(dllexport) chuni_io_get_api_version(); - -/* Initialize JVS-based input. This function will be called before any other - chuni_io_jvs_*() function calls. Errors returned from this function will - manifest as a disconnected JVS bus. - - All subsequent calls may originate from arbitrary threads and some may - overlap with each other. Ensuring synchronization inside your IO DLL is - your responsibility. - - Minimum API version: 0x0100 */ - -HRESULT __declspec(dllexport) chuni_io_jvs_init(); - -/* Poll JVS input. - - opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1 - is Service. - - beam returns the IR beams that are currently broken, where bit 0 is the - lowest IR beam and bit 5 is the highest IR beam, for a total of six beams. - - Both bit masks are active-high. - - Note that you cannot instantly break the entire IR grid in a single frame to - simulate hand movement; this will be judged as a miss. You need to simulate - a gradual raising and lowering of the hands. Consult the proof-of-concept - implementation for details. - - NOTE: Previous releases of Segatools mapped the IR beam inputs incorrectly. - Please ensure that you advertise an API version of at least 0x0101 so that - the correct mapping can be used. - - Minimum API version: 0x0100 - Latest API version: 0x0101 */ - -void __declspec(dllexport) chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); - -/* Read the current state of the coin counter. This value should be incremented - for every coin detected by the coin acceptor mechanism. This count does not - need to persist beyond the lifetime of the process. - - Minimum API version: 0x0100 */ - -void __declspec(dllexport) chuni_io_jvs_read_coin_counter(uint16_t *total); - - -/* Initialize touch slider emulation. This function will be called before any - other chuni_io_slider_*() function calls. - - All subsequent calls may originate from arbitrary threads and some may - overlap with each other. Ensuring synchronization inside your IO DLL is - your responsibility. - - Minimum API version: 0x0100 */ - -HRESULT __declspec(dllexport) chuni_io_slider_init(void); - -/* Chunithm touch slider layout: - - ^^^ Toward screen ^^^ - -----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ - 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 | -----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ - 32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | -----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ - - There are a total of 32 regions on the touch slider. Each region can return - an 8-bit pressure value. The operator menu allows the operator to adjust the - pressure level at which a region is considered to be pressed; the factory - default value for this setting is 20. */ - -/* Callback function supplied to your IO DLL. This must be called with a - pointer to a 32-byte array of pressure values, one byte per slider cell. - See above for layout and pressure threshold information. - - The callback will copy the pressure state data out of your buffer before - returning. The pointer will not be retained. */ - -typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); - -/* Start polling the slider. Your DLL must start a polling thread and call the - supplied function periodically from that thread with new input state. The - update interval is up to you, but if your input device doesn't have any - preferred interval then 1 kHz is a reasonable maximum frequency. - - Note that you do have to have to call the callback "occasionally" even if - nothing is changing, otherwise the game will raise a comm timeout error. - - Minimum API version: 0x0100 */ - -void __declspec(dllexport) chuni_io_slider_start(void *callback); - -/* Stop polling the slider. You must cease to invoke the input callback before - returning from this function. - - This *will* be called in the course of regular operation. For example, - every time you go into the operator menu the slider and all of the other I/O - on the cabinet gets restarted. - - Following on from the above, the slider polling loop *will* be restarted - after being stopped in the course of regular operation. Do not permanently - tear down your input driver in response to this function call. - - Minimum API version: 0x0100 */ - -void __declspec(dllexport) chuni_io_slider_stop(void); - -/* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96 - bytes is supplied. The illuminated areas on the touch slider are some - combination of rectangular regions and dividing lines between these regions - but the exact mapping of this lighting control buffer is still TBD. - - Minimum API version: 0x0100 */ - -void __declspec(dllexport) chuni_io_slider_set_leds(const uint8_t *rgb); - -/* Initialize LED emulation. This function will be called before any - other chuni_io_led_*() function calls. - - All subsequent calls may originate from arbitrary threads and some may - overlap with each other. Ensuring synchronization inside your IO DLL is - your responsibility. - - Minimum API version: 0x0102 */ - -HRESULT __declspec(dllexport) chuni_io_led_init(void); - -/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes. - - Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds). - board 0 is on the left side and board 1 on the right side of the cab - - left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower - right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower - - Each rgb value is comprised of 3 bytes in R,G,B order - - NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...) - - Minimum API version: 0x0102 */ - -void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb); - -#endif //CHUNIIO_BROKENITHM_CHUNIIO_H +// +// Created by beerpsi on 12/30/2023. +// + +#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H +#define CHUNIIO_BROKENITHM_CHUNIIO_H + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +enum { + CHUNI_IO_OPBTN_TEST = 0x01, + CHUNI_IO_OPBTN_SERVICE = 0x02, + CHUNI_IO_OPBTN_COIN = 0x04, +}; + +/* Get the version of the Chunithm IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0101. */ + +uint16_t __declspec(dllexport) chuni_io_get_api_version(); + +/* Initialize JVS-based input. This function will be called before any other + chuni_io_jvs_*() function calls. Errors returned from this function will + manifest as a disconnected JVS bus. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. + + Minimum API version: 0x0100 */ + +HRESULT __declspec(dllexport) chuni_io_jvs_init(); + +/* Poll JVS input. + + opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1 + is Service. + + beam returns the IR beams that are currently broken, where bit 0 is the + lowest IR beam and bit 5 is the highest IR beam, for a total of six beams. + + Both bit masks are active-high. + + Note that you cannot instantly break the entire IR grid in a single frame to + simulate hand movement; this will be judged as a miss. You need to simulate + a gradual raising and lowering of the hands. Consult the proof-of-concept + implementation for details. + + NOTE: Previous releases of Segatools mapped the IR beam inputs incorrectly. + Please ensure that you advertise an API version of at least 0x0101 so that + the correct mapping can be used. + + Minimum API version: 0x0100 + Latest API version: 0x0101 */ + +void __declspec(dllexport) chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); + +/* Read the current state of the coin counter. This value should be incremented + for every coin detected by the coin acceptor mechanism. This count does not + need to persist beyond the lifetime of the process. + + Minimum API version: 0x0100 */ + +void __declspec(dllexport) chuni_io_jvs_read_coin_counter(uint16_t *total); + + +/* Initialize touch slider emulation. This function will be called before any + other chuni_io_slider_*() function calls. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. + + Minimum API version: 0x0100 */ + +HRESULT __declspec(dllexport) chuni_io_slider_init(void); + +/* Chunithm touch slider layout: + + ^^^ Toward screen ^^^ + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + 32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + + There are a total of 32 regions on the touch slider. Each region can return + an 8-bit pressure value. The operator menu allows the operator to adjust the + pressure level at which a region is considered to be pressed; the factory + default value for this setting is 20. */ + +/* Callback function supplied to your IO DLL. This must be called with a + pointer to a 32-byte array of pressure values, one byte per slider cell. + See above for layout and pressure threshold information. + + The callback will copy the pressure state data out of your buffer before + returning. The pointer will not be retained. */ + +typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); + +/* Start polling the slider. Your DLL must start a polling thread and call the + supplied function periodically from that thread with new input state. The + update interval is up to you, but if your input device doesn't have any + preferred interval then 1 kHz is a reasonable maximum frequency. + + Note that you do have to have to call the callback "occasionally" even if + nothing is changing, otherwise the game will raise a comm timeout error. + + Minimum API version: 0x0100 */ + +void __declspec(dllexport) chuni_io_slider_start(void *callback); + +/* Stop polling the slider. You must cease to invoke the input callback before + returning from this function. + + This *will* be called in the course of regular operation. For example, + every time you go into the operator menu the slider and all of the other I/O + on the cabinet gets restarted. + + Following on from the above, the slider polling loop *will* be restarted + after being stopped in the course of regular operation. Do not permanently + tear down your input driver in response to this function call. + + Minimum API version: 0x0100 */ + +void __declspec(dllexport) chuni_io_slider_stop(void); + +/* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96 + bytes is supplied. The illuminated areas on the touch slider are some + combination of rectangular regions and dividing lines between these regions + but the exact mapping of this lighting control buffer is still TBD. + + Minimum API version: 0x0100 */ + +void __declspec(dllexport) chuni_io_slider_set_leds(const uint8_t *rgb); + +/* Initialize LED emulation. This function will be called before any + other chuni_io_led_*() function calls. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. + + Minimum API version: 0x0102 */ + +HRESULT __declspec(dllexport) chuni_io_led_init(void); + +/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes. + + Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds). + board 0 is on the left side and board 1 on the right side of the cab + + left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower + right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower + + Each rgb value is comprised of 3 bytes in R,G,B order + + NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...) + + Minimum API version: 0x0102 */ + +void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb); + +#endif //CHUNIIO_BROKENITHM_CHUNIIO_H diff --git a/src/config.c b/chuniio/src/config.c similarity index 100% rename from src/config.c rename to chuniio/src/config.c diff --git a/src/config.h b/chuniio/src/config.h similarity index 100% rename from src/config.h rename to chuniio/src/config.h diff --git a/chuniio/src/servers/android.c b/chuniio/src/servers/android.c new file mode 100644 index 0000000..78e9c91 --- /dev/null +++ b/chuniio/src/servers/android.c @@ -0,0 +1,466 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#include "android.h" +#include "arch.h" + +#ifdef ENV32BIT +#include +#include +#include +#include +#include + +#include "servers/common.h" +#include "socket.h" + +#define BUFSIZ 512 + +bool tcp_mode = true; +uint16_t server_port = 52468; + +enum CardType { + CARD_AIME, + CARD_FELICA, +}; + +typedef struct { + SOCKET sock; + char remote_address[40]; + uint16_t remote_port; + + atomic_bool exit_flag; + atomic_bool connected; + + uint32_t last_input_packet_id; + uint8_t last_card_id[10]; + + bool has_previous_led_status; + uint8_t previous_led_status[3 * 32]; + uint8_t led_skip_count; + + struct IPCMemoryInfo *memory; +} android_thread_ctx; + +void socket_set_timeout(const 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(const SOCKET sHost, const unsigned long addr, const 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(const SOCKET sHost, const struct sockaddr_in *addr, const char *buf, + const int len) { + return sendto(sHost, buf, len, 0, (struct sockaddr *)&addr, sizeof(&addr)); +} + +int make_ipv4_address(struct sockaddr_in *addr, const char *host, const 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); +} + +void get_socks_address(const struct PacketConnect *pkt, char *address, + const 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 print_card_info(const uint8_t card_type, const uint8_t *card_id) { + switch (card_type) { + case CARD_AIME: + print_err("[Android: INFO] Card type: AiMe, ID: "); + dump_bytes(card_id, 10, true); + break; + case CARD_FELICA: + print_err("[Android: INFO] Card type: FeliCa, ID: "); + dump_bytes(card_id, 8, true); + break; + default: + break; + } +} + +void update_packet_id(android_thread_ctx *ctx, const 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; +} + +unsigned int __stdcall led_broadcast_thread_proc(void *v) { + android_thread_ctx *ctx = v; + + const SOCKET sock = ctx->sock; + const 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'; + + while (!atomic_load(&ctx->exit_flag)) { + uint8_t current_led_status[3 * 32]; + + if (!atomic_load(&ctx->connected)) { + Sleep(50); + continue; + } + + memcpy(current_led_status, memory->ledRgbData, 3 * 32); + + bool same; + + if (!ctx->has_previous_led_status) { + same = memcmp(ctx->previous_led_status, current_led_status, 3 * 32) == 0; + } else { + same = false; + } + + memcpy(ctx->previous_led_status, current_led_status, 3 * 32); + ctx->has_previous_led_status = true; + + if (!same || ++ctx->led_skip_count > 50) { + memcpy(send_buffer + 4, current_led_status, 3 * 32); + + if (socket_send_to(sock, &addr, send_buffer, 100) < 0) { + print_err("[Android:ERROR] Cannot send packet: error %lu\n", WSAGetLastError()); + + if (tcp_mode) { + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { + continue; + } + + print_err("[Android: INFO] Device disconnected!\n"); + ctx->connected = false; + ctx->exit_flag = true; + break; + } + } + + ctx->led_skip_count = 0; + } + + Sleep(10); + } + + return 0; +} + +unsigned int __stdcall input_recv_thread_proc(void *v) { + android_thread_ctx *ctx = v; + + const SOCKET sock = ctx->sock; + struct IPCMemoryInfo *memory = ctx->memory; + + 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)) { + char buffer[BUFSIZ]; + + 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(sock, 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) { + const int read = recv(sock, buffer + recv_len, 4 - recv_len, 0); + + if (read == -1) { + int error = WSAGetLastError(); + + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || + error == WSAETIMEDOUT) { + continue; + } + + print_err("[Android: INFO] Device disconnected (could not read data, errno " + "%d, os error %ld)\n", + errno, error); + 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; + + while (recv_len < packet_len) { + const int read = + recv(sock, buffer + recv_len, packet_len - recv_len, 0); + + if (read == -1) { + int error = WSAGetLastError(); + + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || + error == WSAETIMEDOUT) { + continue; + } + + print_err("[Android: INFO] Device disconnected (could not read data, errno " + "%d, os error %ld)\n", + errno, error); + atomic_store(&ctx->connected, false); + atomic_store(&ctx->exit_flag, true); + break; + } + + recv_len = recv_len + read; + } + } + + if (packet_len >= sizeof(struct PacketInput) && + memcmp(buffer + 1, "INP", 3) == 0) { + const 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) && + memcmp(buffer + 1, "IPT", 3) == 0) { // without air + const 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) && + memcmp(buffer + 1, "FNC", 3) == 0) { + const struct PacketFunction *pkt = (struct PacketFunction *)buffer; + + switch (pkt->funcBtn) { + case FUNCTION_COIN: + memory->coinInsertion = 1; + break; + case FUNCTION_CARD: + memory->cardRead = 1; + break; + default: + break; + } + } else if (packet_len >= sizeof(struct PacketConnect) && + memcmp(buffer + 1, "CON", 3) == 0) { + const struct PacketConnect *pkt = (struct PacketConnect *)buffer; + + get_socks_address(pkt, ctx->remote_address, BUFSIZ - 1, &ctx->remote_port); + print_err("[Android: 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 && memcmp(buffer + 1, "DIS", 3) == 0) { + atomic_store(&ctx->connected, false); + + if (tcp_mode) { + atomic_store(&ctx->exit_flag, true); + print_err("[Android: INFO] Device disconnected (clean disconnect).\n"); + break; + } + + if (strlen(ctx->remote_address)) { + print_err("[Android: INFO] Device %s:%d disconnected.\n", ctx->remote_address, + ctx->remote_port); + memset(ctx->remote_address, 0, BUFSIZ); + } + + if (tcp_mode) { + break; + } + } else if (packet_len >= sizeof(struct PacketPing) && + memcmp(buffer + 1, "PIN", 3) == 0) { + if (!atomic_load(&ctx->connected)) { + continue; + } + + char response[13]; + memcpy(response, buffer, 12); + response[2] = 'O'; + + socket_send_to(sock, &addr, response, 13); + } else if (packet_len >= sizeof(struct PacketCard) && + memcmp(buffer + 1, "CRD", 3) == 0) { + const struct PacketCard *pkt = (struct PacketCard *)buffer; + + if (pkt->remoteCardRead) { + if (memcmp(ctx->last_card_id, pkt->remoteCardId, 10) != 0) { + print_err("[Android: INFO] Got remote card.\n"); + print_card_info(pkt->remoteCardType, pkt->remoteCardId); + memcpy(ctx->last_card_id, pkt->remoteCardId, 10); + } + } else if (memory->remoteCardRead) { + print_err("[Android: INFO] Remote card removed.\n"); + memset(ctx->last_card_id, 0, 10); + } + + memory->remoteCardRead = pkt->remoteCardRead; + memory->remoteCardType = pkt->remoteCardType; + memcpy(memory->remoteCardId, pkt->remoteCardId, 10); + } + } + + free(ctx); + + return 0; +} + +unsigned int __stdcall server_thread_proc(void *v) { + struct IPCMemoryInfo *memory = v; + + if (!tcp_mode) { + print_err("[Android: INFO] Mode: UDP\n"); + + const SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + socket_set_timeout(sock, 2000); + socket_bind(sock, htonl(INADDR_ANY), server_port); + + print_err("[Android: INFO] Waiting for device on port %d...\n", server_port); + + android_thread_ctx *ctx = malloc(sizeof(android_thread_ctx)); + ctx->sock = sock; + ctx->exit_flag = ATOMIC_VAR_INIT(false); + ctx->connected = ATOMIC_VAR_INIT(false); + ctx->last_input_packet_id = 0; + ctx->memory = memory; + ctx->has_previous_led_status = false; + ctx->led_skip_count = 0; + + HANDLE led_thread = + (HANDLE)_beginthreadex(NULL, 0, led_broadcast_thread_proc, ctx, 0, NULL); + HANDLE input_thread = + (HANDLE)_beginthreadex(NULL, 0, input_recv_thread_proc, ctx, 0, NULL); + + WaitForSingleObject(led_thread, INFINITE); + WaitForSingleObject(input_thread, INFINITE); + + CloseHandle(led_thread); + CloseHandle(input_thread); + + free(ctx); + } else { + print_err("[Android: INFO] Mode: TCP\n"); + + const SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + socket_set_timeout(sock, 50); + socket_bind(sock, htonl(INADDR_ANY), server_port); + + listen(sock, 10); + + print_err("[Android: 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; + + while ( + (acc_socket = accept(sock, (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("[Android: INFO] Device %s:%d connected.\n", user_address, + user_socket.sin_port); + } + + android_thread_ctx *ctx = malloc(sizeof(android_thread_ctx)); + ctx->sock = acc_socket; + ctx->exit_flag = ATOMIC_VAR_INIT(false); + ctx->connected = ATOMIC_VAR_INIT(true); + ctx->last_input_packet_id = 0; + ctx->memory = memory; + ctx->has_previous_led_status = false; + ctx->led_skip_count = 0; + + _beginthreadex(NULL, 0, led_broadcast_thread_proc, ctx, 0, NULL); + _beginthreadex(NULL, 0, input_recv_thread_proc, ctx, 0, NULL); + } + } +} +#endif // defined(ENV32BIT) + +HRESULT android_init_server(struct IPCMemoryInfo *memory) { +#ifdef ENV32BIT + tcp_mode = + GetPrivateProfileIntW(L"brokenithm", L"tcp", 1, 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("[Android:ERROR] WSA startup failed, os error %ld\n", + WSAGetLastError()); + return E_FAIL; + } + + _beginthreadex(NULL, 0, server_thread_proc, memory, 0, NULL); +#endif // defined(ENV32BIT) + + return S_OK; +} diff --git a/chuniio/src/servers/android.h b/chuniio/src/servers/android.h new file mode 100644 index 0000000..8ddef6c --- /dev/null +++ b/chuniio/src/servers/android.h @@ -0,0 +1,15 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#ifndef CHUNIIO_BROKENITHM_ANDROID_H +#define CHUNIIO_BROKENITHM_ANDROID_H + +#define WIN32_LEAN_AND_MEAN +#include + +#include "struct.h" + +HRESULT android_init_server(struct IPCMemoryInfo* memory); + +#endif // CHUNIIO_BROKENITHM_ANDROID_H diff --git a/chuniio/src/servers/common.c b/chuniio/src/servers/common.c new file mode 100644 index 0000000..2334671 --- /dev/null +++ b/chuniio/src/servers/common.c @@ -0,0 +1,72 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#include "common.h" + +#include + +#include "util/dprintf.h" + +void print_err(const char* fmt, ...) { + const time_t lt = time(NULL); + const 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 dump_bytes(const void *ptr, const size_t nbytes, const bool hex_string) { + size_t i; + size_t j; + + if (nbytes == 0) { + dprintf("\t--- Empty ---\n"); + } + + const uint8_t* bytes = 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++) { + uint8_t c = bytes[i + j]; + + if (c < 0x20 || c >= 0x7F) { + c = '.'; + } + + dprintf("%c", c); + } + + dprintf("\n"); + } + + dprintf("\n"); +} \ No newline at end of file diff --git a/chuniio/src/servers/common.h b/chuniio/src/servers/common.h new file mode 100644 index 0000000..66e7e65 --- /dev/null +++ b/chuniio/src/servers/common.h @@ -0,0 +1,21 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#ifndef CHUNIIO_BROKENITHM_COMMON_H +#define CHUNIIO_BROKENITHM_COMMON_H + +#include +#include + +enum FunctionButton { + INVALID, + FUNCTION_COIN, + FUNCTION_CARD, +}; + +void print_err(const char* fmt, ...); + +void dump_bytes(const void *ptr, const size_t nbytes, const bool hex_string); + +#endif // CHUNIIO_BROKENITHM_COMMON_H diff --git a/chuniio/src/servers/ios.c b/chuniio/src/servers/ios.c new file mode 100644 index 0000000..20f4283 --- /dev/null +++ b/chuniio/src/servers/ios.c @@ -0,0 +1,239 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#include "ios.h" +#include "arch.h" + +#ifdef ENV32BIT +#include +#include +#include + +#include "servers/common.h" + +typedef struct { + char remote_udid[41]; + idevice_t device; + idevice_connection_t connection; + + atomic_bool exit_flag; + + bool has_previous_led_status; + uint8_t previous_led_status[3 * 32]; + uint8_t led_skip_count; + + struct IPCMemoryInfo* memory; +} ios_thread_ctx; + +unsigned int __stdcall ios_led_broadcast_thread_proc(void *v) { + ios_thread_ctx* ctx = v; + + char send_buffer[4 + 3 * 32]; + send_buffer[0] = 99; + send_buffer[1] = 'L'; + send_buffer[2] = 'E'; + send_buffer[3] = 'D'; + + while (!atomic_load(&ctx->exit_flag)) { + uint8_t current_led_status[3 * 32]; + + memcpy(current_led_status, ctx->memory->ledRgbData, 3 * 32); + + bool same; + + if (!ctx->has_previous_led_status) { + same = memcmp(ctx->previous_led_status, current_led_status, 3 * 32) == 0; + } else { + same = false; + } + + memcpy(ctx->previous_led_status, current_led_status, 3 * 32); + ctx->has_previous_led_status = true; + + if (!same || ++ctx->led_skip_count > 50) { + memcpy(send_buffer + 4, current_led_status, 3 * 32); + + int status; + uint32_t sent; + if ((status = idevice_connection_send(ctx->connection, send_buffer, 100, &sent))) { + print_err("[iOS:ERROR] Cannot send LED packet: error %d\n", status); + } + + ctx->led_skip_count = 0; + } + + Sleep(10); + } + + return 0; +} + +unsigned int __stdcall ios_input_recv_thread_proc(void *v) { + ios_thread_ctx* ctx = v; + + bool air_enabled = true; + + while (!atomic_load(&ctx->exit_flag)) { + char buffer[BUFSIZ]; + int status; + uint32_t read; + + if ((status = idevice_connection_receive_timeout(ctx->connection, buffer, 4, &read, 5))) { + if (status == IDEVICE_E_TIMEOUT) { + continue; + } + + print_err("[iOS:ERROR] Could not read data from device: %d\n", status); + atomic_store(&ctx->exit_flag, true); + + break; + } + + int len = (unsigned char)buffer[0]; + + if ((status = idevice_connection_receive_timeout(ctx->connection, buffer + 4, len - 3, &read, 5))) { + print_err("[iOS:ERROR] Could not read data from device: %d\n", status); + atomic_store(&ctx->exit_flag, true); + + break; + } + + if (len >= sizeof(struct iOSPacketInput) && memcmp(buffer + 1, "INP", 3) == 0) { + struct iOSPacketInput* pkt = (struct iOSPacketInput*)buffer; + + if (air_enabled) { + memcpy(ctx->memory->airIoStatus, pkt->airIoStatus, sizeof(pkt->airIoStatus)); + } + + memcpy(ctx->memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus)); + ctx->memory->testBtn = pkt->testBtn; + ctx->memory->serviceBtn = pkt->serviceBtn; + } else if (len >= 4 && memcmp(buffer + 1, "AIR", 3) == 0) { + air_enabled = buffer[3] != 0; + + print_err("[iOS: INFO] Air input %s", air_enabled ? "enabled" : "disabled"); + } else if (len >= sizeof(struct PacketFunction) && memcmp(buffer + 1, "FNC", 3) == 0) { + const struct PacketFunction *pkt = (struct PacketFunction *) buffer; + + switch (pkt->funcBtn) { + case FUNCTION_COIN: + ctx->memory->coinInsertion = 1; + break; + case FUNCTION_CARD: + ctx->memory->cardRead = 1; + break; + default: + break; + } + } + } + + print_err("[iOS: INFO] Device disconnected."); + + idevice_disconnect(ctx->connection); + ctx->connection = NULL; + + idevice_free(ctx->device); + free(ctx); + + return 0; +} + +unsigned int __stdcall connect_device(void* v) { + ios_thread_ctx* ctx = v; + + int status; + if ((status = idevice_new(&ctx->device, ctx->remote_udid))) { + print_err("[iOS:ERROR] Create device failed: %d\n", status); + idevice_free(ctx->device); + return 1; + } + + if ((status = idevice_connect(ctx->device, 24864, &ctx->connection))) { + print_err("[iOS:ERROR] Connection failed: %d, retrying in 5 seconds\n", status); + + ctx->connection = NULL; + idevice_free(ctx->device); + + Sleep(5000); + + _beginthreadex(NULL, 0, connect_device, ctx, 0, NULL); + + return 1; + } + + char buf[5]; + uint32_t read; + + if ((status = idevice_connection_receive(ctx->connection, buf, 4, &read))) { + print_err("[iOS:ERROR] Receiving data failed: %d\n", status); + + idevice_disconnect(ctx->connection); + ctx->connection = NULL; + + idevice_free(ctx->device); + + return 1; + } + + if (memcmp(buf, "\x03WEL", 4) != 0) { + print_err("[iOS:ERROR] Client sent invalid data\n"); + + idevice_disconnect(ctx->connection); + ctx->connection = NULL; + + idevice_free(ctx->device); + + return 1; + } + + print_err("[iOS: INFO] Connected to device\n"); + atomic_store(&ctx->exit_flag, false); + _beginthreadex(NULL, 0, ios_input_recv_thread_proc, ctx, 0, NULL); + _beginthreadex(NULL, 0, ios_led_broadcast_thread_proc, ctx, 0, NULL); + + return 0; +} + +void device_event_callback(const idevice_event_t* event, void* user_data) { + struct IPCMemoryInfo* memory = user_data; + + switch (event->event) { + case IDEVICE_DEVICE_ADD: + print_err("[iOS: INFO] iDevice added, udid: %s\n", event->udid); + + ios_thread_ctx* args = malloc(sizeof(ios_thread_ctx)); + memcpy(args->remote_udid, event->udid, strlen(event->udid)); + args->device = NULL; + args->connection = NULL; + args->exit_flag = ATOMIC_VAR_INIT(false); + args->has_previous_led_status = false; + args->led_skip_count = 0; + args->memory = memory; + + _beginthreadex(NULL, 0, connect_device, args, 0, NULL); + break; + case IDEVICE_DEVICE_REMOVE: + print_err("[iOS: INFO] iDevice removed, udid: %s\n", event->udid); + break; + case IDEVICE_DEVICE_PAIRED: + print_err("[iOS: INFO] iDevice paired, udid: %s\n", event->udid); + break; + } +} +#endif // defined(ENV32BIT) + +HRESULT ios_init_server(struct IPCMemoryInfo *memory) { +#ifdef ENV32BIT + int status; + if ((status = idevice_event_subscribe(device_event_callback, memory))) { + print_err("[iOS:ERROR] Subscribing for iDevice events failed: %d\n", status); + return E_FAIL; + } else { + print_err("[iOS: INFO] Waiting for iDevices...\n"); + } +#endif // defined(ENV32BIT) + + return S_OK; +} diff --git a/chuniio/src/servers/ios.h b/chuniio/src/servers/ios.h new file mode 100644 index 0000000..5db3098 --- /dev/null +++ b/chuniio/src/servers/ios.h @@ -0,0 +1,15 @@ +// +// Created by beerpsi on 12/31/2023. +// + +#ifndef CHUNIIO_BROKENITHM_IOS_H +#define CHUNIIO_BROKENITHM_IOS_H + +#define WIN32_LEAN_AND_MEAN +#include + +#include "struct.h" + +HRESULT ios_init_server(struct IPCMemoryInfo* memory); + +#endif // CHUNIIO_BROKENITHM_IOS_H diff --git a/src/socket.h b/chuniio/src/socket.h similarity index 95% rename from src/socket.h rename to chuniio/src/socket.h index eb18da1..759a594 100644 --- a/src/socket.h +++ b/chuniio/src/socket.h @@ -1,44 +1,44 @@ -// -// Created by beerpsi on 12/29/2023. -// - -#ifndef CHUNIIO_BROKENITHM_SOCKET_H -#define CHUNIIO_BROKENITHM_SOCKET_H - -#ifdef _WIN32 -#ifndef WINVER -#define WINVER 0x0501 -#endif // WINVER -#include -#include -#else -//translate windows functions to linux functions -#include -#include -#define SOCKET int -#define INVALID_SOCKET (SOCKET)(~0) -#define SOCKET_ERROR (-1) -#define closesocket close -#define SOCKADDR_IN sockaddr_in -#define ZeroMemory(d,l) memset((d), 0, (l)) -#define ioctlsocket ioctl -#ifndef SA_INTERRUPT -#define SA_INTERRUPT 0 //ignore this setting -#endif -#define SD_BOTH SHUT_RDWR -#ifndef __hpux -#include -#endif /* __hpux */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -typedef sockaddr *LPSOCKADDR; -#endif // _WIN32 - -#endif //CHUNIIO_BROKENITHM_SOCKET_H +// +// Created by beerpsi on 12/29/2023. +// + +#ifndef CHUNIIO_BROKENITHM_SOCKET_H +#define CHUNIIO_BROKENITHM_SOCKET_H + +#ifdef _WIN32 +#ifndef WINVER +#define WINVER 0x0501 +#endif // WINVER +#include +#include +#else +//translate windows functions to linux functions +#include +#include +#define SOCKET int +#define INVALID_SOCKET (SOCKET)(~0) +#define SOCKET_ERROR (-1) +#define closesocket close +#define SOCKADDR_IN sockaddr_in +#define ZeroMemory(d,l) memset((d), 0, (l)) +#define ioctlsocket ioctl +#ifndef SA_INTERRUPT +#define SA_INTERRUPT 0 //ignore this setting +#endif +#define SD_BOTH SHUT_RDWR +#ifndef __hpux +#include +#endif /* __hpux */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef sockaddr *LPSOCKADDR; +#endif // _WIN32 + +#endif //CHUNIIO_BROKENITHM_SOCKET_H diff --git a/src/struct.h b/chuniio/src/struct.h similarity index 94% rename from src/struct.h rename to chuniio/src/struct.h index f767cbe..56ae49d 100644 --- a/src/struct.h +++ b/chuniio/src/struct.h @@ -1,100 +1,100 @@ -// -// Created by beerpsi on 12/29/2023. -// - -#ifndef CHUNIIO_BROKENITHM_STRUCT_H -#define CHUNIIO_BROKENITHM_STRUCT_H - -#include - -; -#pragma pack(push) -#pragma pack(1) - -struct IPCMemoryInfo -{ - uint8_t airIoStatus[6]; - uint8_t sliderIoStatus[32]; - uint8_t ledRgbData[32 * 3]; - uint8_t testBtn; - uint8_t serviceBtn; - uint8_t coinInsertion; - uint8_t cardRead; - uint8_t remoteCardRead; - uint8_t remoteCardType; - uint8_t remoteCardId[10]; -}; - -struct iOSPacketInput { - uint8_t packetSize; - uint8_t packetName[3]; - uint8_t airIoStatus[6]; - uint8_t sliderIoStatus[32]; - uint8_t testBtn; - uint8_t serviceBtn; -}; - -struct PacketInput -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint32_t packetId; - uint8_t airIoStatus[6]; - uint8_t sliderIoStatus[32]; - uint8_t testBtn; - uint8_t serviceBtn; -}; - -struct PacketInputNoAir -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint32_t packetId; - uint8_t sliderIoStatus[32]; - uint8_t testBtn; - uint8_t serviceBtn; -}; - -struct PacketFunction -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint8_t funcBtn; -}; - -struct PacketConnect -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint8_t addrType; - uint16_t port; - union - { - struct - { - uint8_t addr[4]; - uint8_t padding[12]; - } addr4; - uint8_t addr6[16]; - } addr; -}; - -struct PacketCard -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint8_t remoteCardRead; - uint8_t remoteCardType; - uint8_t remoteCardId[10]; -}; - -struct PacketPing -{ - uint8_t packetSize; - uint8_t packetName[3]; - uint64_t remotePingTime; -}; - -#pragma pack(pop) - -#endif //CHUNIIO_BROKENITHM_STRUCT_H +// +// Created by beerpsi on 12/29/2023. +// + +#ifndef CHUNIIO_BROKENITHM_STRUCT_H +#define CHUNIIO_BROKENITHM_STRUCT_H + +#include + +; +#pragma pack(push) +#pragma pack(1) + +struct IPCMemoryInfo +{ + uint8_t airIoStatus[6]; + uint8_t sliderIoStatus[32]; + uint8_t ledRgbData[32 * 3]; + uint8_t testBtn; + uint8_t serviceBtn; + uint8_t coinInsertion; + uint8_t cardRead; + uint8_t remoteCardRead; + uint8_t remoteCardType; + uint8_t remoteCardId[10]; +}; + +struct iOSPacketInput { + uint8_t packetSize; + uint8_t packetName[3]; + uint8_t airIoStatus[6]; + uint8_t sliderIoStatus[32]; + uint8_t testBtn; + uint8_t serviceBtn; +}; + +struct PacketInput +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint32_t packetId; + uint8_t airIoStatus[6]; + uint8_t sliderIoStatus[32]; + uint8_t testBtn; + uint8_t serviceBtn; +}; + +struct PacketInputNoAir +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint32_t packetId; + uint8_t sliderIoStatus[32]; + uint8_t testBtn; + uint8_t serviceBtn; +}; + +struct PacketFunction +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint8_t funcBtn; +}; + +struct PacketConnect +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint8_t addrType; + uint16_t port; + union + { + struct + { + uint8_t addr[4]; + uint8_t padding[12]; + } addr4; + uint8_t addr6[16]; + } addr; +}; + +struct PacketCard +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint8_t remoteCardRead; + uint8_t remoteCardType; + uint8_t remoteCardId[10]; +}; + +struct PacketPing +{ + uint8_t packetSize; + uint8_t packetName[3]; + uint64_t remotePingTime; +}; + +#pragma pack(pop) + +#endif //CHUNIIO_BROKENITHM_STRUCT_H diff --git a/src/chuniio.c b/src/chuniio.c deleted file mode 100644 index 766c8b1..0000000 --- a/src/chuniio.c +++ /dev/null @@ -1,889 +0,0 @@ -// -// Created by beerpsi on 12/30/2023. -// - -#include "chuniio.h" - -#include -#include -#include -#include - -#include - -#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"; - -//region Brokenithm Android -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; - bool exit_flag; - bool connected; - uint32_t last_input_packet_id; - struct IPCMemoryInfo* memory; -} android_thread_ctx; - -void socket_set_timeout(const 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(const SOCKET sHost, const unsigned long addr, const 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(const SOCKET sHost, const struct sockaddr_in* addr, const char* buf, const int len) { - return sendto(sHost, buf, len, 0, (struct sockaddr*)&addr, sizeof(&addr)); -} - -void print_err(const char* fmt, ...) { - const time_t lt = time(NULL); - const 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, const 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(android_thread_ctx* ctx, const 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, const size_t nbytes, const bool hex_string) -{ - size_t i; - size_t j; - - if (nbytes == 0) { - dprintf("\t--- Empty ---\n"); - } - - const uint8_t* bytes = 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++) { - uint8_t c = bytes[i + j]; - - if (c < 0x20 || c >= 0x7F) { - c = '.'; - } - - dprintf("%c", c); - } - - dprintf("\n"); - } - - dprintf("\n"); -} - -void print_card_info(const uint8_t card_type, const 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, const char* host, const 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 android_led_broadcast_thread_proc(void *v) { - android_thread_ctx *ctx = v; - - const SOCKET sHost = ctx->sock; - const 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'; - - while (!ctx->exit_flag) { - uint8_t current_led_status[3 * 32]; - - if (!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; - } - - print_err("[INFO] Device disconnected!\n"); - ctx->connected = false; - ctx->exit_flag = true; - break; - } - } - - skip_count = 0; - } - - Sleep(10); - } - - return 0; -} - -uint8_t last_card_id[10]; - -unsigned int __stdcall android_input_recv_thread_proc(void *v) { - android_thread_ctx *ctx = v; - - const SOCKET sHost = ctx->sock; - struct IPCMemoryInfo* memory = ctx->memory; - - struct sockaddr_in addr = {}; - make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port); - - int recv_len, packet_len; - uint8_t real_len; - - while (!ctx->exit_flag) { - char buffer[BUFSIZ]; - - 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) { - const int read = recv(sHost, buffer + recv_len, 4 - recv_len, 0); - - if (read == -1) { - int error = WSAGetLastError(); - - if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || error == WSAETIMEDOUT) { - continue; - } - - print_err("[INFO] Device disconnected (could not read data, errno %d, os error %ld)\n", errno, error); - ctx->connected = false; - ctx->exit_flag = true; - break; - } - - recv_len = recv_len + read; - } - - real_len = buffer[0]; - packet_len = real_len + 1; - - while (recv_len < packet_len) { - const int read = recv(sHost, buffer + recv_len, packet_len - recv_len, 0); - - if (read == -1) { - int error = WSAGetLastError(); - - if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || error == WSAETIMEDOUT) { - continue; - } - - print_err("[INFO] Device disconnected (could not read data, errno %d, os error %ld)\n", errno, error); - ctx->connected = false; - ctx->exit_flag = true; - break; - } - - recv_len = recv_len + read; - } - } - - if (packet_len >= sizeof(struct PacketInput) && memcmp(buffer + 1, "INP", 3) == 0) { - const 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) && memcmp(buffer + 1, "IPT", 3) == 0) { // without air - const 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) && memcmp(buffer + 1, "FNC", 3) == 0) { - const struct PacketFunction* pkt = (struct PacketFunction*)buffer; - - switch (pkt->funcBtn) { - case FUNCTION_COIN: - memory->coinInsertion = 1; - break; - case FUNCTION_CARD: - memory->cardRead = 1; - break; - default: - break; - } - } else if (packet_len >= sizeof(struct PacketConnect) && memcmp(buffer + 1, "CON", 3) == 0) { - const 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; - ctx->connected = true; - } else if (packet_len >= 4 && memcmp(buffer + 1, "DIS", 3) == 0) { - ctx->connected = false; - - if (tcp_mode) { - 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); - } - - if (tcp_mode) { - break; - } - } else if (packet_len >= sizeof(struct PacketPing) && memcmp(buffer + 1, "PIN", 3) == 0) { - if (!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) && memcmp(buffer + 1, "CRD", 3) == 0) { - const 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); - } - } - - free(ctx); - - return 0; -} - -unsigned int __stdcall server_thread_proc(void* ctx) { - struct IPCMemoryInfo* memory = ctx; - - if (!tcp_mode) { - print_err("[INFO] Mode: UDP\n"); - - const SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - socket_set_timeout(sock, 2000); - socket_bind(sock, htonl(INADDR_ANY), server_port); - - print_err("[INFO] Waiting for device on port %d...\n", server_port); - - android_thread_ctx args = { - .sock = sock, - .exit_flag = false, - .connected = false, - .last_input_packet_id = 0, - .memory = memory, - }; - - HANDLE led_thread = (HANDLE) _beginthreadex(NULL, 0, android_led_broadcast_thread_proc, &args, 0, NULL); - HANDLE input_thread = (HANDLE) _beginthreadex(NULL, 0, android_input_recv_thread_proc, &args, 0, NULL); - - WaitForSingleObject(led_thread, INFINITE); - WaitForSingleObject(input_thread, INFINITE); - - CloseHandle(led_thread); - CloseHandle(input_thread); - } else { - print_err("[INFO] Mode: TCP\n"); - const 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" - // ReSharper disable once CppDFAEndlessLoop - for (;;) { - struct sockaddr_in user_socket = {}; - socklen_t sock_size = sizeof(struct sockaddr_in); - const 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); - } - - android_thread_ctx* args = malloc(sizeof(android_thread_ctx)); - args->sock = acc_socket; - args->exit_flag = false; - args->connected = true; - args->last_input_packet_id = 0, - args->memory = memory; - - _beginthreadex(NULL, 0, android_led_broadcast_thread_proc, args, 0, NULL); - _beginthreadex(NULL, 0, android_input_recv_thread_proc, args, 0, NULL); - } -#pragma clang diagnostic pop - } - - return 0; -} -//endregion - -//region Brokenithm iOS -typedef struct { - char remote_udid[41]; - bool exit_flag; - struct IPCMemoryInfo* memory; - idevice_t device; - idevice_connection_t connection; -} ios_thread_ctx; - -unsigned int __stdcall ios_led_broadcast_thread_proc(void *v) { - ios_thread_ctx* ctx = v; - - char send_buffer[4 + 3 * 32]; - send_buffer[0] = 99; - send_buffer[1] = 'L'; - send_buffer[2] = 'E'; - send_buffer[3] = 'D'; - - while (!ctx->exit_flag) { - uint8_t current_led_status[3 * 32]; - - memcpy(current_led_status, ctx->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); - - int status; - uint32_t sent; - if ((status = idevice_connection_send(ctx->connection, send_buffer, 100, &sent))) { - print_err("[ERROR] Cannot send LED packet: error %d\n", status); - } - - skip_count = 0; - } - - Sleep(10); - } - - return 0; -} - -unsigned int __stdcall ios_input_recv_thread_proc(void *v) { - ios_thread_ctx* ctx = v; - - bool air_enabled = true; - - while (!ctx->exit_flag) { - char buffer[BUFSIZ]; - int status; - uint32_t read; - - if ((status = idevice_connection_receive_timeout(ctx->connection, buffer, 4, &read, 5))) { - if (status == IDEVICE_E_TIMEOUT) { - continue; - } - - print_err("[ERROR] Could not read data from device: %d\n", status); - ctx->exit_flag = true; - - break; - } - - int len = (unsigned char)buffer[0]; - - if ((status = idevice_connection_receive_timeout(ctx->connection, buffer + 4, len - 3, &read, 5))) { - print_err("[ERROR] Could not read data from device: %d\n", status); - ctx->exit_flag = true; - - break; - } - - if (len >= sizeof(struct iOSPacketInput) && memcmp(buffer + 1, "INP", 3) == 0) { - struct iOSPacketInput* pkt = (struct iOSPacketInput*)buffer; - - if (air_enabled) { - memcpy(ctx->memory->airIoStatus, pkt->airIoStatus, sizeof(pkt->airIoStatus)); - } - - memcpy(ctx->memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus)); - ctx->memory->testBtn = pkt->testBtn; - ctx->memory->serviceBtn = pkt->serviceBtn; - } else if (len >= 4 && memcmp(buffer + 1, "AIR", 3) == 0) { - air_enabled = buffer[3] != 0; - - print_err("[INFO] Air input %s", air_enabled ? "enabled" : "disabled"); - } else if (len >= sizeof(struct PacketFunction) && memcmp(buffer + 1, "FNC", 3) == 0) { - const struct PacketFunction *pkt = (struct PacketFunction *) buffer; - - switch (pkt->funcBtn) { - case FUNCTION_COIN: - ctx->memory->coinInsertion = 1; - break; - case FUNCTION_CARD: - ctx->memory->cardRead = 1; - break; - default: - break; - } - } - } - - print_err("[INFO] Device disconnected."); - - idevice_disconnect(ctx->connection); - ctx->connection = NULL; - - idevice_free(ctx->device); - free(ctx); - - return 0; -} - -unsigned int __stdcall connect_device(void* v) { - ios_thread_ctx* ctx = v; - - int status; - if ((status = idevice_new(&ctx->device, ctx->remote_udid))) { - print_err("[ERROR] Create device failed: %d\n", status); - idevice_free(ctx->device); - return 1; - } - - if ((status = idevice_connect(ctx->device, 24864, &ctx->connection))) { - print_err("[ERROR] Connection failed: %d, retrying in 5 seconds\n", status); - - ctx->connection = NULL; - idevice_free(ctx->device); - - Sleep(5000); - - _beginthreadex(NULL, 0, connect_device, ctx, 0, NULL); - - return 1; - } - - char buf[5]; - uint32_t read; - - if ((status = idevice_connection_receive(ctx->connection, buf, 4, &read))) { - print_err("[ERROR] Receiving data failed: %d\n", status); - - idevice_disconnect(ctx->connection); - ctx->connection = NULL; - - idevice_free(ctx->device); - - return 1; - } - - if (memcmp(buf, "\x03WEL", 4) != 0) { - print_err("[ERROR] Client sent invalid data\n"); - - idevice_disconnect(ctx->connection); - ctx->connection = NULL; - - idevice_free(ctx->device); - - return 1; - } - - print_err("[INFO] Connected to device\n"); - ctx->exit_flag = false; - _beginthreadex(NULL, 0, ios_input_recv_thread_proc, ctx, 0, NULL); - _beginthreadex(NULL, 0, ios_led_broadcast_thread_proc, ctx, 0, NULL); - - return 0; -} - -void device_event_callback(const idevice_event_t* event, void* user_data) { - struct IPCMemoryInfo* memory = user_data; - - switch (event->event) { - case IDEVICE_DEVICE_ADD: - print_err("[INFO] iDevice added, udid: %s\n", event->udid); - - ios_thread_ctx* args = malloc(sizeof(ios_thread_ctx)); - args->exit_flag = false; - args->memory = memory; - args->device = NULL; - args->connection = NULL; - memcpy(args->remote_udid, event->udid, strlen(event->udid)); - - _beginthreadex(NULL, 0, connect_device, args, 0, NULL); - break; - case IDEVICE_DEVICE_REMOVE: - print_err("[INFO] iDevice removed, udid: %s\n", event->udid); - break; - case IDEVICE_DEVICE_PAIRED: - print_err("[INFO] iDevice paired, udid: %s\n", event->udid); - break; - } -} -//endregion - -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"); - - 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 = 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; - } - - struct WSAData wsaData = {}; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - print_err("[ERROR] WSA startup failed!\n"); - return E_FAIL; - } - - _beginthreadex(NULL, 0, server_thread_proc, memory, 0, NULL); - - int status; - if ((status = idevice_event_subscribe(device_event_callback, memory))) { - print_err("[ERROR] Subscribing for iDevice events failed: %d\n", status); - return E_FAIL; - } else { - print_err("[INFO] Waiting for iDevices...\n"); - } - - 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"); - - const HRESULT result = server_start(); - if (result != S_OK) { - return result; - } - - return S_OK; -} - -void chuni_io_jvs_read_coin_counter(uint16_t *total) { - if (total == 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; - } - } - - *total = 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) -{ - const chuni_io_slider_callback_t callback = ctx; - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "LoopDoesntUseConditionVariableInspection" - // ReSharper disable once CppDFALoopConditionNotUpdated - while (!chuni_io_slider_stop_flag) { - uint8_t pressure[32]; - 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; -} diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..a62e187 --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.27) +project(util) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +include_directories("${CMAKE_SOURCE_DIR}/include/") +set(CMAKE_CXX_STANDARD 17) + +add_library(util OBJECT dprintf.c dprintf.h) diff --git a/src/util/dprintf.c b/util/dprintf.c similarity index 100% rename from src/util/dprintf.c rename to util/dprintf.c diff --git a/src/util/dprintf.h b/util/dprintf.h similarity index 100% rename from src/util/dprintf.h rename to util/dprintf.h