Documentation
On this page

Concepts

Root image and layouts

How the iOS root is made, and the three ways it sits on the phone.

The root image

mkiosroot-macos.sh IPSW OUT.img [SIZE] [DEVICE] [EXTRA] (needs sudo, so owners are kept, and GNU rsync) builds the root straight from your IPSW with macOS’s own APFS and HFS+: one case-sensitive journaled HFS+ volume, no partition map, in a sparse file (SIZE defaults to 16 GB; root_size 16g in device.conf). It holds the OS, the SystemOS and AppOS cryptexes named by the IPSW’s BuildManifest.plist, the same links and keybag as the generic script, and what root-tweaks.py changes. EXTRA is a directory laid over the root first: the files mka stages in out/.../root (our programs, the Mapple app), with mappled’s launchd job when /usr/libexec/mappled is in it. The Mapple kernel has no kexts, so nothing on the volume needs Apple’s file compression: the copy reads through macOS’s APFS driver, which decompresses.

mka rootsparse turns the image’s allocated blocks into an Android sparse image, root.img (for fastboot flash), and writes root.ranges, the list of those blocks that the installer checks the phone’s root against.

Three layouts

lunch picks one (build guide). They differ in where the root is and what the loader does at boot:

DevelopmentSingleSplit
Productmapple_spacedmapple_spaced_singlemapple_spaced_split
Root partitionxnusys, made by handuserdata: the root replaces Android’s dataxnusys, carved off the end of userdata by the installer
mp_ufsrootxnusysuserdata (single_cmdline)xnusys
Loader flags (device.conf)loader + dev_loader (-DROOT_ROOM_MIB=0)loader + single_loader (-DXNU_DEFAULT=1)loader + split_loader (-DSPLIT_MENU=1)
Androidstays; Android boots unless XNU is asked forreplaced: iOS at every boot (your boot image is kept)stays; a boot menu chooses
Installed byhandfastboot flash userdata root.img, then the recovery zipthe split zip (split-carve, then the root, then split-chain)
  • Single. With -DXNU_DEFAULT=1, mtkloader starts XNU at every boot when the root partition’s HFS+ volume checks out (root_check) and Android when its headers are wrong; if the partition cannot be read after three tries it stops. Its single_cmdline is mp_ufsroot=userdata mp_usblink=0: the USB link is off.
  • Split. mapple-install split-carve DISK [--size GIB] carves xnusys (split_size, 22 GiB on spaced) off the end of userdata, zeroing userdata’s start, metadata, and the new partition’s first MiB, saved-area header and last MiB (so a new xnusys starts without a boot-args record). Boot is not touched until split-chain, which keeps the boot image in xnusys’s saved area and chains the loader in front of it. split-uninstall puts the boot image and the partition table back. After the first write that matters, a failure puts back what was written.
  • Development. The volume lies inside the partition and only its headers are checked (ROOT_ROOM_MIB=0).

The end of the root partition

The last 128 MiB are outside the HFS+ volume (mapple-install.c, mtkroot.h). The loader’s check wants that room after the volume in the single and split layouts and none in the development layout.

From the endSizeWhat
128 MiB4 KiB header, then one slot per itemthe saved area: the boot image at +1 MiB (a slot of 48 MiB), then a record that chained one
64 MiB1 MiBtail, settings
63 MiB15 MiBtail, firmware: a ustar archive with connsys/ and touch/, taken off your own phone by the zip
48 MiB47 MiBtail, log
1 MiBnot the installer’s: the first 4 KiB block is the boot-args record; HFS+ also writes an alternate volume header 1 KiB before the end

The loader reads the touch firmware out of the firmware region when the payload carries none.

What the zip checks

mapple-install verify-root RANGES DEV checks both volume headers and the SHA-256 of every allocated block against root.ranges before anything is written. put and save read back and hash what they write; chain puts the loader in front of the Android kernel in the boot image (the Android kernel follows at offset 0x40000), the payload after it, and --backup keeps the boot image as it was. mka refuses to build a payload larger than payload_max (0x3fe0000 on spaced).

Source: tools/mkiosroot-macos.sh, core/mka.sh, installer/mapple-install.c of mapple_build at f7431bb; mtk/loader/mtkroot.h and mtkloader.c of mapple_loader at 2aa5786; installer/split.c of mapple_splitboot at 406b03a; device.conf of the device repository at 40ba6c3.

Applies to realme 8i (spaced), release 1 · Updated 15 November 2026