Compare commits

...

17 Commits

Author SHA1 Message Date
beerpsi 48c5a4f053 add LED pipe for billboard 2024-01-10 13:59:29 +07:00
beerpsi 44ba947998 docs: Put up a note that Brokenithm options are Android-specific 2024-01-03 11:45:09 +00:00
beerpsi 348d6020c5 Enable LTO 2024-01-01 17:17:11 +07:00
beerpsi 1bb616e1ec use a standard way to determine 32-bit/64-bit environment 2024-01-01 17:03:20 +07:00
beerpsi db9ac578e3 simplify server initialization code 2024-01-01 16:55:57 +07:00
beerpsi 1ec4c35ae4 even more atomic shit baby 2024-01-01 16:32:55 +07:00
beerpsi 3dffb242c3 why tf did i use memcpy for strings 2023-12-31 19:34:23 +07:00
beerpsi 255eb07d31 fix compilation error caused by conflicting names 2023-12-31 14:41:41 +07:00
beerpsi 59ce0565e5 Refactor and reformat 2023-12-31 14:37:45 +07:00
beerpsi 33fa1c362a Fixes some buffer overflow waiting to happen 2023-12-31 14:21:47 +07:00
beerpsi 0629198429 aimeio.path64 -> aimeio.path 2023-12-31 06:50:22 +00:00
beerpsi 70a640f9fc It's no longer UDP by default 2023-12-31 06:48:42 +00:00
beerpsi 30f3a23bc6 iOS support and other niceties (#1) 2023-12-31 13:20:04 +07:00
beerpsi b681f877e9 Remove dependency on a specific compiler 2023-12-31 13:16:09 +07:00
beerpsi f101a07bb2 Refactor + AimeIO
- No more global state, every connection has their own context
- Split off server implementations into their own files
- Added Brokenithm AimeIO so users don't have to scour their own files
2023-12-31 13:12:07 +07:00
beerpsi 2da8f0a699 Works (except it crashes when Brokenithm is out of focus) 2023-12-30 23:50:42 +07:00
beerpsi@duck.com cd5503d380 ios code, but it doesn't work because linking bullshit 2023-12-30 17:43:30 +07:00
31 changed files with 2297 additions and 1007 deletions

View File

@ -1,20 +1,18 @@
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_C_STANDARD 11)
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)
include(CheckIPOSupported)
check_ipo_supported(RESULT IPO_SUPPORTED)
if (IPO_SUPPORTED)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif ()
set_target_properties(chuniio_brokenithm PROPERTIES PREFIX "")
# set_target_properties(chuniio_brokenithm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
target_link_libraries(chuniio_brokenithm ws2_32)
include_directories(.)
add_subdirectory(aimeio)
add_subdirectory(chuniio)
add_subdirectory(util)

View File

@ -1,44 +1,56 @@
# chuniio-brokenithm
ChuniIO driver for [Brokenithm-Android](https://github.com/tindy2013/Brokenithm-Android)
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
[chuniio]
path=chuniio_brokenithm.dll
[io3]
; Test button virtual-key code. Default is the 1 key.
test=0x31
; Service button virtual-key code. Default is the 2 key.
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; AIR sensor emulator. Default is the space key.
; If using individual-ray IR (see below), set this value to 0.
ir=0x20
[brokenithm]
; Use TCP instead of UDP for connections (default UDP)
tcp=1
; Port to accept connections on (default 52468)
port=52468
```
## Build instructions
```shell
mkdir cmake-build
cd cmake-build
cmake ..
ninja
ls chuniio_brokenithm.dll
# chuniio-brokenithm
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.
## Configuration
segatools.ini
```ini
[aimeio]
path=aimeio_brokenithm.dll
[chuniio]
path32=chuniio_brokenithm_x86.dll
path64=chuniio_brokenithm_x64.dll
[io3]
; Test button virtual-key code. Default is the 1 key.
test=0x31
; Service button virtual-key code. Default is the 2 key.
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; AIR sensor emulator. Default is the space key.
; If using individual-ray IR (see below), set this value to 0.
ir=0x20
[brokenithm]
; Brokenithm-Android: Use TCP instead of UDP for connections (default TCP)
tcp=1
; Brokenithm-Android: Port to accept connections on (default 52468)
port=52468
```
## Build instructions
```shell
# In MinGW32
pacman -S mingw-w64-i686-libimobiledevice
mkdir -p build/build32
cd build/build32
cmake ../..
ninja
# 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
```

17
aimeio/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
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_C_STANDARD 11)
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")

347
aimeio/src/aimeio.c Normal file
View File

@ -0,0 +1,347 @@
//
// Created by beerpsi on 12/31/2023.
//
#include "aimeio.h"
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#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) {}

