Unity 2020 LTS Android の特に差分の大きい部分についてのメモ
次のように、gradle buildするときにIL2CPPのコードをビルドするように変わっているらしい。
- 2019
- Unity Editorビルド
- C# → C++ → xxx.so 等
- Android Studioビルド
- xxx.so → unityLibrary.rar
- Unity Editorビルド
- 2020
- Unit Editoryビルド
- C# → C++
- Android Studioビルド
- C++ → xxx.so → unityLibrary.rar
- Unit Editoryビルド
unityLibrary/src/main/Il2CppOutputProject以下にiOSと似たようなIL2CPPのソースコードが出力される。
unityLibraryのbuild.gradleは
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
apply plugin: 'com.android.library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'UnityAds', ext:'aar')
implementation(name: 'UnityAdsAndroidPlugin', ext:'aar')
implementation(name: 'billing-3.0.1', ext:'aar')
implementation(name: 'common', ext:'aar')
}
android {
compileSdkVersion 30
buildToolsVersion '28.0.3'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 23
targetSdkVersion 30
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
versionCode 1
versionName '3.12.0'
consumerProguardFiles 'proguard-unity.txt'
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
packagingOptions {
doNotStrip '*/armeabi-v7a/*.so'
doNotStrip '*/arm64-v8a/*.so'
}
}
def getSdkDir() {
Properties local = new Properties()
local.load(new FileInputStream("${rootDir}/local.properties"))
return local.getProperty('sdk.dir')
}
def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) {
exec {
commandLine(workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/netcoreapp3.1/il2cpp",
"--compile-cpp",
"--libil2cpp-static",
"--platform=Android",
"--architecture=" + architecture,
"--configuration=" + configuration,
"--outputpath=" + workingDir + targetDirectory + abi + "/libil2cpp.so",
"--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/external/bdwgc/include",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/include",
"--tool-chain-path=" + android.ndkDirectory,
"--map-file-parser=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/MapFileParser/MapFileParser.exe",
"--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput",
"--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi,
"--dotnetprofile=unityaot")
environment "ANDROID_SDK_ROOT", getSdkDir()
}
delete workingDir + targetDirectory + abi + "/libil2cpp.sym.so"
ant.move(file: workingDir + targetDirectory + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
}
android {
task BuildIl2CppTask {
doLast {
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARMv7', 'armeabi-v7a', 'Release');
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARM64', 'arm64-v8a', 'Release');
}
}
afterEvaluate {
if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders'))
project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders'))
project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
}
sourceSets {
main {
jni.srcDirs = ["src/main/Il2CppOutputProject"]
}
}
}
要するに、BuildIl2CppTask
が入るようになった。
なにができるか?
例えば、スクリプティングレイヤで生成されるコードはunityLibrary/src/main/Il2CppOutputProjectSource/il2cppOutput以下に入るが、この中で頻繁に呼ばれるNullCheck()
はunityLibrary/src/main/Il2CppOutputProjectSource/IL2CPP/libil2cpp/codegen/il2cpp-codegen-il2cpp.hにあり
inline void NullCheck(void* this_ptr)
{
if (this_ptr != NULL)
return;
il2cpp_codegen_raise_null_reference_exception();
}
ここでNullReferenceException
を起こしている。
ここに適当な処理を入れればNullReferenceException
の処理にフックできる。今まででもiOSではやろうと思えばできたが、これがAndroidでもできるようになった。
アプリ側から触るUnityPlayer
はどこにあるのかというとunityLibrary/libs/unity-classes.jarにある。逆コンパイルしない限り改変できない(これは以前と同じ)。
2019 LTSと2020 LTSであまり変更点はないが
% diff UnityPlayer_2020.java UnityPlayer_2019.java
124,125c124,125
< Configuration var5 = this.getResources().getConfiguration();
< this.mNaturalOrientation = this.getNaturalOrientation(var5.orientation);
---
> Configuration var4 = this.getResources().getConfiguration();
> this.mNaturalOrientation = this.getNaturalOrientation(var4.orientation);
131c131
< String var6 = loadNative(this.mContext.getApplicationInfo());
---
> loadNative(this.mContext.getApplicationInfo());
133,136c133,134
< String var3 = "Your hardware does not support this application.";
< g.Log(6, var3);
< AlertDialog var4;
< (var4 = (new Builder(this.mContext)).setTitle("Failure to initialize!").setPositiveButton("OK", new OnClickListener() {
---
> AlertDialog var3;
> (var3 = (new Builder(this.mContext)).setTitle("Failure to initialize!").setPositiveButton("OK", new OnClickListener() {
140,141c138,139
< }).setMessage(var3 + "\n\n" + var6 + "\n\n Press OK to quit.").create()).setCancelable(false);
< var4.show();
---
> }).setMessage("Your hardware does not support this application, sorry!").create()).setCancelable(false);
> var3.show();
511,514c509
< if (n.c()) {
< this.nativeRestartActivityIndicator();
< }
<
---
> this.nativeRestartActivityIndicator();
676,692c671
< private static String logLoadLibMainError(String var0, String var1) {
< var0 = "Failed to load 'libmain.so'\n\n" + var1;
< g.Log(6, var0);
< return var0;
< }
<
< private static String loadNative(ApplicationInfo var0) {
< String var1 = var0.nativeLibraryDir + "/libmain.so";
<
< try {
< System.loadLibrary("main");
< } catch (UnsatisfiedLinkError var2) {
< return logLoadLibMainError(var1, var2.toString());
< } catch (SecurityException var3) {
< return logLoadLibMainError(var1, var3.toString());
< }
<
---
> private static void loadNative(ApplicationInfo var0) {
695d673
< return "";
697,699c675
< String var4 = "NativeLoader.load failure, Unity libraries were not loaded.";
< g.Log(6, var4);
< return var4;
---
> g.Log(6, "NativeLoader.load failure, Unity libraries were not loaded.");
1182a1159,1165
>
> try {
> System.loadLibrary("main");
> } catch (UnsatisfiedLinkError var1) {
> g.Log(6, "Failed to load 'libmain.so', the application will terminate.");
> throw var1;
> }
loadNative
のあたりが変わっている
2019は
public UnityPlayer(Context var1, IUnityPlayerLifecycleEvents var2) {
...
loadNative(this.mContext.getApplicationInfo());
if (!n.c()) {
AlertDialog var3;
(var3 = (new Builder(this.mContext)).setTitle("Failure to initialize!").setPositiveButton("OK", new OnClickListener() {
public final void onClick(DialogInterface var1, int var2) {
UnityPlayer.this.finish();
}
}).setMessage("Your hardware does not support this application, sorry!").create()).setCancelable(false);
var3.show();
}
...
}
private static void loadNative(ApplicationInfo var0) {
if (NativeLoader.load(var0.nativeLibraryDir)) {
n.a();
} else {
g.Log(6, "NativeLoader.load failure, Unity libraries were not loaded.");
}
}
static {
(new m()).a();
try {
System.loadLibrary("main");
} catch (UnsatisfiedLinkError var1) {
g.Log(6, "Failed to load 'libmain.so', the application will terminate.");
throw var1;
}
}
2020は
public UnityPlayer(Context var1, IUnityPlayerLifecycleEvents var2) {
...
String var6 = loadNative(this.mContext.getApplicationInfo());
if (!n.c()) {
String var3 = "Your hardware does not support this application.";
g.Log(6, var3);
AlertDialog var4;
(var4 = (new Builder(this.mContext)).setTitle("Failure to initialize!").setPositiveButton("OK", new OnClickListener() {
public final void onClick(DialogInterface var1, int var2) {
UnityPlayer.this.finish();
}
}).setMessage(var3 + "\n\n" + var6 + "\n\n Press OK to quit.").create()).setCancelable(false);
var4.show();
}
...
}
private static String loadNative(ApplicationInfo var0) {
String var1 = var0.nativeLibraryDir + "/libmain.so";
try {
System.loadLibrary("main");
} catch (UnsatisfiedLinkError var2) {
return logLoadLibMainError(var1, var2.toString());
} catch (SecurityException var3) {
return logLoadLibMainError(var1, var3.toString());
}
if (NativeLoader.load(var0.nativeLibraryDir)) {
n.a();
return "";
} else {
String var4 = "NativeLoader.load failure, Unity libraries were not loaded.";
g.Log(6, var4);
return var4;
}
}
static {
(new m()).a();
}
2019まではlibmain.soをstatic initializerで読み込んでいたのに対して、2020はコンストラクタで読み込むようにしている。ほとんどの場合、これは影響ないと思うが一応覚えておいた方が良い。
2019のときにはsoを読めなかったときに何もできずに完全死亡していたが(ダイアログを出すようなコードがあるように見えるが、ここに到達する前に死ぬ)、2020はダイアログを出して死ぬようになった(と思われる)。