PS1 の電車でGO!コントローラを 4 つのエミュレータで使えるようにする

エミュレータを使う前に

BIOS もゲームの ISO も、必ず自分のPS1・PS2・64・Dreamcast本体・自分のディスク・カセットから用意すること。
ネットで拾ってくるのは論外。ダウンロードも配布も違法。
エミュレータ本体(DuckStation / PCSX2 / Flycast / ares)自体は合法で公式配布されている。問題になるのは常にデータの出どころのほう。

なお自分の持ち物から吸い出す場合も、日本の著作権法には技術的保護手段の回避に関する規定がある。 適法かどうかは各自で確認を。この記事は法的な保証をするものではない。
この作業は Claude(Anthropic の AI)とやりとりしながら進めた。 コードはほぼ書いてもらっている。実機での計測と検証は自分。
PS1 の電車でGO!コントローラでエミュを運転しているところ

ノッチが飛ぶ

PS1 の電車でGO!用コントローラ、SLPH-00051。二ハンドルのやつだ。

SLPH-00051 と PS1/PS2-USBコンバーター

これを零環さんの PS1/PS2-USBコンバーター [LAY-002-01] 経由で PC に挿して、DuckStation で遊んでいた。

動かした。

マスコンの P2 が飛ぶ。

ブレーキもいくつか飛ぶ。PCSX2 でも同じだった。最初はスティックの閾値の問題だと思って調整したが、どう弄っても直らない。これはエミュレータ側の問題じゃないか、というのが出発点だ。

プラグインでは直せなかった

エミュ側の問題なら、入力プラグインを書いて直せばいい。そう思って調べたら、道が塞がっていた。

DuckStation はそもそもプラグイン方式を否定して作られた統合型。PCSX2 も 1.7 以降でプラグイン機構を廃止している。どちらにも入力プラグインの API が存在しない。

エミュを触れないなら、手前のコンバーター側で直すしかない。幸い LAY-002-01 の中身は ProMicro 互換で、ファームを自分で書き換えられる。

電GOコントローラの中身

MarcRiera/ddgo-controller-docs に書いてある。要点は3つ。

普通のデジタルパッドとまったく同じプロトコルで喋っている。 特別な通信は何もしていない。

ハンドルはボタンの組み合わせで段を表す。 力行が3ビット、ブレーキが4ビット。

十字キーの上と下が常時押しっぱなし。 ゲームはこの「普通はありえない組み合わせ」で電GOコントローラだと見分けている。

つまり軸はどこにも出てこない。閾値もデッドゾーンも本来関係ない。

それなのに段が飛ぶのは、コンバーターやエミュのどこかで、ボタンの組み合わせを軸やハットスイッチに変換していて、そこで情報が落ちているからだ。HID のハットスイッチは上と下を同時に表現できない。識別用の上下同時押しが、まずここで消える。

なら、16 ビットをそのまま 16 個の独立したボタンとして PC に流せばいい。解釈する層がなくなれば、段は飛びようがない。

Windows のゲームコントローラー画面(joy.cpl)を見ながら、全部の段を一段ずつ読んだ。

joy.cpl でボタンの点灯を確認しているところ

常時点灯の 5 と 7(上下)を除くとこうなる。

マスコン  点灯          ブレーキ  点灯
N         6, 8          解除      9, 10, 12
P1        6, 13         B1        10, 11, 12
P2        6             B2        10, 12
P3        8, 13         B3        9, 10, 11
P4        8             B4        9, 10
P5        13            B5        10, 11
                        B6        10
                        B7        9, 11, 12
                        B8        9, 12
                        非常      全部消灯

15 状態、全部違う組み合わせ。重複はひとつもない。

飛んでいた P2 も、ここではちゃんと「6 だけ」という固有の値を出している。コントローラもコンバーターも正常。犯人はエミュ側の変換で確定した。