91
aimeio/src/aimeio.h Normal file
View File

@ -0,0 +1,91 @@
//
// Created by beerpsi on 12/31/2023.
//
#ifndef BROKENITHM_AIMEIO_H
#define BROKENITHM_AIMEIO_H
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
/*
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

40
chuniio/CMakeLists.txt Normal file
View File

@ -0,0 +1,40 @@
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_C_STANDARD 11)
link_directories(src)
add_library(chuniio_brokenithm SHARED src/chuniio.c
src/chuniio.h
src/config.c
src/config.h
src/leddata.h
src/ledoutput.c
src/ledoutput.h
src/pipeimpl.c
src/pipeimpl.h
src/serialimpl.c
src/serialimpl.h
src/servers/socket.h
src/ipc_memory_info.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 ()

18
chuniio/src/arch.h Normal file
View File

@ -0,0 +1,18 @@
//
// Created by beerpsi on 12/31/2023.
//
#ifndef BROKENITHM_ARCH_H
#define BROKENITHM_ARCH_H
#include <stdint.h>
#if UINTPTR_MAX == UINT32_MAX
#define ENV32BIT
#elif UINTPTR_MAX == UINT64_MAX
#define ENV64BIT
#else
#error "Environment is neither 32-bit nor 64-bit."
#endif
#endif // BROKENITHM_ARCH_H

221
chuniio/src/chuniio.c Normal file
View File

@ -0,0 +1,221 @@
//
// Created by beerpsi on 12/30/2023.
//
#include "chuniio.h"
#include <process.h>
#include <stdatomic.h>
#include "arch.h"
#include "config.h"
#include "ledoutput.h"
#include "ipc_memory_info.h"
#include "util/dprintf.h"
#ifdef ENV32BIT
#include "servers/android.h"
#include "servers/common.h"
#include "servers/ios.h"
#endif
#define MEM_FILE_NAME "Local\\BROKENITHM_SHARED_BUFFER"
static HANDLE chuni_io_file_mapping_handle;
struct IPCMemoryInfo *chuni_io_file_mapping;
HRESULT chuni_io_init_shared_memory() {
if (chuni_io_file_mapping) {
dprintf("chuni_io_init_shared_memory: shared memory already exists\n");
return E_FAIL;
}
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 E_FAIL;
}
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 E_FAIL;
}
memset(chuni_io_file_mapping, 0, sizeof(struct IPCMemoryInfo));
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
return S_OK;
}
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 volatile atomic_flag chuni_io_slider_run_flag;
static struct chuni_io_config chuni_io_cfg;
uint16_t chuni_io_get_api_version() { return 0x0102; }
HRESULT chuni_io_jvs_init() {
chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini");
HRESULT result;
if ((result = chuni_io_init_shared_memory())) {
return result;
}
led_init_mutex = CreateMutex(NULL, FALSE, NULL);
if (led_init_mutex == NULL) {
return E_FAIL;
}
#ifdef ENV32BIT
if ((result = android_init_server(chuni_io_file_mapping))) {
print_err("[ERROR] Android server intialization failed: %ld", result);
return result;
}
if ((result = ios_init_server(chuni_io_file_mapping))) {
print_err("[ERROR] iOS server initialization failed: %ld", result);
return result;
}
#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;
}
if ((chuni_io_file_mapping && chuni_io_file_mapping->serviceBtn) ||
GetAsyncKeyState(chuni_io_cfg.vk_service)) {
*opbtn |= CHUNI_IO_OPBTN_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 led_output_init(&chuni_io_cfg);
}
void chuni_io_slider_start(void *callback) {
if (chuni_io_slider_thread != NULL) {
return;
}
atomic_flag_test_and_set(&chuni_io_slider_run_flag);
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;
}
atomic_flag_clear(&chuni_io_slider_run_flag);
WaitForSingleObject(chuni_io_slider_thread, INFINITE);
CloseHandle(chuni_io_slider_thread);
chuni_io_slider_thread = NULL;
}
void chuni_io_slider_set_leds(const uint8_t *rgb) {
led_output_update(2, rgb);
if (chuni_io_file_mapping) {
memcpy(chuni_io_file_mapping->ledRgbData, rgb, 32 * 3);
}
}
HRESULT chuni_io_led_init(void) {
return led_output_init(&chuni_io_cfg);
}
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb) {
led_output_update(board, rgb);
}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx) {
const chuni_io_slider_callback_t callback = ctx;
while (atomic_flag_test_and_set(&chuni_io_slider_run_flag)) {
uint8_t pressure[32];
if (chuni_io_file_mapping) {
memcpy(pressure, chuni_io_file_mapping->sliderIoStatus, 32);
}
callback(pressure);
Sleep(1);
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
return TRUE;
}

View File

@ -5,22 +5,6 @@
#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H
#define CHUNIIO_BROKENITHM_CHUNIIO_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
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -40,7 +24,7 @@ enum {
The latest API version as of this writing is 0x0101. */
uint16_t chuni_io_get_api_version();
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
@ -52,7 +36,7 @@ uint16_t chuni_io_get_api_version();
Minimum API version: 0x0100 */
HRESULT chuni_io_jvs_init();
HRESULT __declspec(dllexport) chuni_io_jvs_init();
/* Poll JVS input.
@ -76,7 +60,7 @@ HRESULT chuni_io_jvs_init();
Minimum API version: 0x0100
Latest API version: 0x0101 */
void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
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
@ -84,8 +68,7 @@ void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
Minimum API version: 0x0100 */
void chuni_io_jvs_read_coin_counter(uint16_t *total);
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.
@ -96,7 +79,7 @@ void chuni_io_jvs_read_coin_counter(uint16_t *total);
Minimum API version: 0x0100 */
HRESULT chuni_io_slider_init(void);
HRESULT __declspec(dllexport) chuni_io_slider_init(void);
/* Chunithm touch slider layout:
@ -132,7 +115,7 @@ typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
Minimum API version: 0x0100 */
void chuni_io_slider_start(void *callback);
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.
@ -147,7 +130,7 @@ void chuni_io_slider_start(void *callback);
Minimum API version: 0x0100 */
void chuni_io_slider_stop(void);
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
@ -156,7 +139,7 @@ void chuni_io_slider_stop(void);
Minimum API version: 0x0100 */
void chuni_io_slider_set_leds(const uint8_t *rgb);
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.
@ -167,15 +150,17 @@ void chuni_io_slider_set_leds(const uint8_t *rgb);
Minimum API version: 0x0102 */
HRESULT chuni_io_led_init(void);
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
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
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
@ -183,6 +168,6 @@ HRESULT chuni_io_led_init(void);
Minimum API version: 0x0102 */
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
#endif //CHUNIIO_BROKENITHM_CHUNIIO_H
#endif // CHUNIIO_BROKENITHM_CHUNIIO_H

