2023-11-03に投稿

電Go!! の ZUIKI コンは製造時期による違いがある?

読了目安:11分

電車でGO!!専用ワンハンドルコントローラー - 瑞起 ZUIKI (以下、 ZUIKIコン) には、 型番 : ZKNS-001 の通常版 と、 型番:‎ZKNS-002 の EXCLUSIVE EDITION の二種類ある。

ただどうも、それ以外に製造時期による違いもあるようだ。

その違いのせいで、 JR東日本トレインシミュレータ で ZUIKIコン 使うツールがな機能しなくなって困ったので、その対処をした話。

きっかけ

元々、2021年の発売当初から通常版の ZUIKIコン を購入しており、公式で対応している 電車でGo!! Switch 版 や ソニックパワードの 鉄道にっぽん! シリーズ Real Pro など に加え、 Switch版電車でGOワンハンドルマスコンで操作できるようにしてみました - Steam コミュニティー で紹介されてるツールを使って JR東トレシム で使っていたりしていた。

程よい重量感や操作感で臨場感が爆上がり… というか、無いと面白みが8割減レベルに気に入っており、最近 EXCLUSIVE EDITION を買い増した。

…ところが買い増しした ZKNS-002 は、Switch 版や 鉄道にっぽん! シリーズでは問題なく動くのに、上記ツールを使って JR東トレシム しようとすると以下のようなエラーが出て動かない。
```text/plain
Traceback (most recent call last):
File "main.py", line 24, in
File "SwitchDenGo.py", line 32, in loadStatus
pygame.error: Invalid joystick button
[33064] Failed to execute script 'main' due to unhandled exception!


存在しない joystick 番号を指定されてエラーになってる。 …つまりボタン配置が違う? しかしそもそも、この ZUIKIコン は汎用 USB コントローラ として認識されるモノだし、Windows OS 側のデバイスの設定を見ても、どちらも同じ "One Handle MasCon for Nintendo Switch" と認識され、全くボタン配置で認識されている。 なんでボタン配置がズレるのか、訳がわからないよ。 [![](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-11-old-237x300.png)](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-11-old.png)[![](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-21-new-237x300.png)](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-21-new.png) [上記 Steam コミュニティー](https://steamcommunity.com/app/2111630/discussions/0/3360272431836180307/) でも、同じような問題に遭遇している人が何人かいる。 しかも、 通常版 ZKNS-001 か EXCLUSIVE EDITION ZKNS-002 かを問わず発生するようだ。 つまり、型番違いによる仕様変更が原因ではない…? ## 意外なる原因 せっかく、エラーが出るデバイスと出ないデバイスが揃っているので、もう少し深く掘って調べてみる。 このツール [mipsparc/JRESim_Dengo](https://github.com/mipsparc/JRESim_Dengo/blob/v1.0/SwitchDenGo.py) のソースを取って Python のデバックをしてみたところ、やはりというか [pygame](https://www.pygame.org/news) というライブラリでコントローラーを操作するあたりでエラーがでている。 ```text/plain 031: # ○ボタン 032: if self.joy.get_button(15): # <- ここでエラー 033: self.buttons.append("SW_CIRCLE") 034: # HOMEボタン 035: if self.joy.get_button(5): 036: self.buttons.append("SW_HOME")

試しに、 pygame のジョイスティック実行例コード joystick.py を実行して、 pygame 側でどのように認識されているか確認してみる。

んん゙っ!?
ZKNS-002 のほうは "One Handle MasCon for Nintendo Switch Exclusive Edition" として認識されるものの、
発売日に買った方の ZKNS-001 が "Nintendo Switch Pro Controller" と認識されて、ボタンの認識軸数が大きく変わっている。

pygame は、内部的に SDL2 (libsdl-org/SDL) というライブラリでジョイスティック操作をしている。
その SDL では、既知の様々なコントローラに様々な特殊対応が為されているので、そこらへんが影響していそうだ。

こういったものは、 USB の VID (ベンダーID) と PID (プロダクトID) で場合分けしているものと相場が決まっている。
ということで、デバイスマネージャーのプロパティ(ハードウェアID)から VID と PID を確認してみる。

  • 発売日購入の通常版 ZKNS-001:
    • HID\VID_0F0D&PID_00C1&REV_0106
  • 最近買った EXCLUSIVE EDITION ZKNS-002:
    • HID\VID_33DD&PID_0002&REV_0111

