抹桥的博客
言語
テーマカラー
250

XCOM: Enemy Within 起動から2分でフリーズする問題の調査記録

目次

本来は『XCOM: Enemy Within』を少し遊ぶつもりで起動しただけだったのに、メインメニューが表示されてから2分ほどでフリーズしてしまいました。再起動すると、メニューを少し操作できることもありますが、やはり反応しなくなります。

Steamからこのゲームを起動すると、Enemy UnknownとEnemy Withinのどちらを起動するか選ぶ画面も表示されます。Unknownにも起動時の問題があったため、まずはWithinに絞って原因を調べ、少なくとも遊びたいほうのバージョンを動かせるようにすることにしました。

最終的には今回の起動後のフリーズを解消できましたが、想像していたより少し回り道をすることになりました。最初は古いゲームと新しいシステム、4K解像度との互換性を疑っていましたが、最終的に行き着いたのは、Firaxisの古いオンラインサービスへのリクエストでした。ここでは、調査の経緯と手動での対処方法を記録しておきます。次に別のPCへ乗り換えたとき、また最初から試行錯誤しなくて済むように。

まずはいくつか定番の方法を試した

テストしやすいように、Codexに設定ファイル、セーブデータ、ログのバックアップを手伝ってもらい、条件を一つずつ変更してゲームがどのくらい動き続けるかを観察しました。Windowsで古いゲームを調整するとき、まず思い浮かぶのは解像度、ウィンドウモード、オーバーレイなどの問題です。

そこで、次のような方法を試しました。

試したこと結果
bShouldUseMcp を false に変更関連するリクエストを確実に防ぐことはできず、やはりフリーズした
4Kのボーダーレス表示から1280×720のウィンドウモードに変更起動直後のリソース使用量は変わったが、フリーズは引き続き発生した
SteamとNVIDIAのオーバーレイを調整増え続けるメモリ使用量は改善しなかった。一部の設定をオフにしても、DLLの挿入を完全には防げなかった
DXVK 3.1.1を使用効果なし
オンラインサブシステムの変更、英語への切り替え、CPUコア数の制限、音声の無効化いずれも今回の問題は解決しなかった
-ONETHREAD を追加かえって早く失敗した
Steamを完全に終了ゲームは約40秒後に終了し、正常に使えなかった

途中、一度は解決したように見えたこともありました。選択画面を回避し、適切なSteam環境変数を設定してWithinを直接起動すると、メインメニューを操作できたのです。そのため、起動方法だけの問題だと思いました。

しかし、もう少し待つと、やはりフリーズしました。

今回の調査で特に誤解しやすかったのはこの点です。ゲームが起動することと、ずっと遊び続けられることは別です。特に、今回の不具合は起動直後には発生しません。そこで、「プロセスがまだ動いているか」だけを見るのではなく、ウィンドウが応答しているか、メモリ使用量が増えているか、CPU時間がどう変化しているかを継続的に記録することにしました。

奇妙なネットワークリクエスト

ログとネットワーク接続をさらに調べると、ゲームが次のアドレスにアクセスしていることがわかりました。

prod.xcom-ew.firaxis.com

そのときの環境では、このアドレスは 192.81.242.201 に解決され、ゲームはポート 80 に接続していました。フリーズ時には CLOSE_WAIT 状態の接続が2つ残っていました。

CLOSE_WAIT は、相手側が接続を閉じたものの、ローカルのプログラムがまだ切断処理を完了していないことを意味します。この状態だけでは特に問題があるとは言えず、正常なプログラムでも一時的に発生することがあります。ただ、ゲームが応答しないままメモリ使用量も増えている状況を踏まえると、気になる状態でした。

そこで、フリーズ中のスレッドダンプを取得し、Windowsデバッガーで確認しました。スレッド 19520 は約75秒の間に約51秒分のCPU時間を消費しており、コールスタックの重要な部分は次のとおりでした。

ntdll!NtDeviceIoControlFile
mswsock!WSPRecv
ws2_32!WSARecv
wsock32!recv
XComEW+0x2604b5

ネットワークの受信処理を実行しており、recv の読み取り長を指定する引数は 1、つまり毎回1バイトしか読み取っていませんでした。

コールスタックのスナップショット1つだけで、ゲームのソースコードを直接復元することはできません。しかし、ホットスレッドが継続的にCPUを消費し、ウィンドウが応答を停止し、メモリ使用量が増え続けるという複数の手がかりを合わせると、クライアントがこの古いサービスへのリクエストを処理する際に、異常な読み取りを繰り返しているように見えました。

後で行ったドメイン名の置き換えテストも、この見立てを裏付けていました。

なぜ最終的にメモリ不足になったのか