62
chuniio/src/config.c Normal file
View File

@ -0,0 +1,62 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
static const int chuni_io_default_cells[] = {
'L', 'L', 'L', 'L', 'K', 'K', 'K', 'K', 'J', 'J', 'J', 'J', 'H', 'H', 'H', 'H',
'G', 'G', 'G', 'G', 'F', 'F', 'F', 'F', 'D', 'D', 'D', 'D', 'S', 'S', 'S', 'S',
};
static const int chuni_io_default_ir[] = {'4', '5', '6', '7', '8', '9'};
void chuni_io_config_load(struct chuni_io_config *cfg, const wchar_t *filename) {
wchar_t key[16];
int i;
wchar_t port_input[6];
assert(cfg != NULL);
assert(filename != NULL);
// Technically it's io4 but leave this for compatibility with old configs.
cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", '3', filename);
cfg->vk_ir_emu = GetPrivateProfileIntW(L"io3", L"ir", VK_SPACE, filename);
for (i = 0; i < 6; i++) {
swprintf_s(key, _countof(key), L"ir%i", i + 1);
cfg->vk_ir[i] =
GetPrivateProfileIntW(L"ir", key, chuni_io_default_ir[i], filename);
}
for (i = 0; i < 32; i++) {
swprintf_s(key, _countof(key), L"cell%i", i + 1);
cfg->vk_cell[i] =
GetPrivateProfileIntW(L"slider", key, chuni_io_default_cells[i], filename);
}
cfg->led_output_pipe =
GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename);
cfg->led_output_serial =
GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename);
cfg->slider_led_output_pipe =
GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename);
cfg->slider_led_output_serial =
GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename);
cfg->led_serial_baud =
GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename);
GetPrivateProfileStringW(L"led", L"serialPort", L"COM5", port_input, 6, filename);
// Sanitize the output path. If it's a serial COM port, it needs to be prefixed
// with `\\.\`.
wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4);
wcsncat_s(cfg->led_serial_port, 11, port_input, 6);
}

View File

@ -4,9 +4,9 @@
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
struct chuni_io_config {
uint8_t vk_test;
@ -26,12 +26,9 @@ struct chuni_io_config {
// The name of a COM port to output LED data on, in serial mode
wchar_t led_serial_port[12];
int32_t led_serial_baud;
};
void chuni_io_config_load(
struct chuni_io_config *cfg,
const wchar_t *filename);
void chuni_io_config_load(struct chuni_io_config *cfg, const wchar_t *filename);
#ifdef __cplusplus
}

