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
This commit is contained in:
beerpsi 2023-12-31 13:10:12 +07:00
parent 2da8f0a699
commit f101a07bb2
23 changed files with 1931 additions and 1241 deletions

View File

@ -1,33 +1,12 @@
cmake_minimum_required(VERSION 3.27)
project(chuniio_brokenithm)
project(brokenithm)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
include_directories("${CMAKE_SOURCE_DIR}/include/")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
include_directories(.)
include(CheckIPOSupported)
check_ipo_supported(RESULT supported OUTPUT error)
if (supported)
message(STATUS "IPO / LTO enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(STATUS "IPO / LTO not supported: <${error}>")
endif()
link_directories(src)
add_library(chuniio_brokenithm SHARED src/chuniio.c
src/chuniio.h
src/config.c
src/config.h
src/socket.h
src/struct.h
src/util/dprintf.c
src/util/dprintf.h)
set_target_properties(chuniio_brokenithm PROPERTIES PREFIX "")
set_target_properties(chuniio_brokenithm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--allow-multiple-definition")
target_link_libraries(chuniio_brokenithm "-static-libgcc -Wl,-Bstatic -limobiledevice-1.0 -lssl -lcrypto -lplist-2.0 -lusbmuxd-2.0 -lpthread -Wl,-Bdynamic -lws2_32 -lcrypt32 -liphlpapi")
add_subdirectory(aimeio)
add_subdirectory(chuniio)
add_subdirectory(util)

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

20
aimeio/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.27)
project(aimeio_brokenithm)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
include_directories("${CMAKE_SOURCE_DIR}/include/")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
link_directories(src)
add_library(aimeio_brokenithm SHARED src/aimeio.c
src/aimeio.h)
target_link_libraries(aimeio_brokenithm util)
set_target_properties(aimeio_brokenithm PROPERTIES PREFIX "")
target_include_directories(aimeio_brokenithm PRIVATE src)
target_link_libraries(aimeio_brokenithm "-static-libgcc")

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

36
chuniio/CMakeLists.txt Normal file
View File

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.27)
project(chuniio_brokenithm)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
include_directories("${CMAKE_SOURCE_DIR}/include/")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
link_directories(src)
add_library(chuniio_brokenithm SHARED src/chuniio.c
src/chuniio.h
src/config.c
src/config.h
src/socket.h
src/struct.h
src/servers/android.c
src/servers/android.h
src/servers/common.h
src/servers/common.c
src/servers/ios.c
src/servers/ios.h)
target_link_libraries(chuniio_brokenithm util)
set_target_properties(chuniio_brokenithm PROPERTIES PREFIX "")
target_include_directories(chuniio_brokenithm PRIVATE src)
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
# Ugly hack around libimobiledevice shipping a DllMain for some reason???
set_target_properties(chuniio_brokenithm PROPERTIES LINK_FLAGS "-Wl,--allow-multiple-definition")
target_link_libraries(chuniio_brokenithm "-static-libgcc -Wl,-Bstatic -limobiledevice-1.0 -lssl -lcrypto -lplist-2.0 -lusbmuxd-2.0 -lpthread -Wl,-Bdynamic -lws2_32 -lcrypt32 -liphlpapi")
elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
target_link_libraries(chuniio_brokenithm "-static-libgcc")
endif ()

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

