Steam のゲームの中には Windows 用の専用サーバーしか用意されていないものがあります。depot に Linux バイナリがないので、いつものアドバイスは「Windows マシンで動かせ」です。その必要はありません。Wine を使えば、Windows サーバーを普通の Linux マシンで動かすことができ、Steam に接続して実際のプレイヤーを受け入れます。これは、例として Alien Swarm: Reactive Drop(無料の co-op Source ゲーム)を使って私たちが実際にうまくいったレシピそのものです。同じアプローチは Windows 専用の他の Source 専用サーバーでも通用するはずです。
まず、本当に Linux サーバーが存在しないかを確認する
決めつけないでください。Wine に頼る前に Steam に直接尋ねましょう。steamcmd をそのアプリに向けて、プラットフォームの一覧を読みます:
steamcmd +login anonymous +app_info_print <appid> +quit \
| grep -iE "oslist|name"サーバーの専用サーバーアプリが「oslist windows」だけなら、頼れるネイティブ Linux ビルドはありません。Linux インストールを強制してみることもできます。本当に Linux depot がないときは、その場で失敗します:
steamcmd +@sSteamCmdForcePlatformType linux \
+login anonymous +app_update <appid> validate +quit
# ERROR! Failed to install app '<appid>' (Missing configuration)このエラーと、ELF ファイルが 1 つもダウンロードされないことが、それを裏付けます。ここからは Wine が道です。サーバーと app 1007(Steamworks Common Redistributables)を同じフォルダに、プラットフォームタイプ windows でインストールします。app 1007 はサーバーが必要とする Steam の DLL をもたらします。その理由はステップ 2 でわかります:
steamcmd +@sSteamCmdForcePlatformType windows +login anonymous \
+force_install_dir /path/to/server \
+app_update <appid> validate \
+app_update 1007 validate \
+quitサーバーには Wine プレフィックス内の Microsoft Visual C++ 2019 ランタイムも必要です。winetricks で一度インストールします(ディスプレイが必要なので xvfb-run で包みます):
WINEPREFIX=/wine xvfb-run -a winetricks -q vcrun2019
wineserver -wWine での素朴な試みが失敗する理由
サーバーを Wine から直接動かすと、続けざまに 2 つの壁にぶつかります:
- コンソールがない: サーバーは本物の端末を期待します。/dev/null からリダイレクトすると CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents が出て、起動しません。
- Steam ライブラリを読み込めませんでした: 起動したあとでも「Unable to load Steam support library」と表示され、Steam に登録されないので、誰も見つけられず入れません。
どちらにも直し方があります。ここに完全なレシピを示します。
ステップ 1: 本物の端末(PTY)を与える
ヘッドレスのマシンには端末がなく、Windows のコンソールサーバーは端末なしでは動きません。入力側が開いたままの疑似端末(PTY)が必要で、docker run -it がまさにそれを提供します。よくあるアドバイスは起動を script で包むことです。端末のないマシンではそれこそがバグです。script 自身の入力が EOF に達し、サーバーのコンソール読み取りもそれを受け取り、メインループが止まります。マップは読み込まれても、サーバーはソケットに一切応答しません。代わりに、PTY を開いて決して閉じない小さな Python ラッパーから起動し、偽のディスプレイには Xvfb を使います:
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99 WINEPREFIX=/wine
cat > launch_pty.py <<'EOF'
import os, sys
cmd = ["wine", "srcds_console.exe", "-console", "-condebug",
"-game", "<gamedir>", "+map", "<map>", "-port", "27015"]
pid, master = os.forkpty()
if pid == 0:
os.execvp(cmd[0], cmd)
# Parent: never close master, so the server's input stays open.
# Copy the server output to our own stdout.
while True:
try:
data = os.read(master, 4096)
except OSError:
break
if not data:
break
sys.stdout.buffer.write(data)
sys.stdout.flush()
_, status = os.waitpid(pid, 0)
sys.exit(os.waitstatus_to_exitcode(status))
EOF
python3 launch_pty.pyこれだけでサーバーはマップを読み込んでゲームポートをバインドします。ですが、まだ Steam と話すことができません。
ステップ 2: 2 つの Steam DLL、2 つの異なる入手元
Source エンジンは steamclient.dll と steam.dll の両方をサーバーの exe の隣に必要とします。そしてここに、デバッグを丸 1 周分よけいに費やす羽目になった罠があります: それらは異なる場所から来ていて、app 1007 はそのうち片方しか渡してくれません。
- steamclient.dll: はステップ 1 でインストールした app 1007(Steamworks Common Redistributables)から来ます。正規で、互換性のあるバージョンで、Valve がメンテナンスしています。
- steam.dll: は app 1007 に含まれていません。これは Steam クライアントの一部で、それをきれいに入手する唯一の方法は、Windows 版の steamcmd.exe を Wine の下で一度実行することです。すると自動更新され、steam.dll(と steamclient.dll)を自身のフォルダに置きます。
そこで steamcmd.exe を Wine の下で実行して steam.dll をブートストラップし、そのあと 2 つの DLL をサーバーの exe の隣にコピーします:
# steam.dll: run Windows steamcmd once under Wine (needs Xvfb for a display)
cd "$WINEPREFIX/drive_c/steamcmd"
curl -fsSLO https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
unzip -o steamcmd.zip
DISPLAY=:99 wine steamcmd.exe +quit # lays down steam.dll + steamclient.dll
# place both next to the server exe
cd /path/to/server
cp "$WINEPREFIX/drive_c/steamcmd/steam.dll" .
[ -f steamclient.dll ] || cp "$WINEPREFIX/drive_c/steamcmd/steamclient.dll" .ステップ 3: なぜ steam.dll が肝心の部品なのか
これはほとんど誰も書き留めていない部分です。エラーには「Unable to load Steam support library」と出ますが、そこで言及されているサポートライブラリは steamclient.dll ではなく steam.dll です。Source エンジンは Steam のチェーン全体をブートストラップするために、実行ファイルの隣に steam.dll を探します。steamclient.dll だけがある状態(app 1007 が渡してくれるのはそれだけ)ではまだ失敗します。steam.dll が .exe の隣に置かれた瞬間、ログは失敗から成功へと変わります:
Connection to Steam servers successful.
VAC secure mode is activated.VAC はサーバーが正しくログインしたゲームサーバーでない限り有効になりません。なので、この行はサーバーが登録されたことの証拠です。ほとんどの Source サーバーはここで匿名でログインし、Game Server Login Token(GSLT)は必要ありません。一部のゲーム(CS2 など)だけが必要とします。
落とし穴: A2S が沈黙する
これが一番時間を食った落とし穴です。script ラッパーを使うと、サーバーは A2S_INFO(ツールがサーバー名、マップ、プレイヤー数を読むための UDP クエリ)に応答しませんでした。同じマシンからでもすべてのクエリがタイムアウトし、サーバーは完全に死んでいるように見えました。最初は Wine を疑いました。原因は Step 1 のメインループの停止で、サーバーはソケットをまったく読んでいませんでした。
PTY を開いたままにすると、サーバーは A2S に普通に応答し、実際のプレイヤーが参加できます。ただし最終テストはクエリへの応答ではなく、クライアントの接続です。PTY が正しくなってから、実際のプレイヤーが接続してプレイしました:
Client "player" connected (10.0.0.5:27005).
1/ 8 on map <mapname>実践的な教訓: Wine でホストしたサーバーがマップを読み込むのに A2S が沈黙しているなら、まず Wine を疑わないこと。コンソールの接続方法を確認しましょう。UDP ポートがバインドされていても、サーバーが生きている証拠にはなりません。止まったサーバーはパケットを読まずに溜めたまま、ポートをバインドし続けるからです。
これはすべての Windows ゲームで通用する?
範囲については正直に言います。私たちが実証したのは Source エンジンのサーバーで、同じレシピ(PTY、steamcmd で構築したランタイム、exe の隣の steam.dll)は他の Windows 専用 Source 専用サーバーでも動くはずです。他のエンジンのゲームは Steam の統合方法が違うので、別の実験として扱ってください。どちらにしても覚悟しておくこと: 起動はネイティブの Linux より遅くなります(Wine、steamcmd の手順、Steam ログインの分)。
あるいは全部すっ飛ばす
これは午後がまるごと消えるたぐいの寄り道です。サーバーだけが欲しいなら、Linux のフリートと Wine まわりは私たちが時間単位で運用し、最初の1分からパブリック IP を用意します。Alien Swarm: Reactive Drop はまだ公開デプロイページにありませんが、他の Windows 専用サーバーはあります。たとえば Wreckfest と rFactor 2。すべてのゲームはこちら: 2hours.gg でホストしているものをご覧ください。