View File

@ -0,0 +1,27 @@
//
// Created by beerpsi on 12/29/2023.
//
#ifndef CHUNIIO_BROKENITHM_STRUCT_H
#define CHUNIIO_BROKENITHM_STRUCT_H
#include <stdint.h>
;
#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];
};
#pragma pack(pop)
#endif // CHUNIIO_BROKENITHM_STRUCT_H

22
chuniio/src/leddata.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include <stdint.h>
#define LED_PACKET_FRAMING 0xE0
#define LED_PACKET_ESCAPE 0xD0
#define LED_NUM_MAX 66
#define LED_BOARDS_TOTAL 3
#define LED_OUTPUT_HEADER_SIZE 2
#define LED_OUTPUT_DATA_SIZE_MAX LED_NUM_MAX * 3 * 2 // max if every byte's escaped
#define LED_OUTPUT_TOTAL_SIZE_MAX LED_OUTPUT_HEADER_SIZE + LED_OUTPUT_DATA_SIZE_MAX
// This struct is used to send data related to the slider and billboard LEDs
struct _chuni_led_data_buf_t {
byte framing; // Sync byte
uint8_t board; // LED output the data is for (0-1: billboard, 2: slider)
byte data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs
byte data_len; // How many bytes to output from the buffer
};
static byte chuni_led_board_data_lens[LED_BOARDS_TOTAL] = {53*3, 63*3, 31*3};

131
chuniio/src/ledoutput.c Normal file
View File

@ -0,0 +1,131 @@
#include <windows.h>
#include <stdbool.h>
#include "config.h"
#include "leddata.h"
#include "ledoutput.h"
#include "pipeimpl.h"
#include "serialimpl.h"
static struct _chuni_led_data_buf_t led_unescaped_buf[LED_BOARDS_TOTAL];
static struct _chuni_led_data_buf_t led_escaped_buf[LED_BOARDS_TOTAL];
static bool led_output_is_init = false;
static struct chuni_io_config* config;
static bool any_outputs_enabled;
HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg)
{
DWORD dwWaitResult = WaitForSingleObject(led_init_mutex, INFINITE);
if (dwWaitResult == WAIT_FAILED)
{
return HRESULT_FROM_WIN32(GetLastError());
// return 1;
}
else if (dwWaitResult != WAIT_OBJECT_0)
{
return E_FAIL;
// return 1;
}
if (!led_output_is_init)
{
config = cfg;
// Set up the framing bytes for the packets
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
led_unescaped_buf[i].framing = LED_PACKET_FRAMING;
led_unescaped_buf[i].board = i;
led_unescaped_buf[i].data_len = chuni_led_board_data_lens[i];
led_escaped_buf[i].framing = LED_PACKET_FRAMING;
led_escaped_buf[i].board = i;
led_escaped_buf[i].data_len = chuni_led_board_data_lens[i];
}
any_outputs_enabled = config->led_output_pipe || config->slider_led_output_pipe
|| config->led_output_serial || config->slider_led_output_serial;
if (config->led_output_pipe || config->slider_led_output_pipe)
{
led_pipe_init(); // don't really care about errors here tbh
}
if (config->led_output_serial || config->slider_led_output_serial)
{
led_serial_init(config->led_serial_port, config->led_serial_baud);
}
}
led_output_is_init = true;
ReleaseMutex(led_init_mutex);
return S_OK;
// return 0;
}
struct _chuni_led_data_buf_t* escape_led_data(struct _chuni_led_data_buf_t* unescaped)
{
struct _chuni_led_data_buf_t* out_struct = &led_escaped_buf[unescaped->board];
byte* in_buf = unescaped->data;
byte* out_buf = out_struct->data;
int i = 0;
int o = 0;
while (i < unescaped->data_len)
{
byte b = in_buf[i++];
if (b == LED_PACKET_FRAMING || b == LED_PACKET_ESCAPE)
{
out_buf[o++] = LED_PACKET_ESCAPE;
b--;
}
out_buf[o++] = b;
}
out_struct->data_len = o;
return out_struct;
}
void led_output_update(uint8_t board, const uint8_t* rgb)
{
if (board < 0 || board > 2 || !any_outputs_enabled)
{
return;
}
memcpy(led_unescaped_buf[board].data, rgb, led_unescaped_buf[board].data_len);
struct _chuni_led_data_buf_t* escaped_data = escape_led_data(&led_unescaped_buf[board]);
if (board < 2)
{
// billboard
if (config->led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->led_output_serial)
{
led_serial_update(escaped_data);
}
}
else
{
// slider
if (config->slider_led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->slider_led_output_serial)
{
led_serial_update(escaped_data);
}
}
}