@ -1,172 +1,172 @@
//
// Created by beerpsi on 12/30/2023.
//
#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H
#define CHUNIIO_BROKENITHM_CHUNIIO_H
#include "socket.h"
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
CHUNI_IO_OPBTN_TEST = 0x01,
CHUNI_IO_OPBTN_SERVICE = 0x02,
CHUNI_IO_OPBTN_COIN = 0x04,
};
/* Get the version of the Chunithm IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0101. */
uint16_t __declspec(dllexport) chuni_io_get_api_version();
/* Initialize JVS-based input. This function will be called before any other
chuni_io_jvs_*() function calls. Errors returned from this function will
manifest as a disconnected JVS bus.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0100 */
HRESULT __declspec(dllexport) chuni_io_jvs_init();
/* Poll JVS input.
opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1
is Service.
beam returns the IR beams that are currently broken, where bit 0 is the
lowest IR beam and bit 5 is the highest IR beam, for a total of six beams.
Both bit masks are active-high.
Note that you cannot instantly break the entire IR grid in a single frame to
simulate hand movement; this will be judged as a miss. You need to simulate
a gradual raising and lowering of the hands. Consult the proof-of-concept
implementation for details.
NOTE: Previous releases of Segatools mapped the IR beam inputs incorrectly.
Please ensure that you advertise an API version of at least 0x0101 so that
the correct mapping can be used.
Minimum API version: 0x0100
Latest API version: 0x0101 */
void __declspec(dllexport) chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
/* Read the current state of the coin counter. This value should be incremented
for every coin detected by the coin acceptor mechanism. This count does not
need to persist beyond the lifetime of the process.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_jvs_read_coin_counter(uint16_t *total);
/* Initialize touch slider emulation. This function will be called before any
other chuni_io_slider_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0100 */
HRESULT __declspec(dllexport) chuni_io_slider_init(void);
/* Chunithm touch slider layout:
^^^ Toward screen ^^^
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 |
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 |
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
There are a total of 32 regions on the touch slider. Each region can return
an 8-bit pressure value. The operator menu allows the operator to adjust the
pressure level at which a region is considered to be pressed; the factory
default value for this setting is 20. */
/* Callback function supplied to your IO DLL. This must be called with a
pointer to a 32-byte array of pressure values, one byte per slider cell.
See above for layout and pressure threshold information.
The callback will copy the pressure state data out of your buffer before
returning. The pointer will not be retained. */
typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
/* Start polling the slider. Your DLL must start a polling thread and call the
supplied function periodically from that thread with new input state. The
update interval is up to you, but if your input device doesn't have any
preferred interval then 1 kHz is a reasonable maximum frequency.
Note that you do have to have to call the callback "occasionally" even if
nothing is changing, otherwise the game will raise a comm timeout error.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_start(void *callback);
/* Stop polling the slider. You must cease to invoke the input callback before
returning from this function.
This *will* be called in the course of regular operation. For example,
every time you go into the operator menu the slider and all of the other I/O
on the cabinet gets restarted.
Following on from the above, the slider polling loop *will* be restarted
after being stopped in the course of regular operation. Do not permanently
tear down your input driver in response to this function call.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_stop(void);
/* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96
bytes is supplied. The illuminated areas on the touch slider are some
combination of rectangular regions and dividing lines between these regions
but the exact mapping of this lighting control buffer is still TBD.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_set_leds(const uint8_t *rgb);
/* Initialize LED emulation. This function will be called before any
other chuni_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0102 */
HRESULT __declspec(dllexport) chuni_io_led_init(void);
/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes.
Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds).
board 0 is on the left side and board 1 on the right side of the cab
left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower
right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower
Each rgb value is comprised of 3 bytes in R,G,B order
NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...)
Minimum API version: 0x0102 */
void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
#endif //CHUNIIO_BROKENITHM_CHUNIIO_H
//
// Created by beerpsi on 12/30/2023.
//
#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H
#define CHUNIIO_BROKENITHM_CHUNIIO_H
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
CHUNI_IO_OPBTN_TEST = 0x01,
CHUNI_IO_OPBTN_SERVICE = 0x02,
CHUNI_IO_OPBTN_COIN = 0x04,
};
/* Get the version of the Chunithm IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0101. */
uint16_t __declspec(dllexport) chuni_io_get_api_version();
/* Initialize JVS-based input. This function will be called before any other
chuni_io_jvs_*() function calls. Errors returned from this function will
manifest as a disconnected JVS bus.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0100 */
HRESULT __declspec(dllexport) chuni_io_jvs_init();
/* Poll JVS input.
opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1
is Service.
beam returns the IR beams that are currently broken, where bit 0 is the
lowest IR beam and bit 5 is the highest IR beam, for a total of six beams.
Both bit masks are active-high.
Note that you cannot instantly break the entire IR grid in a single frame to
simulate hand movement; this will be judged as a miss. You need to simulate
a gradual raising and lowering of the hands. Consult the proof-of-concept
implementation for details.
NOTE: Previous releases of Segatools mapped the IR beam inputs incorrectly.
Please ensure that you advertise an API version of at least 0x0101 so that
the correct mapping can be used.
Minimum API version: 0x0100
Latest API version: 0x0101 */
void __declspec(dllexport) chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
/* Read the current state of the coin counter. This value should be incremented
for every coin detected by the coin acceptor mechanism. This count does not
need to persist beyond the lifetime of the process.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_jvs_read_coin_counter(uint16_t *total);
/* Initialize touch slider emulation. This function will be called before any
other chuni_io_slider_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0100 */
HRESULT __declspec(dllexport) chuni_io_slider_init(void);
/* Chunithm touch slider layout:
^^^ Toward screen ^^^
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 |
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 |
----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
There are a total of 32 regions on the touch slider. Each region can return
an 8-bit pressure value. The operator menu allows the operator to adjust the
pressure level at which a region is considered to be pressed; the factory
default value for this setting is 20. */
/* Callback function supplied to your IO DLL. This must be called with a
pointer to a 32-byte array of pressure values, one byte per slider cell.
See above for layout and pressure threshold information.
The callback will copy the pressure state data out of your buffer before
returning. The pointer will not be retained. */
typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
/* Start polling the slider. Your DLL must start a polling thread and call the
supplied function periodically from that thread with new input state. The
update interval is up to you, but if your input device doesn't have any
preferred interval then 1 kHz is a reasonable maximum frequency.
Note that you do have to have to call the callback "occasionally" even if
nothing is changing, otherwise the game will raise a comm timeout error.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_start(void *callback);
/* Stop polling the slider. You must cease to invoke the input callback before
returning from this function.
This *will* be called in the course of regular operation. For example,
every time you go into the operator menu the slider and all of the other I/O
on the cabinet gets restarted.
Following on from the above, the slider polling loop *will* be restarted
after being stopped in the course of regular operation. Do not permanently
tear down your input driver in response to this function call.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_stop(void);
/* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96
bytes is supplied. The illuminated areas on the touch slider are some
combination of rectangular regions and dividing lines between these regions
but the exact mapping of this lighting control buffer is still TBD.
Minimum API version: 0x0100 */
void __declspec(dllexport) chuni_io_slider_set_leds(const uint8_t *rgb);
/* Initialize LED emulation. This function will be called before any
other chuni_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0102 */
HRESULT __declspec(dllexport) chuni_io_led_init(void);
/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes.
Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds).
board 0 is on the left side and board 1 on the right side of the cab
left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower
right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower
Each rgb value is comprised of 3 bytes in R,G,B order
NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...)
Minimum API version: 0x0102 */
void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
#endif //CHUNIIO_BROKENITHM_CHUNIIO_H

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

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

