commit
30f3a23bc6
|
@ -1,20 +1,12 @@
|
||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.27)
|
||||||
project(chuniio_brokenithm)
|
project(brokenithm)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
||||||
include_directories("${CMAKE_SOURCE_DIR}/include/")
|
include_directories("${CMAKE_SOURCE_DIR}/include/")
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
link_directories(src)
|
include_directories(.)
|
||||||
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 "")
|
add_subdirectory(aimeio)
|
||||||
# set_target_properties(chuniio_brokenithm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
add_subdirectory(chuniio)
|
||||||
target_link_libraries(chuniio_brokenithm ws2_32)
|
add_subdirectory(util)
|
||||||
|
|
30
README.md
30
README.md
|
@ -1,17 +1,19 @@
|
||||||
# chuniio-brokenithm
|
# 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.
|
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
|
## Configuration
|
||||||
segatools.ini
|
segatools.ini
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
[aimeio]
|
||||||
|
path64=aimeio_brokenithm.dll
|
||||||
|
|
||||||
[chuniio]
|
[chuniio]
|
||||||
path=chuniio_brokenithm.dll
|
path32=chuniio_brokenithm_x86.dll
|
||||||
|
path64=chuniio_brokenithm_x64.dll
|
||||||
|
|
||||||
[io3]
|
[io3]
|
||||||
; Test button virtual-key code. Default is the 1 key.
|
; Test button virtual-key code. Default is the 1 key.
|
||||||
|
@ -34,11 +36,21 @@ port=52468
|
||||||
|
|
||||||
## Build instructions
|
## Build instructions
|
||||||
```shell
|
```shell
|
||||||
mkdir cmake-build
|
# In MinGW32
|
||||||
cd cmake-build
|
pacman -S mingw-w64-i686-libimobiledevice
|
||||||
|
|
||||||
cmake ..
|
mkdir -p build/build32
|
||||||
|
cd build/build32
|
||||||
|
cmake ../..
|
||||||
ninja
|
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
|
||||||
```
|
```
|
|
@ -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")
|
|
@ -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) {}
|
|
@ -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
|
|
@ -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 ()
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -5,22 +5,6 @@
|
||||||
#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H
|
#ifndef CHUNIIO_BROKENITHM_CHUNIIO_H
|
||||||
#define 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
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
@ -40,7 +24,7 @@ enum {
|
||||||
|
|
||||||
The latest API version as of this writing is 0x0101. */
|
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
|
/* Initialize JVS-based input. This function will be called before any other
|
||||||
chuni_io_jvs_*() function calls. Errors returned from this function will
|
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 */
|
Minimum API version: 0x0100 */
|
||||||
|
|
||||||
HRESULT chuni_io_jvs_init();
|
HRESULT __declspec(dllexport) chuni_io_jvs_init();
|
||||||
|
|
||||||
/* Poll JVS input.
|
/* Poll JVS input.
|
||||||
|
|
||||||
|
@ -76,7 +60,7 @@ HRESULT chuni_io_jvs_init();
|
||||||
Minimum API version: 0x0100
|
Minimum API version: 0x0100
|
||||||
Latest API version: 0x0101 */
|
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
|
/* 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
|
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 */
|
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
|
/* 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 */
|
Minimum API version: 0x0100 */
|
||||||
|
|
||||||
HRESULT chuni_io_slider_init(void);
|
HRESULT __declspec(dllexport) chuni_io_slider_init(void);
|
||||||
|
|
||||||
/* Chunithm touch slider layout:
|
/* Chunithm touch slider layout:
|
||||||
|
|
||||||
|
@ -132,7 +116,7 @@ typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
|
||||||
|
|
||||||
Minimum API version: 0x0100 */
|
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
|
/* Stop polling the slider. You must cease to invoke the input callback before
|
||||||
returning from this function.
|
returning from this function.
|
||||||
|
@ -147,7 +131,7 @@ void chuni_io_slider_start(void *callback);
|
||||||
|
|
||||||
Minimum API version: 0x0100 */
|
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
|
/* 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
|
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 */
|
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
|
/* Initialize LED emulation. This function will be called before any
|
||||||
other chuni_io_led_*() function calls.
|
other chuni_io_led_*() function calls.
|
||||||
|
@ -167,7 +151,7 @@ void chuni_io_slider_set_leds(const uint8_t *rgb);
|
||||||
|
|
||||||
Minimum API version: 0x0102 */
|
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.
|
/* 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 */
|
Minimum API version: 0x0102 */
|
||||||
|
|
||||||
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
|
void __declspec(dllexport) chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);
|
||||||
|
|
||||||
#endif //CHUNIIO_BROKENITHM_CHUNIIO_H
|
#endif //CHUNIIO_BROKENITHM_CHUNIIO_H
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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(<);
|
||||||
|
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");
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -1,44 +1,44 @@
|
||||||
//
|
//
|
||||||
// Created by beerpsi on 12/29/2023.
|
// Created by beerpsi on 12/29/2023.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef CHUNIIO_BROKENITHM_SOCKET_H
|
#ifndef CHUNIIO_BROKENITHM_SOCKET_H
|
||||||
#define CHUNIIO_BROKENITHM_SOCKET_H
|
#define CHUNIIO_BROKENITHM_SOCKET_H
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#ifndef WINVER
|
#ifndef WINVER
|
||||||
#define WINVER 0x0501
|
#define WINVER 0x0501
|
||||||
#endif // WINVER
|
#endif // WINVER
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#else
|
#else
|
||||||
//translate windows functions to linux functions
|
//translate windows functions to linux functions
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#define SOCKET int
|
#define SOCKET int
|
||||||
#define INVALID_SOCKET (SOCKET)(~0)
|
#define INVALID_SOCKET (SOCKET)(~0)
|
||||||
#define SOCKET_ERROR (-1)
|
#define SOCKET_ERROR (-1)
|
||||||
#define closesocket close
|
#define closesocket close
|
||||||
#define SOCKADDR_IN sockaddr_in
|
#define SOCKADDR_IN sockaddr_in
|
||||||
#define ZeroMemory(d,l) memset((d), 0, (l))
|
#define ZeroMemory(d,l) memset((d), 0, (l))
|
||||||
#define ioctlsocket ioctl
|
#define ioctlsocket ioctl
|
||||||
#ifndef SA_INTERRUPT
|
#ifndef SA_INTERRUPT
|
||||||
#define SA_INTERRUPT 0 //ignore this setting
|
#define SA_INTERRUPT 0 //ignore this setting
|
||||||
#endif
|
#endif
|
||||||
#define SD_BOTH SHUT_RDWR
|
#define SD_BOTH SHUT_RDWR
|
||||||
#ifndef __hpux
|
#ifndef __hpux
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
#endif /* __hpux */
|
#endif /* __hpux */
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
typedef sockaddr *LPSOCKADDR;
|
typedef sockaddr *LPSOCKADDR;
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
#endif //CHUNIIO_BROKENITHM_SOCKET_H
|
#endif //CHUNIIO_BROKENITHM_SOCKET_H
|
|
@ -1,91 +1,100 @@
|
||||||
//
|
//
|
||||||
// Created by beerpsi on 12/29/2023.
|
// Created by beerpsi on 12/29/2023.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef CHUNIIO_BROKENITHM_STRUCT_H
|
#ifndef CHUNIIO_BROKENITHM_STRUCT_H
|
||||||
#define CHUNIIO_BROKENITHM_STRUCT_H
|
#define CHUNIIO_BROKENITHM_STRUCT_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
;
|
;
|
||||||
#pragma pack(push)
|
#pragma pack(push)
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
|
|
||||||
struct IPCMemoryInfo
|
struct IPCMemoryInfo
|
||||||
{
|
{
|
||||||
uint8_t airIoStatus[6];
|
uint8_t airIoStatus[6];
|
||||||
uint8_t sliderIoStatus[32];
|
uint8_t sliderIoStatus[32];
|
||||||
uint8_t ledRgbData[32 * 3];
|
uint8_t ledRgbData[32 * 3];
|
||||||
uint8_t testBtn;
|
uint8_t testBtn;
|
||||||
uint8_t serviceBtn;
|
uint8_t serviceBtn;
|
||||||
uint8_t coinInsertion;
|
uint8_t coinInsertion;
|
||||||
uint8_t cardRead;
|
uint8_t cardRead;
|
||||||
uint8_t remoteCardRead;
|
uint8_t remoteCardRead;
|
||||||
uint8_t remoteCardType;
|
uint8_t remoteCardType;
|
||||||
uint8_t remoteCardId[10];
|
uint8_t remoteCardId[10];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PacketInput
|
struct iOSPacketInput {
|
||||||
{
|
uint8_t packetSize;
|
||||||
uint8_t packetSize;
|
uint8_t packetName[3];
|
||||||
uint8_t packetName[3];
|
uint8_t airIoStatus[6];
|
||||||
uint32_t packetId;
|
uint8_t sliderIoStatus[32];
|
||||||
uint8_t airIoStatus[6];
|
uint8_t testBtn;
|
||||||
uint8_t sliderIoStatus[32];
|
uint8_t serviceBtn;
|
||||||
uint8_t testBtn;
|
};
|
||||||
uint8_t serviceBtn;
|
|
||||||
};
|
struct PacketInput
|
||||||
|
{
|
||||||
struct PacketInputNoAir
|
uint8_t packetSize;
|
||||||
{
|
uint8_t packetName[3];
|
||||||
uint8_t packetSize;
|
uint32_t packetId;
|
||||||
uint8_t packetName[3];
|
uint8_t airIoStatus[6];
|
||||||
uint32_t packetId;
|
uint8_t sliderIoStatus[32];
|
||||||
uint8_t sliderIoStatus[32];
|
uint8_t testBtn;
|
||||||
uint8_t testBtn;
|
uint8_t serviceBtn;
|
||||||
uint8_t serviceBtn;
|
};
|
||||||
};
|
|
||||||
|
struct PacketInputNoAir
|
||||||
struct PacketFunction
|
{
|
||||||
{
|
uint8_t packetSize;
|
||||||
uint8_t packetSize;
|
uint8_t packetName[3];
|
||||||
uint8_t packetName[3];
|
uint32_t packetId;
|
||||||
uint8_t funcBtn;
|
uint8_t sliderIoStatus[32];
|
||||||
};
|
uint8_t testBtn;
|
||||||
|
uint8_t serviceBtn;
|
||||||
struct PacketConnect
|
};
|
||||||
{
|
|
||||||
uint8_t packetSize;
|
struct PacketFunction
|
||||||
uint8_t packetName[3];
|
{
|
||||||
uint8_t addrType;
|
uint8_t packetSize;
|
||||||
uint16_t port;
|
uint8_t packetName[3];
|
||||||
union
|
uint8_t funcBtn;
|
||||||
{
|
};
|
||||||
struct
|
|
||||||
{
|
struct PacketConnect
|
||||||
uint8_t addr[4];
|
{
|
||||||
uint8_t padding[12];
|
uint8_t packetSize;
|
||||||
} addr4;
|
uint8_t packetName[3];
|
||||||
uint8_t addr6[16];
|
uint8_t addrType;
|
||||||
} addr;
|
uint16_t port;
|
||||||
};
|
union
|
||||||
|
{
|
||||||
struct PacketCard
|
struct
|
||||||
{
|
{
|
||||||
uint8_t packetSize;
|
uint8_t addr[4];
|
||||||
uint8_t packetName[3];
|
uint8_t padding[12];
|
||||||
uint8_t remoteCardRead;
|
} addr4;
|
||||||
uint8_t remoteCardType;
|
uint8_t addr6[16];
|
||||||
uint8_t remoteCardId[10];
|
} addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PacketPing
|
struct PacketCard
|
||||||
{
|
{
|
||||||
uint8_t packetSize;
|
uint8_t packetSize;
|
||||||
uint8_t packetName[3];
|
uint8_t packetName[3];
|
||||||
uint64_t remotePingTime;
|
uint8_t remoteCardRead;
|
||||||
};
|
uint8_t remoteCardType;
|
||||||
|
uint8_t remoteCardId[10];
|
||||||
#pragma pack(pop)
|
};
|
||||||
|
|
||||||
#endif //CHUNIIO_BROKENITHM_STRUCT_H
|
struct PacketPing
|
||||||
|
{
|
||||||
|
uint8_t packetSize;
|
||||||
|
uint8_t packetName[3];
|
||||||
|
uint64_t remotePingTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#endif //CHUNIIO_BROKENITHM_STRUCT_H
|
733
src/chuniio.c
733
src/chuniio.c
|
@ -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(<);
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -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)
|
Loading…
Reference in New Issue