androidflashairane

Repackage APK with Adobe AIR


There is an Android game made in Flash that was packaged using Adobe AIR, but it no longer works for Android versions above 7. I was wondering if it is possible to repackage this game using HARMAN's Adobe AIR?

I have downloaded HARMAN's Adobe AIR and attempted to repackage the application using the ADT package command. I used the application.xml file that was present in the APK's assets/META-INF/AIR/ folder. However, this application uses extensions, and it appears that ADT expects ANE (AIR Native Extension) files, or is there some other way?

I couldn't find the ANE files online. But some Chinese hackers were able to repackage the app, working in Android 10, and the folder assets/META-INF/AIR/extensions folder has suffered no changes in comparison with the original apk. The files in the folder lib/armeabi-v7a were updated however, and include a new one called libX86Bridge.so.

The extensions that the original app uses are:


Solution

  • So I have finally been able to repackage the APK, and it works on recent Android versions!

    First, I opened the APK file with an archive manager (APK is a ZIP file), and then converted the classes.dex file inside the APK to a JAR file, using the dex2jar tool. Inside the JAR file (which is also a ZIP file) are the compiled Java classes from the AIR Native Extensions that the app uses.

    Then, inside the bin directory of Adobe AIR by HARMAN I created a directory for each extension:

    AIRSDK_Windows\bin
    ├── com.adobe.ane.social
    ├── com.milkmangames.extensions.AndroidIAB
    ├── com.chartboost.plugin.air
    ├── com.milkmangames.extensions.CoreMobile
    ├── com.jirbo.airadc.AirAdColony
    ├── com.milkmangames.extensions.GoViral
    

    The unpacked extension ANEs could be found inside the APK, in the path /assets/META-INF/AIR/extensions/.

    For each extension, added catalog.xml and library.swf into a ZIP file, and renamed the ZIP extension to SWC (e.g.: com.adobe.ane.social.swc). Then, placed the SWC file inside the respective extension's directory.

    Inside each extension's directory in the APK, there is a path META-INF/ANE/ where the file extension.xml and other files can be found (e.g.: /assets/META-INF/AIR/extensions/com.adobe.ane.social/META-INF/ANE/). I placed all the files inside these directories in the directories of the respective extensions that I created in the bin directory. For the com.chartboost.plugin.air extension, for example, this is what the directory structure looks like:

    AIRSDK_Windows\bin
    ├── com.adobe.ane.social
    ├── com.milkmangames.extensions.AndroidIAB
    ├── com.chartboost.plugin.air
    │   ├── com.chartboost.plugin.air.swc
    │   ├── extension.xml
    │   ├── Android-ARM
    ├── com.milkmangames.extensions.CoreMobile
    ├── com.jirbo.airadc.AirAdColony
    ├── com.milkmangames.extensions.GoViral
    

    I opened the extension.xml file, and these were its contents:

    <extension xmlns="http://ns.adobe.com/air/extension/16.0">
        <id>com.chartboost.plugin.air</id>
        <versionNumber>5.5.0</versionNumber>
        <platforms>
            <platform name="Android-ARM">
                <applicationDeployment>
                    <nativeLibrary>libChartboostAir.jar</nativeLibrary>
                    <initializer>com.chartboost.plugin.air.ChartboostExtension</initializer>
                    <finalizer>com.chartboost.plugin.air.ChartboostExtension</finalizer>
                </applicationDeployment>
            </platform>
            
            <platform name="iPhone-ARM">
                <applicationDeployment>
                    <nativeLibrary>libChartboostAir.a</nativeLibrary>
                    <initializer>ChartboostExtInitializer</initializer>
                    <finalizer>ChartboostExtFinalizer</finalizer>
                </applicationDeployment>
            </platform>
            
            <platform name="default">
                <applicationDeployment/>
            </platform>
            
        </platforms>
    </extension>
    

    As I am not packaging the game for iPhone-ARM nor do I have the libChartboostAir.a file, I commented that platform block using XML comments <!-- -->.

    We can see that there is a default platform (I noticed that all extensions have one). So, I created a directory for the default platform and copied library.swf from the Android-ARM directory to it. Then placed both directories inside a platform directory. The directory structure now looks like this:

    AIRSDK_Windows\bin
    ├── com.adobe.ane.social
    ├── com.milkmangames.extensions.AndroidIAB
    ├── com.chartboost.plugin.air
    │   ├── com.chartboost.plugin.air.swc
    │   ├── extension.xml
    │   ├── platform
    │   │   ├── Android-ARM
    │   │   │   ├── library.swf
    │   │   │   ├── <other bunch of files>
    │   │   ├── default
    │   │   │   ├── library.swf
    ├── com.milkmangames.extensions.CoreMobile
    ├── com.jirbo.airadc.AirAdColony
    ├── com.milkmangames.extensions.GoViral
    

    Then, from the JAR file that we got at the beginning (converted from classes.dex), I got all the classes related to com.chartboost.plugin.air and placed them in a ZIP archive named libChartboostAir.jar. I then placed libChartboostAir.jar inside the Android-ARM directory.

    Finally, for this extension, I ran the following command to generate the ANE file:

    ..\adt.bat -package -target ane com.chartboost.plugin.air.ane extension.xml -swc com.chartboost.plugin.air.swc -platform Android-ARM -C platform/Android-ARM . -platform default -C platform/default library.swf
    

    I generated the ANE files for the other extensions following the above steps. At the end, there were some classes in the JAR file (generated from classes.dex) that I hadn't placed in the JAR file from any extension, because I did not know where they belonged. So, I just placed them in a random extension's JAR file. This wasn't a problem because what matters is that all classes end up being placed in the new classes.dex file, using the same directory structure as the original.

    Once all ANE files were compiled, I created a directory in the bin directory for the app that I want to repackage.

    Inside the original APK's assets directory, there were the app's SWF file and a _Pic_Gen directory with the app's icons. I placed them both in the directory I just created. And in the original APK's /assets/META-INF/AIR/ directory there were an application.xml file and an extensions directory (with the unpacked ANEs). I placed the application.xml file inside the directory I created for the app in the bin directory. Inside this directory, I also created an extensions directory and placed the compiled ANE files inside. So, the directory structure for this app now looks like this:

    AIRSDK_Windows\bin
    ├── MyApp
    │   ├── application.xml
    │   ├── _Pic_Gen
    │   ├── MyApp.swf
    │   ├── extensions
    │   │   ├── com.adobe.ane.social.ane
    │   │   ├── com.milkmangames.extensions.AndroidIAB.ane
    │   │   ├── com.chartboost.plugin.air.ane
    │   │   ├── com.milkmangames.extensions.CoreMobile.ane
    │   │   ├── com.jirbo.airadc.AirAdColony.ane
    │   │   └── com.milkmangames.extensions.GoViral.ane
    

    I changed the targetSdkVersion in the manifest inside application.xml to 30, in order to support Android 11 (currently the latest). Android version and SDK correlation: https://developer.android.com/studio/releases/platforms

    I also changed <application xmlns="http://ns.adobe.com/air/application/17.0"> to <application xmlns="http://ns.adobe.com/air/application/33.1">, so that it uses the latest Flash Player version (33.1 is from HARMAN).

    Then, I created a self-signed certificate using OpenSSL (I used Linux for this):

    openssl req -newkey rsa:2048 -x509 -keyout cakey.pem -out cacert.pem -days 10000
    # entered my password
    openssl pkcs12 -export -in cacert.pem -inkey cakey.pem -out myapp.p12
    

    And finally, I could package the new APK, using:

    ..\adt.bat -package -target apk -storetype pkcs12 -keystore myapp.p12 MyApp.apk application.xml -extdir extensions MyApp.swf _Pic_Gen
    # entered certificate's password
    

    Edit 2021-05-19 (Edit 2022-02-19: This step is no longer necessary with the latest Adobe AIR by HARMAN.)

    However, for Android 11 and above, the APK must now be signed using APK Signature Scheme v2 or higher (see this: https://stackoverflow.com/a/66041713/3049315). Using apksigner (available in Android SDK), you may do this as follows:

    C:\Users\<user>\AppData\Local\Android\Sdk\build-tools\<version>\apksigner.bat sign -v --out MyApp_v2signed.apk --ks myapp.p12 MyApp.apk
    

    The APK was then generated and I could play the game on recent Android versions.

    Edit 2021-06-25

    Apparently on some devices there would be an error on opening the APK saying "There was a problem parsing this package".

    The error that I was getting when profiling the APK with Android Studio was: INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED

    Then I came across this issue in AIR SDK. Opening AIRSDK_Windows\lib\adt.cfg showed me the commented line: #UncompressedExtensions=emd,tfl,tflite,pb I uncommented it and added the arsc extension to it, which is the extension of the file resources.arsc that Android Studio was complaining about. This fixed the issue after repackaging the APK with the new configuration.

    Edit 2022-02-11

    With the latest Adobe AIR, the following line should be added to the application.xml file under the <android> section if one does not wish to use the Android SDK for building. See: https://github.com/airsdk/Adobe-Runtime-Support/issues/1527#issuecomment-1007293682

    <BuildLegacyAPK>true</BuildLegacyAPK>
    

    Another option is to use the Android SDK for building, in which case the following lines should be added in the file AIRSDK_Windows\lib\adt.cfg (replace with correct paths):

    AndroidPlatformSDK=C:/Users/<user>/AppData/Local/Android/Sdk
    JAVA_HOME=C:/Program Files/Java/jdk-11.0.14
    

    Note that Java 11 or above must be used.

    Furthermore, Android 12 now requires receivers with intent filters to have the attribute android:exported. Example:

    <receiver android:name="com.milkmangames.extensions.android.CMBootReceiver" android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>