Case note 2026-07Windowsゲームランチャー · 起動・認証 · Electron / WineStatus: closed with open boundary

Windowsランチャーの「動く」を6つに分ける

Windows向けゲーム配信ランチャーが起動直後に落ちる。修正すると窓は残るが中身が黒い。 画面が見えてもブラウザ認証から戻れるとは限らず、そこから起動したゲームには別のruntimeが要る。 一つの「動かない」をprocessと責務の境界へ分け、公式clientとゲーム本体を変更せず閉じた記録である。

1 API起動直後のcrashをdelay importの実callまで追跡
1 processGPUとwindowの所有processを揃えてcontentを可視化
2 / 2公式ランチャー経由の購入済みゲームcanary
§01

成功条件を6つに分けて確認する

標準読者は、Wineとprefixの基礎を知り、Windows向けランチャーの互換性を自分で調べたい技術者である。この記事の目標は、processが生きたことを「動いた」とせず、次の6条件を別々に判定できるようになることだ。

ARTIFACT

公式の配布物

受け入れたversionとhashを照合し、未検証の更新へ自動追従しない。

PASS / vendor channel
BOOTSTRAP

Mac上の配置

Windows installerを完走させず、検証済みpayloadだけをprefixへ安全に配置する。

PASS / transactional
WIN32 API

processの生存

不足関数の候補ではなく、crashしたimport slotまで結んで必要APIを特定する。

PASS / upstream backport
RENDERING

画面の中身

windowの存在とcontentの描画を分け、swapchainとpresentの実callを確認する。

PASS / same process
CALLBACK

認証の戻り先

Mac側がcustom URL schemeを受け、同じprefixとruntimeへ戻す。

PARTIAL / fresh session open
CHILD RUNTIME

ゲームの実行環境

配信clientの描画と、そこから起動する32-bitゲームのfontや動画環境を分ける。

PASS / 2 canaries
持ち帰る原則: ブランド名や症状名から原因を決めない。配布物、process、window、callback、子processの責務を順番に測る。
§02

製品名より先にbinary identityを確定する

検証対象は、ChromiumとNode.jsを同梱するデスクトップアプリ基盤(Electron)で作られたx64 Windows clientだった。同じ配布面には過去のJava系clientも存在したが、別binaryの知見を現行clientへ持ち込むと、調べるruntimeもfailure surfaceもずれる。

01 / LABEL

製品の表示名

入口にはなるが、architectureやframeworkの根拠にはしない。

02 / FINGERPRINT

実binaryを読む

PE architecture、version、framework、delay importを実体から確認する。

03 / PROCESS MODEL

process構成を描く

browser、GPU、renderer、child gameの所有関係を分ける。

§03

公式配布物から再現可能な入口を作る

Windows installerのGUIは、PowerShellを使う「既存アプリ終了」確認で停止した。そこでinstallerを無理に完走させず、公式metadataから固定した配布物をMac側で検証し、Windows installer形式(NSIS)の最終payloadだけをmacOS標準の展開器で取り出した。

01 — PIN

versionと3つの照合値を固定する

公式metadataのsizeとSHA-512に、Mac側で計算したSHA-256を加える。どれか一つでも違えば配置しない。

最新版へ追従することより、検証したbyteを再現できることを優先する。

02 — STAGE

prefixの外で先に検証する

payloadを一時領域へ展開し、主実行ファイルのhashを再確認してから既存配置と交換する。

途中失敗では既存client、認証状態、download済みゲームを壊さない。

03 — RECEIPT

成功後だけ準備済みにする

配置と補助設定が完了した後にreceiptをatomic writeする。fileの存在だけを準備完了にしない。

Windows、仮想マシン、外付けdisk、追加の展開toolを製品依存にしない。
§04

不足APIをcrash slotから特定する

clientは起動直後にERROR_PROC_NOT_FOUND (127)で落ちていた。複数の不足symbolを一覧にしても、どれが実際にprocessを止めたかは分からない。Crashpad dumpの例外位置からdelay-IAT slotへ戻し、実行時まで関数解決を遅らせる仕組み(delay import)の対象を照合すると、load-bearingだったのはUSER32.IsWindowArrangedだった。

FAILURE

関数が無いという推測で止めない

dump annotation、例外address、delay-IAT slot、PE importを一本につなぎ、実際にcrashした関数を決める。

PATCH

upstream実装をprovenance付きで戻す

WineHQで追加済みのstubをforkへbackportした。独自の挙動を発明せず、元commitと返り値を保持する。

VERIFY

process生存を次のgateへ渡す

ImmediateCrashと新規dumpが消え、main、GPU、network、renderer processが生存した。

ここで証明したのは生存だけで、画面の中身はまだ黒い。
§05

黒い画面をwindow ownershipから調べる

API crashを直すとwindowは残ったが、contentは黒いままだった。hard traceが示したのはGPU能力の不足ではなく、GPU processが別process所有のWindows window handle(HWND)へVulkan surfaceを作れない境界だった。

修正前 — 所有processが分かれる

  • browser processがHWNDを所有
  • 別GPU processがsurfaceを作成
  • cross-process境界で失敗
  • windowは存在するがcontentは黒い

修正後 — 描画を同じprocessへ置く

  • title-scoped --in-process-gpu
  • HWNDとsurface作成者が一致
  • swapchain / draw / presentを観測
  • login UIのcontentが可視化
不採用: GPU全体の無効化、compositor flagの付け外し、software GPUへの一括fallback。process ownershipを揃えない条件では同じ境界が残った。API名の大量文字列hitも実callの証拠にしなかった。
§06

認証情報ではなく戻り道を所有する

browser認証では、認証後に独自URL schemeでdesktop clientへ戻る。Macアプリがその戻り先を受け取る仕組みをprotocol handlerと呼ぶ。互換ランチャーは認証値を解釈せず、受け取ったURLを同じ固定client、prefix、runtimeへ渡す。

01

Windows client

macOS既定browserへ認証を依頼する。

02

Browser

ユーザーが公式認証画面を操作する。

03

URL scheme

値を掲載・解析せずMac側handlerへ返す。

04

Mac launcher

許可schemeだけを同じWine環境へ転送する。

05

Wine client

公式client自身がcallbackを処理する。

証拠の境界: handler登録と既存の認証済みsessionは確認した。完全な新規sessionでbrowser往復が最後まで成立する条件は、自然な再認証が発生するまでOPENである。
§07

配信clientと子ゲームを別runtimeとして扱う

Electron製clientの画面が見えても、そこから起動する32-bitゲームのfontや動画が自動で成立するとは限らない。公式client経由で購入済みゲームを起動すると、親clientとは異なるplugin、DirectShow、GStreamer経路が現れた。

surface確認したこと別gateに残したこと
Electron clientlogin UI、library、download / install入口子ゲームのrenderer、font、movie
Purchased game A公式routeの起動、本編、日本語font、起動ロゴ動画一度だけ観測した未裁定hangの再発条件
Purchased game B同じ公式routeで起動・動作未取得のrenderer backend trace

game Aの日本語fontはprefix限定設定で直し、黒い起動ロゴは子processへbundled GStreamer環境が届いていないことをtraceで確定した。親clientの成功を隣のprocessへ転用せず、子ゲーム自身のpipelineを調べた結果である。

§08

成立済みと未証明を同じ表に残す

最後に、確認できたことと確認していないことを同じ粒度で記録する。「マイゲームが見えた」から新規認証や全タイトル互換へ一般化しない。

配布物artifact

公式channelから固定配布物を取得・照合

確認済み
初期配置bootstrap

prefix-localへtransactional配置

確認済み
画面表示client UI

process生存、swapchain、draw、present、visible content

確認済み
URL処理handler

非secretのprobeでMacアプリへのscheme解決

確認済み
ライブラリ表示library

既存認証sessionで購入済みlibraryを表示

確認済み
ゲーム起動child game

公式routeの購入済みゲーム2本を実機確認

2 / 2確認済み
新規認証fresh auth

完全な新規sessionでbrowser callbackをend-to-end確認

未確認
空のprefixempty prefix

prefix完全不在からの初期化branch

未確認
§09

互換ランチャーへ残す5原則

01 / IDENTITY

実binaryから始める

同じ製品名でも世代とframeworkが違えば診断対象は別になる。architectureとimportを先に固定する。

02 / CAUSAL SLOT

候補一覧より実callを追う

不足symbol、stack件数、API名のhit数で決めず、crash slotやactual callへ結ぶ。

03 / OWNERSHIP

能力と所有者を分ける

GPU deviceを作れることと、別process所有windowへ描けることは別問題として測る。

04 / ROUTE

認証値ではなくrouteを持つ

Mac側はURLの戻り先とWine環境を所有し、公式clientの認証内容を読まない。

05 / PARENT & CHILD

子processを別製品として見る

配信client、download、ゲーム起動、font、movie、停止制御を一つの成功へ潰さない。

公開範囲: 実在するゲーム配信ランチャー1件と、そこから起動した購入済みVN 2本の検証を汎用化した。製品名、作品名、認証値、ローカル識別子、private artifact identityは掲載していない。