diff --git a/bbootimg/src/main/kotlin/Helper.kt b/bbootimg/src/main/kotlin/Helper.kt
index ffe5844..60c190e 100644
--- a/bbootimg/src/main/kotlin/Helper.kt
+++ b/bbootimg/src/main/kotlin/Helper.kt
@@ -23,7 +23,6 @@ import java.util.zip.GZIPOutputStream
 import javax.crypto.Cipher
 
 class Helper {
-    @ExperimentalStdlibApi
     @ExperimentalUnsignedTypes
     companion object {
         fun joinWithNulls(vararg source: ByteArray?): ByteArray {
@@ -311,14 +310,28 @@ class Helper {
                 ret = true
             } catch (e: java.lang.IllegalArgumentException) {
                 log.error("$e: can not parse command: [$this]")
+                throw e
             } catch (e: ExecuteException) {
                 log.error("$e: can not exec command")
+                throw e
             } catch (e: IOException) {
                 log.error("$e: can not exec command")
+                throw e
             }
             return ret
         }
 
+        fun String.check_output(): String {
+            val outputStream = ByteArrayOutputStream()
+            log.info(this)
+            DefaultExecutor().let {
+                it.streamHandler = PumpStreamHandler(outputStream)
+                it.execute(CommandLine.parse(this))
+            }
+            log.info(outputStream.toString())
+            return outputStream.toString().trim()
+        }
+
         private val log = LoggerFactory.getLogger("Helper")
     }
 }
diff --git a/bbootimg/src/main/kotlin/cfig/io/Struct3.kt b/bbootimg/src/main/kotlin/cfig/io/Struct3.kt
index 509ef00..71dd6da 100644
--- a/bbootimg/src/main/kotlin/cfig/io/Struct3.kt
+++ b/bbootimg/src/main/kotlin/cfig/io/Struct3.kt
@@ -10,7 +10,6 @@ import java.nio.ByteOrder
 import java.util.*
 import java.util.regex.Pattern
 
