Issue #71: refine dtbo unpack/pack

dtbo unpack:
    save image info
    decompile dtb to dts and yaml dts
    print summary
dtbo pack:
    compile dts to dtb
    print summary
boot v0-v4 pack:
    print summary
pull/80/head v12.0
cfig 4 years ago
parent 1e2592c1c4
commit a358bd6a7b
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -87,7 +87,7 @@ jobs:
run: python -c "import sys; print(sys.version)" run: python -c "import sys; print(sys.version)"
- name: choco - name: choco
run: choco install openssl run: choco install openssl dtc-msys2
- name: Unit Test - name: Unit Test
run: ./gradlew.bat check && ./gradlew.bat clean run: ./gradlew.bat check && ./gradlew.bat clean

@ -8,14 +8,15 @@ A tool for reverse engineering Android ROM images.
#### install required packages #### 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` 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 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. 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. 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 #### Parsing and packing

@ -58,6 +58,7 @@ application {
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().all {
kotlinOptions { kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
jvmTarget = "11" jvmTarget = "11"
} }
} }

@ -147,7 +147,11 @@ class AndroidCpio {
entry.statMode = itemConfig[0].statMode entry.statMode = itemConfig[0].statMode
} }
else -> { 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
} }
} }
} }

@ -16,12 +16,13 @@ package cfig.bootimg.v2
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.utils.EnvironmentVerifier
import cfig.bootimg.Common import cfig.bootimg.Common
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
@ -516,4 +517,14 @@ data class BootV2(
} }
return this return this
} }
fun printPackSummary(): BootV2 {
VendorBoot.printPackSummary(info.output)
return this
}
fun updateVbmeta(): BootV2 {
Avb.updateVbmeta(info.output)
return this
}
} }