19
chuniio/src/ledoutput.h Normal file
View File

@ -0,0 +1,19 @@
/*
LED output functions
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "config.h"
extern HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg);
void led_output_update(uint8_t board, const uint8_t* rgb);

160
chuniio/src/pipeimpl.c Normal file
View File

@ -0,0 +1,160 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "leddata.h"
#include "pipeimpl.h"
static bool pipe_update[LED_BOARDS_TOTAL];
// incoming data is copied into these to ensure it isn't written during output
static struct _chuni_led_data_buf_t pipe_write_buf[LED_BOARDS_TOTAL];
static HANDLE pipe_write_mutex;
static HRESULT pipe_create(LPHANDLE hPipe, LPCWSTR lpszPipename, DWORD dwBufSize)
{
*hPipe = INVALID_HANDLE_VALUE;
*hPipe = CreateNamedPipeW(
lpszPipename, // pipe name
PIPE_ACCESS_OUTBOUND, // read/write access
PIPE_TYPE_BYTE | // byte type pipe
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
dwBufSize, // output buffer size
0, // input buffer size
0, // client time-out
NULL); // default security attribute
if (*hPipe == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
return S_OK;
}
static HRESULT pipe_write(HANDLE hPipe, LPCVOID lpBuffer, DWORD dwSize)
{
DWORD cbWritten = 0;
bool fSuccess = WriteFile(
hPipe,
lpBuffer,
dwSize,
&cbWritten,
NULL);
if (!fSuccess || cbWritten != dwSize)
{
DWORD last_err = GetLastError();
return (last_err == ERROR_BROKEN_PIPE) ? E_ABORT : E_FAIL;
}
return S_OK;
}
static unsigned int __stdcall chuni_io_led_pipe_thread_proc(void *ctx)
{
HANDLE hPipe;
LPCWSTR lpszPipename = L"\\\\.\\pipe\\chuni_led";
while (true)
{
hPipe = INVALID_HANDLE_VALUE;
if (pipe_create(&hPipe, lpszPipename, LED_OUTPUT_TOTAL_SIZE_MAX) != S_OK)
{
continue;
}
// wait for a connection to the pipe
bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
true : (GetLastError() == ERROR_PIPE_CONNECTED);
while (fConnected)
{
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
continue;
}
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
if (pipe_update[i])
{
HRESULT result = pipe_write(
hPipe,
&pipe_write_buf[i],
LED_OUTPUT_HEADER_SIZE + pipe_write_buf[i].data_len);
if (result != S_OK)
{
//if (result == E_ABORT)
//{
fConnected = false;
//}
break;
}
pipe_update[i] = false;
}
}
ReleaseMutex(pipe_write_mutex);
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
return 0;
}
HRESULT led_pipe_init()
{
pipe_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (pipe_write_mutex == NULL)
{
return E_FAIL;
}
// clear out update bools
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
pipe_update[i] = false;
}
_beginthreadex(
NULL,
0,
chuni_io_led_pipe_thread_proc,
0,
0,
NULL);
return S_OK;
}
void led_pipe_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
memcpy(&pipe_write_buf[data->board], data, sizeof(struct _chuni_led_data_buf_t));
pipe_update[data->board] = true;
ReleaseMutex(pipe_write_mutex);
}

14
chuniio/src/pipeimpl.h Normal file
View File

@ -0,0 +1,14 @@
/*
Pipe implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "leddata.h"
HRESULT led_pipe_init();
void led_pipe_update(struct _chuni_led_data_buf_t* data);

99
chuniio/src/serialimpl.c Normal file
View File

@ -0,0 +1,99 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "leddata.h"
#include "serialimpl.h"
#include "util/dprintf.h"
static HANDLE serial_port;
static HANDLE serial_write_mutex;
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud)
{
// Setup the serial communications
BOOL status;
serial_port = CreateFileW(led_com,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (serial_port == INVALID_HANDLE_VALUE)
dprintf("Chunithm Serial LEDs: Failed to open COM port (Attempted on %S)\n", led_com);
else
dprintf("Chunithm Serial LEDs: COM port success!\n");
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
status = GetCommState(serial_port, &dcb_serial_params);
dcb_serial_params.BaudRate = baud;
dcb_serial_params.ByteSize = 8;
dcb_serial_params.StopBits = ONESTOPBIT;
dcb_serial_params.Parity = NOPARITY;
SetCommState(serial_port, &dcb_serial_params);
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(serial_port, &timeouts);
if (!status)
{
return E_FAIL;
}
serial_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (serial_write_mutex == NULL)
{
return E_FAIL;
}
return S_OK;
}
void led_serial_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(serial_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
BOOL status = true;
DWORD bytes_written = 0;
if (serial_port != INVALID_HANDLE_VALUE) {
status = WriteFile(
serial_port,
data,
LED_OUTPUT_HEADER_SIZE + data->data_len,
&bytes_written,
NULL);
}
if (!status) {
DWORD last_err = GetLastError();
// dprintf("Chunithm Serial LEDs: Serial port write failed -- %d\n", last_err);
}
ReleaseMutex(serial_write_mutex);
}

15
chuniio/src/serialimpl.h Normal file
View File

@ -0,0 +1,15 @@
/*
Serial LED implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "leddata.h"
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud);
void led_serial_update(struct _chuni_led_data_buf_t* data);

View File

@ -0,0 +1,471 @@
//
// Created by beerpsi on 12/31/2023.
//
#include "android.h"
#include "arch.h"
#ifdef ENV32BIT
#include <inttypes.h>
#include <process.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include "servers/common.h"
#include "servers/socket.h"
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;
volatile atomic_flag keep_running_flag;
volatile 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_flag_test_and_set(&ctx->keep_running_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;
atomic_flag_clear(&ctx->keep_running_flag);
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_flag_test_and_set(&ctx->keep_running_flag)) {
char buffer[96];
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, 96, 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_flag_clear(&ctx->keep_running_flag);
free(ctx);
return 1;
}
recv_len = recv_len + read;
}
real_len = buffer[0];
packet_len = real_len + 1; // 1 for the packet length
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_flag_clear(&ctx->keep_running_flag);
free(ctx);
return 1;
}
recv_len = recv_len + read;
}
}
if (packet_len >= sizeof(struct AndroidPacketInput) &&
memcmp(buffer + 1, "INP", 3) == 0) {
const struct AndroidPacketInput *pkt = (struct AndroidPacketInput *)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, 40, &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_flag_clear(&ctx->keep_running_flag);
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, 40);
}
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));
atomic_flag_test_and_set(&ctx->keep_running_flag);
ctx->sock = sock;
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));
atomic_flag_test_and_set(&ctx->keep_running_flag);
ctx->sock = acc_socket;
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;
}

View File

@ -1,32 +1,19 @@
//
// Created by beerpsi on 12/29/2023.
// Created by beerpsi on 12/31/2023.
//
#ifndef CHUNIIO_BROKENITHM_STRUCT_H
#define CHUNIIO_BROKENITHM_STRUCT_H
#ifndef CHUNIIO_BROKENITHM_ANDROID_H
#define CHUNIIO_BROKENITHM_ANDROID_H
#include <stdint.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "ipc_memory_info.h"
;
#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 PacketInput
{
struct AndroidPacketInput {
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
@ -36,8 +23,7 @@ struct PacketInput
uint8_t serviceBtn;
};
struct PacketInputNoAir
{
struct PacketInputNoAir {
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
@ -46,23 +32,13 @@ struct PacketInputNoAir
uint8_t serviceBtn;
};
struct PacketFunction
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t funcBtn;
};
struct PacketConnect
{
struct PacketConnect {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t addrType;
uint16_t port;
union
{
struct
{
union {
struct {
uint8_t addr[4];
uint8_t padding[12];
} addr4;
@ -70,8 +46,7 @@ struct PacketConnect
} addr;
};
struct PacketCard
{
struct PacketCard {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t remoteCardRead;
@ -79,13 +54,13 @@ struct PacketCard
uint8_t remoteCardId[10];
};
struct PacketPing
{
struct PacketPing {
uint8_t packetSize;
uint8_t packetName[3];
uint64_t remotePingTime;
};
#pragma pack(pop)
#endif //CHUNIIO_BROKENITHM_STRUCT_H
HRESULT android_init_server(struct IPCMemoryInfo *memory);
#endif // CHUNIIO_BROKENITHM_ANDROID_H

View File

@ -0,0 +1,72 @@
//
// Created by beerpsi on 12/31/2023.
//
#include "common.h"
#include <time.h>
#include "util/dprintf.h"
void print_err(const char *fmt, ...) {
const time_t lt = time(NULL);
const struct tm *local = localtime(&lt);
char tmpbuf[32];
strftime(tmpbuf, 32, "%Y-%m-%d %H:%M:%S", local);
dprintf("brokenithm_server: [%s] ", tmpbuf);
va_list ap;
va_start(ap, fmt);
dprintfv(fmt, ap);
va_end(ap);
}
void 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");
}

View File

@ -0,0 +1,30 @@
//
// Created by beerpsi on 12/31/2023.
//
#ifndef CHUNIIO_BROKENITHM_COMMON_H
#define CHUNIIO_BROKENITHM_COMMON_H
#include <stdbool.h>
#include <stdint.h>
enum FunctionButton {
INVALID __attribute__((unused)),
FUNCTION_COIN,
FUNCTION_CARD,
};
#pragma pack(push)
#pragma pack(1)
struct PacketFunction {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t funcBtn;
};
#pragma pack(pop)
void print_err(const char *fmt, ...);
void dump_bytes(const void *ptr, size_t nbytes, bool hex_string);
#endif // CHUNIIO_BROKENITHM_COMMON_H

246
chuniio/src/servers/ios.c Normal file
View File

@ -0,0 +1,246 @@
//
// Created by beerpsi on 12/31/2023.
//
#include "ios.h"
#include "arch.h"
#ifdef ENV32BIT
#include <libimobiledevice/libimobiledevice.h>
#include <process.h>
#include <stdatomic.h>
#include "servers/common.h"
typedef struct {
char remote_udid[41];
idevice_t device;
idevice_connection_t connection;
volatile atomic_flag keep_running_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_flag_test_and_set(&ctx->keep_running_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_flag_test_and_set(&ctx->keep_running_flag)) {
char buffer[96];
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_flag_clear(&ctx->keep_running_flag);
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_flag_clear(&ctx->keep_running_flag);
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 >= sizeof(struct PacketAirStatus) && memcmp(buffer + 1, "AIR", 3) == 0) {
struct PacketAirStatus *pkt = (struct PacketAirStatus *)buffer;
air_enabled = pkt->airEnabled != 0;
print_err("[iOS: INFO] Air input %s\n", 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.\n");
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");
_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));
strcpy_s(args->remote_udid, 41, event->udid);
atomic_flag_test_and_set(&args->keep_running_flag);
args->device = NULL;
args->connection = NULL;
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;
}

34
chuniio/src/servers/ios.h Normal file
View File

@ -0,0 +1,34 @@
//
// Created by beerpsi on 12/31/2023.
//
#ifndef CHUNIIO_BROKENITHM_IOS_H
#define CHUNIIO_BROKENITHM_IOS_H
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "ipc_memory_info.h"
;
#pragma pack(push)
#pragma pack(1)
struct iOSPacketInput {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t airIoStatus[6];
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketAirStatus {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t airEnabled;
};
#pragma pack(pop)
HRESULT ios_init_server(struct IPCMemoryInfo *memory);
#endif // CHUNIIO_BROKENITHM_IOS_H

View File

@ -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 <ws2tcpip.h>
#include <winsock2.h>
#else
//translate windows functions to linux functions
#include <unistd.h>
#include <string.h>
#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 <sys/select.h>
#endif /* __hpux */
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>
#include <signal.h>
#include <unistd.h>
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 <winsock2.h>
#include <ws2tcpip.h>
#else
// translate windows functions to linux functions
#include <string.h>
#include <unistd.h>
#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 <sys/select.h>
#endif /* __hpux */
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
typedef sockaddr *LPSOCKADDR;
#endif // _WIN32
#endif // CHUNIIO_BROKENITHM_SOCKET_H

