Nintendo SwitchのJoyConのhackを試してみる、という話の続き。(前回から随分と時間が空いてしまったが)
前回はボタン情報を読み取るだけしかできていなかったが、海外では解析が進んでいてJoyConに内蔵された加速度センサーやジャイロセンサーの値も読み出せるらしい。(前に見たときには解析がそこまで進んでいなかったのだが、最近チェックしたら凄く解析が進んでいるので驚いた。)
海外の情報を参考に、昔作ったWiiリモコン用のソフトをベースに改造して作り直して、JoyConのボタン/スティックとセンサーの情報をリアルタイムに表示させるソフトを作ってみた。
ソースコードは以下のようになっている。(zipファイルでソースコードとexeファイルをダウンロードできるようにしておいた)
// JoyCon Test version 0.1 Copyright(c)2018 by E.Kako
// This version supports JoyCon-R only.
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <time.h>
// for tiny_hid_dll
#define FUNC __declspec(dllimport) __stdcall
extern "C" HANDLE FUNC OpenHidHandle(unsigned short vendor_id, unsigned short product_id);
extern "C" void FUNC ReadReport(HANDLE handle,unsigned char *InputReport,int *len);
extern "C" void FUNC WriteReport(HANDLE handle,unsigned char *OutputReport, int *len);
extern "C" void FUNC CloseHidHandle(HANDLE handle);
HANDLE hRsDevHandle;
const USHORT VID = 0x057e; // Nintendo
const USHORT PID = 0x2007; // JonCon - R
#define MAXREPORTSIZE 256
int InputLength,OutputLength;
unsigned char InputReport[MAXREPORTSIZE];
unsigned char OutputReport[MAXREPORTSIZE];
int packet_num = 0;
unsigned char joycon_loop_flag;
unsigned char joycon_in_use;
uintptr_t joycon_th;
unsigned char res_type;
unsigned char button1,button2;
unsigned int StickX,StickY;
unsigned int Ax,Ay,Az;
unsigned int Rx,Ry,Rz;
void Sleep2(int msec)
{
LARGE_INTEGER f; // for hi-res. performance counter
LARGE_INTEGER t1,t2; // for hi-res. performance counter
double t;
QueryPerformanceFrequency(&f);
QueryPerformanceCounter(&t1);
while (1) {
QueryPerformanceCounter(&t2);
t = (double)t2.QuadPart - (double)t1.QuadPart;
t /= (double)f.QuadPart;
t *= 1000;
if (t>(double)msec) { return; }
}
}
void cmd_0x01_parameter_set()
{
OutputReport[0]=0x01;
OutputReport[1]=( (++packet_num) % 16);
OutputReport[2]=0; OutputReport[3]=0; OutputReport[4]=0; OutputReport[5]=0;
OutputReport[6]=0; OutputReport[7]=0; OutputReport[8]=0; OutputReport[9]=0;
}
void Report_subcmd_0x30(unsigned char data)
{
cmd_0x01_parameter_set();
OutputReport[10]= 0x30;
OutputReport[11]= data;
WriteReport( hRsDevHandle , OutputReport , &OutputLength);
ReadReport( hRsDevHandle , InputReport, &InputLength);
}
void Report_subcmd_0x03(unsigned char data)
{
cmd_0x01_parameter_set();
OutputReport[10]= 0x03;
OutputReport[11]= data;
WriteReport( hRsDevHandle , OutputReport , &OutputLength);
ReadReport( hRsDevHandle , InputReport, &InputLength);
}
void Report_subcmd_0x40(unsigned char data)
{
cmd_0x01_parameter_set();
OutputReport[10]= 0x40;
OutputReport[11]= data;
WriteReport( hRsDevHandle , OutputReport , &OutputLength);
ReadReport( hRsDevHandle , InputReport, &InputLength);
}
void Report_subcmd_0x48(unsigned char data)
{
cmd_0x01_parameter_set();
OutputReport[10]= 0x48;
OutputReport[11]= data;
WriteReport( hRsDevHandle , OutputReport , &OutputLength);
ReadReport( hRsDevHandle , InputReport, &InputLength);
}
void Joycon_mode_0x30(void)
{
Report_subcmd_0x03(0x30); // standard full mode
// respond automatically (interval = ??)
}
void Joycon_mode_0x3F(void)
{
Report_subcmd_0x03(0x3F); // button ONLY Report Mode (HID gamepad mode)
// respond when button changes only
}
void Joycon_IMU_enable(void)
{
Report_subcmd_0x40(0x01);
}
void Joycon_IMU_disable(void)
{
Report_subcmd_0x40(0x00);
}
void JoyCon_LED_on()
{
Report_subcmd_0x30(0x01); // set LED data 0x01 ... LED1 on
// 0x01 = led 1(top) , 0x02 = led 2 , 0x04 = led 3 , 0x08 = led 4
// 0x10 = led 1 blink , 0x20 = led 2 blink , 0x40 = led 3 blink , 0x80 = led 4 blink
}
void JoyCon_LED_off()
{
Report_subcmd_0x30(0x00); // set LED data -> all OFF
}
void Joycon_Haptic_enable(void)
{
Report_subcmd_0x48(0x01);
}
void Joycon_Haptic_disable(void)
{
Report_subcmd_0x48(0x00);
}
void JoyCon_Haptic_test(unsigned char d1,unsigned char d2,unsigned char d3,unsigned char d4)
{
OutputReport[0]=0x10;
OutputReport[1]=( (++packet_num) % 16);
OutputReport[2]=d1; OutputReport[3]=d2; OutputReport[4]=d3; OutputReport[5]=d4;
OutputReport[6]=d1; OutputReport[7]=d2; OutputReport[8]=d3; OutputReport[9]=d4;
WriteReport( hRsDevHandle , OutputReport , &OutputLength);
}
void JoyCon_Haptic_off(unsigned char d1,unsigned char d2,unsigned char d3,unsigned char d4)
{
JoyCon_Haptic_test(0x3c,0,0x2f,0x40);
}
void Joycon_Input(void)
{
int l;
ReadReport( hRsDevHandle , InputReport, &InputLength);
res_type=InputReport[0];
if (InputLength == 362) {
// button1=InputReport[5]; button2=InputReport[4]; // JoyCon-L
button1=InputReport[3]; button2=InputReport[4]; // JoyCon-R
// StickX = InputReport[6] | ((unsigned int)InputReport[7]&0x0F)<<8; // JoyCon-L
// StickY = (InputReport[7]>>4 ) | (InputReport[8] <<4 );
StickX = InputReport[9] | ((unsigned int)InputReport[10]&0x0F)<<8; // JoyCon-R
StickY = (InputReport[10]>>4 ) | (InputReport[11] <<4 );
Ax=InputReport[13] | (unsigned int)InputReport[14]<<8;
Ay=InputReport[15] | (unsigned int)InputReport[16]<<8;
Az=InputReport[17] | (unsigned int)InputReport[18]<<8;
Rx=InputReport[19] | (unsigned int)InputReport[20]<<8;
Ry=InputReport[21] | (unsigned int)InputReport[22]<<8;
Rz=InputReport[23] | (unsigned int)InputReport[24]<<8;
} else {
printf("%d\n",InputLength);
}
}
void Joycon_Input_Main(void *)
{
// Joycon input thread
while (joycon_loop_flag) {
joycon_in_use=1;
Joycon_Input();
Sleep2(16);
}
joycon_in_use=0;
return;
}
void Start_JoyCon_th(void)
{
joycon_loop_flag=1;
joycon_th = _beginthread( Joycon_Input_Main , 0 , NULL );
}
int JoyCon_th_check(void)
{
res_type=0;
Sleep2(100);
return (res_type!=0);
}
void Stop_Joycon_th(void)
{
joycon_loop_flag=0;
Sleep2(100);
if (joycon_in_use==1) {
TerminateThread( (HANDLE)joycon_th , -1); // kill thread if it stucks
}
}
void MainLoop()
{
unsigned char c;
while (1) {
if (kbhit()) { c= getch();
if (c==0x1B) { break; }
}
printf("button=%02X,%02X , stick=%04X,%04X , Acc=%04X,%04X,%04X , Gyr=%04X,%04X,%04X\n",
button1,button2,StickX,StickY,Ax,Ay,Az,Rx,Ry,Rz);
Sleep2(16);
}
}
int main( int argc, char *argv[])
{
hRsDevHandle = OpenHidHandle(VID,PID);
if ( hRsDevHandle == INVALID_HANDLE_VALUE) {
MessageBox(0,"JoyCon-R not found.",0,0);
return 1;
}
Joycon_mode_0x30(); // joycon standard mode
Joycon_IMU_enable(); // gyro acc sensor enable
JoyCon_LED_on();
Start_JoyCon_th();
if ( JoyCon_th_check() ) {
MainLoop();
} else {
MessageBox(0,"Joycon-R cannot open.",0,0);
}
Stop_Joycon_th();
JoyCon_LED_off();
Joycon_IMU_disable();
Joycon_mode_0x3F(); // HID mode
CloseHidHandle( hRsDevHandle );
return 0;
}
download: joycon_test_v01.zip (12.6kbyte)
まだ作っている途中なので、JoyCon-Lに対応していないとかの未完成な部分もあるのだが、とりあえず公開しておく。
—
追記
見直してみたら、関数名にJoyCon~と書いてあるものとJoycon~と書いてあるものが混在していてダサい。
あとで直したものを公開する。
→ 公開した。