Skip to content

Commit a358bd6

Browse files
committed
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
1 parent 1e2592c commit a358bd6

File tree

17 files changed

+432
-96
lines changed

17 files changed

+432
-96
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ jobs:
8787
run: python -c "import sys; print(sys.version)"
8888

8989
- name: choco
90-
run: choco install openssl
90+
run: choco install openssl dtc-msys2
9191

9292
- name: Unit Test
9393
run: ./gradlew.bat check && ./gradlew.bat clean

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ A tool for reverse engineering Android ROM images.
88

99
#### install required packages
1010

11-
Mac: `brew install lz4 xz dtc`
12-
1311
Linux: `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python3 python-is-python3`
1412

13+
Mac: `brew install lz4 xz dtc`
14+
1515
Windows Subsystem for Linux(WSL): `sudo apt install git device-tree-compiler lz4 xz-utils zlib1g-dev openjdk-11-jdk gcc g++ python`
1616

1717
Windows: Make sure you have `python3`, `JDK9+` and `openssl` properly installed.
1818
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.
19+
Or install them with chocolate: `choco install openssl dtc-msys2`
1920

2021
#### Parsing and packing
2122

bbootimg/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ application {
5858
tasks.withType<KotlinCompile>().all {
5959
kotlinOptions {
6060
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
61+
freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
6162
jvmTarget = "11"
6263
}
6364
}

bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ class AndroidCpio {
147147
entry.statMode = itemConfig[0].statMode
148148
}
149149
else -> {
150-
throw IllegalArgumentException("${entry.name} has multiple exact-match fsConfig")
150+
//Issue #73: https://github.com/cfig/Android_boot_image_editor/issues/73
151+
//Reason: cpio may have multiple entries with the same name, that's ugly!
152+
//throw IllegalArgumentException("${entry.name} has multiple exact-match fsConfig")
153+
log.warn("${entry.name} has multiple exact-match fsConfig")
154+
entry.statMode = itemConfig[0].statMode
151155
}
152156
}
153157
}

bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ package cfig.bootimg.v2
1616

1717
import avb.AVBInfo
1818
import cfig.Avb
19-
import cfig.utils.EnvironmentVerifier
2019
import cfig.bootimg.Common
2120
import cfig.bootimg.Common.Companion.deleleIfExists
2221
import cfig.bootimg.Signer
22+
import cfig.bootimg.v3.VendorBoot
2323
import cfig.helper.Helper
2424
import cfig.packable.VBMetaParser
25+
import cfig.utils.EnvironmentVerifier
2526
import com.fasterxml.jackson.databind.ObjectMapper
2627
import de.vandermeer.asciitable.AsciiTable
2728
import org.apache.commons.exec.CommandLine
@@ -516,4 +517,14 @@ data class BootV2(
516517
}
517518
return this
518519
}
520+
521+
fun printPackSummary(): BootV2 {
522+
VendorBoot.printPackSummary(info.output)
523+
return this
524+
}
525+
526+
fun updateVbmeta(): BootV2 {
527+
Avb.updateVbmeta(info.output)
528+
return this
529+
}
519530
}

bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt

+10
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ data class BootV3(
290290
return this
291291
}
292292

293+
fun printPackSummary(): BootV3 {
294+
VendorBoot.printPackSummary(info.output)
295+
return this
296+
}
297+
298+
fun updateVbmeta(): BootV3 {
299+
Avb.updateVbmeta(info.output)
300+
return this
301+
}
302+
293303
private fun toCommandLine(): CommandLine {
294304
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
295305
return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).let { ret ->

bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt

+35-1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,30 @@ data class VendorBoot(
208208
ret.info.imageSize = File(fileName).length()
209209
return ret
210210
}
211+
212+
fun printPackSummary(imageName: String) {
213+
val tableHeader = AsciiTable().apply {
214+
addRule()
215+
addRow("What", "Where")
216+
addRule()
217+
}
218+
val tab = AsciiTable().let {
219+
it.addRule()
220+
it.addRow("re-packed $imageName", "$imageName.signed")
221+
it.addRule()
222+
it
223+
}
224+
if (File("vbmeta.img").exists()) {
225+
if (File("vbmeta.img.signed").exists()) {
226+
tab.addRow("re-packed vbmeta", "vbmeta.img.signed")
227+
} else {
228+
tab.addRow("re-packed vbmeta", "-")
229+
}
230+
tab.addRule()
231+
}
232+
log.info("\n\t\t\tPack Summary of ${imageName}\n{}\n{}", tableHeader.render(), tab.render())
233+
}
234+
211235
}
212236