View File

@ -1,733 +0,0 @@
//
// Created by beerpsi on 12/30/2023.
//
#include "chuniio.h"
#include <inttypes.h>
#include <process.h>
#include "config.h"
#include "struct.h"
#include "util/dprintf.h"
#ifdef ENV32BIT
#include <stdatomic.h>
#include <stdio.h>
#include <time.h>
#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(&lt);
char tmpbuf[32];
strftime(tmpbuf, 32, "%Y-%m-%d %H:%M:%S", local);
dprintf("brokenithm_server: [%s] ", tmpbuf);
va_list ap;
va_start(ap, fmt);
dprintfv(fmt, ap);
va_end(ap);
}
void get_socks_address(const struct PacketConnect *pkt, char *address, int address_len,
uint16_t *port) {
if (!pkt || !address || !port) {
return;
}
*port = ntohs(pkt->port);
switch (pkt->addrType) {
case 1:
inet_ntop(AF_INET, pkt->addr.addr4.addr, address, address_len);
break;
case 2:
inet_ntop(AF_INET6, pkt->addr.addr6, address, address_len);
break;
default:
return;
}
}
void update_packet_id(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) {
int error = WSAGetLastError();
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN ||
error == WSAETIMEDOUT) {
continue;
}
print_err("[INFO] Device disconnected (could not send packet), "
"error %d, os error %ld\n",
errno, error);
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) {
int error = WSAGetLastError();
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN ||
error == WSAETIMEDOUT) {
continue;
}
print_err("[INFO] Device disconnected (could not receive packet), "
"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; // 1 for the packetSize
while (recv_len < packet_len) {
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 receive packet), "
"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) && 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 (clean disconnect)\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 = malloc(sizeof(thread_ctx));
args->sock = acc_socket;
args->exit_flag = ATOMIC_VAR_INIT(false);
args->connected = ATOMIC_VAR_INIT(true);
args->last_input_packet_id = 0;
args->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;
}

