iOS support and other niceties (#1)

This commit is contained in:
beerpsi 2023-12-31 13:20:04 +07:00
commit 30f3a23bc6
23 changed files with 1762 additions and 916 deletions

View File

@ -1,20 +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_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_directories(.)
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)
add_subdirectory(aimeio)
add_subdirectory(chuniio)
add_subdirectory(util)

View File

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

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) {}

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

@ -0,0 +1,94 @@
//
// 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

33
chuniio/CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
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/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 ()

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

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

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

@ -0,0 +1,231 @@
//
// Created by beerpsi on 12/30/2023.
//
#include "chuniio.h"
#include <process.h>
#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;
}

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,7 +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
@ -96,7 +80,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 +116,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 +131,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 +140,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,7 +151,7 @@ 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.
@ -183,6 +167,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

View File

@ -0,0 +1,466 @@
//
// 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 "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;
}

View File

@ -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 <windows.h>
#include "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,21 @@
//
// 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,
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

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

@ -0,0 +1,239 @@
//
// Created by beerpsi on 12/31/2023.
//
#include "ios.h"
#include "arch.h"
#ifdef ENV32BIT
#include <process.h>
#include <stdatomic.h>
#include <libimobiledevice/libimobiledevice.h>
#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;
}

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

@ -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 <windows.h>
#include "struct.h"
HRESULT ios_init_server(struct IPCMemoryInfo* memory);
#endif // CHUNIIO_BROKENITHM_IOS_H

View File

@ -25,6 +25,15 @@ struct IPCMemoryInfo
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;

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;
}

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)