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 summarypull/80/head v12.0
parent
1e2592c1c4
commit
a358bd6a7b
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 4957dc9c53ea905f28b82c8ee65f738b6a88297c
|
Subproject commit f79ff7099a342261444797ecb4054806c9dcca22
|
Loading…
Reference in New Issue