Nintendo SwitchのJoyConのhackを試してみる – その2

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~と書いてあるものが混在していてダサい。
あとで直したものを公開する。
公開した。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Time limit is exhausted. Please reload CAPTCHA.

+ 21 = 28