ちなみに非常の位置で値がパラパラ変わるのは、汽車でGO! の自動帯のせいだ。ハンドルの不調ではない。

ファームを書く

やることは単純で、PS1 から来た 16 ビットを、軸もハットも通さずに 16 個の HID ボタンへ順番に流すだけ。

ベースは零環さんが公開しているスケッチ(GPLv3)。ATT ピンと SPI の構成をそのまま引き継いだので、配線は触っていない。

困ったのは割り当て画面だ。上下が常時押されているので、エミュの割り当て画面で「ボタンを押してください」と出た瞬間に、上か下が勝手に拾われる。他のボタンが一切割り当てられない。

そこで、SELECT と START を押しながら挿すと上下だけ出力を止めるモードを入れた。LED が赤く光ったらこのモード。

PS1 用のワンハンドル機 TCPP-20001 も試した。

点滅する。

ハンドルを動かすと一瞬ボタンが光るだけで、安定しない。PsxNewLib で TCPP-20001 が動かないという報告は前からあるが、原因は書かれていない。

観察すると、止めた位置で同じ組み合わせが点いたり消えたりしていた。読み取りが時々失敗して、そのたびに全ボタンが離されている。

なので、読み取りに失敗した回は前の値を保持するようにした。100 回連続で失敗したときだけ、本当に外れたと見なす。

止まった。

ビット配置は SLPH-00051 と完全に同じだった。ワンハンドルなのに二ハンドルと同じ表で読める。ファームも設定も共用できる。

書き込めない

本筋ではないが、一番時間を食ったのはここだ。

Switch 向けに作った Zuiki マスコン用のボード定義を選んだまま書き込もうとしていた。あれは CDC を切ってあるので Serial が存在せず、リンクで落ちる。

しかも CDC を切ったファームが載っている間は、COM ポートが現れない。IDE が自動リセットをかける相手がいない。リセットボタンを押せばブートローダーが 8 秒だけ起動するが、手動では間に合わなかった。

結局、PowerShell でポートの出現を 50ms ごとに見張って、出た瞬間に avrdude を叩いた。

