BepInEx 6 IL2CPP 在 Bottles 無法開啟修復記錄

· 約 5 分鐘閱讀

這是我前陣子解決在 Bottles 裝 BepInEx 6 IL2CPP 無法順利執行的問題。
其實 BepInEx 5 和 6 Mono 也有問題,但是補個啟動參數 WINEDLLOVERRIDES="winhttp=n,b" %command% 就好,我會在之後的文章說明。

WARNING

這是我用 Claude Code 幫我除錯時產生的除錯記錄。

雖然問題已解決,但裡面除了一些特定遊戲和環境產生的 Log,部分通用知識還沒得到進一步驗證。
所以這份記錄僅供參考,可以向我提出哪段地方論述有問題,但本人不會修改這篇文章。

在我寫 Bottles 和 BepInEx 時,我才會回來引用,並說明哪裡錯誤。

IMPORTANT

除錯日誌後期出現了 BepInEx 6 IL2CPP 版本過舊的問題。

我在使用 Claude Code 幫我修復時,有注意到這個資訊,所以在成功之前,有替換 BepInEx 6 IL2CPP 最新版 (785)。
因此文件底下「版本過舊」的問題,很可能也是影響錯誤的原因之一,所以讀的時候,請忽略版本過舊那段,參考那段資訊會影響除錯判斷。

NOTE

  • 運行環境:Bottles
  • 修復工具: Claude Code
  • 模型:Claude Opus 5
  • 其餘環境:見正文

ひと夏の思い出(Hitonatsu.exe)BepInEx 6 + Wine/Bottles 修復筆記#

記錄日期:2026-08-08 執行環境:Bottles(bottle 名稱 Games)、CachyOS (Arch)、GPU: AMD Radeon RX 9070 XT

最終可用設定#

Runnerge-proton11-3

啟動參數(Bottles 遊戲設定 → 啟動參數):

WINEDLLOVERRIDES="winhttp=n,b" DOTNET_EnableWriteXorExecute=0 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 %command%

BepInEx 安裝方式維持原樣:drop-in 在遊戲資料夾(winhttp.dll + BepInEx/ + doorstop_config.ini),沒有额外動過 doorstop 設定檔。


問題起點#

症狀:加上啟動參數後 BepInEx 有被觸發但遊戲打不開;不加參數遊戲能開但 BepInEx 不會注入。一度換了三種 runner(Soda 11.0-4、GE-Proton 11-3、Wine-GE-Proton8-26)都失敗,連 BepInEx 的 Console 都沒跳出。

診斷方式:直接在終端機用 wine Hitonatsu.exe 手動執行,繞過 Bottles UI 才能看到完整的錯誤輸出、Wine 崩潰回報、以及 BepInEx/LogOutput.log / Unity 的 Player.log

崩潰演進過程#

  1. 只有 WINEDLLOVERRIDES:程式在 Unity 引擎完全啟動前就死掉,錯誤是 Cannot get symbol u_charsToUChars from libicuuc。 原因:Wine 的 icuuc.dll 只是個轉接殼,實際會呼叫主機 Linux 的 libicuuc.so。CachyOS 滾動更新,主機 ICU 版本是 78,符號被命名成 u_charsToUChars_78,但 GE-Proton 內建的轉接殼是照舊版 ICU 寫的,對不上就直接崩潰。這一步在 BepInEx 連 log 都還沒寫出來前就已經死了,所以「Console 沒跳出」。

  2. 加上 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1:Unity engine、DXVK、BepInEx/doorstop 全部正常啟動,BepInEx/LogOutput.log 完整寫出,Chainloader initialized。但遊戲在抓完 GPU(RX 9070 XT)之後,於 Discovering subsystems 那一步之後又默默結束(exit code 5)。

  3. 開啟完整 Wine 除錯輸出後,抓到真正例外:

    Unhandled exception: page fault on read access to 0x65
    3 winhttp (+0x2514)   ← doorstop 的 winhttp.dll 代理
    4 unityplayer ...

    懷疑是 doorstop 代理 DLL 轉發真正 WinHTTP API 呼叫時壞掉,於是測試把代理 DLL 分別改名成 version.dllwinmm.dll,三次都在完全相同的內部位移崩潰 —— 證明不是「哪個 API 被冒充」的問題。

  4. 最終透過 Bottles UI(而非手動終端機)重新啟動,使用跟診斷時幾乎相同的參數組合,這次成功了