@ -290,6 +290,16 @@ data class BootV3(
return this return this
} }
fun printPackSummary(): BootV3 {
VendorBoot.printPackSummary(info.output)
return this
}
fun updateVbmeta(): BootV3 {
Avb.updateVbmeta(info.output)
return this
}
private fun toCommandLine(): CommandLine { private fun toCommandLine(): CommandLine {
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).let { ret -> return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).let { ret ->

@ -208,6 +208,30 @@ data class VendorBoot(
ret.info.imageSize = File(fileName).length() ret.info.imageSize = File(fileName).length()
return ret 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 { fun pack(): VendorBoot {
@ -290,6 +314,11 @@ data class VendorBoot(
return this return this
} }
fun updateVbmeta(): VendorBoot {
Avb.updateVbmeta(info.output)
return this
}
private fun toHeader(): VendorBootHeader { private fun toHeader(): VendorBootHeader {
return VendorBootHeader( return VendorBootHeader(
headerVersion = info.headerVersion, headerVersion = info.headerVersion,
@ -352,7 +381,7 @@ data class VendorBoot(
return this return this
} }
fun printSummary(): VendorBoot { fun printUnpackSummary(): VendorBoot {
val tableHeader = AsciiTable().apply { val tableHeader = AsciiTable().apply {
addRule() addRule()
addRow("What", "Where") addRow("What", "Where")
@ -399,6 +428,11 @@ data class VendorBoot(
return this return this
} }
fun printPackSummary(): VendorBoot {
printPackSummary(info.output)
return this
}
private fun toCommandLine(): CommandLine { private fun toCommandLine(): CommandLine {
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).apply { return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).apply {

@ -19,7 +19,6 @@ import cfig.Avb
import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.Common.Companion.probeHeaderVersion
import cfig.bootimg.v2.BootV2 import cfig.bootimg.v2.BootV2
import cfig.bootimg.v3.BootV3 import cfig.bootimg.v3.BootV3
import cfig.helper.Helper
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -29,7 +28,6 @@ import java.io.FileInputStream
class BootImgParser : IPackable { class BootImgParser : IPackable {
override val loopNo: Int override val loopNo: Int
get() = 0 get() = 0
private val workDir = Helper.prop("workDir")
override fun capabilities(): List<String> { override fun capabilities(): List<String> {
return listOf("^boot(-debug)?\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$") return listOf("^boot(-debug)?\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$")
@ -57,7 +55,7 @@ class BootImgParser : IPackable {
} }
override fun pack(fileName: String) { 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") log.info("Loading config from $cfgFile")
if (!File(cfgFile).exists()) { if (!File(cfgFile).exists()) {
val tab = AsciiTable().let { val tab = AsciiTable().let {
@ -74,24 +72,16 @@ class BootImgParser : IPackable {
ObjectMapper().readValue(File(cfgFile), BootV2::class.java) ObjectMapper().readValue(File(cfgFile), BootV2::class.java)
.pack() .pack()
.sign() .sign()
.updateVbmeta()
.printPackSummary()
3, 4 -> 3, 4 ->
ObjectMapper().readValue(File(cfgFile), BootV3::class.java) ObjectMapper().readValue(File(cfgFile), BootV3::class.java)
.pack() .pack()
.sign(fileName) .sign(fileName)
.let { .updateVbmeta()
val tab = AsciiTable().let { tab -> .printPackSummary()
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())
}
else -> throw IllegalArgumentException("do not support header version $hv") else -> throw IllegalArgumentException("do not support header version $hv")
} }
Avb.updateVbmeta(fileName)
} }
override fun flash(fileName: String, deviceName: String) { override fun flash(fileName: String, deviceName: String) {

@ -14,14 +14,14 @@
package cfig.packable package cfig.packable
import avb.blob.Footer
import cfig.utils.EnvironmentVerifier
import cfig.utils.DTC
import cfig.helper.Helper import cfig.helper.Helper
import cfig.utils.DTC
import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import utils.Dtbo
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.* import java.util.*
@ -34,7 +34,6 @@ class DtboParser(val workDir: File) : IPackable {
private val log = LoggerFactory.getLogger(DtboParser::class.java) private val log = LoggerFactory.getLogger(DtboParser::class.java)
private val envv = EnvironmentVerifier() private val envv = EnvironmentVerifier()
private val outDir = Helper.prop("workDir")
private val dtboMaker = Helper.prop("dtboMaker") private val dtboMaker = Helper.prop("dtboMaker")
override fun capabilities(): List<String> { override fun capabilities(): List<String> {
@ -43,32 +42,39 @@ class DtboParser(val workDir: File) : IPackable {
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
cleanUp() cleanUp()
val dtbPath = File("$outDir/dtb").path Dtbo.parse(fileName)
val headerPath = File("$outDir/dtbo.header").path .unpack(outDir)
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" .extractVBMeta()
val cmd = CommandLine.parse("$cmdPrefix$dtboMaker dump $fileName").let { .printSummary()
it.addArguments("--dtb $dtbPath") }
it.addArguments("--output $headerPath")
}
execInDirectory(cmd, this.workDir)
val props = Properties().apply { override fun pack(fileName: String) {
FileInputStream(File(headerPath)).use { fis -> ObjectMapper().readValue(File(outDir + "dtbo.json"), Dtbo::class.java)
load(fis) .pack()
} .sign()
} .updateVbmeta()
if (envv.hasDtc) { .printPackSummary()
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 override fun `@verify`(fileName: String) {
DTC().decompile(inputDtb, outputSrc) 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) { if (!envv.hasDtc) {
log.error("'dtc' is unavailable, task aborted") log.error("'dtc' is unavailable, task aborted")
return return
@ -91,33 +97,31 @@ class DtboParser(val workDir: File) : IPackable {
execInDirectory(cmd, this.workDir) execInDirectory(cmd, this.workDir)
} }
override fun `@verify`(fileName: String) { @Deprecated("for debugging purpose only")
super.`@verify`(fileName) 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 val props = Properties().apply {
fun `@footer`(fileName: String) { FileInputStream(File(headerPath)).use { fis ->
FileInputStream(fileName).use { fis -> load(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")
} }
} }
} if (envv.hasDtc) {
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
private fun execInDirectory(cmd: CommandLine, inWorkDir: File) { val inputDtb = "$dtbPath.$i"
DefaultExecutor().let { val outputSrc = File(outDir + "/" + File(inputDtb).name + ".src").path
it.workingDirectory = inWorkDir DTC().decompile(inputDtb, outputSrc)
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")
} }
} }
} }

@ -25,6 +25,9 @@ import java.io.File
interface IPackable { interface IPackable {
val loopNo: Int val loopNo: Int
val outDir: String
get() = Helper.prop("workDir")
fun capabilities(): List<String> { fun capabilities(): List<String> {
return listOf("^dtbo\\.img$") return listOf("^dtbo\\.img$")
} }

@ -16,7 +16,6 @@ package cfig.packable
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.helper.Helper
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@ -33,7 +32,7 @@ class VBMetaParser: IPackable {
} }
override fun cleanUp() { override fun cleanUp() {
File(Helper.prop("workDir")).mkdirs() File(outDir).mkdirs()
} }
override fun unpack(fileName: String) { override fun unpack(fileName: String) {

@ -14,8 +14,6 @@
package cfig.packable package cfig.packable
import cfig.Avb
import cfig.helper.Helper
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -24,7 +22,6 @@ import java.io.File
class VendorBootParser : IPackable { class VendorBootParser : IPackable {
override val loopNo: Int = 0 override val loopNo: Int = 0
private val log = LoggerFactory.getLogger(VendorBootParser::class.java) private val log = LoggerFactory.getLogger(VendorBootParser::class.java)
private val workDir = Helper.prop("workDir")
override fun capabilities(): List<String> { override fun capabilities(): List<String> {
return listOf("^vendor_boot(-debug)?\\.img$") return listOf("^vendor_boot(-debug)?\\.img$")
} }
@ -32,20 +29,21 @@ class VendorBootParser : IPackable {
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
cleanUp() cleanUp()
val vb = VendorBoot val vb = VendorBoot
.parse(fileName) .parse(fileName)
.extractImages() .extractImages()
.extractVBMeta() .extractVBMeta()
.printSummary() .printUnpackSummary()
log.debug(vb.toString()) log.debug(vb.toString())
} }
override fun pack(fileName: String) { 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") log.info("Loading config from $cfgFile")
ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java) ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java)
.pack() .pack()
.sign() .sign()
Avb.updateVbmeta(fileName) .updateVbmeta()
.printPackSummary()
} }
override fun `@verify`(fileName: String) { override fun `@verify`(fileName: String) {

@ -23,17 +23,16 @@ class DTC {
fun decompile(dtbFile: String, outFile: String): Boolean { fun decompile(dtbFile: String, outFile: String): Boolean {
log.info("parsing DTB: $dtbFile") log.info("parsing DTB: $dtbFile")
val cmd = CommandLine.parse("dtc -q -I dtb -O dts").let { //CommandLine.parse("fdtdump").let {
it.addArguments("$dtbFile") // it.addArguments("$dtbFile")
it.addArguments("-o $outFile") //}
} //dtb-> dts
CommandLine.parse("fdtdump").let {
it.addArguments("$dtbFile")
}
DefaultExecutor().let { DefaultExecutor().let {
try { try {
val cmd = CommandLine.parse("dtc -q -I dtb -O dts").apply {
addArguments(dtbFile)
addArguments("-o $outFile")
}
it.execute(cmd) it.execute(cmd)
log.info(cmd.toString()) log.info(cmd.toString())
} catch (e: org.apache.commons.exec.ExecuteException) { } catch (e: org.apache.commons.exec.ExecuteException) {
@ -41,13 +40,27 @@ class DTC {
return false 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 return true
} }
fun compile(dtsFile: String, outFile: String): Boolean { fun compile(dtsFile: String, outFile: String): Boolean {
log.info("compiling DTS: $dtsFile") log.info("compiling DTS: $dtsFile")
val cmd = CommandLine.parse("dtc -q -I dts -O dtb").let { val cmd = CommandLine.parse("dtc -q -I dts -O dtb").let {
it.addArguments("$dtsFile") it.addArguments(dtsFile)
it.addArguments("-o $outFile") it.addArguments("-o $outFile")
} }

@ -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<DeviceTreeTableEntry> = 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
}
}

@ -48,6 +48,9 @@ dependencies {
} }
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().all {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" kotlinOptions {
kotlinOptions.jvmTarget = "11" freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
jvmTarget = "11"
}
} }

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import shutil, os.path, json, subprocess, hashlib, glob import shutil, os.path, json, subprocess, hashlib, glob
import unittest, logging, sys, lzma, time import unittest, logging, sys, lzma, time, platform
successLogo = """ successLogo = """
+----------------------------------+ +----------------------------------+
@ -145,7 +145,14 @@ def main():
######################################### #########################################
# resource_2 # resource_2
######################################### #########################################
cleanUp()
verifySingleJson("%s/issue_59/recovery.json" % resDir2, func = lambda: shutil.rmtree("build/unzip_boot/root", ignore_errors = False)) 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) log.info(successLogo)

@ -1 +1 @@
Subproject commit 4957dc9c53ea905f28b82c8ee65f738b6a88297c Subproject commit f79ff7099a342261444797ecb4054806c9dcca22
Loading…
Cancel
Save