diff --git a/ModeSwitch/C/modeswitch.exe b/ModeSwitch/C/modeswitch.exe index b3d540f..7920046 100644 Binary files a/ModeSwitch/C/modeswitch.exe and b/ModeSwitch/C/modeswitch.exe differ diff --git a/ModeSwitch/C/src/CMakeLists.txt b/ModeSwitch/C/src/CMakeLists.txt index b09a0bc..c1ba740 100644 --- a/ModeSwitch/C/src/CMakeLists.txt +++ b/ModeSwitch/C/src/CMakeLists.txt @@ -7,11 +7,4 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++ -static-libgcc") include_directories(${popn_modeswitch_SOURCE_DIR}) link_directories(${PROJECT_SOURCE_DIR}/include) -# STATIC LINK HID -find_library(HIDDLL NAMES hid PATHS ${CMAKE_SOURCE_DIR}/lib) -message(STATUS "find_library for hid returned ${HIDDLL}") -add_library(hid MODULE IMPORTED) -set_property(TARGET hid PROPERTY IMPORTED_LOCATION "${HIDDLL}") - -add_executable(modeswitch modeswitch.cpp) -target_link_libraries(modeswitch hid) \ No newline at end of file +add_executable(modeswitch modeswitch.cpp) \ No newline at end of file diff --git a/ModeSwitch/C/src/modeswitch.cpp b/ModeSwitch/C/src/modeswitch.cpp index 7a65dd1..f03caad 100644 --- a/ModeSwitch/C/src/modeswitch.cpp +++ b/ModeSwitch/C/src/modeswitch.cpp @@ -1,13 +1,61 @@ /* TO BE COMPILED WITH MINGW */ /* Visual C++ compilation didn't work for me on the BemaniPC (invalid win32 application) */ +#define WINVER 0x0500 #include #include +HANDLE g_hidHandle; + +/** + * Initialize the g_hidHandle global variable + * It will attempt to read paths from devicepath.dat file if it exists until it finds one which works. + * If nothing is found, as a last resort it will try the two default paths from the Due and Leonardo + * versions of the firmware. + * + * @return 0 on success, -1 on error + */ +static int controller_init(){ + FILE *file; + char path[256]; + + file = fopen("devicepath.dat", "r"); + if (file == NULL) + { + goto last_resort; + } + + while ( fgets(path,256,file) != NULL ) + { + path[strcspn(path, "\r\n")] = 0; + g_hidHandle = CreateFile(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hidHandle != INVALID_HANDLE_VALUE ) + break; + } + fclose(file); + + if ( g_hidHandle == INVALID_HANDLE_VALUE ) + { + goto last_resort; + } + return 0; + +last_resort: + g_hidHandle = CreateFile("\\\\?\\HID#VID_2341&PID_003E&MI_02#7&3156e204&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", + GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hidHandle != INVALID_HANDLE_VALUE ) + return 0; + + g_hidHandle = CreateFile("\\\\?\\HID#VID_2341&PID_8036&MI_02#7&63200bf&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", + GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hidHandle != INVALID_HANDLE_VALUE ) + return 0; + + printf("Couldn't open device. Make sure devicepath.dat exists and contains the correct path.\r\n"); + return -1; +} + int main(int argc, char* argv[]) { - FILE *file; - char path[256]; - HANDLE hidHandle; char OutputReport[5]; int res; int mode; @@ -25,18 +73,8 @@ int main(int argc, char* argv[]) printf("Invalid mode value %d\r\n", mode); return 2; } - - file = fopen("devicepath.dat", "r"); - while ( fgets(path,256,file) != NULL ) - { - path[strcspn(path, "\r\n")] = 0; - hidHandle = CreateFile(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if ( hidHandle != INVALID_HANDLE_VALUE ) - break; - } - fclose(file); - - if ( hidHandle == INVALID_HANDLE_VALUE ) + + if ( controller_init() == -1 ) { printf("Couldn't open device. Make sure devicepath.dat contains the correct path.\r\n"); return 3; @@ -48,7 +86,7 @@ int main(int argc, char* argv[]) OutputReport[3] = 0x00; OutputReport[4] = mode+16; //mode+magicbit - WriteFile(hidHandle, OutputReport, 5, &BytesWritten, NULL); + WriteFile(g_hidHandle, OutputReport, 5, &BytesWritten, NULL); if ( BytesWritten == 5 ) { res = 0; @@ -74,6 +112,6 @@ int main(int argc, char* argv[]) res = 4; } - CloseHandle(hidHandle); + CloseHandle(g_hidHandle); return res; } diff --git a/ModeSwitch/readme.md b/ModeSwitch/readme.md index ef1e1b0..4efe56c 100644 --- a/ModeSwitch/readme.md +++ b/ModeSwitch/readme.md @@ -38,6 +38,8 @@ The included devicepath.dat should work with both Due and Leonardo versions out devicepath.dat file should contain the device path on its own on a single line. Multiple paths on multiple lines should work too in which case it will open the first valid path it finds. +If no valid path is found or if there is no `devicepath.dat` file, it will attempt to open the default paths from the Due and Leonardo versions (same as the ones found in this included devicepath file). + ### spicetools open ```%appdata%/spicetools.xml```, look for a "devid" value starting with ```\\?\HID#VID_2341&PID_003E``` (Due) or ```\\?\HID#VID_2341&PID_8036``` (Leonardo) diff --git a/README.md b/README.md index 27870f9..78efa9b 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ The 00 key is mapped to comma (for direct compatibility with spicetools) and the It also has a cool light animation on boot which you can easily adapt to your liking :) +## ezusb driver + +The I/O is HID so it can be mapped with the usual IO emulation tools, but it can also be used without emulation, just like an official IOBoard, provided you replace the original `ezusb.dll` file with the one from this repo. + +This way the firmware is fully compatible with anything that works on an official cabinet (PopnForwarder (a bit silly but why not :D), DJMame...) + ## Light modes There are 4 different modes : diff --git a/ezusb/ezusb-popn15.dll b/ezusb/ezusb-popn15.dll new file mode 100644 index 0000000..1583f47 Binary files /dev/null and b/ezusb/ezusb-popn15.dll differ diff --git a/ezusb/ezusb.dll b/ezusb/ezusb.dll new file mode 100644 index 0000000..bd339aa Binary files /dev/null and b/ezusb/ezusb.dll differ diff --git a/ezusb/readme.md b/ezusb/readme.md new file mode 100644 index 0000000..cdbb4ba --- /dev/null +++ b/ezusb/readme.md @@ -0,0 +1,19 @@ +# ezusb.dll for the Pop'n Ultimate Controller + +This is a dll meant to seamlessly replace the original one from the different games (and compatible with both the PopnForwarder and DJMame, or really anything else meant to be used with ezusb.dll). + +For ease of use I included pre-compiled dll working on WinXP Embedded (for official Pop'n Music cabinets). + +## Usage + +Replace the game ezusb.dll file with this one and the game will read input and send lights to your Arduino as if it were the original IOBoard. + +Pop'n Music Adventure used a different dll format so you have to set a flag in `ezusb.h` for the dll to compile in a way that will work with this game. + +The dll makes the lights turn on for 1 sec on load so you'll know if the game managed to load your device. + +## How to retrieve devicepath + +The dll will attempt to open the default paths from the Due and Leonardo versions of this firmware, in case it doesn't work you can put a `devicepath.dat` file in the same folder as the dll. + +See instructions from ModeSwitch to know more about the path format and how to find the one corresponding to your device. \ No newline at end of file diff --git a/ezusb/src/CMakeLists.txt b/ezusb/src/CMakeLists.txt new file mode 100644 index 0000000..420948e --- /dev/null +++ b/ezusb/src/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.14) +project(UltimatePopnController-ezusb) + +set(CMAKE_CXX_STANDARD 14) + +include_directories(${ezusb_SOURCE_DIR}) +link_directories(${PROJECT_SOURCE_DIR}/include) +link_directories(${PROJECT_SOURCE_DIR}/lib) + +add_library(ezusb SHARED ezusb.cpp ezusb.h) +target_link_libraries(ezusb hid) +SET_TARGET_PROPERTIES(ezusb PROPERTIES PREFIX "") diff --git a/ezusb/src/ezusb.cpp b/ezusb/src/ezusb.cpp new file mode 100644 index 0000000..3a486f2 --- /dev/null +++ b/ezusb/src/ezusb.cpp @@ -0,0 +1,273 @@ +/* TO BE COMPILED WITH VISUAL STUDIO SO THAT EXPORTS ARE PROPERLY MANGLED */ +#define _CRT_SECURE_NO_WARNINGS +#include +extern "C" { +// This file is in the Windows DDK available from Microsoft. +#include +} +#include +#include +#include "ezusb.h" + +HANDLE g_hid_handle; + +/** + * Initialize the g_hid_handle global variable + * It will attempt to read paths from devicepath.dat file if it exists until it finds one which works. + * If nothing is found, as a last resort it will try the two default paths from the Due and Leonardo + * versions of the firmware. + * + * @return 0 on success, -1 on error + */ +static int controller_init(){ + FILE *file; + char path[256]; + + file = fopen("devicepath.dat", "r"); + if (file == NULL) + { + goto last_resort; + } + + while ( fgets(path,256,file) != NULL ) + { + path[strcspn(path, "\r\n")] = 0; + g_hid_handle = CreateFile(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hid_handle != INVALID_HANDLE_VALUE ) + break; + } + fclose(file); + + if ( g_hid_handle == INVALID_HANDLE_VALUE ) + { + goto last_resort; + } + return 0; + +last_resort: + g_hid_handle = CreateFile("\\\\?\\HID#VID_2341&PID_003E&MI_02#7&3156e204&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", + GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hid_handle != INVALID_HANDLE_VALUE ) + return 0; + + g_hid_handle = CreateFile("\\\\?\\HID#VID_2341&PID_8036&MI_02#7&63200bf&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", + GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( g_hid_handle != INVALID_HANDLE_VALUE ) + return 0; + + printf("Couldn't open device. Make sure devicepath.dat exists and contains the correct path.\r\n"); + return -1; +} + +/** + * Read buttons state from the Arduino and convert them into Pop'n Music format + * + * Pop'n Music processes button states as a uint32_t bitfield with the following format : + * bits 0 (LSB) to 5 : nothing + * bit 6 : service + * bit 7 : test + * bits 8 to 16 : buttons 1 to 9 + * bit 22 : coin mech + * + * @param pad_bits[out] button state bitfield + * @return 0 on success, -1 on error + */ +static int controller_read_buttons(uint32_t *pad_bits){ + DWORD BytesRead = 0; + byte res[4]; + unsigned char buf[6]; // gamepad report length is 3 bytes in Arduino firmware, doubled because NumInputBuffer is set to 2 + + buf[0] = 0x04; // gamepad report ID is 4 in Arduino firmware + *pad_bits = 0; + + ReadFile(g_hid_handle, buf, 6, &BytesRead, NULL); + // BytesRead should either be 6 (if it successfully read 2 reports) or 3 (only one) + if ( BytesRead != 6 && BytesRead != 3 ) + { + return -1; + } + + /* HID read ok, convert latest report bytes to pop'n bitfield */ + res[3] = 0; + res[2] = (buf[BytesRead-1]<<3 | buf[BytesRead-1]) & 0x41; + res[1] = buf[BytesRead-2]; + res[0] = 0; + buf[BytesRead-1] >>= 1; + if ( buf[BytesRead-1]&1 ) res[0] |= 0x80; + buf[BytesRead-1] >>= 1; + if ( buf[BytesRead-1]&1 ) res[0] |= 0x40; + *pad_bits = *(uint32_t *)res; +#ifdef DEBUG + for (int i = 0; i<32; i++){ + printf("%c",( ((uint32_t )*pad_bits>>(31-i))&1)?'1':'0'); + if (((i+1)%4)==0) printf(" "); + } + printf("\n"); +#endif + return 0; +} + +/** + * Read lights state and send the corresponding HID report to the Arduino + * + * Pop'n Music sends lights state as a int32_t bitfield with the following format : + * bits 0 (LSB) to 4 : neons from top to bottom + * bits 8 to 11 : side lamps (left blue, left red, right blue, right red) + * bits 23 to 31 : button lamps from left to right + * + * @param lamp_bits[in] lamp state bitfield + * @return 0 on success, -1 on error + */ +static int controller_write_leds(int32_t lamp_bits){ + DWORD BytesWritten = 0; + byte *src = (byte*) &lamp_bits; //cast as byte array for fast conversion + byte buf[5]; // HID lights report length is 5 bytes in Arduino firmware + + /* convert bitfield into HID Report format */ + buf[4] = 0; + buf[3] = (src[1] >> 2); + buf[2] = (src[1] << 6) | (src[0] << 1) | (src[3] >> 7); + buf[1] = (src[3] << 1) | (src[2] >> 7); + buf[0] = 0x05; // HID lights report id is 5 in Arduino firmware + + /* send HID Report */ + WriteFile(g_hid_handle, buf, 5, &BytesWritten, NULL); + if ( BytesWritten == 5 ) + return 0; + +#ifdef DEBUG + printf("Error sending HID report (%u bytes written)\r\n",BytesWritten); +#endif + return -1; +} + + +/* EZUSB EXPORTS */ + +#if POPN15_FORMAT == 1 +extern "C" { +#endif + +__declspec(dllexport) int __cdecl usbCheckAlive() { + return 1; +} + +__declspec(dllexport) int __cdecl usbCheckSecurityNew(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbCoinGet(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbCoinMode(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbEnd() { + CloseHandle(g_hid_handle); + return 0; +} + +__declspec(dllexport) int __cdecl usbFirmResult() { + return 0; +} + +__declspec(dllexport) int __cdecl usbGetKEYID(unsigned char *a, int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbGetSecurity(int i, unsigned char *a) { + return 0; +} + +__declspec(dllexport) int __cdecl usbLamp(int32_t lamp_bits) { + return controller_write_leds(lamp_bits); +} + +__declspec(dllexport) int __cdecl usbPadRead(unsigned long *pad_bits) { + return controller_read_buttons((uint32_t *)pad_bits); +} + +__declspec(dllexport) int __cdecl usbPadReadLast(uint8_t *a1) { + memset(a1, 0, 4); + return 0; +} + +__declspec(dllexport) int __cdecl usbSecurityInit() { + return 0; +} + +__declspec(dllexport) int __cdecl usbSecurityInitDone() { + return 0; +} + +__declspec(dllexport) int __cdecl usbSecuritySearch() { + return 0; +} + +__declspec(dllexport) int __cdecl usbSecuritySearchDone() { + return 0; +} + +__declspec(dllexport) int __cdecl usbSecuritySelect(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbSecuritySelectDone() { + return 0; +} + +__declspec(dllexport) int __cdecl usbSetExtIo(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbStart(int i) { + if (controller_init() == -1) + { + printf("Could not init device.\n"); + return -1; + } + + bool hidres = HidD_SetNumInputBuffers( + g_hid_handle, + 2); + if (!hidres) + { + printf("error %d setnuminputbuff\n",GetLastError()); + return -1; + } + + /* light everything for 1 sec to get visual confirmation + * the device is working */ + controller_write_leds((uint32_t) 0xFFFFFFFF); + Sleep(1000); + controller_write_leds((uint32_t) 0x00000000); + + return 0; +} + +__declspec(dllexport) int __cdecl usbWdtReset() { + return 0; +} + +__declspec(dllexport) int __cdecl usbWdtStart(int i) { + return 0; +} + +__declspec(dllexport) int __cdecl usbWdtStartDone() { + return 0; +} + +#if POPN15_FORMAT == 1 +} +#endif + +int __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, void *) { + if (fdwReason == 1) { + //https://blogs.msdn.microsoft.com/larryosterman/2004/06/03/little-known-win32-apis-disablethreadlibrarycalls/ + DisableThreadLibraryCalls(hinstDLL); + } + + return 1; +} \ No newline at end of file diff --git a/ezusb/src/ezusb.h b/ezusb/src/ezusb.h new file mode 100644 index 0000000..0cc071a --- /dev/null +++ b/ezusb/src/ezusb.h @@ -0,0 +1,37 @@ +#ifndef EZUSB_UPC_EZUSB_H +#define EZUSB_UPC_EZUSB_H + +/* Pop'n Music Adventure ezusb.dll was compiled in C and thus used unmangled function names + * set this POPN15_FORMAT to 1 to compile a compatible version */ +#define POPN15_FORMAT 0 + +#if POPN15_FORMAT == 1 +extern "C" { +#endif +__declspec(dllexport) int __cdecl usbCheckAlive(); +__declspec(dllexport) int __cdecl usbCheckSecurityNew(int i); +__declspec(dllexport) int __cdecl usbCoinGet(int i); +__declspec(dllexport) int __cdecl usbCoinMode(int i); +__declspec(dllexport) int __cdecl usbEnd(); +__declspec(dllexport) int __cdecl usbFirmResult(); +__declspec(dllexport) int __cdecl usbGetKEYID(unsigned char *a, int i); +__declspec(dllexport) int __cdecl usbGetSecurity(int i, unsigned char *a); +__declspec(dllexport) int __cdecl usbLamp(int32_t lamp_bits); +__declspec(dllexport) int __cdecl usbPadRead(unsigned long *pad_bits); +__declspec(dllexport) int __cdecl usbPadReadLast(uint8_t *a1); +__declspec(dllexport) int __cdecl usbSecurityInit(); +__declspec(dllexport) int __cdecl usbSecurityInitDone(); +__declspec(dllexport) int __cdecl usbSecuritySearch(); +__declspec(dllexport) int __cdecl usbSecuritySearchDone(); +__declspec(dllexport) int __cdecl usbSecuritySelect(int i); +__declspec(dllexport) int __cdecl usbSecuritySelectDone(); +__declspec(dllexport) int __cdecl usbSetExtIo(int i); +__declspec(dllexport) int __cdecl usbStart(int i); +__declspec(dllexport) int __cdecl usbWdtReset(); +__declspec(dllexport) int __cdecl usbWdtStart(int i); +__declspec(dllexport) int __cdecl usbWdtStartDone(); +#if POPN15_FORMAT == 1 +} +#endif + +#endif //EZUSB_UPC_EZUSB_H \ No newline at end of file diff --git a/ezusb/src/lib/hid.lib b/ezusb/src/lib/hid.lib new file mode 100644 index 0000000..2b5e749 Binary files /dev/null and b/ezusb/src/lib/hid.lib differ