From ee414d122bb343315268970c457b3d736e337683 Mon Sep 17 00:00:00 2001 From: CrazyRedMachine Date: Sun, 24 Dec 2023 08:10:29 -0500 Subject: [PATCH] Include chu2to3 engine --- chunihook/dllmain.c | 13 +- chuniio/chu2to3.c | 357 ++++++++++++++++++++++++++++++++++++++ chuniio/chu2to3.h | 26 +++ chuniio/chuniio.h | 22 ++- chuniio/meson.build | 2 + chusanhook/chuni-dll.c | 88 ++++++++-- chusanhook/chuni-dll.h | 1 + chusanhook/chusanhook.def | 10 ++ chusanhook/config.c | 31 ++-- chusanhook/dllmain.c | 13 +- dist/chusan/segatools.ini | 6 +- 11 files changed, 532 insertions(+), 37 deletions(-) create mode 100644 chuniio/chu2to3.c create mode 100644 chuniio/chu2to3.h diff --git a/chunihook/dllmain.c b/chunihook/dllmain.c index 69cf559..1a3fb57 100644 --- a/chunihook/dllmain.c +++ b/chunihook/dllmain.c @@ -97,11 +97,16 @@ static DWORD CALLBACK chuni_pre_startup(void) goto fail; } - hr = led15093_hook_init(&chuni_hook_cfg.led15093, - chuni_dll.led_init, chuni_dll.led_set_leds, 10, 2, 2, 1); + if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL ) + { + dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n"); + } else { + hr = led15093_hook_init(&chuni_hook_cfg.led15093, + chuni_dll.led_init, chuni_dll.led_set_leds, 10, 2, 2, 1); - if (FAILED(hr)) { - goto fail; + if (FAILED(hr)) { + goto fail; + } } hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, 1, chuni_hook_mod); diff --git a/chuniio/chu2to3.c b/chuniio/chu2to3.c new file mode 100644 index 0000000..3569426 --- /dev/null +++ b/chuniio/chu2to3.c @@ -0,0 +1,357 @@ +#include + +#include +#include +#include +#include + +#include "chuniio/chu2to3.h" +#include "util/dprintf.h" + +// Check windows +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +// Check GCC +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +/* chuniio.dll dynamic loading */ +HMODULE hinstLib; +typedef uint16_t (*chuni_io_get_api_version_t)(void); +typedef HRESULT (*chuni_io_jvs_init_t)(void); +typedef void (*chuni_io_jvs_poll_t)(uint8_t*, uint8_t*); +typedef void (*chuni_io_jvs_read_coin_counter_t)(uint16_t *); +typedef HRESULT (*chuni_io_slider_init_t)(void); +typedef void (*chuni_io_slider_set_leds_t)(const uint8_t *); +typedef void (*chuni_io_slider_start_t)(chuni_io_slider_callback_t); +typedef void (*chuni_io_slider_stop_t)(void); +typedef HRESULT (*chuni_io_led_init_t)(void); +typedef void (*chuni_io_led_set_colors_t)(uint8_t, uint8_t *); + +chuni_io_get_api_version_t _chuni_io_get_api_version; +chuni_io_jvs_init_t _chuni_io_jvs_init; +chuni_io_jvs_poll_t _chuni_io_jvs_poll; +chuni_io_jvs_read_coin_counter_t _chuni_io_jvs_read_coin_counter; +chuni_io_slider_init_t _chuni_io_slider_init; +chuni_io_slider_set_leds_t _chuni_io_slider_set_leds; +chuni_io_slider_start_t _chuni_io_slider_start; +chuni_io_slider_stop_t _chuni_io_slider_stop; +chuni_io_led_init_t _chuni_io_led_init; +chuni_io_led_set_colors_t _chuni_io_led_set_colors; + +/* SHMEM Handling */ +#define BUF_SIZE 1024 +#define SHMEM_WRITE(buf, size) CopyMemory((PVOID)g_pBuf, buf, size) +#define SHMEM_READ(buf, size) CopyMemory(buf,(PVOID)g_pBuf, size) +TCHAR g_shmem_name[]=TEXT("Local\\Chu2to3Shmem"); +HANDLE g_hMapFile; +LPVOID g_pBuf; + +#pragma pack(1) +typedef struct shared_data_s { + uint16_t coin_counter; + uint8_t opbtn; + uint8_t beams; + uint16_t version; +} shared_data_t; + +shared_data_t g_shared_data; + +bool shmem_create() +{ + g_hMapFile = CreateFileMapping( + INVALID_HANDLE_VALUE, // use paging file + NULL, // default security + PAGE_READWRITE, // read/write access + 0, // maximum object size (high-order DWORD) + BUF_SIZE, // maximum object size (low-order DWORD) + g_shmem_name); // name of mapping object + + if (g_hMapFile == NULL) + { + dprintf("shmem_create : Could not create file mapping object (%d).\n", + GetLastError()); + return 0; + } + g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + BUF_SIZE); + + if (g_pBuf == NULL) + { + dprintf("shmem_create : Could not map view of file (%d).\n", + GetLastError()); + + CloseHandle(g_hMapFile); + + return 0; + } + + return 1; +} + +bool shmem_load() +{ + g_hMapFile = OpenFileMapping( + FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + g_shmem_name); // name of mapping object + + if (g_hMapFile == NULL) + { + dprintf("shmem_load : Could not open file mapping object (%d).\n", GetLastError()); + return 0; + } + + g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + BUF_SIZE); + + if (g_pBuf == NULL) + { + dprintf("shmem_load : Could not map view of file (%d).\n", GetLastError()); + CloseHandle(g_hMapFile); + return 0; + } + + dprintf("shmem_load : shmem loaded succesfully.\n"); + return 1; +} + +void shmem_free() +{ + UnmapViewOfFile(g_pBuf); + CloseHandle(g_hMapFile); +} + +/* jvs polling thread (to forward info to x64 dll) */ +static HANDLE jvs_poll_thread; +static bool jvs_poll_stop_flag; + +static unsigned int __stdcall jvs_poll_thread_proc(void *ctx) +{ + while (1) { + _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); + g_shared_data.opbtn = 0; + _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + Sleep(1); + } + + return 0; +} + +uint16_t chu2to3_load_dll(wchar_t *dllname) +{ + #if defined(ENV64BIT) + /* x64 must just open the shmem and do nothing else */ + int errcount = 0; + while (!shmem_load()) + { + if (errcount >= 10) + return -1; + Sleep(5000); + errcount++; + } + Sleep(1000); + return S_OK; + #endif + + /* this is the first function called so let's setup the chuniio forwarding */ + hinstLib = LoadLibraryW(dllname); + if (hinstLib == NULL) { + dprintf("ERROR: unable to load %S (error %d)\n",dllname, GetLastError()); + return -1; + } + + _chuni_io_get_api_version = (chuni_io_get_api_version_t)GetProcAddress(hinstLib, "chuni_io_get_api_version"); + _chuni_io_jvs_init = (chuni_io_jvs_init_t)GetProcAddress(hinstLib, "chuni_io_jvs_init"); + _chuni_io_jvs_poll = (chuni_io_jvs_poll_t)GetProcAddress(hinstLib, "chuni_io_jvs_poll"); + _chuni_io_jvs_read_coin_counter = (chuni_io_jvs_read_coin_counter_t)GetProcAddress(hinstLib, "chuni_io_jvs_read_coin_counter"); + _chuni_io_slider_init = (chuni_io_slider_init_t)GetProcAddress(hinstLib, "chuni_io_slider_init"); + _chuni_io_slider_set_leds = (chuni_io_slider_set_leds_t)GetProcAddress(hinstLib, "chuni_io_slider_set_leds"); + _chuni_io_slider_start = (chuni_io_slider_start_t)GetProcAddress(hinstLib, "chuni_io_slider_start"); + _chuni_io_slider_stop = (chuni_io_slider_stop_t)GetProcAddress(hinstLib, "chuni_io_slider_stop"); + _chuni_io_led_init = (chuni_io_led_init_t)GetProcAddress(hinstLib, "chuni_io_led_init"); + _chuni_io_led_set_colors = (chuni_io_led_set_colors_t)GetProcAddress(hinstLib, "chuni_io_led_set_colors"); + + /* x86 has to create the shmem */ + if (!shmem_create()) + { + return -1; + } + + return 0; +} + +/* chuniio exports */ +uint16_t chu2to3_io_get_api_version(void) +{ + #if defined(ENV64BIT) + /* This might be called too soon so let's make sure x86 has time to write to the shmem */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + int errcount = 0; + while (g_shared_data.version == 0) + { + if (errcount >= 3) + { + dprintf("CHU2TO3 X64: Couldn't retrieve api version from shmem, assuming 0x0100\n"); + return 0x0100; + } + Sleep(5000); + errcount++; + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + } + dprintf("CHU2TO3 X64: api version is %04X\n", g_shared_data.version); + return g_shared_data.version; + #endif + + if ( _chuni_io_get_api_version == NULL ) + { + g_shared_data.version = 0x0100; + } + else + { + g_shared_data.version = _chuni_io_get_api_version(); + } + dprintf("CHU2TO3: api version is %04X\n", g_shared_data.version); + + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + + return g_shared_data.version; +} + +HRESULT chu2to3_io_jvs_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + _chuni_io_jvs_init(); + + /* start jvs poll thread now that jvs_init is done */ + if (jvs_poll_thread != NULL) { + return S_OK; + } + + jvs_poll_thread = (HANDLE) _beginthreadex(NULL, + 0, + jvs_poll_thread_proc, + NULL, + 0, + NULL); + return S_OK; +} + +void chu2to3_io_jvs_read_coin_counter(uint16_t *out) +{ + #if defined(ENV32BIT) + /* x86 can perform the call and update shmem (although this call never happens) */ + _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + return; + #endif + + /* x64 must read value from shmem and update arg */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + if (out == NULL) { + return; + } + *out = g_shared_data.coin_counter; +} + +void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) +{ + #if defined(ENV32BIT) + /* x86 can perform the call and update shmem (although this call never happens) */ + _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + return; + #endif + + /* x64 must read value from shmem and update args */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + *opbtn = g_shared_data.opbtn; + *beams = g_shared_data.beams; +} + +HRESULT chu2to3_io_slider_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + return _chuni_io_slider_init(); +} + +void chu2to3_io_slider_start(chuni_io_slider_callback_t callback) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_start(callback); +} + +void chu2to3_io_slider_stop(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_stop(); +} + +void chu2to3_io_slider_set_leds(const uint8_t *rgb) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_set_leds(rgb); +} + +HRESULT chu2to3_io_led_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + if (_chuni_io_led_init != NULL) + return _chuni_io_led_init(); + return S_OK; +} + +void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + if (_chuni_io_led_set_colors != NULL) + { + _chuni_io_led_set_colors(board, rgb); + } +} diff --git a/chuniio/chu2to3.h b/chuniio/chu2to3.h new file mode 100644 index 0000000..bdbab77 --- /dev/null +++ b/chuniio/chu2to3.h @@ -0,0 +1,26 @@ +#pragma once + +/* + CHU2TO3 CUSTOM IO API + + This dll just mirrors chuniio dll binds but with a dynamic library loading and + a SHMEM system to let a single 32bit dll talk with x86 and x64 processes at once +*/ + +#include + +#include +#include + +uint16_t chu2to3_io_get_api_version(void); +HRESULT chu2to3_io_jvs_init(void); +void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); +void chu2to3_io_jvs_read_coin_counter(uint16_t *total); +HRESULT chu2to3_io_slider_init(void); +typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); +void chu2to3_io_slider_start(chuni_io_slider_callback_t callback); +void chu2to3_io_slider_stop(void); +void chu2to3_io_slider_set_leds(const uint8_t *rgb); +HRESULT chu2to3_io_led_init(void); +void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb); +uint16_t chu2to3_load_dll(wchar_t *dllname); \ No newline at end of file diff --git a/chuniio/chuniio.h b/chuniio/chuniio.h index 1ecba4a..e149a52 100644 --- a/chuniio/chuniio.h +++ b/chuniio/chuniio.h @@ -8,6 +8,7 @@ - 0x0100: Initial API version (assumed if chuni_io_get_api_version is not exported) - 0x0101: Fix IR beam mappings + - 0x0102: Add air tower led and billboard support */ #include @@ -151,15 +152,24 @@ void chuni_io_slider_set_leds(const uint8_t *rgb); All subsequent calls may originate from arbitrary threads and some may overlap with each other. Ensuring synchronization inside your IO DLL is - your responsibility. */ + your responsibility. + + Minimum API version: 0x0102 */ HRESULT chuni_io_led_init(void); -/* Update the RGB LEDs. rgb is a pointer to an array of 66 * 3 = 198 - bytes. The majority of these are for the marquee display, but the final - LEDs are for the side partitions. +/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes. + + Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds). + board 0 is on the left side and board 1 on the right side of the cab + + left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower + right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower - Chunithm uses two chains/boards. One is on the left side and one on the - right side of the cab. Exact layout is TBD. */ + Each rgb value is comprised of 3 bytes in R,G,B order + + NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...) + + Minimum API version: 0x0102 */ void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/chuniio/meson.build b/chuniio/meson.build index fabf499..864aa87 100644 --- a/chuniio/meson.build +++ b/chuniio/meson.build @@ -5,6 +5,8 @@ chuniio_lib = static_library( implicit_include_directories : false, c_pch : '../precompiled.h', sources : [ + 'chu2to3.c', + 'chu2to3.h', 'chuniio.c', 'chuniio.h', 'config.c', diff --git a/chusanhook/chuni-dll.c b/chusanhook/chuni-dll.c index 3a100ac..23bd311 100644 --- a/chusanhook/chuni-dll.c +++ b/chusanhook/chuni-dll.c @@ -39,6 +39,50 @@ const struct dll_bind_sym chuni_dll_syms[] = { } }; +const struct dll_bind_sym chu2to3_dll_syms[] = { + { + .sym = "chu2to3_io_jvs_init", + .off = offsetof(struct chuni_dll, jvs_init), + }, { + .sym = "chu2to3_io_jvs_poll", + .off = offsetof(struct chuni_dll, jvs_poll), + }, { + .sym = "chu2to3_io_jvs_read_coin_counter", + .off = offsetof(struct chuni_dll, jvs_read_coin_counter), + }, { + .sym = "chu2to3_io_slider_init", + .off = offsetof(struct chuni_dll, slider_init), + }, { + .sym = "chu2to3_io_slider_start", + .off = offsetof(struct chuni_dll, slider_start), + }, { + .sym = "chu2to3_io_slider_stop", + .off = offsetof(struct chuni_dll, slider_stop), + }, { + .sym = "chu2to3_io_slider_set_leds", + .off = offsetof(struct chuni_dll, slider_set_leds), + }, { + .sym = "chu2to3_io_led_init", + .off = offsetof(struct chuni_dll, led_init), + }, { + .sym = "chu2to3_io_led_set_colors", + .off = offsetof(struct chuni_dll, led_set_leds), + } +}; + +/* Helper function to determine upon dll_bind failure whether the required functions were found + NOTE: relies on symbols order declared above */ +static HRESULT has_enough_symbols(uint16_t version, uint8_t count) +{ + if ( version <= 0x0101 && count == 7 ) + return S_OK; + + if ( version >= 0x0102 && count == 9 ) + return S_OK; + + return E_FAIL; +} + struct chuni_dll chuni_dll; // Copypasta DLL binding and diagnostic message boilerplate. @@ -58,7 +102,12 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self) assert(cfg != NULL); assert(self != NULL); - if (cfg->path[0] != L'\0') { + owned = NULL; + src = self; + + if (cfg->chu2to3) { + dprintf("Chunithm IO: using chu2to3 engine for IO DLL: %S\n", cfg->path); + } else if (cfg->path[0] != L'\0') { owned = LoadLibraryW(cfg->path); if (owned == NULL) { @@ -72,12 +121,18 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self) dprintf("Chunithm IO: Using custom IO DLL: %S\n", cfg->path); src = owned; - } else { - owned = NULL; - src = self; } - get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version"); + if (cfg->chu2to3) { + if (chu2to3_load_dll(cfg->path) != 0) + dprintf("Could not init chu2to3 engine\n"); + + get_api_version = (void *) GetProcAddress(src, "chu2to3_io_get_api_version"); + } + else + { + get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version"); + } if (get_api_version != NULL) { chuni_dll.api_version = get_api_version(); @@ -97,17 +152,26 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self) goto end; } - sym = chuni_dll_syms; + sym = cfg->chu2to3 ? chu2to3_dll_syms : chuni_dll_syms; + const struct dll_bind_sym *init_sym = &sym[0]; + hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms)); if (FAILED(hr)) { if (src != self) { - dprintf("Chunithm IO: Custom IO DLL does not provide function " - "\"%s\". Please contact your IO DLL's developer for " - "further assistance.\n", - sym->sym); - - goto end; + // Might still be ok depending on external dll API version + int bind_count = sym - init_sym; + if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK ) + { + hr = S_OK; + } else { + dprintf("Chunithm IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + dprintf("imported %d symbols\n",bind_count); + goto end; + } } else { dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); } diff --git a/chusanhook/chuni-dll.h b/chusanhook/chuni-dll.h index 8341b48..bfc415c 100644 --- a/chusanhook/chuni-dll.h +++ b/chusanhook/chuni-dll.h @@ -19,6 +19,7 @@ struct chuni_dll { struct chuni_dll_config { wchar_t path[MAX_PATH]; + uint8_t chu2to3; }; extern struct chuni_dll chuni_dll; diff --git a/chusanhook/chusanhook.def b/chusanhook/chusanhook.def index 66ae5ec..ea786f5 100644 --- a/chusanhook/chusanhook.def +++ b/chusanhook/chusanhook.def @@ -22,3 +22,13 @@ EXPORTS chuni_io_slider_stop chuni_io_led_init chuni_io_led_set_colors + chu2to3_io_get_api_version + chu2to3_io_jvs_init + chu2to3_io_jvs_poll + chu2to3_io_jvs_read_coin_counter + chu2to3_io_slider_init + chu2to3_io_slider_set_leds + chu2to3_io_slider_start + chu2to3_io_slider_stop + chu2to3_io_led_init + chu2to3_io_led_set_colors diff --git a/chusanhook/config.c b/chusanhook/config.c index 0361e98..d71ad71 100644 --- a/chusanhook/config.c +++ b/chusanhook/config.c @@ -39,16 +39,27 @@ void chuni_dll_config_load( // Workaround for x64/x86 external IO dlls // path32 for 32bit, path64 for 64bit - // for else.. is that possible? idk + // path for 32bit only dlls (internal chu2to3 engine) - #if defined(ENV32BIT) - GetPrivateProfileStringW( - L"chuniio", - L"path32", - L"", - cfg->path, - _countof(cfg->path), - filename); + GetPrivateProfileStringW( + L"chuniio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); + if (cfg->path[0] != L'\0') { + cfg->chu2to3 = 1; + } else { + cfg->chu2to3 = 0; + #if defined(ENV32BIT) + GetPrivateProfileStringW( + L"chuniio", + L"path32", + L"", + cfg->path, + _countof(cfg->path), + filename); #elif defined(ENV64BIT) GetPrivateProfileStringW( L"chuniio", @@ -60,7 +71,7 @@ void chuni_dll_config_load( #else #error "Unknown environment" #endif - + } } void slider_config_load(struct slider_config *cfg, const wchar_t *filename) diff --git a/chusanhook/dllmain.c b/chusanhook/dllmain.c index 1e3ee2c..e8fce32 100644 --- a/chusanhook/dllmain.c +++ b/chusanhook/dllmain.c @@ -129,11 +129,16 @@ static DWORD CALLBACK chusan_pre_startup(void) } } - hr = led15093_hook_init(&chusan_hook_cfg.led15093, - chuni_dll.led_init, chuni_dll.led_set_leds, first_port, 2, 2, 1); + if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL ) + { + dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n"); + } else { + hr = led15093_hook_init(&chusan_hook_cfg.led15093, + chuni_dll.led_init, chuni_dll.led_set_leds, first_port, 2, 2, 1); - if (FAILED(hr)) { - goto fail; + if (FAILED(hr)) { + goto fail; + } } hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_cvt ? 2: 3, chusan_hook_mod); diff --git a/dist/chusan/segatools.ini b/dist/chusan/segatools.ini index 45e2df9..3531bdf 100644 --- a/dist/chusan/segatools.ini +++ b/dist/chusan/segatools.ini @@ -64,7 +64,11 @@ framed=0 monitor=0 [chuniio] -; Uncomment this if you have custom chuniio implementation. +; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL. +; (will use chu2to3 engine internally) +;path= + +; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs. ; x86 chuniio to path32, x64 to path64. Both are necessary. ;path32= ;path64=