VID ちゃうやんけ。

https://www.usb.org/developers で USB ベンダーID を検索してみると…
0x0F0D (3853): HORI CO., LTD.
0x33DD (13277): ZUIKI Inc.

?? HORI ??

とりあえず、上記の VID と PID をもとに、 SDL2 (SDL 2.0.18) でどのように動作が定義されているか調べてみる。

SDL/src/joystick/controller_type.h at release-2.0.18 · libsdl-org/SDL

    { MAKE_CONTROLLER_ID( 0x0f0d, 0x00c1 ), k_eControllerType_SwitchInputOnlyController, NULL },  // HORIPAD for Nintendo Switch

ホリパッド for Nintendo Switch って書いてあるぞ。

更にここら辺 (SDL/src/joystick/SDL_joystick.c) の処理を経て、 VID: 0x0F0D & PID: 0x00C1 の組み合わせのものが "Nintendo Switch Pro Controller" と認識されているようだ。

なお、 VID: 0x33DD & PID: 0x0002 については、特に SDL 側での特殊処理は定義されていなそうだった。
このため、 USB H/W 側で定義されている "One Handle MasCon for Nintendo Switch Exclusive Edition" の名前がそのまま pygame での認識名として表示されていたのだろう。

うーん、製造当初は適当に(Switch に対応した)ホリパッドを偽装してたのを、後のロットではちゃんと VID や PID とって設定したってコトかしら…?
ライセンス品としてどうなんだソレ。

とりあえず、以下の2点が今回の問題が発生していることがわかった。

  1. 製造時期?によって、ホリパッドを偽装しているものとそうでないものがある
  2. ホリパッドは SDL 側に特殊対応があり、偽装の有無でライブラリからとれるキー配置が異なっている

JRESim_Dengo の修正

原因がわかったところで、 JRESim_Dengo の方をどちらの ZUIKIコン にも対応させたい。

とはいえ、 VID や PID で動作し分けるにしても、私が持っている2台とは別の VID や PID を持つものもありそうだ。
(製造時期の違いからか同じ不具合に遭遇していた、通常版の ZKNS-001 とか)

SDL が動作を書き換えなければ、 USB から出ている信号は同じボタン配置で出ているっぽい為、コントローラーの軸数やボタン数で SDL 側での書き換えが行われたか判別すればよさそうだ。

--- ./SwitchDenGo_old.py    2022-10-03 18:55:53 +0900
+++ ./SwitchDenGo.py        2023-10-08 05:51:32 +0900
@@ -7,36 +7,64 @@

     def __init__(self):
         pygame.init()
         self.joy = pygame.joystick.Joystick(0)
+        self.ctrl_nums = (self.joy.get_numaxes(), self.joy.get_numballs(), self.joy.get_numbuttons(), self.joy.get_numhats())
+        if self.ctrl_nums not in [(6, 0, 16, 0), (4, 0, 14, 1)]:
+            raise Exception("サポートされていないコントローラです")
         self.joy.init()

     def loadStatus(self):
         self.brake_knotch = 0
         self.accel_knotch = 0
         self.buttons = []
         pygame.event.get()