元のプログラムが失敗したとき、次のようなメッセージが表示されました。

Ran out of virtual memory

その後、ダンプには 0xc0000005 のアクセス違反が記録されており、障害オフセットは XComEW.exe+0x00e77ce1 でした。

ここでいう仮想メモリについては、ゲームが32ビットプログラムであることを踏まえて考える必要があります。プロセスが使用できる仮想アドレス空間には上限があり、割り当てを続けると、物理メモリやディスクに空きがあっても、新しいアドレス空間を確保できなくなることがあります。

今回観察された流れは、まずフリーズし、メモリ使用量が増え続け、その後に割り当てに失敗し、最終的にクラッシュするというものでした。解像度を下げれば起動直後の使用量を多少減らせますが、異常な読み取り自体は防げないため、問題の解決にはなりませんでした。

最後に変更したのはドメイン名1つだけ

効果があったのは、元のプログラムの隣に XComEW-Offline.exe という名前のコピーを作り、ファイル内の文字列を1つだけ置き換える方法でした。

prod.xcom-ew.firaxis.com
             ↓
off.xcom-ew.invalid.test

どちらのアドレスも23文字で、UTF-16LEでエンコードすると46バイトです。同じ長さの文字列に置き換えるため、プログラム内の後続データを移動させる必要はありません。

新しいアドレスには、予約済みの .test ドメインを使用しました。今回のネットワーク環境では接続可能なサービスに解決されず、ゲームは元のリクエスト経路を通らなくなりました。Firaxisのサーバーが永久に停止したと証明されたわけではありません。確認できたのは、当時の古いサービスへのリクエストがクライアントの異常を引き起こし、それを回避した後は起動後のフリーズが再発しなかったということです。

元の XComEW.exe はそのまま残し、Hosts、セーブデータ、解像度も恒久的には変更していません。Offline はコピーの名前にすぎず、ゲームを遊ぶには引き続きSteamが必要です。この対処により、置き換えたドメインを通じて提供されるオンライン機能は利用できなくなります。マルチプレイはテストしていません。

その後、デスクトップショートカットも作成しました。Steamが起動済みであることを確認してから環境変数を設定し、このコピーを直接起動するようにしたものです。これで毎回Unknown / Withinの選択画面を経由せずに済みます。

今回のテストで確認できた範囲

修正後、2回に分けて動作を観察しました。

テスト観察結果
修正版を起動して約12分間実行72回のサンプリングすべてで応答あり。ロード後のプライベートメモリは約966 MBで、終了時には約918 MBとなり、直線的な増加は見られなかった
デスクトップショートカットから再起動し、152秒間観察15回のサンプリングすべてで応答あり。プライベートメモリは約802 MBから約738 MBに減少し、古いサービスのIPへの接続は確認されなかった

もう1つ、記録しておくべき細かい点があります。12分間のテスト概要にはダンプファイルが1つ表示されていました。ログを確認したところ、起動から約5秒後に 0x406D1388 例外が発生して作成されたものでした。このコードは通常、古い形式のデバッガーでスレッド名を設定するために使われます。その後、ゲームはテスト終了まで動作し続けたため、「ダンプがある」ことをそのまま「再びクラッシュした」とみなすことはできません。

ProcDumpはダンプ1件の上限に達した時点で終了していました。その後の証拠はプロセスとリソースのサンプリングによるものであり、すべての例外を継続的に監視していたわけではありません。

ここまでの観察期間では、繰り返し発生していた起動後のフリーズは再現しませんでした。ただし、今回の検証は主に起動とメインメニューまでで、キャンペーンや各ミッション、オンライン機能を一通り試したわけではありません。Enemy Unknownの問題も、今回の調査では解決していません。

手動での操作方法

ここまでが調査の経緯です。以下では、実際に必要な作業をまとめます。このセクションは、あとで自分で見返すため、また同じ問題に遭遇した人の参考のために書いています。

Enemy Withinのプログラムを見つける

まずゲームを終了し、Steamライブラリでゲームを右クリックして「管理 → ローカルファイルを閲覧」を選び、次の場所を開きます。

XEW\Binaries\Win32

この中に XComEW.exe があるはずです。探すのはWithinのプログラムです。以下の操作はUnknownには適用できません。

セーブデータをバックアップし、Steamを起動したままにします。次にPowerShellを開き、$gameDirectory を実際のインストール先に変更してください。

修正版のコピーを作成する

今回確認した元ファイルのSHA-256は次のとおりです。

F8D90411AFA46EBB56E65BBFD71098FEE335549768DB7A3FFDAE5E1DE9CDCB7F

