Skip to content

Instantly share code, notes, and snippets.

@n0mimono
Last active May 23, 2022 09:59
Show Gist options
  • Save n0mimono/22b574dc27ae2a3114b8feaa69afb1ca to your computer and use it in GitHub Desktop.
Save n0mimono/22b574dc27ae2a3114b8feaa69afb1ca to your computer and use it in GitHub Desktop.
Unity 2019 LTS vs 2020 LTS, Android

概要

Unity 2020 LTS Android の特に差分の大きい部分についてのメモ

IL2CPP

次のように、gradle buildするときにIL2CPPのコードをビルドするように変わっているらしい。

  • 2019
    • Unity Editorビルド
      • C# → C++ → xxx.so 等
    • Android Studioビルド
      • xxx.so → unityLibrary.rar
  • 2020
    • Unit Editoryビルド
      • C# → C++
    • Android Studioビルド
      • C++ → xxx.so → unityLibrary.rar

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

アプリ側から触る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はダイアログを出して死ぬようになった(と思われる)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment