dwgx@blog:~$dwgx
> cd ../posts

東方 STG に対戦プラットフォーム TH-Platform を作る

// created

TH-Platform は東方弾幕 STG 向けのサードパーティ製デスクトップクライアントで、TH06、TH07、TH08、TH09 にロビー・マッチング・ルームといった対戦機能を被せるものだ。公式は対戦をやらないので、自分で埋めた穴になる。

対戦は二つに分かれる。片方は人をどう集めるか、もう片方はゲームプロセス内で両者の状態をどう同期するか。後者は汚れ仕事で、DLL を注入し、ゲームロジックを hook し、さらに原版 exe のメモリレイアウトと格闘する必要がある。これは伴走リポジトリ TH08-Platform に分けて地道に潰していく。このリポジトリは前者だけを扱う。クライアント本体、つまりロビー・ルーム・グループ・DM・プロフィール・設定という一式のページだ。UI と逆向を絡めるとしんどいので、切り分けてそれぞれ進める方がすっきりする。

フロントは Vite 6 + React 18 + TypeScript、スタイルは Tailwind、コンポーネントは Radix primitive、アイコンは lucide、外側は Tauri 2 のネイティブシェルで包む。狙いはボーダーレスで 1440x900 固定のデスクトップウィンドウであって、ブラウザのアレではない。ルーティングはライブラリを入れず、自前で hash router を書いた。#/lobby/th08#/room/4912 みたいなので十分だし、ブラウザの進む・戻るもタダで使えて依存ゼロだ。バックエンドはまだないので、データ層はまず mock で作る。肝は types.ts という型の契約で、mock と本物のファサードが同じ型を共有する。本物のバックエンドを繋いだとき呼び出し側は一行も変えず、mock を差し替えるだけで済む。テーマはデフォルトでダークの三段階、localStorageth-platform-theme に保存し、index.html にプリレンダースクリプトを仕込んで、入った瞬間に白く光ってから黒に変わるのを防いでいる。

一番硬いのは Windows 専用のゲームローダーで、Tauri 側の Rust で書き、launch_game / terminate_game として公開する。昔ながらの注入の流れだ。対象プロセスをサスペンド状態で起動し、伴走 DLL を注入し、CreateRemoteThreadLoadLibraryA を組み合わせ、それから実行を再開する。対戦パラメータはコマンドラインではなく、環境変数で子プロセスに渡す。

変数 役割
TH08_PLATFORM_PEER 対向アドレス ip:port
TH08_PLATFORM_HOST 自分側がホストであることを示す
TH08_PLATFORM_LISTEN 待ち受けポート。ホストはデフォルト 7480、対向は 7481
TH08_PLATFORM_DISABLE_MULTIPLAYER 対戦挙動の総合スイッチ

TS 側では src/lib/tauri/loader.tslaunchGame を包み、ゲームパス・DLL パス・host モードを組み立てて Rust に投げる。これは Tauri シェルの中でしか生きない。素の pnpm dev で走らせると window.__TAURI__ が存在せず即座に例外を投げる。意図的で、開発時に誤って触ってしまわないようにするためだ。

いまの v0.1.0 はまだ進行中で、本質的には UI の足場に mock データを足したものだ。本物のバックエンドは繋いでおらず、Rust ローダーは組んであるが Windows しか認識しない。次はテーマプリセット、チャンネル、完全なプロフィールといったページを補い、Tauri のパッケージングをきれいに整え、それから mock を本物のバックエンドに繋いで、TH08-Platform 側の DLL インジェクターと本当に串刺しにしていく。