老實說:第 3 步那個 doorstop 代理崩潰,在我手動終端機測試裡穩定重現了四次(winhttp/version/winmm 都一樣),但透過 Bottles 本身的啟動流程卻沒有再發生。可能是 Bottles 啟動時額外設定的環境變數/沙盒行為跟我手動 wine Hitonatsu.exe 不完全一樣,也可能是 doorstop 這種底層 PE 注入本身有時序上的不穩定性。如果未來又遇到「BepInEx 有跑、但抓完 GPU 後又死掉」的情況,換一顆 runner 或重開個兩三次再試,不一定要重新大改設定。


各參數的作用#

參數作用為什麼需要
WINEDLLOVERRIDES="winhttp=n,b"強制 Wine 優先載入遊戲資料夾裡「native」的 winhttp.dll(doorstop 的代理),而不是 Wine 內建的版本doorstop 靠偽裝成一個遊戲會載入的系統 DLL 來搶在遊戲程式碼執行前插入自己。不強制 native 優先,Wine 永遠用自己內建那顆,BepInEx 就完全不會被觸發(這正是一開始「不加參數能開、加了才觸發」的原因)
DOTNET_EnableWriteXorExecute=0關閉 .NET CoreCLR 的 W^X(write-xor-execute)記憶體保護CoreCLR 用這個機制保護 JIT 產生的可執行記憶體,但這個機制在 Wine 底下常跟記憶體管理不相容,會導致 CoreCLR 初始化時直接崩潰。這是「BepInEx 6 IL2CPP(用 CoreCLR 模式)在 Wine/Proton 上執行」這類問題的標準解法
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1讓 CoreCLR 完全跳過 ICU 全球化函式庫,改用最精簡的「不分文化」字串處理避免 CoreCLR 去呼叫 Wine 的 ICU 轉接殼(進而呼叫主機 libicuuc.so)。主機 ICU 版本跟 Wine 內建轉接殼假設的版本對不上時就會崩潰(symbol not found),這個開關讓 CoreCLR 根本不去碰這條路徑

.NET 依賴到底要不要裝?#

結論:不用裝額外的 .NET Framework / .NET Desktop Runtime。

這款遊戲用的是 BepInEx 6(IL2CPP + CoreCLR 模式),運作方式是:

  • doorstop_config.ini 裡指定 coreclr_path = dotnet\coreclr.dllcorlib_dir = dotnet
  • 遊戲資料夾自己就帶了一整包私有的 CoreCLR 執行環境(Hitonatsu 資料夾/dotnet/ 底下),跟你系統或這個 Bottle 裡裝的 .NET Framework 4.x / .NET Desktop Runtime 6 / 10 完全無關,兩者互不干涉。

Bottle 裡目前裝的這些依賴(dotnet40dotnet45dotnet46dotnet48dotnetdesktop6dotnetdesktop10)對這款遊戲的 BepInEx 注入沒有任何幫助,純粹是遊戲本體(Tyrano 引擎相關組件、或其他你裝過的遊戲)可能需要。真正跟 BepInEx 能不能動起來有關的,只有上面那張表的三個環境變數。

Visual C++ Runtime(vcruntime140.dll / msvcp140.dll 等)在這個 Bottle 裡已經存在(應該是安裝 dotnetdesktop6 時被自動帶進來的),這塊也沒有問題,不用額外處理。


一個已知但不影響遊戲開啟的小問題#

BepInEx/LogOutput.log 裡有這一段錯誤:

Unsupported metadata version found! We support 23-31, got 39

你裝的 BepInEx build(6.0.0-be.738)內建的 Cpp2IL 只支援 IL2CPP metadata 版本 23–31,但這款遊戲用的 Unity 6000.3.6f1 是很新的版本,metadata 版本是 39,超出支援範圍。

跟 Wine/Linux 無關,在 Windows 原生環境下也會一樣報這個錯——純粹是 BepInEx/Cpp2IL 還沒跟上這款遊戲用的新版 Unity。目前不影響遊戲本身開啟(BepInEx 會繼續往下跑,只是還沒放任何 plugin,所以無傷大雅)。但如果之後你想裝實際會用到 IL2CPP interop(例如需要呼叫遊戲內部類別)的 plugin,可能會因為這個問題而沒辦法產生正確的 interop 組件,屆時需要找支援更新 metadata 版本的 BepInEx/Cpp2IL 版本。


備份檔案#

winhttp.dll.doorstop-bak 如果還留在資料夾裡是診斷過程中的備份,可以刪除(跟目前使用中的 winhttp.dll 內容相同)。

留言