-        # Xボタン
-        if self.joy.get_button(2):
-            self.buttons.append("SW_X")
-        # Yボタン
-        if self.joy.get_button(3):
-            self.buttons.append("SW_Y")
-        # Aボタン
-        if self.joy.get_button(0):
-            self.buttons.append("SW_A")
-        # Bボタン
-        if self.joy.get_button(1):
-            self.buttons.append("SW_B")
-        # ○ボタン
-        if self.joy.get_button(15):
-            self.buttons.append("SW_CIRCLE")
-        # HOMEボタン
-        if self.joy.get_button(5):
-            self.buttons.append("SW_HOME")
-
-        knotch_level = self.joy.get_axis(1)
+        # ロンチ版
+        if self.ctrl_nums == (6, 0, 16, 0):
+            # Xボタン
+            if self.joy.get_button(2):
+                self.buttons.append("SW_X")
+            # Yボタン
+            if self.joy.get_button(3):
+                self.buttons.append("SW_Y")
+            # Aボタン
+            if self.joy.get_button(0):
+                self.buttons.append("SW_A")
+            # Bボタン
+            if self.joy.get_button(1):
+                self.buttons.append("SW_B")
+            # ○ボタン
+            if self.joy.get_button(15):
+                self.buttons.append("SW_CIRCLE")
+            # HOMEボタン
+            if self.joy.get_button(5):
+                self.buttons.append("SW_HOME")
+
+            knotch_level = self.joy.get_axis(1)
+        elif self.ctrl_nums == (4, 0, 14, 1):
+            # Xボタン
+            if self.joy.get_button(3):
+                self.buttons.append("SW_X")
+            # Yボタン
+            if self.joy.get_button(0):
+                self.buttons.append("SW_Y")
+            # Aボタン
+            if self.joy.get_button(2):
+                self.buttons.append("SW_A")
+            # Bボタン
+            if self.joy.get_button(1):
+                self.buttons.append("SW_B")
+            # ○ボタン
+            if self.joy.get_button(13):
+                self.buttons.append("SW_CIRCLE")
+            # HOMEボタン
+            if self.joy.get_button(12):
+                self.buttons.append("SW_HOME")
+
+            knotch_level = self.joy.get_axis(1)
+        else:
+            raise Exception("サポートされていないコントローラです")

         if knotch_level > 0.95:
             self.accel_knotch = 5
         elif knotch_level > 0.75:

こんな感じの修正を入れてビルドしたものを以下のリリースで展開した。

Release JRESim_Dengo v1.0p1 · advanceboy/JRESim_Dengo

もし、最近 ZUIKIコン を JREトレシム で使おうと思って困っている人がいればどうぞ。

元の作者の方にもプルリク投げとくか。

補足

実装のために調査した、各環境・デバイス毎の、キー割り当ては以下の通りだった。

DirectInput からの取得 (ロンチ版, 最近の Exclusive Edition 共通):

```text/plain
index 1軸: マスコン
Button 1: Y
Button 2: B
Button 3: A
Button 4: X
Button 5: L
Button 6: R
Button 7: ZL, EB
Button 8: ZR
Button 9: -
Button 10: +
Button 11: N/A
Button 12: N/A
Button 13: HOME
Button 14: Capture
PoVハット 1: 方向キー


### pygame 経由の、ロンチ版 ```text/plain index 1軸: マスコン index 4軸: ZL index 5軸: ZR Button 0: A Button 1: B Button 2: X Button 3: Y Button 4: - Button 5: Home Button 6: + Button 7: N/A Button 8: N/A Button 9: L Button 10: R Button 11: Up Button 12: Left Button 13: Down Button 14: Right Button 15: Capture

# ハードウェアID: 'HID\VID_0F0D&PID_00C1&REV_0106'
joy = pygame.joystick.Joystick(0)
joy.get_guid()
# -&gt; '030000000d0f0000c100000006016800'
joy.get_name()
# -&gt; 'Nintendo Switch Pro Controller'
joy.get_numaxes()
# -&gt; 6
joy.get_numballs()
# -&gt; 0
joy.get_numbuttons()
# -&gt; 16
joy.get_numhats()
# -&gt; 0

pygame 経由の、 Exclusive Edition

```text/plain
index 1軸: マスコン
Button 0: Y
Button 1: B
Button 2: A
Button 3: X
Button 4: L
Button 5: R
Button 6: ZL
Button 7: ZR
Button 8: -
Button 9: +
Button 10: N/A
Button 11: N/A
Button 12: Home
Button 13: Capture
PoVハット 1: 方向キー

[![](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-22-new.png)](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-22-new.png)

```python
# ハードウェアID: 'HID\VID_33DD&PID_0002&REV_0111' 
joy = pygame.joystick.Joystick(0)
joy.get_guid()
# -&gt; '03000000dd3300000200000000000000'
joy.get_name()
# -&gt; 'One Handle MasCon for Nintendo Switch Exclusive Edition'
joy.get_numaxes()
# -&gt; 4
joy.get_numballs()
# -&gt; 0
joy.get_numbuttons()
# -&gt; 14
joy.get_numhats()
# -&gt; 1
Originally published at aquasoftware.net
ツイッターでシェア
みんなに共有、忘れないようにメモ

advanceboy

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント