Uzzu::Blog

Software Design, and my life.

Development BuildなUnityアプリをtargetSdk 34に対応させる

公式対応を待つべきで、他人に推奨できるような内容ではないので、参考にされる方は自己責任でお願いします。 UnityにはBug Report済です。

動作の変更点: Android 14 以上をターゲットとするアプリ に記載の通り、Android 14をターゲットとする(targetSdk 34)アプリでは、実行時にBroadcastReceiverを登録する場合、BroadcastReceiverをデバイスの他のすべてのアプリに対してexportするかどうかを示すフラグを指定する必要があります。指定がない場合、以下のような例外をスローします。

java.lang.SecurityException: co.uzzu.doushite.debug: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts

通常のAndroidアプリ開発であればただ該当箇所のソースコードを修正するだけで良いのですが、JARファイルのみ配布されているような特殊な環境下では、JARひいてはclassファイルに含まれる対象のbytecodeを削除するか、いい感じに動くように修正しなければなりません。

UnityにおいてはtargetSdkを34にした場合、Development BuildにおいてUnityPlayerのコンストラクタの呼び出し時点でクラッシュするという現象が発生します。幸いにも、BroadcastReceiverを実行時に登録する実装が含まれているのはDevelopment Build版のみで、Release Build版には含まれていないようです。

とはいえ、Developement Build版のunity-classes.jarが使用できないのも困るので、(該当処理がなくなることの影響を飲みつつ) 該当のbytecodeを削除します。

JARファイルはZIPファイルなのでunzipしつつ、 javap -cp . -c com.unity3d.player.UnityPlayer などと唱えると、UnityPlayerのbytecodeが出力されます。関係ある所はこのあたりでしょうか。実行してみれば分かるので、内容は一部省略しています。

     482: new           #74                 // class com/unity3d/player/UnityPlayer$2
     485: dup
     486: dup
     487: astore_1
     488: aload_0
     489: invokespecial #452                // Method com/unity3d/player/UnityPlayer$2."<init>":(Lcom/unity3d/player/UnityPlayer;)V
     492: putfield      #247                // Field mKillingIsMyBusiness:Landroid/content/BroadcastReceiver;
     495: getfield      #319                // Field mContext:Landroid/content/Context;
     498: aload_1
     499: new           #454                // class android/content/IntentFilter
     502: dup
     503: astore_1
     504: ldc_w         #456                // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     507: invokespecial #458                // Method android/content/IntentFilter."<init>":(Ljava/lang/String;)V
     510: aload_1
     511: invokevirtual #462                // Method android/content/Context.registerReceiver:(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;
     514: pop

上記出力でいう番号の隣のいかにも命令っぽい文字列がJVMのInstructions(命令)になっているので、これを https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5 などをみながらOpcodeなHex文字列に置き換えていきます。 bb5959... な文字列になっていくでしょう。

あとは、これらのinstructionをなかった事にしていきます。直接書き換えてもいいですが、JVM bytecodeの仕様上、このbyte列だけ削除すればいいというものではないので、JavassistApache Commons BCEL™ を使うと良いと思います。

BCELを使用した実装を以下に置いておきました。

https://github.com/uzzu/outcast

やっている内容は以下のとおりです。

  • JarFileのEntryからUnityPlayerクラスを探す
  • UnityPlayerの該当処理が含まれるコンストラクタを探す
  • コンストラクタの中の該当のInstructionの範囲を探す
  • コンストラクタから該当のInstructionを削除する
  • 元のコンストラクタを削除して、これまでに作成したコンストラクタを追加する
  • UnityPlayerクラスを新たに生成する
  • JarのEntryをコピー&保存しながら、UnityPlayerのみ新たなクラスに置き換える

流石にすべての情報は載せられないので、そのまま動くようにはしていませんが、OpcodeのHex文字列と入出力のConfiguration実装を改変すれば使えるようになっています。 注意点としては、UnityのAndroid SDKに付属してるJava Runtimeを使用して実行するようにしてください。 出力ファイルを開いて該当コードが削除されているのを確認するとより安心です。

2023年にやることではないなと思いました。終わり。

すべてのUnityのバージョンは見てられないのであれですが、少なくともLTS最新版であるUnity2022.3.8f1と、ベータ版最新のUnity 2023.2.0b7では修正されていませんでした。 Unity 2023では該当処理が含まれているメソッドのシグネチャも変わっている(コンストラクタ上には該当処理はなくなっている)ため、この記事に添付したGitHub Repositoryのコードはそのままでは利用できません。ご留意ください。

comments powered by Disqus