AndroidPatch Module Development Guide
APatch provides a modular mechanism (AndroidPatch Module) for modifying a system partition while preserving its integrity. This mechanism is often referred to as systemless
.
APatch module implementation copied and modified from KernelSU.
The modified code can be found here:
KernelSU: https://github.com/tiann/KernelSU/tree/main/userspace/ksud
APatch: https://github.com/bmax121/APatch/tree/main/apd
The following documentation was copied and modified from the KernelSU documentation, and much of the content is the same. The main points to note are as follows:
- File location
- Environment variables
- SELinux support, APatch directly uses
magiskpolicy
The mechanism of APatch modules operation is almost the same as Magisk. If you are familiar with the development of Magisk modules, the development of APatch modules is very similar. You can skip the presentation of the modules below, just read what the differences are.
BusyBox
APatch ships with a feature-complete BusyBox binary (including full SELinux support). The executable is located at /data/adb/ap/bin/busybox
. APatch's BusyBox supports runtime toggle-able "ASH Standalone Shell Mode". What this Standalone Mode means is that when running in the ash
shell of BusyBox, every single command will directly use the applet within BusyBox, regardless of what is set as PATH
. For example, commands like ls
, rm
, chmod
will NOT use what is in PATH
(in the case of Android by default it will be /system/bin/ls
, /system/bin/rm
and /system/bin/chmod
, respectively), but will instead directly call internal BusyBox applets. This makes sure that scripts always run in a predictable environment and always have the full suite of commands no matter which Android version it is running on. To force a command not to use BusyBox, you have to call the executable with full paths.
Every single shell script running in the context of APatch will be executed in BusyBox's ash
shell with Standalone Mode enabled. For what is relevant to 3rd party developers, this includes all boot scripts and module installation scripts.
For those who want to use this Standalone Mode feature outside of APatch, there are 2 ways to enable it:
- Set environment variable
ASH_STANDALONE
to1
.
Example:ASH_STANDALONE=1 /data/adb/ap/bin/busybox sh <script>
- Toggle with command-line options:
/data/adb/ap/bin/busybox sh -o standalone <script>
To make sure all subsequent sh
shell executed also runs in Standalone Mode, option 1 is the preferred method (and this is what APatch and the APatch Manager internally use) as environment variables are inherited down to child processes.
DIFFERENCES WITH KERNELSU
The location of BusyBox has been changed from /data/adb/ksu/bin/busybox
to /data/adb/ap/bin/busybox
.
DIFFERENCES WITH MAGISK
APatch's BusyBox is now using the binary file compiled directly from the Magisk project. Thanks to Magisk! Therefore, you don't need to worry about compatibility issues between BusyBox scripts in Magisk and APatch, as they're exactly the same!
APM Modules
A APatch module is a folder placed in /data/adb/modules
with the structure below:
/data/adb/modules
├── .
├── .
|
├── $MODID <--- The folder is named with the ID of the module
│ │
│ │ *** Module Identity ***
│ │
│ ├── module.prop <--- This file stores the metadata of the module
│ │
│ │ *** Main Contents ***
│ │
│ ├── system <--- This folder will be mounted if skip_mount does not exist
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ │ *** Status Flags ***
│ │
│ ├── skip_mount <--- If exists, the `/system` folder of the module will not be mounted
│ ├── disable <--- If exists, the module will be disabled
│ ├── remove <--- If exists, the module will be removed next reboot
│ │
│ │ *** Optional Files ***
│ │
│ ├── post-fs-data.sh <--- This script will be executed in post-fs-data
│ ├── post-mount.sh <--- This script will be executed in post-mount
│ ├── service.sh <--- This script will be executed in late_start service
│ ├── boot-completed.sh <--- This script will be executed on boot completed
| ├── uninstall.sh <--- This script will be executed when APatch removes your module
| ├── action.sh <--- This script will be executed when user click the Action button in APatch Manager
│ ├── system.prop <--- Properties in this file will be loaded as system properties by resetprop
│ ├── sepolicy.rule <--- Additional custom sepolicy rules
│ │
│ │ *** Auto Generated, DO NOT MANUALLY CREATE OR MODIFY ***
│ │
│ ├── vendor <--- A symlink to $MODID/system/vendor
│ ├── product <--- A symlink to $MODID/system/product
│ ├── system_ext <--- A symlink to $MODID/system/system_ext
│ │
│ │ *** Any additional files / folders are allowed ***
│ │
│ ├── ...
│ └── ...
|
├── another_module
│ ├── .
│ └── .
├── .
├── .
DIFFERENCES WITH MAGISK
APatch doesn't have built-in support for Zygisk, so there is no content related to Zygisk in the module. However, you can follow this: Zygisk Support? to support Zygisk modules. In this case, the content of the Zygisk module is identical to that supported by Magisk.
module.prop
module.prop
is a configuration file for a module. In APatch, if a module doesn't contain this file, it won't be recognized as a module. The format of this file is as follows:
id=<string>
name=<string>
version=<string>
versionCode=<int>
author=<string>
description=<string>
id
has to match this regular expression:^[a-zA-Z][a-zA-Z0-9._-]+$
Example: ✓a_module
, ✓a.module
, ✓module-101
, ✗a module
, ✗1_module
, ✗-a-module
This is the unique identifier of your module. You should not change it once published.versionCode
has to be an integer. This is used to compare versions.- Others that were not mentioned above can be any single line string.
- Make sure to use the
UNIX (LF)
line break type and not theWindows (CR+LF)
orMacintosh (CR)
.
Shell scripts
The differences between post-fs-data.sh
, post-mount.sh
, service.sh
and boot-completed.sh
are described in Boot scripts. For most module developers, service.sh
should be good enough if you just need to run a boot script, if you need to run the script after boot completed, please use boot-completed.sh
. If you want to do something after mounting OverlayFS, please use post-mount.sh
.
In all scripts of your module, please use MODDIR=${0%/*}
to get your module's base directory path; do NOT hardcode your module path in scripts.
DIFFERENCES WITH MAGISK AND KERNELSU
You can determine if the script is running in APatch by using the APATCH
environment variable. If running in APatch, this value will be set to true
.
system
directory
The contents of this directory will be overlaid on top of the system's /system
partition using OverlayFS after the system is booted. This means that:
- Files with the same name as those in the corresponding directory in the system will be overwritten by the files in this directory.
- Folders with the same name as those in the corresponding directory in the system will be merged with the folders in this directory.
If you want to delete a file or folder in the original system directory, you need to create a file with the same name as the file/folder in the module directory using mknod filename c 0 0
. This way, the OverlayFS system will automatically "whiteout" this file as if it has been deleted (the /system partition isn't actually changed).
You can also declare a variable named REMOVE
containing a list of directories in customize.sh
to execute removal operations, and APatch will automatically execute mknod <TARGET> c 0 0
in the corresponding directories of the module. For example:
REMOVE="
/system/app/YouTube
/system/app/Bloatware
"
The above list will execute mknod $MODPATH/system/app/YouTube c 0 0
and mknod $MODPATH/system/app/Bloatware c 0 0
, /system/app/YouTube
and /system/app/Bloatware
will be removed after the module takes effect.
If you want to replace a directory in the system, you need to create a directory with the same path in your module directory, and then set the attribute setfattr -n trusted.overlay.opaque -v y <TARGET>
for this directory. This way, the OverlayFS system will automatically replace the corresponding directory in the system (without changing the /system partition).
You can declare a variable named REPLACE
in your customize.sh
file, which includes a list of directories to be replaced, and APatch will automatically perform the corresponding operations in your module directory. For example:
REPLACE="
/system/app/YouTube
/system/app/Bloatware
"
This list will automatically create the directories $MODPATH/system/app/YouTube
and $MODPATH/system/app/Bloatware
, and then execute setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/YouTube
and setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/Bloatware
. After the module takes effect, /system/app/YouTube
and /system/app/Bloatware
will be replaced with empty directories.
DIFFERENCES WITH MAGISK
APatch's systemless mechanism is implemented through the kernel's OverlayFS, while Magisk currently uses magic mount (bind mount). These two implementation methods have significant differences, but the ultimate goal is the same: modifying /system
files without physically modifying the /system
partition.
If you're interested in OverlayFS, it's recommended to read the Linux Kernel's documentation on OverlayFS.
system.prop
This file follows the same format as build.prop
. Each line comprises of [key]=[value]
.
sepolicy.rule
If your module requires some additional sepolicy patches, please add those rules into this file. Each line in this file will be treated as a policy statement.
Module installer
The APatch module installation package is a ZIP file that can be flashed through the APatch Manager, and the format of this ZIP file is as follows:
module.zip
│
├── customize.sh <--- (Optional, more details later)
│ This script will be sourced by update-binary
├── ...
├── ... /* The rest of module's files */
│
WARNING
APatch module is NOT compatible for installation in a custom Recovery!
Customization
If you need to customize the module installation process, optionally you can create a script in the installer named customize.sh
. This script will be sourced (not executed) by the module installer script after all files are extracted and default permissions and secontext are applied. This is very useful if your module requires additional setup based on the device ABI, or you need to set special permissions/secontext for some of your module files.
If you would like to fully control and customize the installation process, declare SKIPUNZIP=1
in customize.sh
to skip all default installation steps. By doing so, your customize.sh
will be responsible to install everything by itself.
The customize.sh
script runs in APatch's BusyBox ash
shell with Standalone Mode enabled. The following variables and functions are available:
Variables
KERNELPATCH
(bool): Mark this script to run in the APatch environment, and the value of this variable will always betrue
.KERNEL_VERSION
(hex): Inherited from KernelPatch, the kernel version number (e.g.50a01
means 5.10.1).KERNELPATCH_VERSION
(hex): Inherited from KernelPatch, the version number of KernelPatch (e.g.a05
means 0.10.5).SUPERKEY
(string): Inherited from KernelPatch, used to call kpatch or supercall.APATCH
(bool): Mark this script to run in the APatch environment, and the value of this variable will always betrue
.APATCH_VER_CODE
(int): Current APatch version number (e.g.10672
).APATCH_VER
(string): The name of the current version of APatch (e.g.10672
).BOOTMODE
(bool): In APatch, this variable will always have the valuetrue
.MODPATH
(path): Installation directory of the current module.TMPDIR
(path): A directory where temporary files can be stored.ZIPFILE
(path): Installation package file for the current module.ARCH
(string): Device processor architecture,arm64
only.IS64BIT
(bool): Is this device 64-bit.API
(int): Current Android API version of the device (e.g.23
on Android 6.0).
WARNING
In APatch, MAGISK_VER_CODE
is always 27000
, and MAGISK_VER
is always v27.0
.
Functions
ui_print <msg>
print <msg> to console
Avoid using 'echo' as it will not display in custom recovery's console
abort <msg>
print error message <msg> to console and terminate the installation
Avoid using 'exit' as it will skip the termination cleanup steps
set_perm <target> <owner> <group> <permission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
this function is a shorthand for the following commands:
chown owner.group target
chmod permission target
chcon context target
set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
for all files in <directory>, it will call:
set_perm file owner group filepermission context
for all directories in <directory> (including itself), it will call:
set_perm dir owner group dirpermission context
Boot scripts
In APatch, scripts are divided into two types based on their running mode: post-fs-data mode and late_start service mode.
post-fs-data mode
- This stage is BLOCKING. The boot process is paused before execution is done or after 10 seconds.
- Scripts run before any modules are mounted. This allows a module developer to dynamically adjust their modules before it gets mounted.
- This stage happens before Zygote is started, which pretty much means everything in Android.
- WARNING: Using
setprop
will deadlock the boot process! Please useresetprop -n <prop_name> <prop_value>
instead. - Only run scripts in this mode if necessary.
late_start service mode
- This stage is NON-BLOCKING. Your script runs in parallel with the rest of the booting process.
- This is the recommended stage to run most scripts.
In APatch, startup scripts are divided into two types based on their storage location: General scripts and Module scripts.
General scripts
- Placed in
/data/adb/post-fs-data.d
,/data/adb/service.d
,/data/adb/post-mount.d
or/data/adb/boot-completed.d
. - Only executed if the script is set as executable (
chmod +x script.sh
). - Scripts in
post-fs-data.d
runs in post-fs-data mode, and scripts inservice.d
runs in late_start service mode. - Modules should NOT add general scripts during installation.
- Placed in
Module scripts
- Placed in the module's own folder.
- Only executed if the module is enabled.
post-fs-data.sh
runs in post-fs-data mode,post-mount.sh
runs in post-mount mode, andservice.sh
runs in late_start service mode, andboot-completed
runs in service mode after the Android boot is complete.
All boot scripts will run in APatch's BusyBox ash
shell with Standalone Mode enabled.