diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bbc8ff5..ef38ce7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,7 +87,7 @@ jobs: run: python -c "import sys; print(sys.version)" - name: choco - run: choco install openssl + run: choco install openssl dtc-msys2 - name: Unit Test run: ./gradlew.bat check && ./gradlew.bat clean diff --git a/README.md b/README.md index 1b6f1b1..bdaeb5e 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,15 @@ A tool for reverse engineering Android ROM images. #### install required packages -Mac: `brew install lz4 xz dtc` - Linux: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python3 python-is-python3` +Mac: `brew install lz4 xz dtc` + Windows Subsystem for Linux(WSL): `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python` Windows: Make sure you have `python3`, `JDK9+` and `openssl` properly installed. An easy way is to install [Anaconda](https://www.anaconda.com/products/individual#windows) and [Oracle JDK 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), then run the program under anaconda PowerShell. +Or install them with chocolate: `choco install openssl dtc-msys2` #### Parsing and packing diff --git a/bbootimg/build.gradle.kts b/bbootimg/build.gradle.kts index e47b369..b4d5712 100644 --- a/bbootimg/build.gradle.kts +++ b/bbootimg/build.gradle.kts @@ -58,6 +58,7 @@ application { tasks.withType().all { kotlinOptions { freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes" jvmTarget = "11" } } diff --git a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt index d390c38..9a60a39 100644 --- a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt +++ b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt @@ -147,7 +147,11 @@ class AndroidCpio { entry.statMode = itemConfig[0].statMode } else -> { - throw IllegalArgumentException("${entry.name} has multiple exact-match fsConfig") + //Issue #73: https://github.com/cfig/Android_boot_image_editor/issues/73 + //Reason: cpio may have multiple entries with the same name, that's ugly! + //throw IllegalArgumentException("${entry.name} has multiple exact-match fsConfig") + log.warn("${entry.name} has multiple exact-match fsConfig") + entry.statMode = itemConfig[0].statMode } } } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index 7ec0a4b..459649f 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -16,12 +16,13 @@ package cfig.bootimg.v2 import avb.AVBInfo import cfig.Avb -import cfig.utils.EnvironmentVerifier import cfig.bootimg.Common import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Signer +import cfig.bootimg.v3.VendorBoot import cfig.helper.Helper import cfig.packable.VBMetaParser +import cfig.utils.EnvironmentVerifier import com.fasterxml.jackson.databind.ObjectMapper import de.vandermeer.asciitable.AsciiTable import org.apache.commons.exec.CommandLine @@ -516,4 +517,14 @@ data class BootV2( } return this } + + fun printPackSummary(): BootV2 { + VendorBoot.printPackSummary(info.output) + return this + } + + fun updateVbmeta(): BootV2 { + Avb.updateVbmeta(info.output) + return this + } } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index 2ae56ad..9e96950 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -290,6 +290,16 @@ data class BootV3( return this } + fun printPackSummary(): BootV3 { + VendorBoot.printPackSummary(info.output) + return this + } + + fun updateVbmeta(): BootV3 { + Avb.updateVbmeta(info.output) + return this + } + private fun toCommandLine(): CommandLine { val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).let { ret -> diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 9ed481e..18fbb1e 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -208,6 +208,30 @@ data class VendorBoot( ret.info.imageSize = File(fileName).length() return ret } + + fun printPackSummary(imageName: String) { + val tableHeader = AsciiTable().apply { + addRule() + addRow("What", "Where") + addRule() + } + val tab = AsciiTable().let { + it.addRule() + it.addRow("re-packed $imageName", "$imageName.signed") + it.addRule() + it + } + if (File("vbmeta.img").exists()) { + if (File("vbmeta.img.signed").exists()) { + tab.addRow("re-packed vbmeta", "vbmeta.img.signed") + } else { + tab.addRow("re-packed vbmeta", "-") + } + tab.addRule() + } + log.info("\n\t\t\tPack Summary of ${imageName}\n{}\n{}", tableHeader.render(), tab.render()) + } + } fun pack(): VendorBoot { @@ -290,6 +314,11 @@ data class VendorBoot( return this } + fun updateVbmeta(): VendorBoot { + Avb.updateVbmeta(info.output) + return this + } + private fun toHeader(): VendorBootHeader { return VendorBootHeader( headerVersion = info.headerVersion, @@ -352,7 +381,7 @@ data class VendorBoot( return this } - fun printSummary(): VendorBoot { + fun printUnpackSummary(): VendorBoot { val tableHeader = AsciiTable().apply { addRule() addRow("What", "Where") @@ -399,6 +428,11 @@ data class VendorBoot( return this } + fun printPackSummary(): VendorBoot { + printPackSummary(info.output) + return this + } + private fun toCommandLine(): CommandLine { val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).apply { diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index 3968a41..e91e804 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -19,7 +19,6 @@ import cfig.Avb import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.v2.BootV2 import cfig.bootimg.v3.BootV3 -import cfig.helper.Helper import com.fasterxml.jackson.databind.ObjectMapper import de.vandermeer.asciitable.AsciiTable import org.slf4j.LoggerFactory @@ -29,7 +28,6 @@ import java.io.FileInputStream class BootImgParser : IPackable { override val loopNo: Int get() = 0 - private val workDir = Helper.prop("workDir") override fun capabilities(): List { return listOf("^boot(-debug)?\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$") @@ -57,7 +55,7 @@ class BootImgParser : IPackable { } override fun pack(fileName: String) { - val cfgFile = workDir + fileName.removeSuffix(".img") + ".json" + val cfgFile = outDir + fileName.removeSuffix(".img") + ".json" log.info("Loading config from $cfgFile") if (!File(cfgFile).exists()) { val tab = AsciiTable().let { @@ -74,24 +72,16 @@ class BootImgParser : IPackable { ObjectMapper().readValue(File(cfgFile), BootV2::class.java) .pack() .sign() + .updateVbmeta() + .printPackSummary() 3, 4 -> ObjectMapper().readValue(File(cfgFile), BootV3::class.java) .pack() .sign(fileName) - .let { - val tab = AsciiTable().let { tab -> - tab.addRule() - val outFileSuffix = - if (File(Avb.getJsonFileName(it.info.output)).exists()) ".signed" else ".clear" - tab.addRow("${it.info.output}${outFileSuffix} is ready") - tab.addRule() - tab - } - log.info("\n{}", tab.render()) - } + .updateVbmeta() + .printPackSummary() else -> throw IllegalArgumentException("do not support header version $hv") } - Avb.updateVbmeta(fileName) } override fun flash(fileName: String, deviceName: String) { diff --git a/bbootimg/src/main/kotlin/packable/DtboParser.kt b/bbootimg/src/main/kotlin/packable/DtboParser.kt index d52d8eb..de1a287 100644 --- a/bbootimg/src/main/kotlin/packable/DtboParser.kt +++ b/bbootimg/src/main/kotlin/packable/DtboParser.kt @@ -14,14 +14,14 @@ package cfig.packable -import avb.blob.Footer -import cfig.utils.EnvironmentVerifier -import cfig.utils.DTC import cfig.helper.Helper +import cfig.utils.DTC +import cfig.utils.EnvironmentVerifier import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.slf4j.LoggerFactory +import utils.Dtbo import java.io.File import java.io.FileInputStream import java.util.* @@ -34,7 +34,6 @@ class DtboParser(val workDir: File) : IPackable { private val log = LoggerFactory.getLogger(DtboParser::class.java) private val envv = EnvironmentVerifier() - private val outDir = Helper.prop("workDir") private val dtboMaker = Helper.prop("dtboMaker") override fun capabilities(): List { @@ -43,32 +42,39 @@ class DtboParser(val workDir: File) : IPackable { override fun unpack(fileName: String) { cleanUp() - val dtbPath = File("$outDir/dtb").path - val headerPath = File("$outDir/dtbo.header").path - val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" - val cmd = CommandLine.parse("$cmdPrefix$dtboMaker dump $fileName").let { - it.addArguments("--dtb $dtbPath") - it.addArguments("--output $headerPath") - } - execInDirectory(cmd, this.workDir) + Dtbo.parse(fileName) + .unpack(outDir) + .extractVBMeta() + .printSummary() + } - val props = Properties().apply { - FileInputStream(File(headerPath)).use { fis -> - load(fis) - } - } - if (envv.hasDtc) { - for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) { - val inputDtb = "$dtbPath.$i" - val outputSrc = File(outDir + "/" + File(inputDtb).name + ".src").path - DTC().decompile(inputDtb, outputSrc) + override fun pack(fileName: String) { + ObjectMapper().readValue(File(outDir + "dtbo.json"), Dtbo::class.java) + .pack() + .sign() + .updateVbmeta() + .printPackSummary() + } + + override fun `@verify`(fileName: String) { + super.`@verify`(fileName) + } + + private fun execInDirectory(cmd: CommandLine, inWorkDir: File) { + DefaultExecutor().let { + it.workingDirectory = inWorkDir + try { + log.info(cmd.toString()) + it.execute(cmd) + } catch (e: org.apache.commons.exec.ExecuteException) { + log.error("can not exec command") + return } - } else { - log.error("'dtc' is unavailable, task aborted") } } - override fun pack(fileName: String) { + @Deprecated("for debugging purpose only") + fun packLegacy(fileName: String) { if (!envv.hasDtc) { log.error("'dtc' is unavailable, task aborted") return @@ -91,33 +97,31 @@ class DtboParser(val workDir: File) : IPackable { execInDirectory(cmd, this.workDir) } - override fun `@verify`(fileName: String) { - super.`@verify`(fileName) - } + @Deprecated("for debugging purpose only") + fun unpackLegacy(fileName: String) { + cleanUp() + val dtbPath = File("$outDir/dtb").path + val headerPath = File("$outDir/dtbo.header").path + val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" + val cmd = CommandLine.parse("$cmdPrefix$dtboMaker dump $fileName").let { + it.addArguments("--dtb $dtbPath") + it.addArguments("--output $headerPath") + } + execInDirectory(cmd, this.workDir) - // invoked solely by reflection - fun `@footer`(fileName: String) { - FileInputStream(fileName).use { fis -> - fis.skip(File(fileName).length() - Footer.SIZE) - try { - val footer = Footer(fis) - log.info("\n" + ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(footer)) - } catch (e: IllegalArgumentException) { - log.info("image $fileName has no AVB Footer") + val props = Properties().apply { + FileInputStream(File(headerPath)).use { fis -> + load(fis) } } - } - - private fun execInDirectory(cmd: CommandLine, inWorkDir: File) { - DefaultExecutor().let { - it.workingDirectory = inWorkDir - try { - log.info(cmd.toString()) - it.execute(cmd) - } catch (e: org.apache.commons.exec.ExecuteException) { - log.error("can not exec command") - return + if (envv.hasDtc) { + for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) { + val inputDtb = "$dtbPath.$i" + val outputSrc = File(outDir + "/" + File(inputDtb).name + ".src").path + DTC().decompile(inputDtb, outputSrc) } + } else { + log.error("'dtc' is unavailable, task aborted") } } } diff --git a/bbootimg/src/main/kotlin/packable/IPackable.kt b/bbootimg/src/main/kotlin/packable/IPackable.kt index a162c27..213c92b 100644 --- a/bbootimg/src/main/kotlin/packable/IPackable.kt +++ b/bbootimg/src/main/kotlin/packable/IPackable.kt @@ -25,6 +25,9 @@ import java.io.File interface IPackable { val loopNo: Int + val outDir: String + get() = Helper.prop("workDir") + fun capabilities(): List { return listOf("^dtbo\\.img$") } diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt index e0e6ea2..bba2aca 100644 --- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt +++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt @@ -16,7 +16,6 @@ package cfig.packable import avb.AVBInfo import cfig.Avb -import cfig.helper.Helper import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import java.io.File @@ -33,7 +32,7 @@ class VBMetaParser: IPackable { } override fun cleanUp() { - File(Helper.prop("workDir")).mkdirs() + File(outDir).mkdirs() } override fun unpack(fileName: String) { diff --git a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt index 495de6e..ad1ddd4 100644 --- a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt +++ b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt @@ -14,8 +14,6 @@ package cfig.packable -import cfig.Avb -import cfig.helper.Helper import cfig.bootimg.v3.VendorBoot import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory @@ -24,7 +22,6 @@ import java.io.File class VendorBootParser : IPackable { override val loopNo: Int = 0 private val log = LoggerFactory.getLogger(VendorBootParser::class.java) - private val workDir = Helper.prop("workDir") override fun capabilities(): List { return listOf("^vendor_boot(-debug)?\\.img$") } @@ -32,20 +29,21 @@ class VendorBootParser : IPackable { override fun unpack(fileName: String) { cleanUp() val vb = VendorBoot - .parse(fileName) - .extractImages() - .extractVBMeta() - .printSummary() + .parse(fileName) + .extractImages() + .extractVBMeta() + .printUnpackSummary() log.debug(vb.toString()) } override fun pack(fileName: String) { - val cfgFile = "$workDir/${fileName.removeSuffix(".img")}.json" + val cfgFile = "$outDir/${fileName.removeSuffix(".img")}.json" log.info("Loading config from $cfgFile") ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java) - .pack() - .sign() - Avb.updateVbmeta(fileName) + .pack() + .sign() + .updateVbmeta() + .printPackSummary() } override fun `@verify`(fileName: String) { diff --git a/bbootimg/src/main/kotlin/utils/DTC.kt b/bbootimg/src/main/kotlin/utils/DTC.kt index 5ce1323..675265b 100644 --- a/bbootimg/src/main/kotlin/utils/DTC.kt +++ b/bbootimg/src/main/kotlin/utils/DTC.kt @@ -23,17 +23,16 @@ class DTC { fun decompile(dtbFile: String, outFile: String): Boolean { log.info("parsing DTB: $dtbFile") - val cmd = CommandLine.parse("dtc -q -I dtb -O dts").let { - it.addArguments("$dtbFile") - it.addArguments("-o $outFile") - } - - CommandLine.parse("fdtdump").let { - it.addArguments("$dtbFile") - } - + //CommandLine.parse("fdtdump").let { + // it.addArguments("$dtbFile") + //} + //dtb-> dts DefaultExecutor().let { try { + val cmd = CommandLine.parse("dtc -q -I dtb -O dts").apply { + addArguments(dtbFile) + addArguments("-o $outFile") + } it.execute(cmd) log.info(cmd.toString()) } catch (e: org.apache.commons.exec.ExecuteException) { @@ -41,13 +40,27 @@ class DTC { return false } } + //dts -> yaml + DefaultExecutor().let { + try { + val cmd = CommandLine.parse("dtc -q -I dts -O yaml").apply { + addArguments(outFile) + addArguments("-o $outFile.yaml") + } + it.execute(cmd) + log.info(cmd.toString()) + } catch (e: org.apache.commons.exec.ExecuteException) { + log.error("can not transform DTS: $outFile") + return false + } + } return true } fun compile(dtsFile: String, outFile: String): Boolean { log.info("compiling DTS: $dtsFile") val cmd = CommandLine.parse("dtc -q -I dts -O dtb").let { - it.addArguments("$dtsFile") + it.addArguments(dtsFile) it.addArguments("-o $outFile") } diff --git a/bbootimg/src/main/kotlin/utils/Dtbo.kt b/bbootimg/src/main/kotlin/utils/Dtbo.kt new file mode 100644 index 0000000..b01ef4d --- /dev/null +++ b/bbootimg/src/main/kotlin/utils/Dtbo.kt @@ -0,0 +1,258 @@ +package utils + +import avb.AVBInfo +import cfig.Avb +import cfig.bootimg.Common +import cfig.bootimg.Signer +import cfig.bootimg.v3.VendorBoot +import cfig.helper.Helper +import cfig.io.Struct3 +import cfig.packable.VBMetaParser +import cfig.utils.DTC +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.io.FileOutputStream +import java.io.InputStream + +class Dtbo( + var info: DtboInfo = DtboInfo(), + var header: DtboHeader = DtboHeader(), + var dtEntries: MutableList = mutableListOf() +) { + class DtboInfo( + var output: String = "", + var json: String = "", + var imageSize: Int = 0 + ) + + // part I: header + data class DtboHeader( + var totalSize: Int = 0, + var headerSize: Int = 0, + var entrySize: Int = 0, + var entryCount: Int = 0, + var entryOffset: Int = 0, + var pageSize: Int = 0, + var version: Int = 0 + ) { + companion object { + const val magic = 0xd7b7ab1e + private const val FORMAT_STRING = ">I7i" + internal const val SIZE = 32 + + init { + assert(Struct3(FORMAT_STRING).calcSize() == SIZE) + } + } + + constructor(iS: InputStream?) : this() { + if (iS == null) { + return + } + val info = Struct3(FORMAT_STRING).unpack(iS) + assert(8 == info.size) + if ((info[0] as UInt).toLong() != magic) { + throw IllegalArgumentException("stream doesn't look like DTBO header") + } + totalSize = info[1] as Int + headerSize = info[2] as Int + if (headerSize != DtboHeader.SIZE) { + log.warn("headerSize $headerSize != ${DtboHeader.SIZE}") + } + entrySize = info[3] as Int + if (entrySize != DeviceTreeTableEntry.SIZE) { + log.warn("entrySize $entrySize != ${DeviceTreeTableEntry.SIZE}") + } + entryCount = info[4] as Int + entryOffset = info[5] as Int + pageSize = info[6] as Int + version = info[7] as Int + } + + fun encode(): ByteArray { + return Struct3(FORMAT_STRING).pack( + magic, + totalSize, + headerSize, + entrySize, + entryCount, + entryOffset, + pageSize, + version + ) + } + } + + // part II: dt entry table + data class DeviceTreeTableEntry( + var sequenceNo: Int = 0, + var entrySize: Int = 0, + var entryOffset: Int = 0, + var id: Int = 0, + var rev: Int = 0, + var flags: Int = 0, + var reserved1: Int = 0, + var reserved2: Int = 0, + var reserved3: Int = 0, + ) { + companion object { + private const val FORMAT_STRING = ">8i" + internal const val SIZE = 32 + + init { + assert(Struct3(FORMAT_STRING).calcSize() == SIZE) + } + } + + constructor(iS: InputStream) : this() { + val info = Struct3(FORMAT_STRING).unpack(iS) + assert(8 == info.size) + entrySize = info[0] as Int + entryOffset = info[1] as Int + id = info[2] as Int + rev = info[3] as Int + flags = info[4] as Int + reserved1 = info[5] as Int + reserved2 = info[6] as Int + reserved3 = info[7] as Int + } + + fun encode(): ByteArray { + return Struct3(FORMAT_STRING).pack( + entrySize, + entryOffset, + id, + rev, + flags, + reserved1, + reserved2, + reserved3 + ) + } + } + + companion object { + fun parse(fileName: String): Dtbo { + val ret = Dtbo() + ret.info.output = fileName + ret.info.imageSize = File(fileName).length().toInt() + ret.info.json = fileName.removeSuffix(".img") + ".json" + FileInputStream(fileName).use { fis -> + ret.header = DtboHeader(fis) + for (i in 0 until ret.header.entryCount) { + ret.dtEntries.add(DeviceTreeTableEntry(fis).apply { sequenceNo = i }) + } + } + return ret + } + + private val log = LoggerFactory.getLogger(Dtbo::class.java) + private val outDir = Helper.prop("workDir") + } + + fun extractVBMeta(): Dtbo { + try { + AVBInfo.parseFrom(info.output).dumpDefault(info.output) + } catch (e: Exception) { + log.error("extraceVBMeta(): $e") + } + if (File("vbmeta.img").exists()) { + log.warn("Found vbmeta.img, parsing ...") + VBMetaParser().unpack("vbmeta.img") + } + return this + } + + fun unpack(outDir: String): Dtbo { + File("${outDir}dt").mkdir() + ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File("${outDir}dtbo.json"), this) + dtEntries.forEach { + Common.dumpDtb(Helper.Slice(info.output, it.entryOffset, it.entrySize, "${outDir}dt/dt.${it.sequenceNo}")) + } + return this + } + + fun pack(): Dtbo { + FileOutputStream(info.output + ".clear").use { fos -> + // Part I + this.header.entryCount = this.dtEntries.size + this.header.totalSize = (DtboHeader.SIZE + + (header.entryCount * DeviceTreeTableEntry.SIZE) + + this.dtEntries.sumOf { File("${outDir}dt/dt.${it.sequenceNo}").length() }) + .toInt() + // Part II - a + for (index in 0 until dtEntries.size) { + DTC().compile("${outDir}dt/dt.${index}.src", "${outDir}dt/dt.${index}") + } + // Part II - b + var offset = DtboHeader.SIZE + (header.entryCount * DeviceTreeTableEntry.SIZE) + this.dtEntries.forEachIndexed { index, deviceTreeTableEntry -> + deviceTreeTableEntry.entrySize = File("${outDir}dt/dt.${index}").length().toInt() + deviceTreeTableEntry.entryOffset = offset + offset += deviceTreeTableEntry.entrySize + } + + // + Part I + fos.write(header.encode()) + // + Part II + this.dtEntries.forEach { + fos.write(it.encode()) + } + // + Part III + for (index in 0 until dtEntries.size) { + fos.write(File("${outDir}dt/dt.${index}").readBytes()) + } + } + return this + } + + fun printSummary(): Dtbo { + val tableHeader = AsciiTable().apply { + addRule() + addRow("What", "Where") + addRule() + } + val tab = AsciiTable().let { + it.addRule() + it.addRow("image info", outDir + info.output.removeSuffix(".img") + ".json") + it.addRule() + it.addRow("device-tree blob (${this.header.entryCount} blobs)", "${outDir}dt/dt.*") + it.addRow("\\-- device-tree source ", "${outDir}dt/dt.*.src") + it.addRule() + it.addRow("AVB info", Avb.getJsonFileName(info.output)) + it.addRule() + it + } + val tabVBMeta = AsciiTable().let { + if (File("vbmeta.img").exists()) { + it.addRule() + it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) + it.addRule() + "\n" + it.render() + } else { + "" + } + } + log.info("\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}", tableHeader.render(), tab.render(), tabVBMeta) + return this + } + + fun sign(): Dtbo { + val avbtool = String.format(Helper.prop("avbtool"), "v1.2") + Signer.signAVB(info.output, info.imageSize.toLong(), avbtool) + return this + } + + fun updateVbmeta(): Dtbo { + Avb.updateVbmeta(info.output) + return this + } + + fun printPackSummary(): Dtbo { + VendorBoot.printPackSummary(info.output) + return this + } +} diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts index 02df4fe..c587323 100644 --- a/helper/build.gradle.kts +++ b/helper/build.gradle.kts @@ -48,6 +48,9 @@ dependencies { } tasks.withType().all { - kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" - kotlinOptions.jvmTarget = "11" + kotlinOptions { + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes" + jvmTarget = "11" + } } diff --git a/integrationTest.py b/integrationTest.py index e5909c7..a062652 100755 --- a/integrationTest.py +++ b/integrationTest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import shutil, os.path, json, subprocess, hashlib, glob -import unittest, logging, sys, lzma, time +import unittest, logging, sys, lzma, time, platform successLogo = """ +----------------------------------+ @@ -145,7 +145,14 @@ def main(): ######################################### # resource_2 ######################################### + cleanUp() verifySingleJson("%s/issue_59/recovery.json" % resDir2, func = lambda: shutil.rmtree("build/unzip_boot/root", ignore_errors = False)) + # Issue 71: dtbo + if platform.system() != "Darwin": + verifySingleDir(resDir2, "issue_71") + verifySingleDir(resDir2, "issue_71/redfin") + else: + log.info("dtbo not fully supported on MacOS, skip testing") log.info(successLogo) diff --git a/src/integrationTest/resources_2 b/src/integrationTest/resources_2 index 4957dc9..f79ff70 160000 --- a/src/integrationTest/resources_2 +++ b/src/integrationTest/resources_2 @@ -1 +1 @@ -Subproject commit 4957dc9c53ea905f28b82c8ee65f738b6a88297c +Subproject commit f79ff7099a342261444797ecb4054806c9dcca22