213237
fun pack(): VendorBoot {
@@ -290,6 +314,11 @@ data class VendorBoot(
290314
return this
291315
}
292316

317+
fun updateVbmeta(): VendorBoot {
318+
Avb.updateVbmeta(info.output)
319+
return this
320+
}
321+
293322
private fun toHeader(): VendorBootHeader {
294323
return VendorBootHeader(
295324
headerVersion = info.headerVersion,
@@ -352,7 +381,7 @@ data class VendorBoot(
352381
return this
353382
}
354383

355-
fun printSummary(): VendorBoot {
384+
fun printUnpackSummary(): VendorBoot {
356385
val tableHeader = AsciiTable().apply {
357386
addRule()
358387
addRow("What", "Where")
@@ -399,6 +428,11 @@ data class VendorBoot(
399428
return this
400429
}
401430

431+
fun printPackSummary(): VendorBoot {
432+
printPackSummary(info.output)
433+
return this
434+
}
435+
402436
private fun toCommandLine(): CommandLine {
403437
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
404438
return CommandLine.parse(cmdPrefix + Helper.prop("mkbootimg")).apply {

bbootimg/src/main/kotlin/packable/BootImgParser.kt

+5-15
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import cfig.Avb
1919
import cfig.bootimg.Common.Companion.probeHeaderVersion
2020
import cfig.bootimg.v2.BootV2
2121
import cfig.bootimg.v3.BootV3
22-
import cfig.helper.Helper
2322
import com.fasterxml.jackson.databind.ObjectMapper
2423
import de.vandermeer.asciitable.AsciiTable
2524
import org.slf4j.LoggerFactory
@@ -29,7 +28,6 @@ import java.io.FileInputStream
2928
class BootImgParser : IPackable {
3029
override val loopNo: Int
3130
get() = 0
32-
private val workDir = Helper.prop("workDir")
3331

3432
override fun capabilities(): List<String> {
3533
return listOf("^boot(-debug)?\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$")
@@ -57,7 +55,7 @@ class BootImgParser : IPackable {
5755
}
5856

5957
override fun pack(fileName: String) {
60-
val cfgFile = workDir + fileName.removeSuffix(".img") + ".json"
58+
val cfgFile = outDir + fileName.removeSuffix(".img") + ".json"
6159
log.info("Loading config from $cfgFile")
6260
if (!File(cfgFile).exists()) {
6361
val tab = AsciiTable().let {
@@ -74,24 +72,16 @@ class BootImgParser : IPackable {
7472
ObjectMapper().readValue(File(cfgFile), BootV2::class.java)
7573
.pack()
7674
.sign()
75+
.updateVbmeta()
76+
.printPackSummary()
7777
3, 4 ->
7878
ObjectMapper().readValue(File(cfgFile), BootV3::class.java)
7979
.pack()
8080
.sign(fileName)
81-
.let {
82-
val tab = AsciiTable().let { tab ->
83-
tab.addRule()
84-
val outFileSuffix =
85-
if (File(Avb.getJsonFileName(it.info.output)).exists()) ".signed" else ".clear"
86-
tab.addRow("${it.info.output}${outFileSuffix} is ready")
87-
tab.addRule()
88-
tab
89-
}
90-
log.info("\n{}", tab.render())
91-
}
81+
.updateVbmeta()
82+
.printPackSummary()
9283
else -> throw IllegalArgumentException("do not support header version $hv")
9384
}
94-
Avb.updateVbmeta(fileName)
9585
}
9686

9787
override fun flash(fileName: String, deviceName: String) {

bbootimg/src/main/kotlin/packable/DtboParser.kt

+52-48
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414

1515
package cfig.packable
1616

17-
import avb.blob.Footer
18-
import cfig.utils.EnvironmentVerifier
19-
import cfig.utils.DTC
2017
import cfig.helper.Helper
18+
import cfig.utils.DTC
19+
import cfig.utils.EnvironmentVerifier
2120
import com.fasterxml.jackson.databind.ObjectMapper
2221
import org.apache.commons.exec.CommandLine
2322
import org.apache.commons.exec.DefaultExecutor
2423
import org.slf4j.LoggerFactory
24+
import utils.Dtbo
2525
import java.io.File
2626
import java.io.FileInputStream
2727
import java.util.*
@@ -34,7 +34,6 @@ class DtboParser(val workDir: File) : IPackable {
3434

3535
private val log = LoggerFactory.getLogger(DtboParser::class.java)
3636
private val envv = EnvironmentVerifier()
37-
private val outDir = Helper.prop("workDir")
3837
private val dtboMaker = Helper.prop("dtboMaker")
3938

4039
override fun capabilities(): List<String> {
@@ -43,32 +42,39 @@ class DtboParser(val workDir: File) : IPackable {
4342

4443
override fun unpack(fileName: String) {
4544
cleanUp()
46-
val dtbPath = File("$outDir/dtb").path
47-
val headerPath = File("$outDir/dtbo.header").path
48-
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
49-
val cmd = CommandLine.parse("$cmdPrefix$dtboMaker dump $fileName").let {
50-
it.addArguments("--dtb $dtbPath")
51-
it.addArguments("--output $headerPath")
52-
}
53-
execInDirectory(cmd, this.workDir)
45+
Dtbo.parse(fileName)
46+
.unpack(outDir)
47+
.extractVBMeta()
48+
.printSummary()
49+
}
5450

55-
val props = Properties().apply {
56-
FileInputStream(File(headerPath)).use { fis ->
57-
load(fis)
58-
}
59-
}
60-
if (envv.hasDtc) {
61-
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
62-
val inputDtb = "$dtbPath.$i"
63-
val outputSrc = File(outDir + "/" + File(inputDtb).name + ".src").path
64-
DTC().decompile(inputDtb, outputSrc)
51+
override fun pack(fileName: String) {
52+
ObjectMapper().readValue(File(outDir + "dtbo.json"), Dtbo::class.java)
53+
.pack()
54+
.sign()
55+
.updateVbmeta()
56+
.printPackSummary()
57+
}
58+
59+
override fun `@verify`(fileName: String) {
60+
super.`@verify`(fileName)
61+
}
62+
63+
private fun execInDirectory(cmd: CommandLine, inWorkDir: File) {
64+
DefaultExecutor().let {
65+
it.workingDirectory = inWorkDir
66+
try {
67+
log.info(cmd.toString())
68+
it.execute(cmd)
69+
} catch (e: org.apache.commons.exec.ExecuteException) {
70+
log.error("can not exec command")
71+
return
6572
}
66-
} else {
67-
log.error("'dtc' is unavailable, task aborted")
6873
}
6974
}
7075

71-
override fun pack(fileName: String) {
76+
@Deprecated("for debugging purpose only")
77+
fun packLegacy(fileName: String) {
7278
if (!envv.hasDtc) {
7379
log.error("'dtc' is unavailable, task aborted")
7480
return
@@ -91,33 +97,31 @@ class DtboParser(val workDir: File) : IPackable {
9197
execInDirectory(cmd, this.workDir)
9298
}
9399

94-
override fun `@verify`(fileName: String) {
95-
super.`@verify`(fileName)
96-
}
100+
@Deprecated("for debugging purpose only")
101+
fun unpackLegacy(fileName: String) {
102+
cleanUp()
103+
val dtbPath = File("$outDir/dtb").path
104+
val headerPath = File("$outDir/dtbo.header").path
105+
val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else ""
106+
val cmd = CommandLine.parse("$cmdPrefix$dtboMaker dump $fileName").let {
107+
it.addArguments("--dtb $dtbPath")
108+
it.addArguments("--output $headerPath")
109+
}
110+
execInDirectory(cmd, this.workDir)
97111

98-
// invoked solely by reflection
99-
fun `@footer`(fileName: String) {
100-
FileInputStream(fileName).use { fis ->
101-
fis.skip(File(fileName).length() - Footer.SIZE)
102-
try {
103-
val footer = Footer(fis)
104-
log.info("\n" + ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(footer))
105-
} catch (e: IllegalArgumentException) {
106-
log.info("image $fileName has no AVB Footer")
112+
val props = Properties().apply {
113+
FileInputStream(File(headerPath)).use { fis ->
114+
load(fis)
107115
}
108116
}
109-
}
110-
111-
private fun execInDirectory(cmd: CommandLine, inWorkDir: File) {
112-
DefaultExecutor().let {
113-
it.workingDirectory = inWorkDir
114-
try {
115-
log.info(cmd.toString())
116-
it.execute(cmd)
117-
} catch (e: org.apache.commons.exec.ExecuteException) {
118-
log.error("can not exec command")
119-
return
117+
if (envv.hasDtc) {
118+
for (i in 0 until Integer.parseInt(props.getProperty("dt_entry_count"))) {
119+
val inputDtb = "$dtbPath.$i"
120+
val outputSrc = File(outDir + "/" + File(inputDtb).name + ".src").path
121+
DTC().decompile(inputDtb, outputSrc)
120122
}
123+
} else {
124+
log.error("'dtc' is unavailable, task aborted")
121125
}
122126
}
123127
}

bbootimg/src/main/kotlin/packable/IPackable.kt

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import java.io.File
2525

2626
interface IPackable {
2727
val loopNo: Int
28+
val outDir: String
29+
get() = Helper.prop("workDir")
30+
2831
fun capabilities(): List<String> {
2932
return listOf("^dtbo\\.img$")
3033
}

bbootimg/src/main/kotlin/packable/VBMetaParser.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package cfig.packable
1616

1717
import avb.AVBInfo
1818
import cfig.Avb
19-
import cfig.helper.Helper
2019
import com.fasterxml.jackson.databind.ObjectMapper
2120
import org.slf4j.LoggerFactory
2221
import java.io.File
@@ -33,7 +32,7 @@ class VBMetaParser: IPackable {
3332
}
3433

3534
override fun cleanUp() {
36-
File(Helper.prop("workDir")).mkdirs()
35+
File(outDir).mkdirs()
3736
}
3837

3938
override fun unpack(fileName: String) {

0 commit comments

Comments
 (0)