View File

@ -1,100 +1,100 @@
//
// 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];
};
struct iOSPacketInput {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t airIoStatus[6];
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketInput
{
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
uint8_t airIoStatus[6];
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketInputNoAir
{
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketFunction
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t funcBtn;
};
struct PacketConnect
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t addrType;
uint16_t port;
union
{
struct
{
uint8_t addr[4];
uint8_t padding[12];
} addr4;
uint8_t addr6[16];
} addr;
};
struct PacketCard
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t remoteCardRead;
uint8_t remoteCardType;
uint8_t remoteCardId[10];
};
struct PacketPing
{
uint8_t packetSize;
uint8_t packetName[3];
uint64_t remotePingTime;
};
#pragma pack(pop)
#endif //CHUNIIO_BROKENITHM_STRUCT_H
//
// Created by beerpsi on 12/29/2023.
//
#ifndef CHUNIIO_BROKENITHM_STRUCT_H
#define CHUNIIO_BROKENITHM_STRUCT_H
#include <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];
};
struct iOSPacketInput {
uint8_t packetSize;
uint8_t packetName[3];
uint8_t airIoStatus[6];
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketInput
{
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
uint8_t airIoStatus[6];
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketInputNoAir
{
uint8_t packetSize;
uint8_t packetName[3];
uint32_t packetId;
uint8_t sliderIoStatus[32];
uint8_t testBtn;
uint8_t serviceBtn;
};
struct PacketFunction
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t funcBtn;
};
struct PacketConnect
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t addrType;
uint16_t port;
union
{
struct
{
uint8_t addr[4];
uint8_t padding[12];
} addr4;
uint8_t addr6[16];
} addr;
};
struct PacketCard
{
uint8_t packetSize;
uint8_t packetName[3];
uint8_t remoteCardRead;
uint8_t remoteCardType;
uint8_t remoteCardId[10];
};
struct PacketPing
{
uint8_t packetSize;
uint8_t packetName[3];
uint64_t remotePingTime;
};
#pragma pack(pop)
#endif //CHUNIIO_BROKENITHM_STRUCT_H

View File

