├── README.md ├── build32.bat ├── build64.bat ├── meson.build └── src ├── chu2to3 ├── chu2to3.c ├── chu2to3.def ├── chu2to3.h └── meson.build └── precompiled.h /README.md: -------------------------------------------------------------------------------- 1 | # chu2to3 2 | 3 | chu2to3 is a chuniio wrapper which allows you to use chuniio.dll in chusan. 4 | 5 | It's been tested on the RedBoard, Yubideck, Laverita and Tasoller so far but should work with other chuniio implementations. 6 | 7 | # Acknowledgments 8 | 9 | Special thanks to akiroz for telling me about CreateFileMapping, and One3 for the way to the easy compilation :) 10 | 11 | # How to use 12 | 13 | 1. Place both files in your chusan folder, **as well as your desired chuniio.dll** 14 | 15 | 2. Set the following in your tools.ini 16 | 17 | ``` 18 | [chuniio] 19 | path32=chu2to3_x86.dll 20 | path64=chu2to3_x64.dll 21 | ``` 22 | 23 | 3. enjoy your favorite controller 24 | 25 | # How to build 26 | 27 | 1. Install MSVC (build tools), meson & ninja 28 | 2. run `build32.bat` and `build64.bat` 29 | 3. retrieve your files in `bin` folder 30 | 31 | Note: one might have to update the path for vcvarsall.bat in both build32.bat and build64.bat, 32 | I'm using MSVC 2022 so it currently is `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\` for me 33 | -------------------------------------------------------------------------------- /build32.bat: -------------------------------------------------------------------------------- 1 | cd /d %~dp0 2 | call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64_x86 3 | meson setup build32 --buildtype=release 4 | ninja -C build32 5 | mkdir bin 6 | copy build32\src\chu2to3\chu2to3.dll bin\chu2to3_x86.dll 7 | pause -------------------------------------------------------------------------------- /build64.bat: -------------------------------------------------------------------------------- 1 | cd /d %~dp0 2 | call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64 3 | meson setup build64 --buildtype=release 4 | ninja -C build64 5 | mkdir bin 6 | copy build64\src\chu2to3\chu2to3.dll bin\chu2to3_x64.dll 7 | pause -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('rb_dll', 'c', version: '0.1.0') 2 | 3 | add_project_arguments( 4 | '-DCOBJMACROS', 5 | '-DDIRECTINPUT_VERSION=0x0800', 6 | '-DWIN32_LEAN_AND_MEAN', 7 | '-D_WIN32_WINNT=_WIN32_WINNT_WIN7', 8 | '-DMINGW_HAS_SECURE_API=1', 9 | language: 'c', 10 | ) 11 | 12 | # Use get_argument_syntax() instead once Meson 0.49.0 releases 13 | if meson.get_compiler('c').get_id() != 'msvc' 14 | add_project_arguments( 15 | '-ffunction-sections', 16 | '-fdata-sections', 17 | language: 'c', 18 | ) 19 | 20 | add_project_link_arguments( 21 | '-Wl,--enable-stdcall-fixup', 22 | '-Wl,--exclude-all-symbols', 23 | '-Wl,--gc-sections', 24 | '-static-libgcc', 25 | language: 'c', 26 | ) 27 | endif 28 | 29 | cc = meson.get_compiler('c') 30 | 31 | inc = include_directories('.') 32 | 33 | subdir('src\chu2to3') -------------------------------------------------------------------------------- /src/chu2to3/chu2to3.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "chu2to3.h" 10 | 11 | /* chuniio.dll dynamic loading */ 12 | HMODULE hinstLib; 13 | typedef uint16_t (*chuni_io_get_api_version_t)(void); 14 | typedef HRESULT (*chuni_io_jvs_init_t)(void); 15 | typedef void (*chuni_io_jvs_poll_t)(uint8_t*, uint8_t*); 16 | typedef void (*chuni_io_jvs_read_coin_counter_t)(uint16_t *); 17 | 18 | typedef HRESULT (*chuni_io_slider_init_t)(void); 19 | typedef void (*chuni_io_slider_set_leds_t)(const uint8_t *); 20 | typedef void (*chuni_io_slider_start_t)(chuni_io_slider_callback_t); 21 | typedef void (*chuni_io_slider_stop_t)(void); 22 | 23 | chuni_io_get_api_version_t _chuni_io_get_api_version; 24 | chuni_io_jvs_init_t _chuni_io_jvs_init; 25 | chuni_io_jvs_poll_t _chuni_io_jvs_poll; 26 | chuni_io_jvs_read_coin_counter_t _chuni_io_jvs_read_coin_counter; 27 | chuni_io_slider_init_t _chuni_io_slider_init; 28 | chuni_io_slider_set_leds_t _chuni_io_slider_set_leds; 29 | chuni_io_slider_start_t _chuni_io_slider_start; 30 | chuni_io_slider_stop_t _chuni_io_slider_stop; 31 | 32 | /* SHMEM Handling */ 33 | #define BUF_SIZE 1024 34 | #define SHMEM_WRITE(buf, size) CopyMemory((PVOID)g_pBuf, buf, size) 35 | #define SHMEM_READ(buf, size) CopyMemory(buf,(PVOID)g_pBuf, size) 36 | TCHAR g_shmem_name[]=TEXT("Local\\Chu2to3Shmem"); 37 | HANDLE g_hMapFile; 38 | LPVOID g_pBuf; 39 | 40 | #pragma pack(1) 41 | typedef struct shared_data_s { 42 | uint16_t coin_counter; 43 | uint8_t opbtn; 44 | uint8_t beams; 45 | uint16_t version; 46 | } shared_data_t; 47 | 48 | shared_data_t g_shared_data; 49 | 50 | bool shmem_create() 51 | { 52 | g_hMapFile = CreateFileMapping( 53 | INVALID_HANDLE_VALUE, // use paging file 54 | NULL, // default security 55 | PAGE_READWRITE, // read/write access 56 | 0, // maximum object size (high-order DWORD) 57 | BUF_SIZE, // maximum object size (low-order DWORD) 58 | g_shmem_name); // name of mapping object 59 | 60 | if (g_hMapFile == NULL) 61 | { 62 | printf("shmem_create : Could not create file mapping object (%d).\n", 63 | GetLastError()); 64 | return 0; 65 | } 66 | g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object 67 | FILE_MAP_ALL_ACCESS, // read/write permission 68 | 0, 69 | 0, 70 | BUF_SIZE); 71 | 72 | if (g_pBuf == NULL) 73 | { 74 | printf("shmem_create : Could not map view of file (%d).\n", 75 | GetLastError()); 76 | 77 | CloseHandle(g_hMapFile); 78 | 79 | return 0; 80 | } 81 | 82 | return 1; 83 | } 84 | 85 | bool shmem_load() 86 | { 87 | g_hMapFile = OpenFileMapping( 88 | FILE_MAP_ALL_ACCESS, // read/write access 89 | FALSE, // do not inherit the name 90 | g_shmem_name); // name of mapping object 91 | 92 | if (g_hMapFile == NULL) 93 | { 94 | printf("shmem_load : Could not open file mapping object (%d).\n", 95 | GetLastError()); 96 | return 0; 97 | } 98 | 99 | g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object 100 | FILE_MAP_ALL_ACCESS, // read/write permission 101 | 0, 102 | 0, 103 | BUF_SIZE); 104 | 105 | if (g_pBuf == NULL) 106 | { 107 | printf("shmem_load : Could not map view of file (%d).\n", 108 | GetLastError()); 109 | 110 | CloseHandle(g_hMapFile); 111 | 112 | return 0; 113 | } 114 | 115 | return 1; 116 | } 117 | 118 | void shmem_free() 119 | { 120 | UnmapViewOfFile(g_pBuf); 121 | CloseHandle(g_hMapFile); 122 | } 123 | 124 | /* jvs polling thread (to forward info to x64 dll) */ 125 | static HANDLE jvs_poll_thread; 126 | static bool jvs_poll_stop_flag; 127 | 128 | static unsigned int __stdcall jvs_poll_thread_proc(void *ctx) 129 | { 130 | while (1) { 131 | _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); 132 | g_shared_data.opbtn = 0; 133 | _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); 134 | SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); 135 | Sleep(1); 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | /* chuniio exports */ 142 | uint16_t __cdecl chuni_io_get_api_version(void) 143 | { 144 | #ifdef _WIN64 145 | /* x64 must just open the shmem and do nothing else */ 146 | if (!shmem_load()) 147 | { 148 | return -1; 149 | } 150 | return g_shared_data.version; 151 | #endif 152 | 153 | /* this is the first function called so let's setup the chuniio forwarding */ 154 | hinstLib = LoadLibrary("chuniio.dll"); 155 | if (hinstLib == NULL) { 156 | printf("ERROR: unable to load chuniio.dll (error %d)\n",GetLastError()); 157 | return -1; 158 | } 159 | 160 | _chuni_io_get_api_version = (chuni_io_get_api_version_t)GetProcAddress(hinstLib, "chuni_io_get_api_version"); 161 | _chuni_io_jvs_init = (chuni_io_jvs_init_t)GetProcAddress(hinstLib, "chuni_io_jvs_init"); 162 | _chuni_io_jvs_poll = (chuni_io_jvs_poll_t)GetProcAddress(hinstLib, "chuni_io_jvs_poll"); 163 | _chuni_io_jvs_read_coin_counter = (chuni_io_jvs_read_coin_counter_t)GetProcAddress(hinstLib, "chuni_io_jvs_read_coin_counter"); 164 | _chuni_io_slider_init = (chuni_io_slider_init_t)GetProcAddress(hinstLib, "chuni_io_slider_init"); 165 | _chuni_io_slider_set_leds = (chuni_io_slider_set_leds_t)GetProcAddress(hinstLib, "chuni_io_slider_set_leds"); 166 | _chuni_io_slider_start = (chuni_io_slider_start_t)GetProcAddress(hinstLib, "chuni_io_slider_start"); 167 | _chuni_io_slider_stop = (chuni_io_slider_stop_t)GetProcAddress(hinstLib, "chuni_io_slider_stop"); 168 | 169 | /* x86 has to create the shmem */ 170 | if (!shmem_create()) 171 | { 172 | return -1; 173 | } 174 | 175 | if ( _chuni_io_get_api_version == NULL ) 176 | { 177 | g_shared_data.version = 0x0100; 178 | } 179 | else 180 | { 181 | g_shared_data.version = _chuni_io_get_api_version(); 182 | if (g_shared_data.version > 0x0101) 183 | g_shared_data.version = 0x0101; 184 | } 185 | 186 | SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); 187 | 188 | return g_shared_data.version; 189 | } 190 | 191 | HRESULT __cdecl chuni_io_jvs_init(void) 192 | { 193 | #ifdef _WIN64 194 | /* x86 only */ 195 | return S_OK; 196 | #endif 197 | _chuni_io_jvs_init(); 198 | 199 | /* start jvs poll thread now that jvs_init is done */ 200 | if (jvs_poll_thread != NULL) { 201 | return S_OK; 202 | } 203 | 204 | jvs_poll_thread = (HANDLE) _beginthreadex(NULL, 205 | 0, 206 | jvs_poll_thread_proc, 207 | NULL, 208 | 0, 209 | NULL); 210 | return S_OK; 211 | } 212 | 213 | void __cdecl chuni_io_jvs_read_coin_counter(uint16_t *out) 214 | { 215 | #ifndef _WIN64 216 | /* x86 can perform the call and update shmem (although this call never happens) */ 217 | _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); 218 | SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); 219 | return; 220 | #endif 221 | /* x64 must read value from shmem and update arg */ 222 | SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); 223 | if (out == NULL) { 224 | return; 225 | } 226 | *out = g_shared_data.coin_counter; 227 | } 228 | 229 | void __cdecl chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) 230 | { 231 | #ifndef _WIN64 232 | /* x86 can perform the call and update shmem (although this call never happens) */ 233 | _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); 234 | SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); 235 | return; 236 | #endif 237 | /* x64 must read value from shmem and update args */ 238 | SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); 239 | *opbtn = g_shared_data.opbtn; 240 | *beams = g_shared_data.beams; 241 | } 242 | 243 | HRESULT __cdecl chuni_io_slider_init(void) 244 | { 245 | #ifdef _WIN64 246 | /* x86 only */ 247 | return S_OK; 248 | #endif 249 | 250 | return _chuni_io_slider_init(); 251 | } 252 | 253 | void __cdecl chuni_io_slider_start(chuni_io_slider_callback_t callback) 254 | { 255 | #ifdef _WIN64 256 | /* x86 only */ 257 | return; 258 | #endif 259 | 260 | _chuni_io_slider_start(callback); 261 | } 262 | 263 | void __cdecl chuni_io_slider_stop(void) 264 | { 265 | #ifdef _WIN64 266 | /* x86 only */ 267 | return; 268 | #endif 269 | _chuni_io_slider_stop(); 270 | } 271 | 272 | void __cdecl chuni_io_slider_set_leds(const uint8_t *rgb) 273 | { 274 | #ifdef _WIN64 275 | /* x86 only */ 276 | return; 277 | #endif 278 | 279 | _chuni_io_slider_set_leds(rgb); 280 | } -------------------------------------------------------------------------------- /src/chu2to3/chu2to3.def: -------------------------------------------------------------------------------- 1 | LIBRARY chu2to3 2 | 3 | EXPORTS 4 | chuni_io_get_api_version 5 | chuni_io_jvs_init 6 | chuni_io_jvs_poll 7 | chuni_io_jvs_read_coin_counter 8 | chuni_io_slider_init 9 | chuni_io_slider_set_leds 10 | chuni_io_slider_start 11 | chuni_io_slider_stop -------------------------------------------------------------------------------- /src/chu2to3/chu2to3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | CHUNITHM CUSTOM IO API 5 | 6 | Changelog: 7 | 8 | - 0x0100: Initial API version (assumed if chuni_io_get_api_version is not 9 | exported) 10 | - 0x0101: Fix IR beam mappings 11 | */ 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | /* Get the version of the Chunithm IO API that this DLL supports. This 19 | function should return a positive 16-bit integer, where the high byte is 20 | the major version and the low byte is the minor version (as defined by the 21 | Semantic Versioning standard). 22 | 23 | The latest API version as of this writing is 0x0101. */ 24 | 25 | uint16_t chuni_io_get_api_version(void); 26 | 27 | /* Initialize JVS-based input. This function will be called before any other 28 | chuni_io_jvs_*() function calls. Errors returned from this function will 29 | manifest as a disconnected JVS bus. 30 | 31 | All subsequent calls may originate from arbitrary threads and some may 32 | overlap with each other. Ensuring synchronization inside your IO DLL is 33 | your responsibility. 34 | 35 | Minimum API version: 0x0100 */ 36 | 37 | HRESULT chuni_io_jvs_init(void); 38 | 39 | /* Poll JVS input. 40 | 41 | opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1 42 | is Service. 43 | 44 | beam returns the IR beams that are currently broken, where bit 0 is the 45 | lowest IR beam and bit 5 is the highest IR beam, for a total of six beams. 46 | 47 | Both bit masks are active-high. 48 | 49 | Note that you cannot instantly break the entire IR grid in a single frame to 50 | simulate hand movement; this will be judged as a miss. You need to simulate 51 | a gradual raising and lowering of the hands. Consult the proof-of-concept 52 | implementation for details. 53 | 54 | NOTE: Previous releases of Segatools mapped the IR beam inputs incorrectly. 55 | Please ensure that you advertise an API version of at least 0x0101 so that 56 | the correct mapping can be used. 57 | 58 | Minimum API version: 0x0100 59 | Latest API version: 0x0101 */ 60 | 61 | void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); 62 | 63 | /* Read the current state of the coin counter. This value should be incremented 64 | for every coin detected by the coin acceptor mechanism. This count does not 65 | need to persist beyond the lifetime of the process. 66 | 67 | Minimum API version: 0x0100 */ 68 | 69 | void chuni_io_jvs_read_coin_counter(uint16_t *total); 70 | 71 | 72 | /* Initialize touch slider emulation. This function will be called before any 73 | other chuni_io_slider_*() function calls. 74 | 75 | All subsequent calls may originate from arbitrary threads and some may 76 | overlap with each other. Ensuring synchronization inside your IO DLL is 77 | your responsibility. 78 | 79 | Minimum API version: 0x0100 */ 80 | 81 | HRESULT chuni_io_slider_init(void); 82 | 83 | /* Chunithm touch slider layout: 84 | 85 | ^^^ Toward screen ^^^ 86 | 87 | ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ 88 | 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 | 89 | ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ 90 | 32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | 91 | ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ 92 | 93 | There are a total of 32 regions on the touch slider. Each region can return 94 | an 8-bit pressure value. The operator menu allows the operator to adjust the 95 | pressure level at which a region is considered to be pressed; the factory 96 | default value for this setting is 20. */ 97 | 98 | /* Callback function supplied to your IO DLL. This must be called with a 99 | pointer to a 32-byte array of pressure values, one byte per slider cell. 100 | See above for layout and pressure threshold information. 101 | 102 | The callback will copy the pressure state data out of your buffer before 103 | returning. The pointer will not be retained. */ 104 | 105 | typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); 106 | 107 | /* Start polling the slider. Your DLL must start a polling thread and call the 108 | supplied function periodically from that thread with new input state. The 109 | update interval is up to you, but if your input device doesn't have any 110 | preferred interval then 1 kHz is a reasonable maximum frequency. 111 | 112 | Note that you do have to have to call the callback "occasionally" even if 113 | nothing is changing, otherwise the game will raise a comm timeout error. 114 | 115 | Minimum API version: 0x0100 */ 116 | 117 | void chuni_io_slider_start(chuni_io_slider_callback_t callback); 118 | 119 | /* Stop polling the slider. You must cease to invoke the input callback before 120 | returning from this function. 121 | 122 | This *will* be called in the course of regular operation. For example, 123 | every time you go into the operator menu the slider and all of the other I/O 124 | on the cabinet gets restarted. 125 | 126 | Following on from the above, the slider polling loop *will* be restarted 127 | after being stopped in the course of regular operation. Do not permanently 128 | tear down your input driver in response to this function call. 129 | 130 | Minimum API version: 0x0100 */ 131 | 132 | void chuni_io_slider_stop(void); 133 | 134 | /* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96 135 | bytes is supplied. The illuminated areas on the touch slider are some 136 | combination of rectangular regions and dividing lines between these regions 137 | but the exact mapping of this lighting control buffer is still TBD. 138 | 139 | Minimum API version: 0x0100 */ 140 | 141 | void chuni_io_slider_set_leds(const uint8_t *rgb); 142 | -------------------------------------------------------------------------------- /src/chu2to3/meson.build: -------------------------------------------------------------------------------- 1 | chu2to3_dll = shared_library( 2 | 'chu2to3', 3 | name_prefix : '', 4 | include_directories : inc, 5 | implicit_include_directories : false, 6 | vs_module_defs : 'chu2to3.def', 7 | c_pch : '../precompiled.h', 8 | sources : [ 9 | 'chu2to3.c', 10 | 'chu2to3.h', 11 | ], 12 | ) -------------------------------------------------------------------------------- /src/precompiled.h: -------------------------------------------------------------------------------- 1 | /* 2 | Making NTSTATUS available is slightly awkward. See: 3 | https://kirkshoop.github.io/2011/09/20/ntstatus.html 4 | */ 5 | 6 | /* Win32 user-mode API */ 7 | #define WIN32_NO_STATUS 8 | #include 9 | #undef WIN32_NO_STATUS 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* Win32 kernel-mode definitions */ 20 | #ifdef __GNUC__ 21 | /* MinGW needs to include this for PHYSICAL_ADDRESS to be defined. 22 | The MS SDK throws a bunch of duplicate symbol errors instead. */ 23 | #include 24 | #else 25 | #include 26 | #endif 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | --------------------------------------------------------------------------------