while ($true) {
  if ([System.IO.Ports.SerialPort]::getportnames() -contains "COM9") {
    & "$a\bin\avrdude.exe" -C"$a\etc\avrdude.conf" -patmega32u4 -cavr109 `
      -PCOM9 -b57600 -D -Uflash:w:"$hex":i
    break
  }
  Start-Sleep -Milliseconds 50
}

もうひとつ引っかかったのが COM 番号。Pro Micro は、スケッチが動いているときとブートローダーのときで COM 番号が違う。

IDE で選ぶのはスケッチが動いているほう。そっちを選んでおけば、IDE が勝手にリセットをかけて書き込んでくれる。ブートローダー側の番号を選んでいたから「開けない」と言われていた。

CDC ありのボード定義(SparkFun Pro Micro / 3.3V 8MHz)で焼き直してからは、普通の Arduino と同じ感覚で書き込めるようになった。

DuckStation

パッドをデジタルコントローラーにして、16 個を 1:1 で割り当てる。

段は複数ボタンが同時に点くので、GUI で割り当てるのは難しい。設定ファイルに直接書いた。

[Pad1]
Type = DigitalController
Select = SDL-0/Button0
L3 = SDL-0/Button1
R3 = SDL-0/Button2
Start = SDL-0/Button3
Up = SDL-0/Button4
Right = SDL-0/Button5
Down = SDL-0/Button6
Left = SDL-0/Button7
L2 = SDL-0/Button8
R2 = SDL-0/Button9
L1 = SDL-0/Button10
R1 = SDL-0/Button11
Triangle = SDL-0/Button12
Circle = SDL-0/Button13
Cross = SDL-0/Button14
Square = SDL-0/Button15

ハマったのは設定ファイルの場所。新しめの DuckStation は %LOCALAPPDATA%\DuckStation に保存している。ドキュメント側にも古い settings.ini が残っていて、そっちを何度書き換えても画面に反映されなかった。

DuckStation で電車でGO!をプレイしている画面

P2 が飛ばなくなった。最初の見立てはこれで裏付けが取れた。

PCSX2

電車でGO!3 は PS1 のコントローラが使える。同じやり方で行けると思ったら、ボタンは効くのにハンドルだけ効かない。

PCSX2 のパッドは DualShock2 しか選べない。本物の SLPH-00051 は DualShock2 向けの設定コマンドに応答しないので、ゲームはそこで「これは電GOコントローラだ」と判断しているようだ。エミュの DualShock2 は応答してしまうので、普通のパッド扱いになる。

そこで方針を変えて、USB の「Train Controller(Type 2)」として見せることにした。

ファームに X 軸 = 力行、Y 軸 = ブレーキの出力を足す。値は、PCSX2 が Type 2 として段に区切る範囲のちょうど真ん中に置いた。16 ボタンの出力はそのまま残してあるので、DuckStation の設定には響かない。

コントローラー端子 1   未接続
USB ポート 1           Train Controller / Type 2
Axes Passthrough       オフ

ついでに2つ足した。

START だけ押しながら挿すと、ボタンを出さず軸だけ出す。 軸を割り当てるときに、段ごとボタンが拾われないようにするためだ。LED が青になる。

入力プロファイルを分ける。 電GO3 にだけ当てておけば、他のゲームには影響しない。

ひとつ引っかかったのは書式。PCSX2 は生のボタンを SDL-0/JoyButton13 と書く。DuckStation と同じ Button13 で書くと、閉じたときに勝手に別の名前へ書き換えられてしまう。

PCSX2 で電車でGO!3 をプレイしている画面

Flycast

次は DC 版の電車でGO!2。

Flycast には「電車でGO!」コントローラがちゃんと実装されている。ソースを見ると、ボタンのビットをそのままゲームに渡す作りだった。

PS1 と DC の電GOコントローラは、同じビットの組み合わせを、機種ごとのボタン名に載せているだけ。なので並べ替えれば済む。軸の変換はいらない。

役割                  ファームの番号     DC のボタン
SELECT                0                  D
START                 3                  Start
A / B / C             15 / 14 / 13       A / B / C
力行 1 / 2 / 3        12 / 7 / 5         Z / Y / X
ブレーキ 1 / 2 / 3 / 4  10 / 8 / 11 / 9   上 / 下 / 左 / 右

常時押しの上下(4 と 6)は割り当てない。DC ではブレーキが十字キーに載っているので、割り当てるとブレーキが壊れる。

[digital]
bind0 = 0:btn_d
bind1 = 3:btn_start
bind2 = 15:btn_a
bind3 = 14:btn_b
bind4 = 13:btn_c
bind5 = 12:btn_z
bind6 = 7:btn_y
bind7 = 5:btn_x
bind8 = 10:btn_dpad1_up
bind9 = 8:btn_dpad1_down
bind10 = 11:btn_dpad1_left
bind11 = 9:btn_dpad1_right

[analog] は空にしておく。PCSX2 用に足した軸が、アナログスティックとして拾われてしまうからだ。

困ったのはメニュー。ブレーキを解除に入れていると、十字キーの下・左・右が押しっぱなし扱いになって、Flycast のメニューでカーソルが流れ続ける。

メニューを触るときはブレーキを非常に入れておく。非常は 4 ビット全部オフなので、十字キーが何も押されていない状態になる。

Flycast で電車でGO!2 をプレイしている画面

ares

最後に N64 の電車でGO!64。

ares に電GO専用のコントローラはない。ただ N64 版もボタンの組み合わせ方式なので、普通のゲームパッドとして並べ替えればいい。

気になったのは、ares が十字キーの上下・左右の同時押しを打ち消す作りになっていること。調べると C ボタンは打ち消していない。N64 版はブレーキが C ボタン、力行が十字キーの右・上と Z なので、反対方向を同時に押す場面がない。問題なかった。

役割                  ファームの番号     N64 のボタン
SELECT                0                  R
START                 3                  Start
A / B / C             15 / 14 / 13       B / A / L
力行 1 / 2 / 3        12 / 7 / 5         十字右 / 十字上 / Z
ブレーキ 1 / 2 / 3 / 4  10 / 8 / 11 / 9   C右 / C左 / C下 / C上

電GO64 はコントローラをコネクタ 3 に挿す前提なので、Controller Port 3 に割り当てる。

そして起動直後のコントローラ選択画面で詰んだ。

電車でGO!64 のコントローラ選択画面

「コネクタ 1 のコントローラで選択」と書いてある。ポート 1 に何も割り当てていないと、ここから動けない。ポート 1 の左右と A をキーボードに割り当てておくこと。

あと、N64 には L + R + Start の同時押しでリセット扱いになる仕様がある。この並べ方だと C + SELECT + START がそれに当たる。同時に押さないように。

ares で電車でGO!64 をプレイしている画面

完成

DuckStation   PS1 版各種          16 ボタンを 1:1
PCSX2         電車でGO!3          軸にして USB の Type 2 へ
Flycast       DC 版 電車でGO!2    DC の電GO配置に並べ替え
ares          電車でGO!64         ポート 3 に N64 配置で並べ替え

ファームは 1 本のまま。変えたのはエミュ側の割り当てだけだ。

4 つのエミュレータで電車でGO!を動かしたところ

このスクショ撮るの大変でした汗

最初は「DuckStation で段が飛ばなければいいや」くらいの気持ちだったのに、気付いたら 4 つのエミュのソースを読んでいた。

一番おもしろかったのは、軸を通さないほど素直に動くということ。エミュに解釈させる余地をなくすのが正解で、遠回りに見えた「生のビットを 1:1」が、結局どのエミュでも通用した。

この記事について

一連の作業は Claude(Anthropic の AI)とやりとりしながら進めた。隠すことでもないので書いておく。

ファームのコードと設定ファイルの書き換えは、ほぼ Claude に書いてもらった。PCSX2、Flycast、ares の入力まわりのソースを読んで「ビットをそのまま渡している」「C ボタンは打ち消されない」と判断材料を出してきたのも、「閾値じゃない気がする」という自分の勘に、資料で「そもそも軸が存在しない」と裏付けを付けてきたのも Claude だ。自分だけなら何日かかったか分からない。

一方、実機を触るところは全部こちらの仕事だった。joy.cpl を見ながら全部の段を読んで表を作ったのも、TCPP-20001 で「同じ組み合わせが点滅している」と観察して伝えたのも自分だ。非常位置で値が変わるのを「汽車でGO! の自動帯だ」と見抜いたのもこちらで、Claude はハンドルの不調を疑っていた。

間違いもあった。PCSX2 の機能を実在しない名前で説明してきたり、「パススルー機能で直る」と勧めてきたものが、実は動かないままマージされていたり。書き込みで詰まったときは次々に別の手を出してきたが、ボードの選び間違いや COM 番号の取り違えといった、もっと単純な原因を先に潰すべき場面が何度かあった。

AI に投げれば完成する、という話ではない。実機を持っていて、症状を正確に伝えられて、言われたことをその場で試せる人間がいないと一歩も進まない。そういう分担だった。

0 件のコメント:

コメントを投稿

Blogger の旧ブログをデザインそのまま残して、同じドメインで新ブログを始める

この作業は Claude(Anthropic の AI)とやりとりしながら進めた。 コマンドとスクリプトはほぼ書いてもらっている。どう残したいかを決めたのと、画面を見て確かめたのは自分。 目次 ブログを作り替えたい URL を壊したくない 丸ごと落とす ...