Wiiリモコン拡張コントローラープロトコル解析

Wiiリモコンの拡張コネクタに接続するコントローラーを自作するため、通信仕様を調べてみます。

(ここではWiiリモコンをホスト、外部拡張コネクタに接続するものをコントローラーと表記します)

コントローラーはI2Cの通信で、マイコン等に簡単に接続することが出来ます。

Arduinoをホストにしてヌンチャク等コントローラーを使用する例は多く見かけるのですが、コントローラー側を自作する例は多くありません。

以下、参考になりそうな情報です。

  • http://wiibrew.org/wiki/Wiimote/Extension_Controllers
  • http://www.kako.com/neta/2009-008/2009-008.html
  • https://www.raphnet.net/electronique/extenmote/index_en.php
  • http://honeylab.hatenablog.jp/entry/2016/11/19/053212
  • https://osu.ppy.sh/community/forums/topics/258400
  • https://github.com/vancegroup/wiimote-encoder
  • https://github.com/bootsector/wii-retropad-adapter
  • http://forum.arduino.cc/index.php?topic=20938.0
  • https://github.com/denizkoekden/arduino-wiimote
  • https://illegal-function.at.webry.info/200906/article_1.html
  • http://www.fumi2kick.com/komekame/archives/221
  • http://hardwareandsoonlab.blogspot.com/2017/04/nes-classic-edition_82.html

 

上記、URL内の情報を見るとわかるのですが、Wiiの外部コントローラーの通信は暗号化されているため、容易に製作することが出来ないようです。

しかし、暗号強度は弱いため解析されてしまっているのが現状です。

Hector Martin氏が解析した情報を元にプログラムが出回っているようです。

  • http://www.derkeiler.com/Newsgroups/sci.crypt/2008-11/msg00110.html
  • https://web.archive.org/web/20131106235350/http://www.derkeiler.com/pdf/Newsgroups/sci.crypt/2008-11/msg00110.pdf

Sボックスのテーブル定数と、6バイトの配列テーブル定数を使用した独自暗号のようです。

 

実際に通信を見てみよう

とりあえず、通信内容を見るためにロジックアナライザに接続してみます。

wii-remote-extension-controller-001

Amazonで販売されていた延長ケーブルを途中で切って、測定器を割り込ませます。

Arduino Pro Mini(3.3V)も基板に取り付けて実験できるようにしました。

3.3V駆動のため、5V動作のArduino Uno等ではそのまま接続できないので注意が必要です。

 

拡張コネクタのピンアサイン

wii-remote-extension-controller-002

 

ピン番号 概要 ケーブル色
1 3.3V 赤色
2 SCL 黄色
3 Detect 黒色
4 無し 無し
5 SDA 緑色
6 GND 白色
- Shield 青色

今回使用した延長ケーブルについての色情報ですので、ケーブルによって違う可能性があります。

 

通信方式

基本は3.3V仕様のI2C通信で、速度は400kHzのようです。

コントローラー側で3.3Vにプルアップが必要です。

3番ピンはコントローラー側でVCCに接続されているらしいです。 ホスト側は3番ピンがHighになったことでコントローラーが接続されたと認識するとのこと。

コントローラー内にはアドレス8bitのレジスタを持っていて、特定のアドレスに対して読み書きすることによって通信しています。

デバイス自体のI2Cアドレスは0x52で、I2Cの仕様でLSB側の1ビットがリードライトビットのため、書き込み時は1ビット左シフトして0xA4、読み込み時はリードライトビットを立てるため、0xA5となります。

書き込み時は0xA4に続いて、1バイトのアドレス指定、その後データが続きます。

読み込み時は0xA4に続いて、読み出すアドレスを指定し、その後0xA5に続きデータが続きます。ホスト側からのNACKで止められます。

 

レジスタマップ

アドレス バイト数 概要
0x00-0x05 6 コントローラーボタン・スティックの状態
0x20-0x2F 16 キャリブレーションデータ?
0x30-0x3F 16 キャリブレーションデータ?
0x40-0x49 10 暗号化用乱数(rand)
0x4A-0x4F 6 暗号化用キー(key)
0xF0 1 暗号化ON/OFF(0xAAでON・0x55でOFF)
0xFA-0xFF 6 デバイス識別子

調べる限り、このようなレジスタマップになりました。

では、実際にロジックアナライザでキャプチャしてみます。

 

ヌンチャクを差し込んだときの動作シーケンス

wii-remote-extension-controller-003

ヌンチャクをWiiリモコンに差し込んだ際のデータを取得してみます。

 

動作 I2C 実データ 概要
書込 書込 0xA4 コントローラー存在確認?
書込 書込 0xA4 0xF0 0x55 暗号化解除(0xF0に対して、0x55指定)
読込 書込 0xA4 0xFA 0xFAを読み込むためのアドレス指定
読込 読込 0xA5 0x00 0x00 0xA4 0x20 0x00 0x00 デバイス識別子返答(Nunchuk:0000 A420 0000)
読込 書込 0xA4 0x20 0x20を読み込むためのアドレス指定
読込 読込 0xA5 0x77 0x7C 0x7F 0x08 0xAC 0xAC 0xB0 0x3C
0xDF 0x1B 0x7F 0xDE 0x20 0x87 0x11 0x66
キャリブレーションデータ?
書込 書込 0xA4 0xF0 0x55 暗号化解除(0xF0に対して、0x55指定)
書込 書込 0xA4 0xFB 0x00 不明(0xFBに対して、0x00指定)
読込 書込 0xA4 0xFA 0xFAを読み込むためのアドレス指定
読込 読込 0xA5 0x00 0x00 0xA4 0x20 0x00 0x00 デバイス識別子返答(Nunchuk:0000 A420 0000)
書込 書込 0xA4 0xF0 0xAA 暗号化(0xF0に対して、0xAA指定)
書込 書込 0xA4 0x40 0xF2 0xA2 0xF2 0xA2 0xF2 0xA2 暗号化用の乱数とキーの書込(0x40-0x45)
書込 書込 0xA4 0x46 0xF2 0xA2 0xF2 0xA2 0xFA 0x74 暗号化用の乱数とキーの書込(0x46-0x4B)
書込 書込 0xA4 0x4C 0x13 0x69 0x8B 0xEA 暗号化用の乱数とキーの書込(0x4C-0x4F)
読込 書込 0xA4 0x20 0x20を読み込むためのアドレス指定
読込 読込 0xA5 0xF2 0xA1 0x8C 0x8D 0x82 0xA7 0xFD 0xB2
0x0A 0x06 0x8C 0x53 0x36 0x80 0x5C 0x58
読込 書込 0xA4 0x30 0x30を読み込むためのアドレス指定
読込 読込 0xA5 0xF2 0xA1 0x8C 0x8D 0x82 0xA7 0xFD 0xB2
0x0A 0x06 0x8C 0x53 0x36 0x80 0x5C 0x58
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xEB 0x91 0xC3 0x32 0x5A 0x18
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xEB 0x91 0xC2 0x32 0x5B 0xBC
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xEB 0x91 0xC3 0x32 0x5A 0x48
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xEB 0x91 0xC2 0x31 0x5A 0x2C

以下、0x00の読込動作を繰り返し

 

タタコンを差し込んだときの動作シーケンス

wii-remote-extension-controller-004

タタコンをWiiリモコンに差し込んだ際のデータを取得してみます。

 

動作 I2C 実データ 概要
書込 書込 0xA4 コントローラー存在確認?
書込 書込 0xA4 0xF0 0x55 暗号化解除(0xF0に対して、0x55指定)
読込 書込 0xA4 0xFA 0xFAを読み込むためのアドレス指定
読込 読込 0xA5 0x00 0x00 0xA4 0x20 0x01 0x11 デバイス識別子返答(TaTaCon:0000 A420 0111)
読込 書込 0xA4 0x20 0x20を読み込むためのアドレス指定
読込 読込 0xA5 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
キャリブレーションデータ?
書込 書込 0xA4 0xF0 0x55 暗号化解除(0xF0に対して、0x55指定)
書込 書込 0xA4 0xFB 0x00 不明(0xFBに対して、0x00指定)
読込 書込 0xA4 0xFA 0xFAを読み込むためのアドレス指定
読込 読込 0xA5 0x00 0x00 0xA4 0x20 0x01 0x11 デバイス識別子返答(TaTaCon:0000 A420 0111)
書込 書込 0xA4 0xF0 0xAA 暗号化(0xF0に対して、0xAA指定)
書込 書込 0xA4 0x40 0x5C 0xA7 0x43 0x01 0x19 0x13 暗号化用の乱数とキーの書込(0x40-0x45)
書込 書込 0xA4 0x46 0x4B 0x16 0x0D 0x62 0xA0 0xFA 暗号化用の乱数とキーの書込(0x46-0x4B)
書込 書込 0xA4 0x4C 0x89 0xCB 0x58 0x59 暗号化用の乱数とキーの書込(0x4C-0x4F)
読込 書込 0xA4 0x20 0x20を読み込むためのアドレス指定
読込 読込 0xA5 0x09 0x99 0x04 0x61 0xF3 0x44 0x04 0x44
0x09 0x99 0x04 0x61 0xF3 0x44 0x04 0x44
読込 書込 0xA4 0x30 0x30を読み込むためのアドレス指定
読込 読込 0xA5 0x09 0x99 0x04 0x61 0xF3 0x44 0x04 0x44
0x09 0x99 0x04 0x61 0xF3 0x44 0x04 0x44
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xA8 0xF9 0x3B 0x7E 0xF2 0x45
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xA8 0xF9 0xDB 0x1E 0xF2 0x45
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xA8 0xF9 0x3B 0x7E 0xF2 0x45
読込 書込 0xA4 0x00 0x00を読み込むためのアドレス指定
読込 読込 0xA5 0xA8 0xF9 0xDB 0x1E 0xF2 0x45

以下、0x00の読込動作を繰り返し

 

暗号化・復号化プログラムの作成

動作を確認できるように、暗号化・復号化プログラムを作成します。

今回は適当にJavaScriptで書きました。

 

 

上記リンクをクリックで開きます。

動作についてはソースコードを見てください。

 

ヌンチャクの暗号化・復号化確認

上記、動作シーケンスでのロジックアナライザを使用したキャプチャ内容から、正常に暗号化・復号化できるか試してみます。

暗号化が有効になる前に、0x20番地を読み込みしています。また、暗号化が有効になったあとも、0x20番地を読み込みしています。

根拠は無いのですが同じ番地のためこれらの値を同一とし、比較することで正常に暗号化・復号化できるのかを確認してみましょう。

0x40番地から書き込まれた暗号化用乱数は通信内容から「F2 A2 F2 A2 F2 A2 F2 A2 F2 A2」となります。

0x4A番地から書き込まれた暗号化用キーは通信内容から「FA 74 13 69 8B EA」となります。

 

JavaScriptでの暗号化・復号化フォームの「wm_rand」に乱数を、「wm_key」にキーを入力します。

「data」には暗号化前の0x20番地のデータ「77 7C 7F 08 AC AC B0 3C DF 1B 7F DE 20 87 11 66」を入力します。

「Encrypt」ボタンを押すと、暗号化された結果が表示されます。

「F2 A1 8C 8D 82 A7 FD B2 0A 06 8C 53 36 80 5C 58」となりました。

暗号化後の通信内容と比較すると、同一であることがわかります。

 

また、「F2 A1 8C 8D 82 A7 FD B2 0A 06 8C 53 36 80 5C 58」を入力し、「Decrypt」ボタンを押すと復号化が出来ます。

「77 7C 7F 08 AC AC B0 3C DF 1B 7F DE 20 87 11 66」となり、無事復号化できているようです。

 

タタコンの暗号化・復号化確認

0x40番地から書き込まれた暗号化用乱数は通信内容から「5C A7 43 01 19 13 4B 16 0D 62」となります。

0x4A番地から書き込まれた暗号化用キーは通信内容から「A0 FA 89 CB 58 59」となります。

暗号化前の0x20番地のデータは「00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00」です。

暗号化後の0x20番地のデータは「09 99 04 61 F3 44 04 44 09 99 04 61 F3 44 04 44」です。

しかしこれらを入力してみても、それぞれ暗号化・復号化が一致しません。

 

暗号化・復号化アルゴリズムの確認

アルゴリズムに関して、再度確認してみましょう。

  • http://www.derkeiler.com/Newsgroups/sci.crypt/2008-11/msg00110.html

暗号化・復号化に必要な8バイトの配列、ftとsbを生成するためには、idxが必要です。

idxは0~6の値となっていて、keyに関してはrandとidxに依存しています。

この実装では0~6のidxと、入力されたrandからkeyを生成し、もしkeyが一致した場合にそのidxを使用するようです。

かなりの力技です。

コントローラーは8bitマイコンなどのそれほど能力が無いものが使用されるため、実際に内蔵されいてるアルゴリズムは単純なものでしょう。

そして、この方法は任天堂純正のコントローラーに関してで、サードパーティのコントローラーは別のルーチンが存在するとのこと。

異なるsboxで、同じansテーブルとありますが、実際には解析してみないとわからないでしょう。

 

 

この時点で、私が書いた上記のJavaScriptに、idx、ft、sbの表示機能を実装しました。

 

Arduinoにコントローラーを実装する

基本的に、以下のコードで動作するようです。

  • https://github.com/denizkoekden/arduino-wiimote

上記idxの件があるため、ロジックアナライザでデータを取得してみることにします。

 

 

ロジックアナライザで接続時に取得したデータの一覧です。

100回分のデータをCSVファイル(拡張子:txt)にしてあります。

JavaScriptのフォームに入力すると、暗号化・復号化ができることが確認できます。

計算したidxも記述しています。

 

Arduinoにタタコンを実装する

デバイス識別子をタタコンにした上で動作を確認します。

wii-remote-extension-controller-005 wii-remote-extension-controller-006

プログラムを書き込んだArduinoをWiiリモコンに接続すると、タタコンとして見えているようです!

認識まではうまくいくのですが、やはり暗号化・復号化の時点でidxが範囲外となり、うまく動作していないようです。

「太鼓の達人」を起動するとタタコンとして見えていますが、操作はできません。

 

 

こちらも、ロジックアナライザで接続時に取得したデータです。

100回分のデータをCSVファイル(拡張子:txt)にしてあります。

idx値がすべて7となり、計算が合いません。

サードパーティ用のアルゴリズムを解析しないといけない気がします。