こんにちは、Unityエンジニアのオオバです。

お悩みさん
お悩みさん
  • Unity 7で開いたらFatal Errorで落ちる
  • Firebase.Analyticsが読み込めないと言われる
  • Unity 6では普通に動いていたのになぜ?
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    Unity 7、さっそく触ってみたくなりますよね。

    僕も開発中の「勇者のどうぐ屋さん」をUnity 7で開いてみました。

    結果、エディタが起動途中で落ちて、Quitボタンしか押せない状態に。

    開いた瞬間に終わるのは、なかなか心にきます。

    本記事では、Firebaseを入れたプロジェクトがUnity 7で起動しなくなった原因と、僕が実際にやった解決方法をまとめます。

    同じエラーで止まっている方の助けになればうれしいです。

    FirebaseのDLLに付いた「32bit専用フラグ」を落とせばUnity 7で起動します

    先に結論です。

    Firebase Unity SDKのDLLは、32bitプロセス専用のフラグ(32BITREQUIRED)付きでビルドされています。

    Unity 6まではMonoがこのフラグを無視していたので、問題が表に出ませんでした。

    Unity 7はエディタのスクリプト実行がCoreCLRに変わり、フラグを守るようになっています。

    64bit(arm64)のエディタプロセスで読み込みを拒否され、起動できなくなるわけです。

    DLLのCLIヘッダから「32BITREQUIRED」のビットだけを落とすと、起動するところまで進みました。

    この記事のポイント
    • 原因はFirebase DLLの32BITREQUIREDフラグ
    • Unity 7(CoreCLR)はフラグを守るので読み込みを拒否する
    • フラグを落としてAnyCPU扱いにすれば起動する
    • Firebase SDKを更新するたびに同じ処理が必要

    起きたこと

    環境は次のとおりです。

    もともとUnity 6000.3.9f1で開発しているプロジェクトを、Unity 7000.0.0a8で開きました。

    起動途中で「Fatal Error!」のダイアログが出て、Quitしか押せません。

    Unity 7で開くとFatal Errorで落ちる

    Unity 7で開くとFatal Errorで落ちる

    ダイアログのエラー文は次のとおりです。

    FileLoadException: Could not load file or assembly 'Firebase.Analytics, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The assembly architecture is not compatible with the current process architecture.  
    System.Runtime.Loader.AssemblyLoadContext.InternalLoad(ReadOnlySpan`1 arrAssembly, ReadOnlySpan`1 arrSymbols) in /Users/bokken/build/output/unity/dotnet-runtime/unity/unity-jit/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs:line 84  
    

    注目したいのはスタックトレースの AssemblyLoadContext.CoreCLR.cs です。

    Unity 7では、エディタのスクリプト実行がMonoからCoreCLRに変わっています。

    ランタイムが変わったことが関係していそうだと、ここで当たりを付けました。

    Firebaseの導入方法も書いておきます。

    導入方法の違いは、あとで解決するときに効いてきます。

    原因:DLLに32bit専用フラグが付いていた

    エラー文には「architecture is not compatible」とあります。

    そこでDLLのPEヘッダを確認しました。

    file コマンドの結果はこちら。

    PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly  
    

    CLIヘッダのFlagsは 0x3 でした。

    内訳は ILONLY (0x1) と 32BITREQUIRED (0x2) です。

    つまりFirebaseのDLLは「32bitプロセスでしか動かさないでください」という印付きでビルドされていました。

    MonoとCoreCLRで扱いが違う

    同じDLLなのに、Unity 6では普通に動いていました。

    理由はランタイムによるフラグの扱いの差です。

    Unity 6のエディタも64bitで動いていたので、実はずっと「フラグと合わない状態」で動いていたことになります。

    Monoが大目に見てくれていただけ、という話でした。

    フラグ付きのDLLを探す

    プロジェクト内の .NET アセンブリを全部調べたところ、フラグ付きはFirebaseの13個だけでした。

    調べるのに使ったスクリプトを載せておきます。

    Pythonの標準ライブラリだけで動きます。

    import struct, sys
    
    # PEヘッダのCLIフラグを読み、32BITREQUIRED(0x2)付きのアセンブリを列挙する
    for p in sys.argv[1:]:  
        d = open(p, 'rb').read()  
        if d[:2] != b'MZ':  
            continue  
        pe = struct.unpack_from('<I', d, 0x3c)[0]  
        magic = struct.unpack_from('<H', d, pe + 24)[0]  
        rva = struct.unpack_from('<I', d, pe + 24 + (96 if magic == 0x10b else 112) + 14 * 8)[0]  
        if rva == 0:  
            continue  # .NET アセンブリではない  
        nsec = struct.unpack_from('<H', d, pe + 6)[0]  
        sec = pe + 24 + struct.unpack_from('<H', d, pe + 20)[0]  
        for i in range(nsec):  
            vs, va, rs, rp = struct.unpack_from('<IIII', d, sec + i * 40 + 8)  
            if va <= rva < va + max(vs, rs):  
                flags = struct.unpack_from('<I', d, rva - va + rp + 16)[0]  
                if flags & 0x2:  
                    print("32BITREQUIRED", p)  
    

    corflags.py という名前で保存して、プロジェクトのルートで次のように実行します。

    find Assets Packages Library/PackageCache -name "*.dll" -print0 | xargs -0 python3 corflags.py  
    

    ネイティブDLL(.NET アセンブリでないもの)にはCLIヘッダがありません。

    スクリプトでは読み飛ばしているので、調査の対象外です。

    「フラグ付きはFirebaseだけ」と言えるのは、.NET アセンブリに限った話になります。

    解決:32BITREQUIREDのビットだけを落とす

    DLLの中身(IL)には触りません。

    CLIヘッダの「32BITREQUIRED」ビットだけを落として、AnyCPU扱いにしました。

    Windowsの CorFlags.exe /32BITREQ- と同じことを、Pythonでやった形です。

    書き換えても大丈夫と判断した理由

    DLLのバイナリを直接いじるので、正直ちょっと怖いですよね。

    僕も手を付ける前に、壊れないかを確認しました。

    2つ目の理由が大きかったです。

    Unity 6に戻したときに壊れないなら、試す価値はあると判断しました。

    フラグを落とすコード

    フラグを落とす関数はこちらです。

    def clear_flag(data):  
        d = bytearray(data)  
        pe = struct.unpack_from('<I', d, 0x3c)[0]  
        magic = struct.unpack_from('<H', d, pe + 24)[0]  
        rva = struct.unpack_from('<I', d, pe + 24 + (96 if magic == 0x10b else 112) + 14 * 8)[0]  
        nsec = struct.unpack_from('<H', d, pe + 6)[0]  
        sec = pe + 24 + struct.unpack_from('<H', d, pe + 20)[0]  
        for i in range(nsec):  
            vs, va, rs, rp = struct.unpack_from('<IIII', d, sec + i * 40 + 8)  
            if va <= rva < va + max(vs, rs):  
                fo = rva - va + rp + 16  
                flags = struct.unpack_from('<I', d, fo)[0]  
                struct.pack_into('<I', d, fo, flags & ~0x2)  
                return bytes(d)  
    

    調査用スクリプトと同じ手順でCLIヘッダのFlagsの位置を探し、0x2 のビットだけを落としています。

    tgzで入れたパッケージはtgzごと直す

    ここが地味なハマりどころです。

    tgzで入れたパッケージは、Library/PackageCache のDLLだけを直しても、再展開で元に戻ってしまいます。

    そこで導入方法ごとに直し方を分けました。

    結果

    Unity 7000.0.0a8で、エディタが起動するところまで進みました。

    ログを確認すると「architecture is not compatible」は0件です。

    注意点

    この対処はあくまで応急処置です。

    気を付けたい点が2つあります。

    1つ目は、Firebase SDKを更新するとフラグ付きのDLLに戻ること。

    更新のたびに同じ処理が必要です。

    2つ目は、根本的な解決はFirebase側の対応待ちになること。

    FirebaseがAnyCPUでビルドし直してくれれば、この作業は要らなくなります。

    起動のあとにもう1つ壁があった

    エディタは起動しました。

    ところが今度は、Unity 7で削除・変更されたAPIのせいで外部ライブラリがコンパイルエラーに。

    Unity 7への移行には、2段階の壁があると感じました。

    1. 起動できない(ランタイムの変更)
    2. コンパイルが通らない(APIの変更)

    今回の記事で扱ったのは1段目です。

    2段目は別の記事でまとめる予定です。

    まとめ

    Unity 7でFirebase入りのプロジェクトが起動しない原因は、FirebaseのDLLに付いた「32BITREQUIRED」フラグでした。

    エラー文だけ見ると「Firebaseが壊れた?」と焦ります。

    実際はランタイムが変わって、昔からあった印が急に効き始めただけでした。

    Unity 7への移行は、ランタイムが変わる大きな節目です。

    同じように外部SDKで止まる方は、まずエラー文の「architecture」とスタックトレースの「CoreCLR」を探してみてください。

    👉 Importing small AssetsでUnityが起動しない時に試す6つの方法

    オススメ記事
    検証環境