Hex patcher

Hex patcher is a developer option added in App Cloner 2.4. It is available to users with the huge or giant donation since the option could also be used to change the app’s package name and generate any number of clones.

The option allows hex-based search & replace operations on arbitrary files contained inside the APK, whether DEX files, resource files or any other files.

The option shows a text editor where you can enter (or paste) one or more hex patches. Each hex patch must be separated by an empty line and itself consists of 3 or 4 lines:

  1. An optional comment line starting with # or //. This may describe what the patch does.
  2. The path and name of the file to which the patch should be applied. Wildcards are allowed, e.g. you can use *.dex to apply the patch to all DEX files, e.g. classes.dex, classes2.dex, etc.
  3. The search bytes as hex values. Each by must be separated by a single space.
  4. The replace bytes as hex values. Each by must be separated by a single space. The length of the search and replace bytes must be the same.

Tap the plus icon in the toolbar to open an editor to add a hex patch in the correct format. Tap the edit icon to edit the hex patch where the cursor is currently located. You can use the menu to delete the hex patch at the current cursor location or to reset / clear all hex patches, effectively disabling the option.

The search and replace bytes may contain ?? or ** to represent wildcard bytes. In the search bytes they will match any byte. In the replace bytes they will be skipped (no replacement will be made). You may also use ? or * in the upper or lower part of a byte to match nibbles or half-bytes, for example 0? would match a byte from 00 to 0F.

You may also use the patterns $0, $1, etc. until $F, which in the search bytes will match any byte (exactly like ??) but also remembering the matched byte’s index 0, 1, etc. In the replace bytes you can use $0, $1, etc. to reference and include the matched byte. This allows moving bytes to different positions within the pattern without changing them.

In the replace bytes you may also use S0, S1, etc. until SF. This will set the upper nibble (half-byte) to the value 0, 1, etc. For example, with S0 a byte 43 would become 03, with S1 a byte 04 would become 14.

Lastly, in the replace bytes you can use increments by writing +1, +2, etc. or decrements by writing -1, -2, etc.

All hex patches are processed in the order they appear. This means a hex patch may modify the result of a previous hex patch.

Use the Hex patcher option with great care. You’ll need to know what you’re doing, otherwise the clone could simply crash.

Send broadcast on start

Send broadcast on start is a developer option, which sends an arbitrary broadcast when the cloned app is started. The broadcast is sent as soon as the app’s process is started, before any app activities may (or may not) be shown. This means the broadcast will also get sent when the cloned app starts any background services. The broadcast’s intent is empty and does not have any action nor intent extras set.

You need to specify the the broadcast receiver’s fully qualified component name in the form of <packageName>/<className>. Do not use the abbreviated form of <className>. When declaring the broadcast receiver in the manifest, make sure to mark it exported.

You may also use this option along with the Shared user ID and Process name options to trigger loading an external extension app into the same process as the cloned main app.

Shared user ID & Process name

App Cloner 1.5.22 adds two new developer options called Shared user ID and Process name.

The Shared user ID option allows generating clones with a specific shared user ID, an arbitrary string value, which must contain at least one ‘.’ separator. Apps with the same shared user ID can access each other’s data and, if desired, run in the same process, provided the apps are also signed with the same certificate.

The Process name option forces all app components to run in the named process. The process name must start with a lower case character and must also contain at least one ‘.’ separator.

This allows creating your own add-ons in separate apps, extending the clone’s functionality.

By signing the clone using a custom certificate (using the Custom certificate option) and creating your own app using the same shared user ID, process name and certificate, the clone and your app will run in the same process and virtual machine (VM).

Each app is executed using its own class loader, which loads classes from the corresponding APK .dex files, so a simple Class.forName("...") won’t work. To access the cloned app’s classes via reflection you must enumerate all threads, find the thread called ContextClassLoaderThread, which is provided by App Cloner, and use its context class loader to load the app’s classes.

Here’s an example in Kotlin:

val threads = arrayOfNulls<Thread>(Thread.activeCount())
Thread.enumerate(threads)
for (thread in threads) {
    if ("ContextClassLoaderThread" == thread!!.getName()) {
        val contextClassLoader = thread.getContextClassLoader()
        val clazz = contextClassLoader.loadClass("...")
        // TODO: Do something with the class
        break
    }
}

If the ContextClassLoaderThread thread cannot be found it means the clone hasn’t yet been launched into the process.

In order to get the cloned app’s Application instance you can use the Utils class provided by App Cloner:

val utilsClass = contextClassLoader.loadClass("com.applisto.appcloner.classes.Utils")
val getApplicationMethod = utilsClass.getMethod("getApplication")
val application: Application = getApplicationMethod.invoke(null) as Application

Once you have the Application instance you can register ActivityLifecycleCallbacks to get notified when the cloned app appears on the screen:

application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity?) {}
override fun onActivityDestroyed(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}
override fun onActivityStopped(activity: Activity?) {}
})

The Shared user ID option is available with the medium donation, the Process name option is available with the large donation.

App valid from & App valid until

App valid from and App valid until are two developer options that allow restricting the use of a clone to a certain time range, the latter one probably being more useful.

It adds the Internet permission to the app so that the clone can fetch the current date & time from an NTP time server on the internet, which means it’s not dependent on the system clock being correct.