ファイルサイズは27,793,464バイトで、置き換え位置は0から数えてバイトオフセット 23899942 です。以下のコードはこのバージョン専用です。ハッシュが一致しなければ処理を停止します。別のバージョンでは文字列の位置を改めて特定してください。チェックを削除して無理に実行しないでください。

以下のコード全体をPowerShellに貼り付けて実行します。インストール先への書き込み権限がない場合は、権限のあるPowerShellを使用してください。

$ErrorActionPreference = 'Stop'
$gameDirectory = 'D:\SteamLibrary\steamapps\common\XCom-Enemy-Unknown\XEW\Binaries\Win32'
$sourcePath = Join-Path $gameDirectory 'XComEW.exe'
$targetPath = Join-Path $gameDirectory 'XComEW-Offline.exe'
$expectedSourceHash = 'F8D90411AFA46EBB56E65BBFD71098FEE335549768DB7A3FFDAE5E1DE9CDCB7F'
$expectedTargetHash = '74648BF164D249D3E8211F2411681C01B8EDA8148AF978AA86E6B37F5C2B64C9'
$patchOffset = 23899942
$originalBytes = [Text.Encoding]::Unicode.GetBytes('prod.xcom-ew.firaxis.com')
$replacementBytes = [Text.Encoding]::Unicode.GetBytes('off.xcom-ew.invalid.test')

$sourceHash = (Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256).Hash
if ($sourceHash -ne $expectedSourceHash) {
    throw 'Original executable version does not match. Stop and inspect this version separately.'
}
if (Test-Path -LiteralPath $targetPath) {
    throw 'XComEW-Offline.exe already exists. Verify it or back it up before creating another copy.'
}

$bytes = [IO.File]::ReadAllBytes($sourcePath)
if ($bytes.Length -ne 27793464 -or $originalBytes.Length -ne $replacementBytes.Length) {
    throw 'Executable size or replacement length does not match.'
}
for ($byteIndex = 0; $byteIndex -lt $originalBytes.Length; $byteIndex++) {
    if ($bytes[$patchOffset + $byteIndex] -ne $originalBytes[$byteIndex]) {
        throw 'Original hostname was not found at the verified offset.'
    }
}

[Array]::Copy($replacementBytes, 0, $bytes, $patchOffset, $replacementBytes.Length)
$hasher = [Security.Cryptography.SHA256]::Create()
try {
    $patchedHash = [BitConverter]::ToString($hasher.ComputeHash($bytes)).Replace('-', '')
} finally {
    $hasher.Dispose()
}
if ($patchedHash -ne $expectedTargetHash) {
    throw 'Patched data did not match the verified result.'
}

$outputStream = [IO.File]::Open($targetPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write)
try {
    $outputStream.Write($bytes, 0, $bytes.Length)
} finally {
    $outputStream.Dispose()
}
if ((Get-FileHash -LiteralPath $targetPath -Algorithm SHA256).Hash -ne $expectedTargetHash) {
    throw 'Written copy verification failed. Do not launch this copy.'
}
if ((Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256).Hash -ne $sourceHash) {
    throw 'Original executable hash changed. Inspect the original before continuing.'
}
Get-FileHash -LiteralPath $sourcePath, $targetPath -Algorithm SHA256

成功すると、コピーのSHA-256は次の値になるはずです。

74648BF164D249D3E8211F2411681C01B8EDA8148AF978AA86E6B37F5C2B64C9

コピーがすでに存在する場合は、コードの最後の行でハッシュを確認できます。一致していれば、再作成する必要はありません。元のファイルは常に残してあるため、あとで簡単に元に戻せます。

まず一度起動してみる

同じPowerShellウィンドウで、続けて次を実行します。

$env:SteamAppId = '200510'
$env:SteamGameId = '200510'
Start-Process -FilePath (Join-Path $gameDirectory 'XComEW-Offline.exe') -ArgumentList '-FROMLAUNCHER', '-LANGUAGE=CHT' -WorkingDirectory $gameDirectory

-LANGUAGE=CHT は今回使用した繁体字中国語です。英語にする場合は -LANGUAGE=INT に変更できます。Steamの環境変数と作業ディレクトリも起動方法の一部です。EXEを直接ダブルクリックしても、同じ結果になるとは限りません。

メニューが表示されたら、少なくとも10~15分待ち、メニューを実際に切り替えたり、セーブデータを読み込んだり、ミッションに入ったりすることをおすすめします。これらは今後追加で行う必要があるプレイ検証です。ここまでのテスト記録では、キャンペーン全体を通した確認はしていません。

デスクトップショートカットを作成する

先ほどのゲームを終了し、同じPowerShellウィンドウで次のコードを実行します。$gameDirectory は前の変数をそのまま使い、$steamPath は自分のSteamのインストール先に合わせて変更してください。