View File

@ -1,80 +0,0 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
static const int chuni_io_default_cells[] = {
'L', 'L', 'L', 'L',
'K', 'K', 'K', 'K',
'J', 'J', 'J', 'J',
'H', 'H', 'H', 'H',
'G', 'G', 'G', 'G',
'F', 'F', 'F', 'F',
'D', 'D', 'D', 'D',
'S', 'S', 'S', 'S',
};
static const int chuni_io_default_ir[] = {
'4', '5', '6', '7', '8', '9'
};
void chuni_io_config_load(
struct chuni_io_config *cfg,
const wchar_t *filename)
{
wchar_t key[16];
int i;
wchar_t port_input[6];
assert(cfg != NULL);
assert(filename != NULL);
// Technically it's io4 but leave this for compatibility with old configs.
cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", '3', filename);
cfg->vk_ir_emu = GetPrivateProfileIntW(L"io3", L"ir", VK_SPACE, filename);
for (i = 0 ; i < 6 ; i++) {
swprintf_s(key, _countof(key), L"ir%i", i + 1);
cfg->vk_ir[i] = GetPrivateProfileIntW(
L"ir",
key,
chuni_io_default_ir[i],
filename);
}
for (i = 0 ; i < 32 ; i++) {
swprintf_s(key, _countof(key), L"cell%i", i + 1);
cfg->vk_cell[i] = GetPrivateProfileIntW(
L"slider",
key,
chuni_io_default_cells[i],
filename);
}
cfg->led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename);
cfg->led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename);
cfg->slider_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename);
cfg->slider_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename);
cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename);
GetPrivateProfileStringW(
L"led",
L"serialPort",
L"COM5",
port_input,
6,
filename);
// Sanitize the output path. If it's a serial COM port, it needs to be prefixed
// with `\\.\`.
wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4);
wcsncat_s(cfg->led_serial_port, 11, port_input, 6);
}

