AndroidPatch Module (APM)
About 2142 wordsAbout 7 min
2025-12-23
FolkPatch provides a module mechanism (AndroidPatch Module), which can modify system partitions while maintaining their integrity; this mechanism is commonly referred to as systemless.
FolkPatch's module implementation is copied and modified from Apatch modules, thanks to Apatch.
The following documentation is largely derived from KernelSU documentation, with most content consistent with KernelSU. The main differences to note are:
- File locations
- Environment variables
- SELinux support, FolkPatch directly uses
magiskpolicy
FolkPatch's module operation mechanism is almost identical to Magisk. If you are familiar with Magisk module development, developing FolkPatch modules is very similar. You can skip the module introduction below and just understand the differences between APatch modules and Magisk modules (Writing in progress).
BusyBox
FolkPatch provides a fully featured BusyBox binary (including full SELinux support). The executable is located at /data/adb/ap/bin/busybox. FolkPatch's BusyBox supports runtime toggleable "ASH Standalone Shell Mode". This standalone mode means that when running BusyBox's ash shell, every command will directly use the applet built into BusyBox, regardless of what PATH is set to. For example, commands like ls, rm, chmod will not use the commands set in PATH (in Android's case, usually /system/bin/ls, /system/bin/rm, and /system/bin/chmod by default), but instead call BusyBox's built-in applets directly. This ensures that scripts always run in a predictable environment and always have a full suite of commands, regardless of which Android version it is running on. To force a command not to use BusyBox, you must call the executable with its full path.
Every shell script running in the FolkPatch context will run in BusyBox's ash shell in standalone mode. This includes all boot scripts and module installation scripts relevant to third-party developers.
For users who want to use this "standalone mode" feature outside of APatch, there are two ways to enable it:
- Set environment variable
ASH_STANDALONEto1.
Example:ASH_STANDALONE=1 /data/adb/ap/bin/busybox sh <script>. - Use command line option toggle:
/data/adb/ap/bin/busybox sh -o standalone <script>.
To ensure all subsequent sh shells execute in standalone mode, the first method is preferred (this is also the method used internally by APatch and APatch Manager), as environment variables are inherited by child processes.
Difference from Magisk
FolkPatch's BusyBox is now a binary compiled directly using the Magisk project, thanks to Magisk! Therefore, you don't have to worry about BusyBox script compatibility between Magisk and APatch, because they are exactly the same!
FolkPatch Module
A FolkPatch module is a folder placed inside /data/adb/modules that satisfies the following structure:
/data/adb/modules
├── .
├── .
|
├── $MODID <--- Module folder name is the same as module ID
│ │
│ │ *** Module Config File ***
│ │
│ ├── module.prop <--- This file saves some module configs like module ID, version, etc.
│ │
│ │ *** Module Content ***
│ │
│ ├── system <--- This folder will usually be mounted to system
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ │ *** Marker Files ***
│ │
│ ├── skip_mount <--- If this file exists, module's `/system` will not be mounted
│ ├── disable <--- If this file exists, module will be disabled
│ ├── remove <--- If this file exists, module will be removed on next reboot
│ │
│ │ *** Optional Files ***
│ │
│ ├── post-fs-data.sh <--- This script will run in post-fs-data mode
│ ├── post-mount.sh <--- This script will run in post-mount mode
│ ├── service.sh <--- This script will run in late_start service mode
│ ├── boot-completed.sh <--- This script will run in service mode after Android system boot completed
| ├── uninstall.sh <--- This script will run when module is uninstalled
| ├── action.sh <--- This script will run when clicking Action in manager module
│ ├── system.prop <--- Properties specified in this file will be changed via resetprop at boot
│ ├── sepolicy.rule <--- SELinux policies in this file will be loaded at boot
│ │
│ │ *** Auto-generated Directories, DO NOT Create or Modify Manually! ***
│ │
│ ├── 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
│ ├── .
│ └── .
├── .
├── .Difference from Magisk
FolkPatch does not have built-in Zygisk support, so there is no Zygisk related content in modules.
module.prop
module.prop is a configuration file for the module. In FolkPatch, if a module does not contain this file, it will not 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 must match this regex:
^[a-zA-Z][a-zA-Z0-9._-]+$. Examples: ✓a_module, ✓a.module, ✓module-101, ✗a module, ✗1_module, ✗-a-module. This is your module's unique identifier and should not be changed after release. - versionCode must be an integer, used for version comparison.
- Other content not mentioned above can be any single-line string.
- Please ensure to use
UNIX (LF)line endings, notWindows (CR + LF)orMacintosh (CR).
Shell Scripts
Please read the Boot Scripts section to understand the differences between post-fs-data.sh, post-mount.sh, service.sh, and boot-completed.sh. For most module developers, if you only need to run a startup script, service.sh should be sufficient.
In all your module scripts, please use MODDIR=${0%/*} to get your module's base directory path; do not hardcode your module path in scripts.
Difference from Magisk, KernelSU
You can use the environment variable APATCH to determine if the script is running in FolkPatch. If running in FolkPatch, this value will be set to true.
FolkPatch does not have any core functional changes, for modules, it is the same as Apatch.
system Directory
The contents of this directory will be overlaid on top of the system's /system partition using OverlayFS after system boot, which means:
- Files with the same name in the corresponding directory in the system will be overwritten by files in this directory.
- Folders with the same name in the corresponding directory in the system will be merged with 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 filename in the module directory using mknod filename c 0 0; this way the OverlayFS system will automatically whiteout effectively deleting this file (/system partition is not changed).
You can also declare a variable named REMOVE containing a list of directories in customize.sh to perform deletion operations. APatch will automatically execute mknod <TARGET> c 0 0 for you in the corresponding module directory. For example:
REMOVE="
/system/app/YouTube
/system/app/Bloatware
"The list above will execute: mknod $MODPATH/system/app/YouTube c 0 0 and mknod $MODPATH/system/app/Bloatware c 0 0; and /system/app/YouTube and /system/app/Bloatware will be deleted after the module takes effect.
If you want to replace a system directory, you need to create a directory with the same path in the module directory, and then set this attribute for the directory: setfattr -n trusted.overlay.opaque -v y <TARGET>; this way the OverlayFS system will automatically replace the corresponding directory in the system (/system partition is not changed).
You can declare a variable named REPLACE containing a list of directories in customize.sh to perform replacement operations. APatch will automatically execute related operations for you in the module directory. For example:
REPLACE="
/system/app/YouTube
/system/app/Bloatware
"The list above will: automatically create directories $MODPATH/system/app/YouTube and $MODPATH/system/app/Bloatware, 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; and /system/app/YouTube and /system/app/Bloatware will be replaced with empty directories after the module takes effect.
Difference from Magisk
FolkPatch's systemless mechanism is implemented via kernel OverlayFS, while Magisk currently uses magic mount (bind mount). The implementation methods are vastly different, but the ultimate goal is consistent: to modify /system files without modifying the physical /system partition.
Apatch has defaulted to switch to magic mount (bind mount) implementation for mounting, and FolkPatch inherits this.
If you are interested in OverlayFS, it is recommended to read the Linux Kernel OverlayFS documentation.
system.prop
The format of this file is exactly the same as build.prop: each line is in the form of [key]=[value].
sepolicy.rule
If your module needs some additional SELinux policy patches, please add these rules to this file. Each line in this file will be treated as a policy statement.
Module Installer
A FolkPatch module installation package is a zip file that can be flashed via the APatch Manager APP. 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
├── ...
├── ... /* Other module files */
│注意
FolkPatch modules do not support installation in Recovery!
Customizing Installation
If you want to control the module installation process, you can create a file named customize.sh in the module directory. This script will be sourced into the current shell after the module is extracted. This script is very useful if your module needs to perform additional operations based on device API version or architecture.
If you want to fully control the script installation process, you can declare SKIPUNZIP=1 in customize.sh to skip all default installation steps; in this case, you need to handle all installation processes yourself (such as extracting modules, setting permissions, etc.).
The customize.sh script runs in APatch's BusyBox ash shell in "standalone mode". You can use the following variables and functions:
Variables
KERNELPATCH(bool): Marks that this script is running in APatch environment, this variable's value will always betrueKERNEL_VERSION(hex): Inherited from KernelPatch, kernel version number (e.g.,50a01means5.10.1)KERNELPATCH_VERSION(hex): Inherited from KernelPatch, KernelPatch version number (e.g.,a05means0.10.5)SUPERKEY(string): Inherited from KernelPatch, used to call kpatch or supercallAPATCH(bool): Marks that this script is running in APatch environment, this variable's value will always betrueAPATCH_VER_CODE(int): APatch current version code (e.g.,10672)APATCH_VER(string): APatch current version name (e.g.,10672)BOOTMODE(bool): This variable is alwaystruein APatchMODPATH(path): Current module installation directoryTMPDIR(path): Directory for storing temporary filesZIPFILE(path): Current module installation package fileARCH(string): Device CPU architecture, onlyarm64IS64BIT(bool): Whether it is a 64-bit deviceAPI(int): Current device Android API version (e.g.,23on Android 6.0)
注意
MAGISK_VER_CODE in APatch is 27000, MAGISK_VER is 27.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 contextBoot Scripts
In FolkPatch, depending on the script running mode, there are two types: post-fs-data mode and late_start service mode.
post-fs-data mode
- This stage is blocking. The boot process is paused until execution is complete or 10 seconds have passed.
- Scripts run before any modules are mounted. This allows module developers to dynamically adjust their modules before they are mounted.
- This stage happens before Zygote starts.
- Using
setpropwill cause boot process deadlock! Please useresetprop -n <prop_name> <prop_value>instead. - Run scripts in this mode only when necessary.
late_start service mode
- This stage is non-blocking. Your script runs in parallel with the rest of the boot process.
- Most scripts are recommended to run in this mode.
In FolkPatch, boot scripts are also divided into two types based on storage location: general scripts and module scripts.
General Scripts
- Placed in
/data/adb/post-fs-data.d,/data/adb/post-mount.d,/data/adb/service.dor/data/adb/boot-completed.d. - Only executed when the script is set as executable (
chmod +x script.sh). - Scripts in
post-fs-data.drun in post-fs-data mode, scripts inservice.drun 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 when the module is enabled.
post-fs-data.shruns in post-fs-data mode,post-mount.shruns in post-mount mode,service.shruns in late_start service mode, andboot-completedruns in service mode after Android system boot completed.
All boot scripts will run in FolkPatch's BusyBox ash shell with "standalone mode" enabled.
Copyright
Copyright Ownership:Apatch Document
This article is reprint from:https://apatch.dev/zh_CN/apm-guide.html
License under:Attribution-ShareAlike 4.0 International