-@ExperimentalStdlibApi
 @ExperimentalUnsignedTypes
 class Struct3 {
     private val log = LoggerFactory.getLogger(Struct3::class.java)
@@ -411,7 +410,7 @@ class Struct3 {
             if (format[0] === String) {
                 val data = ByteArray(format[1] as Int)
                 Assert.assertEquals(format[1] as Int, iS.read(data))
-                ret.add(data.decodeToString())
+                ret.add(Helper.toCString(data))
                 continue
             }
 
diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt
index 3373099..7e211d0 100644
--- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt
+++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt
@@ -9,10 +9,6 @@ import java.lang.IllegalArgumentException
 
 @ExperimentalUnsignedTypes
 class BootImgParser : IPackable {
-    override fun flash(fileName: String) {
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
-    }
-
     private val log = LoggerFactory.getLogger(BootImgParser::class.java)
 
     override fun capabilities(): List<String> {
@@ -53,4 +49,9 @@ class BootImgParser : IPackable {
         Packer().pack(mkbootfsBin = "./aosp/mkbootfs/build/install/main/release/$osSuffix/mkbootfs")
         Signer.sign(avbtool = "avb/avbtool", bootSigner = "aosp/boot_signer/build/libs/boot_signer.jar")
     }
+
+    override fun flash(fileName: String, deviceName: String) {
+        val stem = fileName.substring(0, fileName.indexOf("."))
+        super.flash("$fileName.signed", stem)
+    }
 }
diff --git a/bbootimg/src/main/kotlin/packable/DtboParser.kt b/bbootimg/src/main/kotlin/packable/DtboParser.kt
index 6b19c31..6ade4e0 100644
--- a/bbootimg/src/main/kotlin/packable/DtboParser.kt
+++ b/bbootimg/src/main/kotlin/packable/DtboParser.kt
@@ -12,10 +12,6 @@ import java.util.*
 
 @ExperimentalUnsignedTypes
 class DtboParser(val workDir: File) : IPackable {
-    override fun flash(fileName: String) {
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
-    }
-
     constructor() : this(File("."))
 
     private val log = LoggerFactory.getLogger(DtboParser::class.java)
@@ -33,17 +29,7 @@ class DtboParser(val workDir: File) : IPackable {
             it.addArguments("--dtb $dtbPath")
             it.addArguments("--output $headerPath")
         }
-
-        DefaultExecutor().let {
-            it.workingDirectory = this.workDir
-            try {
-                log.info(cmd.toString())
-                it.execute(cmd)
-            } catch (e: org.apache.commons.exec.ExecuteException) {
-                log.error("can not parse $fileName")
-                return
-            }
-        }
+        execInDirectory(cmd, this.workDir)
 
         val props = Properties()
         props.load(FileInputStream(File(headerPath)))
@@ -75,14 +61,17 @@ class DtboParser(val workDir: File) : IPackable {
             }
             it
         }
+        execInDirectory(cmd, this.workDir)
+    }
 
+    private fun execInDirectory(cmd: CommandLine, inWorkDir: File) {
         DefaultExecutor().let {
-            it.workingDirectory = this.workDir
+            it.workingDirectory = inWorkDir
             try {
                 log.info(cmd.toString())
                 it.execute(cmd)
             } catch (e: org.apache.commons.exec.ExecuteException) {
-                log.error("can not parse $fileName")
+                log.error("can not exec command")
                 return
             }
         }
diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt
index acb89f3..2411b75 100644
--- a/bbootimg/src/main/kotlin/packable/IPackable.kt
+++ b/bbootimg/src/main/kotlin/packable/IPackable.kt
@@ -1,12 +1,33 @@
 package cfig.packable
 
+import cfig.Helper.Companion.check_call
+import cfig.Helper.Companion.check_output
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
 interface IPackable {
     fun capabilities(): List<String> {
         return listOf("^dtbo\\.img$")
     }
+
     fun unpack(fileName: String = "dtbo.img")
     fun pack(fileName: String = "dtbo.img")
-    fun flash(fileName: String = "dtbo.img") {
+    fun flash(fileName: String = "dtbo.img", deviceName: String = "dtbo") {
+        "adb root".check_call()
+        val abUpdateProp = "adb shell getprop ro.build.ab_update".check_output()
+        log.info("ro.build.ab_update=$abUpdateProp")
+        val slotSuffix = if (abUpdateProp == "true") {
+            "adb shell getprop ro.boot.slot_suffix".check_output()
+        } else {
+            ""
+        }
+        log.info("slot suffix = $slotSuffix")
+        "adb push $fileName /cache/file.to.burn".check_call()
+        "adb shell dd if=/cache/file.to.burn of=/dev/block/by-name/$deviceName$slotSuffix".check_call()
+        "adb shell rm /cache/file.to.burn".check_call()
+    }
 
+    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 83982ad..ac6f09b 100644
--- a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt
+++ b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt
@@ -28,7 +28,7 @@ fun main(args: Array<String>) {
             packablePool.forEach { p ->
                 for (item in p.key) {
                     if (Pattern.compile(item).matcher(file.name).matches()) {
-                        log.debug("Found: "  + file.name + ", " + item)
+                        log.debug("Found: " + file.name + ", " + item)
                         targetFile = file.name
                         targetHandler = p.value
                         return@found
@@ -49,6 +49,9 @@ fun main(args: Array<String>) {
             "pack" -> {
                 targetHandler!!.createInstance().pack(targetFile!!)
             }
+            "flash" -> {
+                targetHandler!!.createInstance().flash(targetFile!!)
+            }
             else -> {
                 log.error("Unknown cmd: " + args[0])
             }
diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
index 370805b..853349d 100644
--- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
+++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt
@@ -4,10 +4,6 @@ import cfig.Avb
 
 @ExperimentalUnsignedTypes
 class VBMetaParser: IPackable {
-    override fun flash(fileName: String) {
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
-    }
-
     override fun capabilities(): List<String> {
         return listOf("^vbmeta\\.img$", "^vbmeta\\_[a-z]+.img$")
     }
@@ -19,4 +15,9 @@ class VBMetaParser: IPackable {
     override fun pack(fileName: String) {
         Avb().packVbMetaWithPadding(fileName)
     }
+
+    override fun flash(fileName: String, deviceName: String) {
+        val stem = fileName.substring(0, fileName.indexOf("."))
+        super.flash("$fileName.signed", stem)
+    }
 }
diff --git a/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt b/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt
index f443645..7517cbb 100644
--- a/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt
+++ b/bbootimg/src/main/kotlin/sparse_util/SparseImg.kt
@@ -44,4 +44,8 @@ class SparseImg : IPackable {
         "file $sparseOut".check_call()
         log.info("transformed Android sparse image: $flatIn -> $sparseOut")
     }
+
+    override fun flash(fileName: String, deviceName: String) {
+        TODO("not implemented")
+    }
 }
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index f40114e..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,167 +0,0 @@
-apply plugin: "java"
-
-buildscript {
-    repositories {
-        jcenter()
-    }
-    dependencies {
-        classpath "org.apache.commons:commons-exec:1.3"
-    }
-}
-
-subprojects {
-    tasks.withType(JavaCompile) {
-        //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
-    }
-}
-
-// ----------------------------------------------------------------------------
-//                       global
-// ----------------------------------------------------------------------------
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-import org.apache.commons.exec.CommandLine
-import org.apache.commons.exec.DefaultExecutor
-import org.apache.commons.exec.PumpStreamHandler
-
-if (parseGradleVersion(gradle.gradleVersion) < 5) {
-    logger.error("ERROR: Gradle Version MUST >= 5.0, current is {}", gradle.gradleVersion)
-    throw new RuntimeException("ERROR: Gradle Version")
-} else {
-    logger.info("Gradle Version {}", gradle.gradleVersion)
-}
-
-def GROUP_ANDROID = "Android"
-project.ext.bootSigner = new File("boot_signer/build/libs/boot_signer.jar").getAbsolutePath()
-
-// ----------------------------------------------------------------------------
-//                       tasks
-// ----------------------------------------------------------------------------
-task _setup(type: Copy) {
-    group GROUP_ANDROID
-    from 'src/test/resources/boot.img'
-    into '.'
-}
-
-task pull() {
-    group GROUP_ANDROID
-    doFirst {
-        println("Pulling ...")
-    }
-
-    doLast {
-        String avb_version = adbCmd("getprop ro.boot.avb_version2")
-        Boolean isAvbEnabled = false
-        if (avb_version.isEmpty()) {
-            isAvbEnabled = true
-        }
-        println("AVB: $isAvbEnabled")
-        if (project.findProperty("group")) {
-            println("Pull: $group")
-        } else {
-            println("Pull /boot, /recovery, /vbmeta")
-            pullDefault(isAvbEnabled)
-        }
-    }
-}
-
-void pullDefault(Boolean avb = true) {
-    Run(["adb", "shell", "dd if=/dev/block/by-name/boot of=/cache/boot.img"])
-    Run(["adb", "shell", "dd if=/dev/block/by-name/recovery of=/cache/recovery.img"])
-    if (avb) Run(["adb", "shell", "dd if=/dev/block/by-name/vbmeta of=/cache/vbmeta.img"])
-
-    Run(["adb", "pull", "/cache/boot.img"])
-    Run(["adb", "pull", "/cache/recovery.img"])
-    if (avb) Run(["adb", "pull", "/cache/vbmeta.img"])
-
-    Run(["adb", "shell", "rm /cache/boot.img"])
-    Run(["adb", "shell", "rm /cache/recovery.img"])
-    if (avb) Run(["adb", "shell", "rm /cache/vbmeta.img"])
-}
-
-void Run(List<String> inCmd, String inWorkdir = null) {
-    println("CMD:" + inCmd)
-    if (inWorkdir == null) {
-        inWorkdir = ".";
-    }
-    ProcessBuilder pb = new ProcessBuilder(inCmd)
-            .directory(new File(inWorkdir))
-            .redirectErrorStream(true);
-    Process p = pb.start()
-    p.inputStream.eachLine { println it }
-    p.waitFor();
-    assert 0 == p.exitValue()
-}
-
-void Run(String inCmd, String inWorkdir = null) {
-    Run(Arrays.asList(inCmd.split()), inWorkdir);
-}
-
-void rebootRecovery() {
-    Run("adb reboot recovery")
-}
-
-task rr {
-    group GROUP_ANDROID
-    doLast {
-        rebootRecovery()
-    }
-}
-
-task unpack(type: JavaExec, dependsOn: ["bbootimg:jar"]) {
-    group GROUP_ANDROID
-    main = "cfig.packable.PackableLauncherKt"
-    classpath = files("bbootimg/build/libs/bbootimg.jar")
-    maxHeapSize '512m'
-    args "unpack"
-}
-
-task pack(type: JavaExec, dependsOn: ["bbootimg:jar", "aosp:boot_signer:build"]) {
-    group GROUP_ANDROID
-    main = "cfig.packable.PackableLauncherKt"
-    classpath = files("bbootimg/build/libs/bbootimg.jar")
-    maxHeapSize '512m'
-    args "pack"
-}
-
-task flash(type: JavaExec, dependsOn: ["bbootimg:jar"]) {
-    group GROUP_ANDROID
-    main = "cfig.packable.PackableLauncherKt"
-    classpath = files("bbootimg/build/libs/bbootimg.jar")
-    maxHeapSize '512m'
-    args "flash"
-}
-
-//sparse image dependencies
-if (System.getProperty("os.name").contains("Mac")) {
-    unpack.dependsOn("aosp:libsparse:simg2img:installReleaseMacos")
-    pack.dependsOn("aosp:libsparse:img2simg:installReleaseMacos")
-    pack.dependsOn("aosp:mkbootfs:installReleaseMacos")
-} else {
-    unpack.dependsOn("aosp:libsparse:simg2img:installReleaseLinux")
-    pack.dependsOn("aosp:libsparse:img2simg:installReleaseLinux")
-    pack.dependsOn("aosp:mkbootfs:installReleaseLinux")
-}
-
-int parseGradleVersion(String version) {
-    Pattern VERSION_PATTERN = Pattern.compile("((\\d+)(\\.\\d+)+)(-(\\p{Alpha}+)-(\\w+))?(-(SNAPSHOT|\\d{14}([-+]\\d{4})?))?")
-    Matcher matcher = VERSION_PATTERN.matcher(version)
-    if (!matcher.matches()) {
-        throw new IllegalArgumentException(format("'%s' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')", version))
-    }
-    String versionPart = matcher.group(1)
-    int majorPart = Integer.parseInt(matcher.group(2), 10)
-    logger.info("Gradle: versionPart {}, majorPart {}", versionPart, majorPart)
-    return majorPart
-}
-
-String adbCmd(String cmd) {
-    ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
-    def exec = new DefaultExecutor()
-    exec.streamHandler = new PumpStreamHandler(outputStream)
-    def cmdline = "adb shell $cmd"
-    //println(cmdline)
-    exec.execute(CommandLine.parse(cmdline))
-    //println(outputStream)
-    return outputStream.toString().trim()
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..d452ec6
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,74 @@
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+import org.apache.commons.exec.CommandLine
+import org.apache.commons.exec.DefaultExecutor
+import org.apache.commons.exec.PumpStreamHandler
+
+val GROUP_ANDROID = "android"
+if (parseGradleVersion(gradle.gradleVersion) < 5) {
+    logger.error("ERROR: Gradle Version MUST >= 5.0, current is {}", gradle.gradleVersion)
+    throw RuntimeException("ERROR: Gradle Version")
+} else {
+    logger.info("Gradle Version {}", gradle.gradleVersion)
+}
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath("org.apache.commons:commons-exec:1.3")
+    }
+}
+
+tasks {
+    val unpackTask by register<JavaExec>("unpack") {
+        group = GROUP_ANDROID
+        main = "cfig.packable.PackableLauncherKt"
+        classpath = files("bbootimg/build/libs/bbootimg.jar")
+        this.maxHeapSize = "512m"
+        args("unpack")
+    }
+    unpackTask.dependsOn("bbootimg:jar")
+
+    val packTask by register<JavaExec>("pack") {
+        group = GROUP_ANDROID
+        main = "cfig.packable.PackableLauncherKt"
+        classpath = files("bbootimg/build/libs/bbootimg.jar")
+        this.maxHeapSize = "512m"
+        args("pack")
+    }
+    packTask.dependsOn("bbootimg:jar", "aosp:boot_signer:build")
+
+    val flashTask by register("flash", JavaExec::class) {
+        group = GROUP_ANDROID
+        main = "cfig.packable.PackableLauncherKt"
+        classpath = files("bbootimg/build/libs/bbootimg.jar")
+        this.maxHeapSize = "512m"
+        args("flash")
+    }
+    flashTask.dependsOn("bbootimg:jar")
+
+    //sparse image dependencies
+    if (System.getProperty("os.name").contains("Mac")) {
+        unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseMacos")
+        packTask.dependsOn("aosp:libsparse:img2simg:installReleaseMacos")
+        packTask.dependsOn("aosp:mkbootfs:installReleaseMacos")
+    } else {
+        unpackTask.dependsOn("aosp:libsparse:simg2img:installReleaseLinux")
+        packTask.dependsOn("aosp:libsparse:img2simg:installReleaseLinux")
+        packTask.dependsOn("aosp:mkbootfs:installReleaseLinux")
+    }
+}
+
+fun parseGradleVersion(version: String): Int {
+    val VERSION_PATTERN = Pattern.compile("((\\d+)(\\.\\d+)+)(-(\\p{Alpha}+)-(\\w+))?(-(SNAPSHOT|\\d{14}([-+]\\d{4})?))?")
+    val matcher = VERSION_PATTERN.matcher(version)
+    if (!matcher.matches()) {
+        throw IllegalArgumentException(String.format("'%s' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')", version))
+    }
+    val versionPart: String = matcher.group(1)
+    val majorPart = Integer.parseInt(matcher.group(2), 10)
+    logger.info("Gradle: versionPart {}, majorPart {}", versionPart, majorPart)
+    return majorPart
+}
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index a1fdb7d..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-include "bbootimg"
-include "aosp:boot_signer"
-include "aosp:mkbootfs"
-include "aosp:libsparse:base"
-include "aosp:libsparse:sparse"
-include "aosp:libsparse:img2simg"
-include "aosp:libsparse:simg2img"
-include "aosp:libsparse:simg2simg"
-include "aosp:libsparse:append2simg"
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..c1c33fb
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,9 @@
+include("bbootimg")
+include("aosp:boot_signer")
+include("aosp:mkbootfs")
+include("aosp:libsparse:base")
+include("aosp:libsparse:sparse")
+include("aosp:libsparse:img2simg")
+include("aosp:libsparse:simg2img")
+include("aosp:libsparse:simg2simg")
+include("aosp:libsparse:append2simg")