8
util/CMakeLists.txt Normal file
View File

@ -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)

View File

@ -14,8 +14,7 @@ static CRITICAL_SECTION dbg_buf_lock;
static char dbg_buf[16384];
static size_t dbg_buf_pos;
void dprintf(const char *fmt, ...)
{
void dprintf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
@ -23,8 +22,7 @@ void dprintf(const char *fmt, ...)
va_end(ap);
}
void dprintfv(const char *fmt, va_list ap)
{
void dprintfv(const char *fmt, va_list ap) {
long init;
/* Static constructors in C are difficult to do in a way that works under
@ -45,12 +43,8 @@ void dprintfv(const char *fmt, va_list ap)
EnterCriticalSection(&dbg_buf_lock);
dbg_buf_pos += vsnprintf_s(
dbg_buf + dbg_buf_pos,
sizeof(dbg_buf) - dbg_buf_pos,
sizeof(dbg_buf) - dbg_buf_pos - 1,
fmt,
ap);
dbg_buf_pos += vsnprintf_s(dbg_buf + dbg_buf_pos, sizeof(dbg_buf) - dbg_buf_pos,
sizeof(dbg_buf) - dbg_buf_pos - 1, fmt, ap);
if (dbg_buf_pos + 1 > sizeof(dbg_buf)) {
abort();
@ -65,8 +59,7 @@ void dprintfv(const char *fmt, va_list ap)
LeaveCriticalSection(&dbg_buf_lock);
}
void dwprintf(const wchar_t *fmt, ...)
{
void dwprintf(const wchar_t *fmt, ...) {
va_list ap;
va_start(ap, fmt);
@ -74,8 +67,7 @@ void dwprintf(const wchar_t *fmt, ...)
va_end(ap);
}
void dwprintfv(const wchar_t *fmt, va_list ap)
{
void dwprintfv(const wchar_t *fmt, va_list ap) {
wchar_t msg[512];
_vsnwprintf_s(msg, _countof(msg), _countof(msg) - 1, fmt, ap);

View File

@ -8,7 +8,7 @@ extern "C" {
#include <stddef.h>
#ifdef __GNUC__
#define DPRINTF_CHK __attribute__(( format(printf, 1, 2) ))
#define DPRINTF_CHK __attribute__((format(printf, 1, 2)))
#else
#define DPRINTF_CHK
#endif