@ -1,889 +0,0 @@
//
// Created by beerpsi on 12/30/2023.
//
#include "chuniio.h"
#include <stdio.h>
#include <inttypes.h>
#include <time.h>
#include <process.h>
#include <libimobiledevice/libimobiledevice.h>
#include "config.h"
#include "struct.h"
#include "util/dprintf.h"
//region Brokenithm
struct IPCMemoryInfo* chuni_io_file_mapping;
const char *memFileName = "Local\\BROKENITHM_SHARED_BUFFER";
//region Brokenithm Android
uint16_t server_port = 52468;
bool tcp_mode = false;
enum {
CARD_AIME,
CARD_FELICA,
};
enum {
FUNCTION_COIN = 1,
FUNCTION_CARD
};
typedef struct {
SOCKET sock;
char remote_address[BUFSIZ];
uint16_t remote_port;
bool exit_flag;
bool connected;
uint32_t last_input_packet_id;
struct IPCMemoryInfo* memory;
} android_thread_ctx;
void socket_set_timeout(const SOCKET sHost, int timeout) {
setsockopt(sHost, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(int));
setsockopt(sHost, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int));
}
int socket_bind(const SOCKET sHost, const unsigned long addr, const uint16_t port) {
struct sockaddr_in srcaddr = {};
memset(&srcaddr, 0, sizeof(srcaddr));
srcaddr.sin_family = AF_INET;
srcaddr.sin_addr.s_addr = addr;
srcaddr.sin_port = htons(port);
return bind(sHost, (struct sockaddr*)&srcaddr, sizeof(srcaddr));
}
int socket_send_to(const SOCKET sHost, const struct sockaddr_in* addr, const char* buf, const int len) {
return sendto(sHost, buf, len, 0, (struct sockaddr*)&addr, sizeof(&addr));
}
void print_err(const char* fmt, ...) {
const time_t lt = time(NULL);
const struct tm *local = localtime(&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, const int address_len, uint16_t *port) {
if (!pkt || !address || !port) {
return;
}
*port = ntohs(pkt->port);
switch (pkt->addrType) {
case 1:
inet_ntop(AF_INET, pkt->addr.addr4.addr, address, address_len);
break;
case 2:
inet_ntop(AF_INET6, pkt->addr.addr6, address, address_len);
break;
default:
return;
}
}
void update_packet_id(android_thread_ctx* ctx, const uint32_t new_packet_id)
{
if (ctx->last_input_packet_id > new_packet_id) {
print_err("[WARN] Packet #%" PRIu32 " came too late\n", new_packet_id);
} else if (new_packet_id > ctx->last_input_packet_id + 1) {
print_err("[WARN] Packets between #%" PRIu32 " and #%" PRIu32 " total %" PRIu32 " packet(s) are missing, probably too late or dropped\n",
ctx->last_input_packet_id, new_packet_id, new_packet_id - ctx->last_input_packet_id - 1);
} else if (new_packet_id == ctx->last_input_packet_id) {
print_err("[WARN] Packet #%" PRIu32 " duplicated\n", new_packet_id);
}
ctx->last_input_packet_id = new_packet_id;
}
void dump_bytes(const void *ptr, const size_t nbytes, const bool hex_string)
{
size_t i;
size_t j;
if (nbytes == 0) {
dprintf("\t--- Empty ---\n");
}
const uint8_t* bytes = ptr;
if (hex_string) {
for (i = 0 ; i < nbytes ; i++) {
dprintf("%02x", bytes[i]);
}
dprintf("\n");
return;
}
for (i = 0 ; i < nbytes ; i += 16) {
dprintf(" %08x:", (int) i);
for (j = 0 ; i + j < nbytes && j < 16 ; j++) {
dprintf(" %02x", bytes[i + j]);
}
while (j < 16) {
dprintf(" ");
j++;
}
dprintf(" ");
for (j = 0 ; i + j < nbytes && j < 16 ; j++) {
uint8_t c = bytes[i + j];
if (c < 0x20 || c >= 0x7F) {
c = '.';
}
dprintf("%c", c);
}
dprintf("\n");
}
dprintf("\n");
}
void print_card_info(const uint8_t card_type, const uint8_t *card_id) {
switch (card_type) {
case CARD_AIME:
print_err("[INFO] Card type: AiMe, ID: ");
dump_bytes(card_id, 10, true);
break;
case CARD_FELICA:
print_err("[INFO] Card type: FeliCa, ID: ");
dump_bytes(card_id, 8, true);
break;
default:
break;
}
}
int make_ipv4_address(struct sockaddr_in* addr, const char* host, const uint16_t port) {
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
return inet_pton(AF_INET, host, (struct in_addr *)&addr->sin_addr.s_addr);
}
uint8_t previous_led_status[3 * 32];
bool has_previous_led_status = false;
int skip_count = 0;
unsigned int __stdcall android_led_broadcast_thread_proc(void *v) {
android_thread_ctx *ctx = v;
const SOCKET sHost = ctx->sock;
const struct IPCMemoryInfo* memory = ctx->memory;
struct sockaddr_in addr = {};
make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port);
char send_buffer[4 + 3 * 32];
send_buffer[0] = 99;
send_buffer[1] = 'L';
send_buffer[2] = 'E';
send_buffer[3] = 'D';
while (!ctx->exit_flag) {
uint8_t current_led_status[3 * 32];
if (!ctx->connected) {
Sleep(50);
continue;
}
memcpy(current_led_status, memory->ledRgbData, 3 * 32);
bool same;
if (!has_previous_led_status) {
same = memcmp(previous_led_status, current_led_status, 3 * 32) == 0;
} else {
same = false;
}
memcpy(previous_led_status, current_led_status, 3 * 32);
has_previous_led_status = true;
if (!same || ++skip_count > 50) {
memcpy(send_buffer + 4, current_led_status, 3 * 32);
if (socket_send_to(sHost, &addr, send_buffer, 100) < 0) {
print_err("[ERROR] Cannot send packet: error %lu\n", GetLastError());
if (tcp_mode) {
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
continue;
}
print_err("[INFO] Device disconnected!\n");
ctx->connected = false;
ctx->exit_flag = true;
break;
}
}
skip_count = 0;
}
Sleep(10);
}
return 0;
}
uint8_t last_card_id[10];
unsigned int __stdcall android_input_recv_thread_proc(void *v) {
android_thread_ctx *ctx = v;
const SOCKET sHost = ctx->sock;
struct IPCMemoryInfo* memory = ctx->memory;
struct sockaddr_in addr = {};
make_ipv4_address(&addr, ctx->remote_address, ctx->remote_port);
int recv_len, packet_len;
uint8_t real_len;
while (!ctx->exit_flag) {
char buffer[BUFSIZ];
if (!tcp_mode) {
/**
on UDP mode data is sent as packets, so just receive into a buffer big enough for 1 packet
each recvfrom call will only get 1 packet of data, the remaining data is discarded
**/
if ((recv_len = recvfrom(sHost, buffer, BUFSIZ - 1, 0, NULL, NULL)) == -1) {
continue;
}
real_len = (unsigned char)buffer[0];
if (real_len > recv_len) {
continue;
}
packet_len = real_len + 1;
} else {
/**
on TCP mode packets are length-prefixed, so we read in the first 4 bytes to
figure out how much we need to read, then read in the full data.
**/
recv_len = 0;
while (recv_len < 4) {
const int read = recv(sHost, buffer + recv_len, 4 - recv_len, 0);
if (read == -1) {
int error = WSAGetLastError();
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || error == WSAETIMEDOUT) {
continue;
}
print_err("[INFO] Device disconnected (could not read data, errno %d, os error %ld)\n", errno, error);
ctx->connected = false;
ctx->exit_flag = true;
break;
}
recv_len = recv_len + read;
}
real_len = buffer[0];
packet_len = real_len + 1;
while (recv_len < packet_len) {
const int read = recv(sHost, buffer + recv_len, packet_len - recv_len, 0);
if (read == -1) {
int error = WSAGetLastError();
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN || error == WSAETIMEDOUT) {
continue;
}
print_err("[INFO] Device disconnected (could not read data, errno %d, os error %ld)\n", errno, error);
ctx->connected = false;
ctx->exit_flag = true;
break;
}
recv_len = recv_len + read;
}
}
if (packet_len >= sizeof(struct PacketInput) && memcmp(buffer + 1, "INP", 3) == 0) {
const struct PacketInput* pkt = (struct PacketInput*)buffer;
memcpy(memory->airIoStatus, pkt->airIoStatus, sizeof(pkt->airIoStatus));
memcpy(memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus));
memory->testBtn = pkt->testBtn;
memory->serviceBtn = pkt->serviceBtn;
update_packet_id(ctx, ntohl(pkt->packetId));
} else if (packet_len >= sizeof(struct PacketInputNoAir) && memcmp(buffer + 1, "IPT", 3) == 0) { // without air
const struct PacketInputNoAir* pkt = (struct PacketInputNoAir*)buffer;
memcpy(memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus));
memory->testBtn = pkt->testBtn;
memory->serviceBtn = pkt->serviceBtn;
update_packet_id(ctx, ntohl(pkt->packetId));
} else if (packet_len >= sizeof(struct PacketFunction) && memcmp(buffer + 1, "FNC", 3) == 0) {
const struct PacketFunction* pkt = (struct PacketFunction*)buffer;
switch (pkt->funcBtn) {
case FUNCTION_COIN:
memory->coinInsertion = 1;
break;
case FUNCTION_CARD:
memory->cardRead = 1;
break;
default:
break;
}
} else if (packet_len >= sizeof(struct PacketConnect) && memcmp(buffer + 1, "CON", 3) == 0) {
const struct PacketConnect* pkt = (struct PacketConnect*)buffer;
get_socks_address(pkt, ctx->remote_address, BUFSIZ - 1, &ctx->remote_port);
print_err("[INFO] Device %s:%d connected.\n", ctx->remote_address, ctx->remote_port);
ctx->last_input_packet_id = 0;
ctx->connected = true;
} else if (packet_len >= 4 && memcmp(buffer + 1, "DIS", 3) == 0) {
ctx->connected = false;
if (tcp_mode) {
ctx->exit_flag = true;
print_err("[INFO] Device disconnected!\n");
break;
}
if (strlen(ctx->remote_address)) {
print_err("[INFO] Device %s:%d disconnected.\n", ctx->remote_address, ctx->remote_port);
memset(ctx->remote_address, 0, BUFSIZ);
}
if (tcp_mode) {
break;
}
} else if (packet_len >= sizeof(struct PacketPing) && memcmp(buffer + 1, "PIN", 3) == 0) {
if (!ctx->connected) {
continue;
}
char response[13];
memcpy(response, buffer, 12);
response[2] = 'O';
socket_send_to(sHost, &addr, response, 13);
} else if (packet_len >= sizeof(struct PacketCard) && memcmp(buffer + 1, "CRD", 3) == 0) {
const struct PacketCard* pkt = (struct PacketCard*)buffer;
if (pkt->remoteCardRead) {
if (memcmp(last_card_id, pkt->remoteCardId, 10) != 0) {
print_err("[INFO] Got remote card.\n");
print_card_info(pkt->remoteCardType, pkt->remoteCardId);
memcpy(last_card_id, pkt->remoteCardId, 10);
}
} else if (memory->remoteCardRead) {
print_err("[INFO] Remote card removed.\n");
memset(last_card_id, 0, 10);
}
memory->remoteCardRead = pkt->remoteCardRead;
memory->remoteCardType = pkt->remoteCardType;
memcpy(memory->remoteCardId, pkt->remoteCardId, 10);
}
}
free(ctx);
return 0;
}
unsigned int __stdcall server_thread_proc(void* ctx) {
struct IPCMemoryInfo* memory = ctx;
if (!tcp_mode) {
print_err("[INFO] Mode: UDP\n");
const SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
socket_set_timeout(sock, 2000);
socket_bind(sock, htonl(INADDR_ANY), server_port);
print_err("[INFO] Waiting for device on port %d...\n", server_port);
android_thread_ctx args = {
.sock = sock,
.exit_flag = false,
.connected = false,
.last_input_packet_id = 0,
.memory = memory,
};
HANDLE led_thread = (HANDLE) _beginthreadex(NULL, 0, android_led_broadcast_thread_proc, &args, 0, NULL);
HANDLE input_thread = (HANDLE) _beginthreadex(NULL, 0, android_input_recv_thread_proc, &args, 0, NULL);
WaitForSingleObject(led_thread, INFINITE);
WaitForSingleObject(input_thread, INFINITE);
CloseHandle(led_thread);
CloseHandle(input_thread);
} else {
print_err("[INFO] Mode: TCP\n");
const SOCKET sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
socket_set_timeout(sHost, 50);
socket_bind(sHost, htonl(INADDR_ANY), server_port);
listen(sHost, 10);
print_err("[INFO] Waiting for device on port %d...\n", server_port);
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
// ReSharper disable once CppDFAEndlessLoop
for (;;) {
struct sockaddr_in user_socket = {};
socklen_t sock_size = sizeof(struct sockaddr_in);
const SOCKET acc_socket = accept(sHost, (struct sockaddr *)&user_socket, &sock_size);
char buffer[20] = {};
const char* user_address = inet_ntop(AF_INET, &user_socket.sin_addr, buffer, 20);
if (user_address != NULL) {
print_err("[INFO] Device %s:%d connected.\n", user_address, user_socket.sin_port);
}
android_thread_ctx* args = malloc(sizeof(android_thread_ctx));
args->sock = acc_socket;
args->exit_flag = false;
args->connected = true;
args->last_input_packet_id = 0,
args->memory = memory;
_beginthreadex(NULL, 0, android_led_broadcast_thread_proc, args, 0, NULL);
_beginthreadex(NULL, 0, android_input_recv_thread_proc, args, 0, NULL);
}
#pragma clang diagnostic pop
}
return 0;
}
//endregion
//region Brokenithm iOS
typedef struct {
char remote_udid[41];
bool exit_flag;
struct IPCMemoryInfo* memory;
idevice_t device;
idevice_connection_t connection;
} ios_thread_ctx;
unsigned int __stdcall ios_led_broadcast_thread_proc(void *v) {
ios_thread_ctx* ctx = v;
char send_buffer[4 + 3 * 32];
send_buffer[0] = 99;
send_buffer[1] = 'L';
send_buffer[2] = 'E';
send_buffer[3] = 'D';
while (!ctx->exit_flag) {
uint8_t current_led_status[3 * 32];
memcpy(current_led_status, ctx->memory->ledRgbData, 3 * 32);
bool same;
if (!has_previous_led_status) {
same = memcmp(previous_led_status, current_led_status, 3 * 32) == 0;
} else {
same = false;
}
memcpy(previous_led_status, current_led_status, 3 * 32);
has_previous_led_status = true;
if (!same || ++skip_count > 50) {
memcpy(send_buffer + 4, current_led_status, 3 * 32);
int status;
uint32_t sent;
if ((status = idevice_connection_send(ctx->connection, send_buffer, 100, &sent))) {
print_err("[ERROR] Cannot send LED packet: error %d\n", status);
}
skip_count = 0;
}
Sleep(10);
}
return 0;
}
unsigned int __stdcall ios_input_recv_thread_proc(void *v) {
ios_thread_ctx* ctx = v;
bool air_enabled = true;
while (!ctx->exit_flag) {
char buffer[BUFSIZ];
int status;
uint32_t read;
if ((status = idevice_connection_receive_timeout(ctx->connection, buffer, 4, &read, 5))) {
if (status == IDEVICE_E_TIMEOUT) {
continue;
}
print_err("[ERROR] Could not read data from device: %d\n", status);
ctx->exit_flag = true;
break;
}
int len = (unsigned char)buffer[0];
if ((status = idevice_connection_receive_timeout(ctx->connection, buffer + 4, len - 3, &read, 5))) {
print_err("[ERROR] Could not read data from device: %d\n", status);
ctx->exit_flag = true;
break;
}
if (len >= sizeof(struct iOSPacketInput) && memcmp(buffer + 1, "INP", 3) == 0) {
struct iOSPacketInput* pkt = (struct iOSPacketInput*)buffer;
if (air_enabled) {
memcpy(ctx->memory->airIoStatus, pkt->airIoStatus, sizeof(pkt->airIoStatus));
}
memcpy(ctx->memory->sliderIoStatus, pkt->sliderIoStatus, sizeof(pkt->sliderIoStatus));
ctx->memory->testBtn = pkt->testBtn;
ctx->memory->serviceBtn = pkt->serviceBtn;
} else if (len >= 4 && memcmp(buffer + 1, "AIR", 3) == 0) {
air_enabled = buffer[3] != 0;
print_err("[INFO] Air input %s", air_enabled ? "enabled" : "disabled");
} else if (len >= sizeof(struct PacketFunction) && memcmp(buffer + 1, "FNC", 3) == 0) {
const struct PacketFunction *pkt = (struct PacketFunction *) buffer;
switch (pkt->funcBtn) {
case FUNCTION_COIN:
ctx->memory->coinInsertion = 1;
break;
case FUNCTION_CARD:
ctx->memory->cardRead = 1;
break;
default:
break;
}
}
}
print_err("[INFO] Device disconnected.");
idevice_disconnect(ctx->connection);
ctx->connection = NULL;
idevice_free(ctx->device);
free(ctx);
return 0;
}
unsigned int __stdcall connect_device(void* v) {
ios_thread_ctx* ctx = v;
int status;
if ((status = idevice_new(&ctx->device, ctx->remote_udid))) {
print_err("[ERROR] Create device failed: %d\n", status);
idevice_free(ctx->device);
return 1;
}
if ((status = idevice_connect(ctx->device, 24864, &ctx->connection))) {
print_err("[ERROR] Connection failed: %d, retrying in 5 seconds\n", status);
ctx->connection = NULL;
idevice_free(ctx->device);
Sleep(5000);
_beginthreadex(NULL, 0, connect_device, ctx, 0, NULL);
return 1;
}
char buf[5];
uint32_t read;
if ((status = idevice_connection_receive(ctx->connection, buf, 4, &read))) {
print_err("[ERROR] Receiving data failed: %d\n", status);
idevice_disconnect(ctx->connection);
ctx->connection = NULL;
idevice_free(ctx->device);
return 1;
}
if (memcmp(buf, "\x03WEL", 4) != 0) {
print_err("[ERROR] Client sent invalid data\n");
idevice_disconnect(ctx->connection);
ctx->connection = NULL;
idevice_free(ctx->device);
return 1;
}
print_err("[INFO] Connected to device\n");
ctx->exit_flag = false;
_beginthreadex(NULL, 0, ios_input_recv_thread_proc, ctx, 0, NULL);
_beginthreadex(NULL, 0, ios_led_broadcast_thread_proc, ctx, 0, NULL);
return 0;
}
void device_event_callback(const idevice_event_t* event, void* user_data) {
struct IPCMemoryInfo* memory = user_data;
switch (event->event) {
case IDEVICE_DEVICE_ADD:
print_err("[INFO] iDevice added, udid: %s\n", event->udid);
ios_thread_ctx* args = malloc(sizeof(ios_thread_ctx));
args->exit_flag = false;
args->memory = memory;
args->device = NULL;
args->connection = NULL;
memcpy(args->remote_udid, event->udid, strlen(event->udid));
_beginthreadex(NULL, 0, connect_device, args, 0, NULL);
break;
case IDEVICE_DEVICE_REMOVE:
print_err("[INFO] iDevice removed, udid: %s\n", event->udid);
break;
case IDEVICE_DEVICE_PAIRED:
print_err("[INFO] iDevice paired, udid: %s\n", event->udid);
break;
}
}
//endregion
HRESULT server_start() {
tcp_mode = GetPrivateProfileIntW(L"brokenithm", L"tcp", 0, L".\\segatools.ini") == 1;
server_port = GetPrivateProfileIntW(L"brokenithm", L"port", 52468, L".\\segatools.ini");
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, memFileName);
if (hMapFile == NULL) {
hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, memFileName);
if (hMapFile == NULL) {
print_err("[ERROR] CreateFileMapping failed! error: %lu\n", GetLastError());
return E_FAIL;
}
}
struct IPCMemoryInfo* memory = MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1024, NULL);
chuni_io_file_mapping = memory;
if (memory == NULL) {
print_err("[ERROR] Cannot get view of memory map! error: %lu\n", GetLastError());
return E_FAIL;
}
struct WSAData wsaData = {};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
print_err("[ERROR] WSA startup failed!\n");
return E_FAIL;
}
_beginthreadex(NULL, 0, server_thread_proc, memory, 0, NULL);
int status;
if ((status = idevice_event_subscribe(device_event_callback, memory))) {
print_err("[ERROR] Subscribing for iDevice events failed: %d\n", status);
return E_FAIL;
} else {
print_err("[INFO] Waiting for iDevices...\n");
}
return S_OK;
}
//endregion
//region ChuniIO stuff
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx);
static bool chuni_io_coin;
static uint16_t chuni_io_coins;
static uint8_t chuni_io_hand_pos;
static HANDLE chuni_io_slider_thread;
static bool chuni_io_slider_stop_flag;
static struct chuni_io_config chuni_io_cfg;
uint16_t chuni_io_get_api_version() {
return 0x0102;
}
HRESULT chuni_io_jvs_init() {
chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini");
const HRESULT result = server_start();
if (result != S_OK) {
return result;
}
return S_OK;
}
void chuni_io_jvs_read_coin_counter(uint16_t *total) {
if (total == NULL) {
return;
}
if (chuni_io_file_mapping && chuni_io_file_mapping->coinInsertion) {
chuni_io_coins++;
chuni_io_file_mapping->coinInsertion = 0;
} else {
if (GetAsyncKeyState(chuni_io_cfg.vk_coin)) {
if (!chuni_io_coin) {
chuni_io_coin = true;
chuni_io_coins++;
}
} else {
chuni_io_coin = false;
}
}
*total = chuni_io_coins;
}
void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) {
size_t i;
if ((chuni_io_file_mapping && chuni_io_file_mapping->testBtn) || GetAsyncKeyState(chuni_io_cfg.vk_test)) {
*opbtn |= CHUNI_IO_OPBTN_TEST; /* Test */
}
if ((chuni_io_file_mapping && chuni_io_file_mapping->serviceBtn) || GetAsyncKeyState(chuni_io_cfg.vk_service)) {
*opbtn |= CHUNI_IO_OPBTN_SERVICE; /* Service */
}
if (GetAsyncKeyState(chuni_io_cfg.vk_ir_emu)) {
if (chuni_io_hand_pos < 6) {
chuni_io_hand_pos++;
}
} else {
if (chuni_io_hand_pos > 0) {
chuni_io_hand_pos--;
}
}
for (i = 0 ; i < 6 ; i++) {
if (chuni_io_hand_pos > i) {
*beams |= (1 << i);
}
}
// IR format is beams[5:0] = {b5,b6,b3,b4,b1,b2};
for (i = 0 ; i < 3 ; i++) {
if (chuni_io_file_mapping && chuni_io_file_mapping->airIoStatus[i*2])
*beams |= 1 << (i * 2 + 1);
if (chuni_io_file_mapping && chuni_io_file_mapping->airIoStatus[i*2+1])
*beams |= 1 << i * 2;
}
}
HRESULT chuni_io_slider_init() {
return S_OK;
}
void chuni_io_slider_start(void* callback) {
if (chuni_io_slider_thread != NULL) {
return;
}
chuni_io_slider_thread = (HANDLE) _beginthreadex(
NULL,
0,
chuni_io_slider_thread_proc,
callback,
0,
NULL);
}
void chuni_io_slider_stop(void) {
if (chuni_io_slider_thread == NULL) {
return;
}
chuni_io_slider_stop_flag = true;
WaitForSingleObject(chuni_io_slider_thread, INFINITE);
CloseHandle(chuni_io_slider_thread);
chuni_io_slider_thread = NULL;
chuni_io_slider_stop_flag = false;
}
void chuni_io_slider_set_leds(const uint8_t *rgb) {
if (chuni_io_file_mapping) {
memcpy(chuni_io_file_mapping->ledRgbData, rgb, 32 * 3);
}
}
HRESULT chuni_io_led_init(void) {
return S_OK;
}
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb) {}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
{
const chuni_io_slider_callback_t callback = ctx;
#pragma clang diagnostic push
#pragma ide diagnostic ignored "LoopDoesntUseConditionVariableInspection"
// ReSharper disable once CppDFALoopConditionNotUpdated
while (!chuni_io_slider_stop_flag) {
uint8_t pressure[32];
if (chuni_io_file_mapping) {
memcpy(pressure, chuni_io_file_mapping->sliderIoStatus, 32);
}
callback(pressure);
Sleep(1);
}
#pragma clang diagnostic pop
return 0;
}
//endregion
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
return TRUE;
}

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)