$steamPath = 'C:\Program Files (x86)\Steam\steam.exe'
$gamePath = Join-Path $gameDirectory 'XComEW-Offline.exe'
$desktopPath = [Environment]::GetFolderPath('Desktop')
$shortcutPath = Join-Path $desktopPath 'XCOM Enemy Within 修复版.lnk'
if (-not (Test-Path -LiteralPath $steamPath)) { throw 'Steam executable was not found.' }
if (-not (Test-Path -LiteralPath $gamePath)) { throw 'Patched game executable was not found.' }
if (Test-Path -LiteralPath $shortcutPath) { throw 'Shortcut already exists. Use a different name.' }

$quotedSteam = $steamPath.Replace("'", "''")
$quotedGame = $gamePath.Replace("'", "''")
$quotedDirectory = $gameDirectory.Replace("'", "''")
$launchCommand = "if (-not (Get-Process -Name steam -ErrorAction SilentlyContinue)) { Start-Process -FilePath '$quotedSteam'; Start-Sleep -Seconds 12 }; "
$launchCommand += "`$env:SteamAppId='200510'; `$env:SteamGameId='200510'; "
$launchCommand += "Start-Process -FilePath '$quotedGame' -ArgumentList '-FROMLAUNCHER','-LANGUAGE=CHT' -WorkingDirectory '$quotedDirectory'"
$encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($launchCommand))
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe'
$shortcut.Arguments = "-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand $encodedCommand"
$shortcut.WorkingDirectory = $gameDirectory
$shortcut.IconLocation = "$gamePath,0"
$shortcut.WindowStyle = 7
$shortcut.Description = 'XCOM Enemy Within: bypass the legacy Firaxis service request'
$shortcut.Save()
Get-Item -LiteralPath $shortcutPath

今後はこのショートカットから起動してください。Steamがすでに起動していることを確認してから修正版のコピーを実行するため、バージョン選択画面をスキップできます。Steamライブラリから直接起動した場合は元のプログラムが起動するため、同じ問題が再発する可能性があります。

Steamに初めてログインするときや、更新中、起動に時間がかかっているときは、完全に準備が整うまで待ってからショートカットを使ってください。ここでの12秒は起動待ち時間であり、すべてのPCでSteamの準備がその時間内に完了することを保証するものではありません。

効果の確認と元に戻す方法

ゲームの実行中に、PowerShellをもう1つ開いて次のように確認できます。

Get-Process -Name XComEW-Offline | Select-Object Id, Responding, @{Name='PrivateMB'; Expression={[math]::Round($_.PrivateMemorySize64 / 1MB, 1)}}
$gameProcess = Get-Process -Name XComEW-Offline | Select-Object -First 1
Get-NetTCPConnection -OwningProcess $gameProcess.Id -ErrorAction SilentlyContinue | Where-Object RemoteAddress -eq '192.81.242.201'

起動時のロードが終わった後は、メモリ使用量が一方向に増え続けないはずです。2つ目の項目については、今回のテストで古いサービスへの接続は見つかりませんでした。ただし、プロセスが応答していることや該当の接続がないことはあくまで補助的な確認です。最終的には、実際にゲームを操作して確認してください。

ログは通常、Windowsの「ドキュメント」にある My Games\XCOM - Enemy Within\XComGame\Logs\Launch.log に保存されます。OneDriveでドキュメントのリダイレクトを有効にしている場合は、実際のパスがOneDrive内になっていることがあります。ログには、置き換え後のドメインに対するDNS問い合わせが記録されているはずです。

元に戻すには、ゲームを終了し、自分で作成したコピーとショートカットを削除してから、Steamで起動してください。Steamの更新後は、元のファイルのハッシュも改めて確認してください。古いコピーは、更新後のほかのゲームファイルと組み合わせると正常に動かない可能性があります。

最後に

少しゲームを遊ぶつもりが、結局半日ほどデバッグすることになり、なんとも言えない気分でした。それでも今回は、「再起動したら直った気がする」という段階で終わらず、ネットワーク受信経路とメモリ使用量の増加を確認し、ごく小さな変更で比較検証できました。

このように時間が経ってから発生する問題では、少し長めに待ってみることが大切です。メインメニューまで進めることは、メインメニューまで進めたというだけです。今後同じような問題に遭遇したら、まず不具合の前後で応答状況、CPU、メモリを記録しておけば、回り道を減らせるはずです。

XCOM: Enemy Within 起動から2分でフリーズする問題の調査記録
https://blog.kisnows.com/ja-JP/2026/09/27/xcom-enemy-within-startup-freeze/
作者
Kisnows
公開日
2026-09-27
ライセンス
CC BY-NC-ND 4.0