diff --git a/README.md b/README.md
index ff36f9b..f04d34a 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Supported images:
  - recovery.img (also recovery-two-step.img)
  - vbmeta.img (also vbmeta\_system.img, vbmeta\_vendor.img etc.)
  - dtbo.img (only 'unpack' is supported)
+ - sparse images (system.img, vendor.img ...)
 
 (2) These utilities are known to work for Nexus/Pixel boot.img for the following Android releases:
 
@@ -85,7 +86,7 @@ We now support both VB 1.0 and AVB 2.0 layouts.
 | Pixel 3 (blueline)             | Google       | Y                    | Q preview (qpp2.190228.023, <Br>2019)| [more ...](doc/additional_tricks.md#pixel-3-blueline) |
 | Pixel XL (marlin)              | HTC          | Y                    | 9.0.0 (PPR2.180905.006, <Br>Sep 2018)| [more ...](doc/additional_tricks.md#pixel-xl-marlin) |
 | K3 (CPH1955)                   | OPPO         | Y for recovery.img<Br> N for boot.img  | Pie    | [more](doc/additional_tricks.md#k3-cph1955) |
-| Z18(NX606J)                    | ZTE          | Y                    | 8.1.0                    | [more...](doc/additional_tricks.md#nx606j) |
+| Z18 (NX606J)                    | ZTE          | Y                    | 8.1.0                    | [more...](doc/additional_tricks.md#nx606j) |
 | Nexus 9 (volantis/flounder)    | HTC          | Y(with some tricks)  | 7.1.1 (N9F27M, Oct 2017) | [tricks](doc/additional_tricks.md#tricks-for-nexus-9volantis)|
 | Nexus 5x (bullhead)            | LG           | Y                    | 6.0.0_r12 (MDA89E)       |      |
 | Moto X (2013) T-Mobile         | Motorola     | N                    |                          |      |
@@ -116,3 +117,6 @@ https://android.googlesource.com/platform/system/libufdt/
 
 libsparse
 https://android.googlesource.com/platform/system/core/+/refs/heads/master/libsparse/
+
+Android Nexus/Pixle factory images
+https://developers.google.cn/android/images
diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt
index 5f7fd53..e590713 100644
--- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt
+++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt
@@ -1,12 +1,14 @@
 package cfig.packable
 
 import avb.AVBInfo
+import avb.blob.Footer
 import cfig.*
 import cfig.bootimg.BootImgInfo
 import com.fasterxml.jackson.databind.ObjectMapper
 import de.vandermeer.asciitable.AsciiTable
 import org.slf4j.LoggerFactory
 import java.io.File
+import java.io.FileInputStream
 import java.lang.IllegalArgumentException
 
 @ExperimentalUnsignedTypes
@@ -20,18 +22,17 @@ class BootImgParser() : IPackable {
     }
 
     private fun unpackVBMeta(): Boolean {
-        if (File("vbmeta.img").exists()) {
+        return if (File("vbmeta.img").exists()) {
             log.warn("Found vbmeta.img, parsing ...")
             VBMetaParser().unpack("vbmeta.img")
-            return true
+            true
         } else {
-            return false
+            false
         }
     }
 
     override fun unpack(fileName: String) {
-        if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively()
-        File(UnifiedConfig.workDir).mkdirs()
+        cleanUp()
         try {
             val info = Parser().parseBootImgHeader(fileName, avbtool = "aosp/avb/avbtool")
             InfoTable.instance.addRule()
@@ -98,4 +99,17 @@ class BootImgParser() : IPackable {
         val stem = fileName.substring(0, fileName.indexOf("."))
         super.flash("$fileName.signed", stem)
     }
+
+    // invoked solely by reflection
+    fun `@footer`(image_file: String) {
+        FileInputStream(image_file).use { fis ->
+            fis.skip(File(image_file).length() - Footer.SIZE)
+            try {
+                val footer = Footer(fis)
+                log.info("\n" + ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(footer))
+            } catch (e: IllegalArgumentException) {
+                log.info("image $image_file has no AVB Footer")
+            }
+        }
+    }
 }
diff --git a/bbootimg/src/main/kotlin/packable/DtboParser.kt b/bbootimg/src/main/kotlin/packable/DtboParser.kt
index c078aab..ed20113 100644
--- a/bbootimg/src/main/kotlin/packable/DtboParser.kt
+++ b/bbootimg/src/main/kotlin/packable/DtboParser.kt
@@ -25,6 +25,7 @@ class DtboParser(val workDir: File) : IPackable {
     }
 
     override fun unpack(fileName: String) {
+        cleanUp()
         val outputDir = UnifiedConfig.workDir
         val dtbPath = File("$outputDir/dtb").path!!
         val headerPath = File("$outputDir/dtbo.header").path!!
diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt
index 2bfde8e..49986ca 100644
--- a/bbootimg/src/main/kotlin/packable/IPackable.kt
+++ b/bbootimg/src/main/kotlin/packable/IPackable.kt
@@ -2,8 +2,10 @@ package cfig.packable
 
 import cfig.Helper.Companion.check_call
 import cfig.Helper.Companion.check_output
+import cfig.UnifiedConfig
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import java.io.File
 
 @ExperimentalUnsignedTypes
 interface IPackable {
@@ -29,6 +31,11 @@ interface IPackable {
         "adb shell rm /cache/file.to.burn".check_call()
     }
 
+    fun cleanUp() {
+        if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively()
+        File(UnifiedConfig.workDir).mkdirs()
+    }
+
     companion object {
         val log: Logger = LoggerFactory.getLogger(IPackable::class.java)
     }
diff --git a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt
index 0bbf7c8..5889cdc 100644
--- a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt
+++ b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt
@@ -1,12 +1,13 @@
 package cfig.packable
 
-import cfig.UnifiedConfig
-import cfig.sparse_util.SparseImg
+import cfig.sparse_util.SparseImgParser
 import org.slf4j.LoggerFactory
 import java.io.File
 import java.util.regex.Pattern
 import kotlin.reflect.KClass
 import kotlin.reflect.full.createInstance
+import kotlin.reflect.full.declaredFunctions
+import kotlin.system.exitProcess
 
 class PackableLauncher
 
@@ -14,7 +15,7 @@ class PackableLauncher
 fun main(args: Array<String>) {
     val log = LoggerFactory.getLogger(PackableLauncher::class.java)
     val packablePool = mutableMapOf<List<String>, KClass<IPackable>>()
-    listOf(DtboParser(), VBMetaParser(), BootImgParser(), SparseImg()).forEach {
+    listOf(DtboParser(), VBMetaParser(), BootImgParser(), SparseImgParser()).forEach {
         @Suppress("UNCHECKED_CAST")
         packablePool.put(it.capabilities(), it::class as KClass<IPackable>)
     }
@@ -24,56 +25,83 @@ fun main(args: Array<String>) {
     var targetFile: String? = null
     var targetHandler: KClass<IPackable>? = null
     run found@{
-        File(".").listFiles().forEach { file ->
-            packablePool
-                    .filter { it.value.createInstance().loopNo == 0 }
-                    .forEach { p ->
-                        for (item in p.key) {
-                            if (Pattern.compile(item).matcher(file.name).matches()) {
-                                log.debug("Found: " + file.name + ", " + item)
-                                targetFile = file.name
-                                targetHandler = p.value
-                                return@found
+        for (currentLoopNo in 0..1) { //currently we have only 2 loops
+            File(".").listFiles()!!.forEach { file ->
+                packablePool
+                        .filter { it.value.createInstance().loopNo == currentLoopNo }
+                        .forEach { p ->
+                            for (item in p.key) {
+                                if (Pattern.compile(item).matcher(file.name).matches()) {
+                                    log.debug("Found: " + file.name + ", " + item)
+                                    targetFile = file.name
+                                    targetHandler = p.value
+                                    return@found
+                                }
                             }
                         }
-                    }
-        }
+            }//end-of-file-traversing
+        }//end-of-range-loop
+    }//end-of-found@
 
-        File(".").listFiles().forEach { file ->
-            packablePool
-                    .filter { it.value.createInstance().loopNo != 0 }
-                    .forEach { p ->
-                        for (item in p.key) {
-                            if (Pattern.compile(item).matcher(file.name).matches()) {
-                                log.debug("Found: " + file.name + ", " + item)
-                                targetFile = file.name
-                                targetHandler = p.value
-                                return@found
-                            }
-                        }
-                    }
+    // /* 1 */ no-args & no-handler: help for IPackable
+    // /* 2 */ no-args & handler   : help for Handler
+    // /* 3 */ args    & no-handler: do nothing
+    // /* 4 */ args    & handler   : work
+    when (listOf(args.isEmpty(), targetHandler == null)) {
+        listOf(true, true) -> { /* 1 */
+            log.info("help:")
+            log.info("available IPackable subcommands are:")
+            IPackable::class.declaredFunctions.forEach {
+                log.info("\t" + it.name)
+            }
+            exitProcess(1)
+        }
+        listOf(true, false) -> {/* 2 */
+            log.info("available ${targetHandler!!.simpleName} subcommands are:")
+            targetHandler!!.declaredFunctions.forEach {
+                log.info("\t" + it.name)
+            }
+            exitProcess(1)
+        }
+        listOf(false, true) -> {/* 3 */
+            log.warn("No handler is activated, DO NOTHING!")
+            exitProcess(2)
+        }
+        listOf(false, false) -> {/* 4 */
+            log.debug("continue ...")
         }
     }
 
-    if (targetHandler != null) {
-        log.warn("Active image target: $targetFile")
-        when (args[0]) {
-            "unpack" -> {
-                if (File(UnifiedConfig.workDir).exists()) File(UnifiedConfig.workDir).deleteRecursively()
-                File(UnifiedConfig.workDir).mkdirs()
-                targetHandler!!.createInstance().unpack(targetFile!!)
+    targetHandler?.let {
+        log.warn("[$targetFile] will be handled by [${it.simpleName}]")
+        val functions = it.declaredFunctions.filter { funcItem -> funcItem.name == args[0] }
+        if (functions.size != 1) {
+            log.error("command '${args[0]}' can not be recognized")
+            log.info("available ${it.simpleName} subcommands are:")
+            it.declaredFunctions.forEach {
+                log.info("\t" + it.name)
             }
-            "pack" -> {
-                targetHandler!!.createInstance().pack(targetFile!!)
+            exitProcess(3)
+        }
+        log.warn("'${args[0]}' sequence initialized")
+        val reflectRet = when (functions[0].parameters.size) {
+            1 -> {
+                functions[0].call(it.createInstance())
             }
-            "flash" -> {
-                targetHandler!!.createInstance().flash(targetFile!!)
+            2 -> {
+                functions[0].call(it.createInstance(), targetFile!!)
             }
             else -> {
-                log.error("Unknown cmd: " + args[0])
+                functions[0].parameters.forEach { kp ->
+                    println("Param: " + kp.index + " " + kp.type + " " + kp.name)
+                }
+                log.error("I am confused by so many parameters")
+                exitProcess(4)
             }
         }
-    } else {
-        log.warn("Nothing to do")
+        if (functions[0].returnType.toString() != Unit.toString()) {
+            log.info("ret: $reflectRet")
+        }
+        log.warn("'${args[0]}' sequence completed")
     }
 }
diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
index 8f366ea..cd30aed 100644
--- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
+++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
@@ -1,6 +1,8 @@
 package cfig.packable
 
 import cfig.Avb
+import cfig.UnifiedConfig
+import java.io.File
 
 @ExperimentalUnsignedTypes
 class VBMetaParser: IPackable {
@@ -11,7 +13,12 @@ class VBMetaParser: IPackable {
         return listOf("^vbmeta\\.img$", "^vbmeta\\_[a-z]+.img$")
     }
 
+    override fun cleanUp() {
+        File(UnifiedConfig.workDir).mkdirs()
+    }
+
     override fun unpack(fileName: String) {
+        cleanUp()
         Avb().parseVbMeta(fileName)
     }
 
diff --git a/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt b/bbootimg/src/main/kotlin/sparse_util/SparseImgParser.kt
similarity index 93%
rename from bbootimg/src/main/kotlin/sparse_util/SparseImg.kt
rename to bbootimg/src/main/kotlin/sparse_util/SparseImgParser.kt
index fa8067a..15b5b95 100644
--- a/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt
+++ b/bbootimg/src/main/kotlin/sparse_util/SparseImgParser.kt
@@ -6,10 +6,10 @@ import org.slf4j.LoggerFactory
 import cfig.Helper.Companion.check_call
 
 @ExperimentalUnsignedTypes
-class SparseImg : IPackable {
+class SparseImgParser : IPackable {
     override val loopNo: Int
         get() = 0
-    private val log = LoggerFactory.getLogger(SparseImg::class.java)
+    private val log = LoggerFactory.getLogger(SparseImgParser::class.java)
     private val simg2imgBin: String
     private val img2simgBin: String
 
@@ -24,6 +24,7 @@ class SparseImg : IPackable {
     }
 
     override fun unpack(fileName: String) {
+        cleanUp()
         simg2img(fileName, "$fileName.unsparse")
     }
 
diff --git a/integrationTest.py b/integrationTest.py
index 81776ce..be034bf 100755
--- a/integrationTest.py
+++ b/integrationTest.py
@@ -34,10 +34,12 @@ def cleanUp():
     deleteIfExists("boot.img.clear")
     deleteIfExists("boot.img.google")
     deleteIfExists("boot.img.signed")
+    deleteIfExists("boot.img.signed2")
     deleteIfExists("recovery.img")
     deleteIfExists("recovery.img.clear")
     deleteIfExists("recovery.img.google")
     deleteIfExists("recovery.img.signed")
+    deleteIfExists("recovery.img.signed2")
     deleteIfExists("vbmeta.img")
     deleteIfExists("vbmeta.img.signed")