massive update for upcoming Android Pi
- update mkbootimg: AOSP commit 147b355bb34cbd457a260c585be93193cef43b8e - migrate all code from groovy to kotlin - support DTBO and header_version, resolve #12 - upate README.expert.md too - support AVBpull/20/head
parent
e3c3386571
commit
5d97d46c62
@ -1,69 +0,0 @@
|
||||
apply plugin: 'groovy'
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||
compile group: 'net.sf.jopt-simple', name: 'jopt-simple', version: '5.0.2'
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
}
|
||||
|
||||
task abootimg(type: Jar, dependsOn:['build']) {
|
||||
from files(sourceSets.main.output.classesDir)
|
||||
from files("build/classes/groovy/main")
|
||||
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
|
||||
|
||||
baseName = 'abootimg'
|
||||
manifest {
|
||||
attributes 'Main-Class': 'cfig.bootimg.abootimg'
|
||||
}
|
||||
}
|
||||
|
||||
task mkbootimg(type: Jar, dependsOn:['build']) {
|
||||
from files(sourceSets.main.output.classesDir)
|
||||
from files("build/classes/groovy/main")
|
||||
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
|
||||
|
||||
baseName = 'mkbootimg'
|
||||
manifest {
|
||||
attributes 'Main-Class': 'cfig.bootimg.mkbootimg'
|
||||
}
|
||||
}
|
||||
|
||||
task repack(type: Jar, dependsOn:['build']) {
|
||||
from files(sourceSets.main.output.classesDir)
|
||||
from files("build/classes/groovy/main")
|
||||
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
|
||||
|
||||
baseName = 'repack'
|
||||
manifest {
|
||||
attributes 'Main-Class': 'cfig.bootimg.repack'
|
||||
}
|
||||
}
|
||||
|
||||
//call Google's 'mkbootimg' program
|
||||
task pack_clear(type: JavaExec, dependsOn:[':pack_ramdisk_and_gz', 'build']) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = "cfig.bootimg.repack_with_cmd"
|
||||
args rootProject.outClearIMg, rootProject.rootWorkDir, rootProject.mkbootimgBin
|
||||
}
|
||||
|
||||
//call our 'mkbootimg.groovy'
|
||||
task pack_clear2(type: JavaExec, dependsOn:[':pack_ramdisk_and_gz', 'mkbootimg']) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = "cfig.bootimg.repack_with_cmd"
|
||||
args rootProject.outClearIMg + "2" , rootProject.rootWorkDir, "java -jar build/libs/mkbootimg.jar"
|
||||
}
|
||||
|
||||
//directly calls engine of 'mkbootimg.groovy' from 'repack'
|
||||
task pack_clear3(type: JavaExec, dependsOn: [':pack_ramdisk_and_gz', 'repack']) {
|
||||
classpath = files("build/libs/repack.jar")
|
||||
main = 'cfig.bootimg.repack'
|
||||
maxHeapSize '512m'
|
||||
args rootProject.outClearIMg + "3", rootProject.rootWorkDir
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Created by yu at 09:57 on 2016-06-17
|
||||
*/
|
||||
@groovy.transform.TypeChecked
|
||||
@ToString(includeNames = true, includeFields = true, excludes = "toCommandLine, CArgs")
|
||||
class CArgs {
|
||||
public String kernel;
|
||||
public String ramdisk;
|
||||
public String output;
|
||||
public String cfg;
|
||||
public String board;
|
||||
public String second;
|
||||
public String cmdline;
|
||||
public String os_version;
|
||||
public String os_patch_level;
|
||||
public long base;
|
||||
public long kernel_offset;
|
||||
public long ramdisk_offset;
|
||||
public long second_offset;
|
||||
public int pagesize;
|
||||
public long tags_offset;
|
||||
public boolean id;
|
||||
|
||||
public CArgs() {
|
||||
kernel = "kernel";
|
||||
ramdisk = "ramdisk.img.gz";
|
||||
second = "second";
|
||||
output = "boot.img";
|
||||
cfg = "bootimg.json";
|
||||
}
|
||||
|
||||
public List<String> toCommandList() {
|
||||
List<String> ret = new ArrayList<String>();
|
||||
ret.add("--base");
|
||||
ret.add("0x" + Long.toHexString(base));
|
||||
ret.add("--kernel");
|
||||
ret.add(kernel);
|
||||
ret.add("--kernel_offset");
|
||||
ret.add("0x" + Long.toHexString(kernel_offset));
|
||||
if (null != ramdisk) {
|
||||
ret.add("--ramdisk");
|
||||
ret.add(ramdisk);
|
||||
}
|
||||
ret.add("--ramdisk_offset");
|
||||
ret.add("0x" + Long.toHexString(ramdisk_offset));
|
||||
if (null != second) {
|
||||
ret.add("--second");
|
||||
ret.add(second);
|
||||
}
|
||||
ret.add("--second_offset");
|
||||
ret.add("0x" + Long.toHexString(second_offset));
|
||||
if (null != board) {
|
||||
ret.add("--board");
|
||||
ret.add(board);
|
||||
}
|
||||
ret.add("--pagesize");
|
||||
ret.add(Integer.toString(pagesize));
|
||||
ret.add("--cmdline");
|
||||
ret.add(cmdline);
|
||||
if (null != os_version) {
|
||||
ret.add("--os_version");
|
||||
ret.add(os_version);
|
||||
}
|
||||
if (null != os_patch_level) {
|
||||
ret.add("--os_patch_level");
|
||||
ret.add(os_patch_level);
|
||||
}
|
||||
ret.add("--tags_offset");
|
||||
ret.add("0x" + Long.toHexString(tags_offset));
|
||||
if (id) {
|
||||
ret.add("--id");
|
||||
}
|
||||
ret.add("--output");
|
||||
ret.add(output);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Created by yu at 09:58 on 2016-06-17
|
||||
*/
|
||||
@ToString(includeNames=true, includeFields=true, includeSuper = true)
|
||||
class CImgInfo extends CArgs {
|
||||
public int kernel_len;
|
||||
public int ramdisk_len;
|
||||
public int second_len;
|
||||
public int kernel_pos;
|
||||
public int ramdisk_pos;
|
||||
public int second_pos;
|
||||
public byte[] hash;
|
||||
|
||||
public static CImgInfo fromJson(String outFile, String workDir) {
|
||||
CImgInfo aArg = new CImgInfo();
|
||||
//preset info
|
||||
aArg.kernel = workDir + File.separator + aArg.kernel;
|
||||
aArg.ramdisk = workDir + File.separator + aArg.ramdisk;
|
||||
aArg.second = workDir + File.separator + aArg.second;
|
||||
aArg.cfg = workDir + File.separator + aArg.cfg;
|
||||
aArg.output = outFile;
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
Map result = jsonSlurper.parseText(new File(aArg.cfg).text);
|
||||
|
||||
//arg info
|
||||
aArg.board = result.bootimg.args.board;
|
||||
aArg.cmdline = result.bootimg.args.cmdline;
|
||||
aArg.base = Long.decode(result.bootimg.args.base);
|
||||
aArg.kernel_offset = Long.decode(result.bootimg.args.kernel_offset);
|
||||
aArg.ramdisk_offset = Long.decode(result.bootimg.args.ramdisk_offset);
|
||||
aArg.second_offset = Long.decode(result.bootimg.args.second_offset);
|
||||
aArg.tags_offset = Long.decode(result.bootimg.args.tags_offset);
|
||||
aArg.id = true;
|
||||
aArg.pagesize = result.bootimg.args.pagesize;
|
||||
aArg.os_version = result.bootimg.args.os_version;
|
||||
aArg.os_patch_level = result.bootimg.args.os_patch_level;
|
||||
//image info
|
||||
aArg.kernel_len = Integer.decode(result.bootimg.img.kernel_len);
|
||||
aArg.ramdisk_len = Integer.decode(result.bootimg.img.ramdisk_len);
|
||||
aArg.second_len = Integer.decode(result.bootimg.img.second_len);
|
||||
//adjust preset info
|
||||
if (0 == aArg.ramdisk_len) {
|
||||
aArg.ramdisk = null;
|
||||
}
|
||||
if (0 == aArg.second_len) {
|
||||
aArg.second = null;
|
||||
}
|
||||
|
||||
return aArg;
|
||||
}
|
||||
|
||||
private String bytes2String(byte[] inData) {
|
||||
StringBuilder sb = new StringBuilder("");
|
||||
for (int i = 0; i < inData.length; i++) {
|
||||
sb.append(Integer.toString((inData[i] & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void toJson() {
|
||||
JsonBuilder jb = new JsonBuilder();
|
||||
String hashString = bytes2String(this.hash);
|
||||
jb.bootimg {
|
||||
args {
|
||||
base "0x" + Integer.toHexString((int)this.base);
|
||||
kernel_offset "0x" + Integer.toHexString((int)this.kernel_offset);
|
||||
ramdisk_offset "0x" + Integer.toHexString((int)this.ramdisk_offset);
|
||||
second_offset "0x" + Integer.toHexString((int)this.second_offset);
|
||||
tags_offset "0x" + Integer.toHexString((int)this.tags_offset);
|
||||
pagesize this.pagesize;
|
||||
board this.board;
|
||||
cmdline this.cmdline;
|
||||
os_version this.os_version;
|
||||
os_patch_level this.os_patch_level;
|
||||
id this.id;
|
||||
}
|
||||
img {
|
||||
kernel_pos "0x" + Integer.toHexString(this.kernel_pos);
|
||||
kernel_len "0x" + Integer.toHexString(this.kernel_len);
|
||||
ramdisk_pos "0x" + Integer.toHexString(this.ramdisk_pos);
|
||||
ramdisk_len "0x" + Integer.toHexString(this.ramdisk_len);
|
||||
second_pos "0x" + Integer.toHexString(this.second_pos);
|
||||
second_len "0x" + Integer.toHexString(this.second_len);
|
||||
hash hashString;
|
||||
}
|
||||
}
|
||||
FileWriter fw = new FileWriter(this.cfg);
|
||||
fw.write(jb.toPrettyString());
|
||||
fw.flush();
|
||||
fw.close();
|
||||
}
|
||||
}
|
@ -1,353 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* Created by yu at 10:52 on 2016-06-18
|
||||
*/
|
||||
@groovy.transform.CompileStatic
|
||||
class Packer {
|
||||
CArgs parse_cmdline(String[] inArgs) {
|
||||
OptionParser parser = new OptionParser();
|
||||
parser.accepts("kernel", "path to the kernel").withRequiredArg();
|
||||
parser.accepts("ramdisk", "path to the ramdisk").withRequiredArg();
|
||||
parser.accepts("second", "path to the 2nd bootloader").withRequiredArg();
|
||||
parser.accepts("cmdline", "extra arguments to be passed on the kernel command line").withRequiredArg();
|
||||
parser.accepts("base", "base address").withRequiredArg();
|
||||
parser.accepts("kernel_offset", "kernel offset").withRequiredArg();
|
||||
parser.accepts("ramdisk_offset", "ramdisk offset").withRequiredArg();
|
||||
parser.accepts("second_offset", "2nd bootloader offset").withRequiredArg();
|
||||
parser.accepts("os_version", "operating system version").withRequiredArg();
|
||||
parser.accepts("os_patch_level", "operating system patch level").withRequiredArg();
|
||||
parser.accepts("tags_offset", "tags offset").withRequiredArg();
|
||||
parser.accepts("board", "board name").withRequiredArg();
|
||||
parser.accepts("pagesize", "page size").withRequiredArg();
|
||||
parser.accepts("id", "print the image ID on standard output");
|
||||
parser.accepts("output", "output file name").withRequiredArg();
|
||||
|
||||
OptionSet options = parser.parse(inArgs)
|
||||
CArgs ret = new CArgs();
|
||||
|
||||
ret.kernel = options.valueOf("kernel")
|
||||
|
||||
ret.output = options.valueOf("output")
|
||||
|
||||
ret.ramdisk = options.valueOf("ramdisk")
|
||||
|
||||
ret.second = options.valueOf("second")
|
||||
|
||||
if (options.has("board")) {
|
||||
ret.board = options.valueOf("board")
|
||||
} else {
|
||||
ret.board = ""
|
||||
}
|
||||
|
||||
ret.id = options.has("id")
|
||||
|
||||
if (options.has("base")) {
|
||||
ret.base = Long.decode(String.valueOf(options.valueOf("base")))
|
||||
} else {
|
||||
ret.base = 0x10000000;
|
||||
}
|
||||
|
||||
if (options.has("kernel_offset")) {
|
||||
ret.kernel_offset = Long.decode(String.valueOf(options.valueOf("kernel_offset")))
|
||||
} else {
|
||||
ret.kernel_offset = 0x00008000;
|
||||
}
|
||||
|
||||
if (options.has("ramdisk_offset")) {
|
||||
ret.ramdisk_offset = Long.decode(String.valueOf(options.valueOf("ramdisk_offset")))
|
||||
} else {
|
||||
ret.ramdisk_offset = 0x01000000
|
||||
}
|
||||
|
||||
ret.os_version = options.valueOf("os_version")
|
||||
|
||||
ret.os_patch_level = options.valueOf("os_patch_level")
|
||||
|
||||
if (options.has("second_offset")) {
|
||||
ret.second_offset = Long.decode(String.valueOf(options.valueOf("second_offset")))
|
||||
} else {
|
||||
ret.second_offset = 0x00f00000
|
||||
}
|
||||
|
||||
if (options.has("tags_offset")) {
|
||||
ret.tags_offset = Long.decode(String.valueOf(options.valueOf("tags_offset")))
|
||||
} else {
|
||||
ret.tags_offset = 0x00000100
|
||||
}
|
||||
|
||||
if (options.has("pagesize")) {
|
||||
ret.pagesize = Integer.decode(String.valueOf(options.valueOf("pagesize")))
|
||||
} else {
|
||||
ret.pagesize = 2048
|
||||
}
|
||||
|
||||
if (options.has("cmdline")) {
|
||||
ret.cmdline = String.valueOf(options.valueOf("cmdline"))
|
||||
} else {
|
||||
ret.cmdline = ""
|
||||
}
|
||||
|
||||
if (ret.cmdline.length() > 1536) {
|
||||
println("cmdline length must <= 1536, current is " + ret.cmdline.length());
|
||||
printUsage(parser);
|
||||
}
|
||||
if (null == ret.kernel) {
|
||||
println("kernel must not be empty");
|
||||
printUsage(parser);
|
||||
}
|
||||
if (null == ret.output) {
|
||||
println("output file must not be empty");
|
||||
printUsage(parser);
|
||||
}
|
||||
if (ret.board.length() > 16) {
|
||||
println("board name length must <= 16")
|
||||
printUsage(parser);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
byte[] write_header(CArgs inArgs) {
|
||||
ByteBuffer bf = ByteBuffer.allocate(1024 * 32);
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
//header start
|
||||
bf.put("ANDROID!".getBytes())
|
||||
bf.putInt((int) new File(inArgs.kernel).length());
|
||||
bf.putInt((int)(inArgs.base + inArgs.kernel_offset));
|
||||
|
||||
if (null == inArgs.ramdisk) {
|
||||
bf.putInt(0)
|
||||
} else {
|
||||
bf.putInt((int) new File(inArgs.ramdisk).length());
|
||||
}
|
||||
|
||||
bf.putInt((int)(inArgs.base + inArgs.ramdisk_offset));
|
||||
|
||||
if (null == inArgs.second) {
|
||||
bf.putInt(0)
|
||||
} else {
|
||||
bf.putInt((int) new File(inArgs.second).length());
|
||||
}
|
||||
|
||||
bf.putInt((int)(inArgs.base + inArgs.second_offset));
|
||||
bf.putInt((int)(inArgs.base + inArgs.tags_offset));
|
||||
bf.putInt(inArgs.pagesize)
|
||||
bf.putInt(0);
|
||||
bf.putInt((parse_os_version(inArgs.os_version) << 11) | parse_os_patch_level(inArgs.os_patch_level))
|
||||
|
||||
if (null == inArgs.board) {
|
||||
bf.put(new byte[16]);
|
||||
} else {
|
||||
bf.put(inArgs.board.getBytes())
|
||||
bf.put(new byte[16 - inArgs.board.length()])
|
||||
}
|
||||
|
||||
bf.put(inArgs.cmdline.substring(0, Math.min(512, inArgs.cmdline.length())).getBytes())
|
||||
bf.put(new byte[512 - Math.min(512, inArgs.cmdline.length())])
|
||||
byte[] img_id = hashFile(inArgs.kernel, inArgs.ramdisk, inArgs.second)
|
||||
bf.put(img_id)
|
||||
bf.put(new byte[32 - img_id.length])
|
||||
|
||||
if (inArgs.cmdline.length() > 512) {
|
||||
bf.put(inArgs.cmdline.substring(512).getBytes())
|
||||
bf.put(new byte[1024 + 512 - inArgs.cmdline.length()])
|
||||
} else {
|
||||
bf.put(new byte[1024])
|
||||
}
|
||||
|
||||
//padding
|
||||
pad_file(bf, inArgs.pagesize)
|
||||
|
||||
//write
|
||||
FileOutputStream fos = new FileOutputStream(inArgs.output, false);
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
fos.close();
|
||||
|
||||
return img_id;
|
||||
}
|
||||
|
||||
void printUsage(OptionParser p) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write("Usage: mkbootimg <option>\n".getBytes());
|
||||
p.printHelpOn(out);
|
||||
System.out.println(out.toString());
|
||||
out.close();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
void write_padded_file(ByteBuffer inBF, String srcFile, int padding) {
|
||||
if (null == srcFile) {
|
||||
return;
|
||||
}
|
||||
InputStream is = new FileInputStream(new File(srcFile))
|
||||
int byteRead;
|
||||
byte[] dataRead = new byte[128]
|
||||
while (true) {
|
||||
byteRead = is.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break;
|
||||
}
|
||||
inBF.put(dataRead, 0, byteRead);
|
||||
}
|
||||
is.close();
|
||||
pad_file(inBF, padding)
|
||||
}
|
||||
|
||||
void pad_file(ByteBuffer inBF, int padding) {
|
||||
int pad = (padding - (inBF.position() & (padding - 1))) & (padding - 1);
|
||||
inBF.put(new byte[pad]);
|
||||
}
|
||||
|
||||
void write_data(CArgs inArgs) {
|
||||
ByteBuffer bf = ByteBuffer.allocate(1024 * 1024 * 64);
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
write_padded_file(bf, inArgs.kernel, inArgs.pagesize)
|
||||
write_padded_file(bf, inArgs.ramdisk, inArgs.pagesize)
|
||||
write_padded_file(bf, inArgs.second, inArgs.pagesize)
|
||||
|
||||
//write
|
||||
FileOutputStream fos = new FileOutputStream(inArgs.output, true);
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
fos.close();
|
||||
}
|
||||
|
||||
int parse_os_patch_level(String x) {
|
||||
if (null == x) {
|
||||
return 0;
|
||||
}
|
||||
int ret = 0
|
||||
Pattern pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})")
|
||||
Matcher matcher = pattern.matcher(x)
|
||||
if (matcher.find()) {
|
||||
int y = Integer.parseInt(matcher.group(1), 10) - 2000
|
||||
int m = Integer.parseInt(matcher.group(2), 10)
|
||||
// 7 bits allocated for the year, 4 bits for the month
|
||||
assert y >= 0 && y < 128
|
||||
assert m > 0 && m <= 12
|
||||
ret = (y << 4) | m
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid os_patch_level")
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int parse_os_version(String x) {
|
||||
int ret = 0;
|
||||
if (null != x) {
|
||||
Pattern pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?");
|
||||
Matcher m = pattern.matcher(x)
|
||||
if (m.find()) {
|
||||
int a = Integer.decode(m.group(1))
|
||||
int b = 0;
|
||||
int c = 0;
|
||||
if (m.groupCount() >= 2) {
|
||||
b = Integer.decode(m.group(2))
|
||||
}
|
||||
if (m.groupCount() == 3) {
|
||||
c = Integer.decode(m.group(3))
|
||||
}
|
||||
assert a < 128
|
||||
assert b < 128
|
||||
assert c < 128
|
||||
ret = ((a << 14) | (b << 7) | c)
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid os_version")
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void test() {
|
||||
ByteBuffer b2 = ByteBuffer.allocate(1024);
|
||||
b2.order(ByteOrder.LITTLE_ENDIAN);
|
||||
b2.putInt(Integer.MAX_VALUE); //4 bytes
|
||||
println("max: " + Integer.MAX_VALUE)
|
||||
println("min: " + Integer.MIN_VALUE)
|
||||
b2.putInt(0x11111111)
|
||||
b2.putInt(Integer.MIN_VALUE);
|
||||
b2.putInt(0x11111111)
|
||||
b2.put("welcome".getBytes())
|
||||
b2.put(new byte[5]);
|
||||
b2.putInt(0x11111111)
|
||||
b2.putInt(0);
|
||||
b2.putInt(0x11111111)
|
||||
//b2.put((byte)0);
|
||||
b2.flip();
|
||||
FileChannel fc2 = new FileOutputStream(new File("ftest"), false).getChannel();
|
||||
fc2.write(b2);
|
||||
fc2.close();
|
||||
|
||||
//ByteBuffer bf = ByteBuffer.allocate(1024 * 1024 * 50);
|
||||
//bf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
//bf.flip()
|
||||
//boolean append = false;
|
||||
//FileChannel fc = new FileOutputStream(new File("f1"), append).getChannel();
|
||||
//fc.write(bf);
|
||||
//fc.close();
|
||||
//
|
||||
//FileOutputStream stream = new FileOutputStream("f2");
|
||||
//stream.write(bf.array(), 0, bf.position())
|
||||
//stream.close();
|
||||
}
|
||||
|
||||
byte[] hashFile(String... inFiles) {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1")
|
||||
|
||||
for (String item : inFiles) {
|
||||
ByteBuffer itemBF = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (null == item) {
|
||||
md.update(itemBF.putInt(0).array())
|
||||
} else {
|
||||
InputStream is = new FileInputStream(new File(item))
|
||||
int byteRead;
|
||||
byte[] dataRead = new byte[128]
|
||||
while (true) {
|
||||
byteRead = is.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break;
|
||||
}
|
||||
md.update(dataRead, 0, byteRead)
|
||||
}
|
||||
is.close();
|
||||
md.update(itemBF.putInt((int) new File(item).length()).array())
|
||||
}
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
void dumpBytes(byte[] inData) {
|
||||
StringBuffer sb = new StringBuffer("");
|
||||
for (int i = 0; i < inData.length; i++) {
|
||||
sb.append(Integer.toString((inData[i] & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
println("0x" + sb.toString());
|
||||
}
|
||||
|
||||
void mkbootimg(CArgs theArgs) {
|
||||
byte[] img_id = write_header(theArgs)
|
||||
write_data(theArgs)
|
||||
if (theArgs.id) {
|
||||
ByteBuffer bf = ByteBuffer.allocate(32);
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bf.put(img_id);
|
||||
bf.put(new byte[32 - img_id.length])
|
||||
dumpBytes(bf.array());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
/**
|
||||
* Created by yu at 10:58 on 2016-06-18
|
||||
*/
|
||||
@groovy.transform.CompileStatic
|
||||
class Parser {
|
||||
|
||||
int readInt(InputStream is) {
|
||||
ByteBuffer bf = ByteBuffer.allocate(128);
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
byte[] data4 = new byte[4];
|
||||
assert 4 == is.read(data4)
|
||||
bf.clear();
|
||||
bf.put(data4);
|
||||
bf.flip();
|
||||
return bf.getInt();
|
||||
}
|
||||
|
||||
byte[] readBytes(InputStream is, int len) {
|
||||
byte[] data4 = new byte[len];
|
||||
assert len == is.read(data4)
|
||||
return data4;
|
||||
}
|
||||
|
||||
String unparse_os_version(int x) {
|
||||
int a = x >> 14;
|
||||
int b = (x - (a << 14)) >> 7;
|
||||
int c = x & 0x7f;
|
||||
|
||||
return String.format("%d.%d.%d", a, b, c);
|
||||
}
|
||||
|
||||
String unparse_os_patch_level(int x) {
|
||||
int y = x >> 4;
|
||||
int m = x & 0xf;
|
||||
y += 2000;
|
||||
|
||||
return String.format("%d-%02d-%02d", y, m, 0);
|
||||
}
|
||||
|
||||
int get_header_len(int pagesize) {
|
||||
int pad = (pagesize - (1632 & (pagesize - 1))) & (pagesize - 1);
|
||||
return pad + 1632;
|
||||
}
|
||||
|
||||
int get_pad_len(int position, int pagesize) {
|
||||
return (pagesize - (position & (pagesize - 1))) & (pagesize - 1);
|
||||
}
|
||||
|
||||
void parse_header(String fileName, CImgInfo inImgInfo) {
|
||||
InputStream is = new FileInputStream(new File(fileName))
|
||||
assert Arrays.equals(readBytes(is, 8), "ANDROID!".getBytes())
|
||||
inImgInfo.kernel_len = readInt(is);
|
||||
inImgInfo.kernel_offset = readInt(is);
|
||||
inImgInfo.ramdisk_len = readInt(is);
|
||||
inImgInfo.ramdisk_offset = readInt(is);
|
||||
inImgInfo.second_len = readInt(is);
|
||||
inImgInfo.second_offset = readInt(is);
|
||||
inImgInfo.tags_offset = readInt(is);
|
||||
inImgInfo.pagesize = readInt(is);
|
||||
assert 0 == readInt(is) //reserved
|
||||
int os_and_patch = readInt(is)
|
||||
if (0 != os_and_patch) { //treated as 'reserved' in this boot image
|
||||
inImgInfo.os_version = unparse_os_version(os_and_patch >> 11)
|
||||
inImgInfo.os_patch_level = unparse_os_patch_level(os_and_patch & 0x7ff)
|
||||
}
|
||||
inImgInfo.board = new String(readBytes(is, 16), "UTF-8").trim();
|
||||
if (0 == inImgInfo.board.length()) {
|
||||
inImgInfo.board = null;
|
||||
}
|
||||
inImgInfo.cmdline = new String(readBytes(is, 512), "UTF-8")
|
||||
inImgInfo.hash = readBytes(is, 32); //hash
|
||||
inImgInfo.cmdline += new String(readBytes(is, 1024), "UTF-8")
|
||||
inImgInfo.cmdline = inImgInfo.cmdline.trim();
|
||||
is.close();
|
||||
|
||||
//calc subimg positions
|
||||
inImgInfo.kernel_pos = get_header_len(inImgInfo.pagesize)
|
||||
inImgInfo.ramdisk_pos = inImgInfo.kernel_pos + inImgInfo.kernel_len + get_pad_len(inImgInfo.kernel_len, inImgInfo.pagesize)
|
||||
inImgInfo.second_pos = inImgInfo.ramdisk_pos + inImgInfo.ramdisk_len + get_pad_len(inImgInfo.ramdisk_len, inImgInfo.pagesize)
|
||||
|
||||
//adjust args
|
||||
if (inImgInfo.kernel_offset > 0x10000000) {
|
||||
inImgInfo.base = 0x10000000;
|
||||
inImgInfo.kernel_offset -= inImgInfo.base;
|
||||
inImgInfo.ramdisk_offset -= inImgInfo.base;
|
||||
inImgInfo.second_offset -= inImgInfo.base;
|
||||
inImgInfo.tags_offset -= inImgInfo.base;
|
||||
}
|
||||
}
|
||||
|
||||
void extract_img_data(String inBootImg, String outImgName, int offset, int length) {
|
||||
if (0 == length) {
|
||||
return;
|
||||
}
|
||||
RandomAccessFile inRaf = new RandomAccessFile(inBootImg, "r");
|
||||
RandomAccessFile outRaf = new RandomAccessFile(outImgName, "rw");
|
||||
inRaf.seek(offset);
|
||||
byte[] data = new byte[length];
|
||||
assert length == inRaf.read(data)
|
||||
outRaf.write(data);
|
||||
outRaf.close();
|
||||
inRaf.close();
|
||||
}
|
||||
|
||||
void printUsage() {
|
||||
println("Usage: abootimg <path_to_boot_image> [work_dir]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
void abootimg(String[] arg) {
|
||||
CImgInfo imgInfo = new CImgInfo();
|
||||
String fileName;
|
||||
String workDir = "unzip_boot";
|
||||
if (1 == arg.length) {
|
||||
fileName = arg[0];
|
||||
} else if (2 == arg.length) {
|
||||
fileName = arg[0];
|
||||
workDir = arg[1];
|
||||
} else {
|
||||
printUsage();
|
||||
}
|
||||
imgInfo.kernel = workDir + File.separator + imgInfo.kernel;
|
||||
imgInfo.ramdisk = workDir + File.separator + imgInfo.ramdisk;
|
||||
imgInfo.second = workDir + File.separator + imgInfo.second;
|
||||
imgInfo.cfg = workDir + File.separator + imgInfo.cfg;
|
||||
|
||||
parse_header(fileName, imgInfo);
|
||||
new File(workDir).mkdirs();
|
||||
extract_img_data(fileName, imgInfo.kernel, imgInfo.kernel_pos, imgInfo.kernel_len)
|
||||
extract_img_data(fileName, imgInfo.ramdisk, imgInfo.ramdisk_pos, imgInfo.ramdisk_len)
|
||||
extract_img_data(fileName, imgInfo.second, imgInfo.second_pos, imgInfo.second_len)
|
||||
imgInfo.toJson();
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
// http://mvnrepository.com/artifact/net.sf.jopt-simple/jopt-simple
|
||||
//@Grapes(
|
||||
// @Grab(group='net.sf.jopt-simple', module='jopt-simple', version='5.0.1')
|
||||
//)
|
||||
|
||||
new Parser().abootimg(args);
|
@ -1,9 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
// http://mvnrepository.com/artifact/net.sf.jopt-simple/jopt-simple
|
||||
//@Grapes(
|
||||
// @Grab(group='net.sf.jopt-simple', module='jopt-simple', version='5.0.1')
|
||||
//)
|
||||
Packer thePacker = new Packer();
|
||||
CArgs theArgs = thePacker.parse_cmdline(args)
|
||||
thePacker.mkbootimg(theArgs);
|
@ -1,11 +0,0 @@
|
||||
package cfig.bootimg
|
||||
|
||||
if (2 != args.length) {
|
||||
println("Usage:\n\trepack <out_file> <work_dir>");
|
||||
println("It will create <out_file> from <work_dir>/kernel, <work_dir>/ramdisk.img.gz and <work_dir>/second");
|
||||
println("Example:\n\trepack boot.img.clear build/unzip_boot");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
CImgInfo aInfo = CImgInfo.fromJson(args[0], args[1]);
|
||||
new Packer().mkbootimg(aInfo);
|
@ -1,32 +0,0 @@
|
||||
package cfig.bootimg;
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
if (3 != args.length) {
|
||||
println("Usage:\n\trepack_with_cmd <out_file> <work_dir> <mkbootimg_bin>");
|
||||
println("It will create <out_file> from <work_dir>/kernel, <work_dir>/ramdisk.img.gz and <work_dir>/second");
|
||||
println("with the program <mkbootimg_bin>");
|
||||
println("Example:\n\trepack boot.img.clear build/unzip_boot mkbootimg");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
CImgInfo aInfo = CImgInfo.fromJson(args[0], args[1]);
|
||||
List<String> cmdArgs = aInfo.toCommandList();
|
||||
int i = 0;
|
||||
for (String item : args[2].split()) {
|
||||
cmdArgs.add(i, item);
|
||||
i++;
|
||||
}
|
||||
Run(cmdArgs);
|
@ -0,0 +1,14 @@
|
||||
diff --git a/avb/avbtool b/avb/avbtool
|
||||
index b742466..2830e20 100755
|
||||
--- a/avb/avbtool
|
||||
+++ b/avb/avbtool
|
||||
@@ -2142,7 +2142,8 @@ class Avb(object):
|
||||
expected_chain_partitions_map[partition_name] = (rollback_index_location, pk_blob)
|
||||
|
||||
image_dir = os.path.dirname(image_filename)
|
||||
- image_ext = os.path.splitext(image_filename)[1]
|
||||
+ #image_ext = os.path.splitext(image_filename)[1]
|
||||
+ image_ext = image_filename[image_filename.index('.'):]
|
||||
|
||||
key_blob = None
|
||||
if key_path:
|
@ -0,0 +1,56 @@
|
||||
buildscript {
|
||||
ext {
|
||||
kotlinVersion = '1.2.41'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ["-Xjsr305=strict"]
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ["-Xjsr305=strict"]
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
compile("org.jetbrains.kotlin:kotlin-reflect")
|
||||
compile("org.slf4j:slf4j-simple:1.7.25")
|
||||
compile("org.slf4j:slf4j-api:1.7.25")
|
||||
compile("com.fasterxml.jackson.core:jackson-annotations:2.9.4")
|
||||
compile("com.fasterxml.jackson.core:jackson-databind:2.9.4")
|
||||
compile("org.apache.commons:commons-exec:1.3")
|
||||
compile("junit:junit:4.12")
|
||||
}
|
||||
|
||||
mainClassName = "cfig.RKt"
|
||||
|
||||
jar {
|
||||
from files("build/classes/kotlin/main")
|
||||
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
|
||||
|
||||
manifest {
|
||||
attributes "Main-Class": "cfig.RKt"
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package cfig
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import org.junit.Assert.*
|
||||
|
||||
class Helper{
|
||||
companion object {
|
||||
fun toHexString(inData: ByteArray): String {
|
||||
val sb = StringBuilder()
|
||||
for (i in inData.indices) {
|
||||
sb.append(Integer.toString((inData[i].toInt().and(0xff)) + 0x100, 16).substring(1))
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun fromHexString(s: String): ByteArray {
|
||||
val len = s.length
|
||||
val data = ByteArray(len / 2)
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
|
||||
i += 2
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
//similar to this.toString(StandardCharsets.UTF_8).replace("${Character.MIN_VALUE}", "")
|
||||
fun byteArray2CString(ba: ByteArray): String {
|
||||
val str = ba.toString(StandardCharsets.UTF_8)
|
||||
val nullPos = str.indexOf(Character.MIN_VALUE)
|
||||
return if (nullPos >= 0) {
|
||||
str.substring(0, nullPos)
|
||||
} else {
|
||||
str
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun gnuZipFile(compressedFile: String, decompressedFile: String) {
|
||||
val buffer = ByteArray(1024)
|
||||
FileOutputStream(compressedFile).use { fos ->
|
||||
GZIPOutputStream(fos).use { gos ->
|
||||
FileInputStream(decompressedFile).use { fis->
|
||||
var bytesRead: Int
|
||||
while (true) {
|
||||
bytesRead = fis.read(buffer)
|
||||
if (bytesRead <= 0) break
|
||||
gos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
gos.finish()
|
||||
log.info("gzip done: $decompressedFile -> $compressedFile")
|
||||
}//file-input-stream
|
||||
}//gzip-output-stream
|
||||
}//file-output-stream
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun unGnuzipFile(compressedFile: String, decompressedFile: String) {
|
||||
val buffer = ByteArray(1024)
|
||||
FileInputStream(compressedFile).use { fileIn ->
|
||||
//src
|
||||
GZIPInputStream(fileIn).use { gZIPInputStream ->
|
||||
//src
|
||||
FileOutputStream(decompressedFile).use { fileOutputStream ->
|
||||
var bytesRead: Int
|
||||
while (true) {
|
||||
bytesRead = gZIPInputStream.read(buffer)
|
||||
if (bytesRead <= 0) break
|
||||
fileOutputStream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
log.info("un-gzip done: $compressedFile -> $decompressedFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extractImageData(fileName: String, outImgName: String, offset: Long, length: Int) {
|
||||
if (0 == length) {
|
||||
return
|
||||
}
|
||||
RandomAccessFile(fileName, "r").use { inRaf ->
|
||||
RandomAccessFile(outImgName, "rw").use { outRaf ->
|
||||
inRaf.seek(offset)
|
||||
val data = ByteArray(length)
|
||||
assertTrue(length == inRaf.read(data))
|
||||
outRaf.write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val log = LoggerFactory.getLogger("Helper")
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package cfig
|
||||
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import java.util.*
|
||||
|
||||
data class ImgArgs(
|
||||
//file input
|
||||
var kernel: String = UnifiedConfig.workDir + "kernel",
|
||||
var ramdisk: String? = UnifiedConfig.workDir + "ramdisk.img.gz",
|
||||
var second: String? = UnifiedConfig.workDir + "second",
|
||||
var dtbo: String? = UnifiedConfig.workDir + "dtbo",
|
||||
//file output
|
||||
var output: String = "boot.img",
|
||||
var cfg: String = UnifiedConfig.workDir + "bootimg.json",
|
||||
|
||||
//load address
|
||||
internal var base: Long = 0,
|
||||
internal var kernelOffset: Long = 0,
|
||||
var ramdiskOffset: Long = 0,
|
||||
var secondOffset: Long = 0,
|
||||
var tagsOffset: Long = 0,
|
||||
var dtboOffset: Long = 0,
|
||||
|
||||
var board: String = "",
|
||||
var cmdline: String = "",
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null,
|
||||
var headerVersion: Int = 0,
|
||||
var pageSize: Int = 0,
|
||||
var id: Boolean = true,
|
||||
//internal
|
||||
var mkbootimg: String = "../src/mkbootimg/mkbootimg",
|
||||
|
||||
//signature
|
||||
var verifyType: VerifyType = VerifyType.VERIFY
|
||||
) {
|
||||
enum class VerifyType {
|
||||
VERIFY,
|
||||
AVB
|
||||
}
|
||||
|
||||
fun toCommandList(): List<String> {
|
||||
val ret = ArrayList<String>()
|
||||
ret.add(mkbootimg)
|
||||
ret.add("--header_version")
|
||||
ret.add(headerVersion.toString())
|
||||
ret.add("--base")
|
||||
ret.add("0x" + java.lang.Long.toHexString(base))
|
||||
ret.add("--kernel")
|
||||
ret.add(kernel)
|
||||
ret.add("--kernel_offset")
|
||||
ret.add("0x" + java.lang.Long.toHexString(kernelOffset))
|
||||
ramdisk?.let {
|
||||
ret.add("--ramdisk")
|
||||
ret.add(it)
|
||||
}
|
||||
ret.add("--ramdisk_offset")
|
||||
ret.add("0x" + java.lang.Long.toHexString(ramdiskOffset))
|
||||
second?.let {
|
||||
ret.add("--second")
|
||||
ret.add(it)
|
||||
}
|
||||
ret.add("--second_offset")
|
||||
ret.add("0x" + java.lang.Long.toHexString(secondOffset))
|
||||
if (!board.isBlank()) {
|
||||
ret.add("--board")
|
||||
ret.add(board)
|
||||
}
|
||||
if (headerVersion > 0) {
|
||||
dtbo?.let { dtbo ->
|
||||
ret.add("--recovery_dtbo")
|
||||
ret.add(dtbo)
|
||||
}
|
||||
ret.add("--recovery_dtbo_offset")
|
||||
ret.add("0x" + java.lang.Long.toHexString(dtboOffset))
|
||||
}
|
||||
ret.add("--pagesize")
|
||||
ret.add(Integer.toString(pageSize))
|
||||
ret.add("--cmdline")
|
||||
ret.add(cmdline)
|
||||
if (!osVersion.isNullOrBlank()) {
|
||||
ret.add("--os_version")
|
||||
ret.add(osVersion!!)
|
||||
}
|
||||
if (!osPatchLevel.isNullOrBlank()) {
|
||||
ret.add("--os_patch_level")
|
||||
ret.add(osPatchLevel!!)
|
||||
}
|
||||
ret.add("--tags_offset")
|
||||
ret.add("0x" + java.lang.Long.toHexString(tagsOffset))
|
||||
if (id) {
|
||||
ret.add("--id")
|
||||
}
|
||||
ret.add("--output")
|
||||
ret.add(output + ".google")
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
fun toCommandString(): String {
|
||||
val ret = StringBuilder()
|
||||
ret.append(mkbootimg)
|
||||
ret.append(" --header_version ")
|
||||
ret.append(headerVersion.toString())
|
||||
ret.append(" --base ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(base))
|
||||
ret.append(" --kernel ")
|
||||
ret.append(kernel)
|
||||
ret.append(" --kernel_offset ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(kernelOffset))
|
||||
ramdisk?.let {
|
||||
ret.append(" --ramdisk ")
|
||||
ret.append(it)
|
||||
}
|
||||
ret.append(" --ramdisk_offset ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(ramdiskOffset))
|
||||
second?.let {
|
||||
ret.append(" --second ")
|
||||
ret.append(it)
|
||||
}
|
||||
ret.append(" --second_offset ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(secondOffset))
|
||||
if (!board.isBlank()) {
|
||||
ret.append(" --board ")
|
||||
ret.append(board)
|
||||
}
|
||||
if (headerVersion > 0) {
|
||||
dtbo?.let { dtbo ->
|
||||
ret.append(" --recovery_dtbo ")
|
||||
ret.append(dtbo)
|
||||
}
|
||||
ret.append(" --recovery_dtbo_offset ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(dtboOffset))
|
||||
}
|
||||
ret.append(" --pagesize ")
|
||||
ret.append(Integer.toString(pageSize))
|
||||
ret.append(" --cmdline ")
|
||||
ret.append("\"" + cmdline + "\"")
|
||||
if (!osVersion.isNullOrBlank()) {
|
||||
ret.append(" --os_version ")
|
||||
ret.append(osVersion)
|
||||
}
|
||||
if (!osPatchLevel.isNullOrBlank()) {
|
||||
ret.append(" --os_patch_level ")
|
||||
ret.append(osPatchLevel)
|
||||
}
|
||||
ret.append(" --tags_offset ")
|
||||
ret.append("0x" + java.lang.Long.toHexString(tagsOffset))
|
||||
if (id) {
|
||||
ret.append(" --id ")
|
||||
}
|
||||
ret.append(" --output ")
|
||||
ret.append(output + ".google")
|
||||
|
||||
return ret.toString()
|
||||
}
|
||||
|
||||
fun toCommandLine(): CommandLine {
|
||||
val ret = CommandLine(mkbootimg)
|
||||
ret.addArgument(" --header_version ")
|
||||
ret.addArgument(headerVersion.toString())
|
||||
ret.addArgument(" --base ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(base))
|
||||
ret.addArgument(" --kernel ")
|
||||
ret.addArgument(kernel)
|
||||
ret.addArgument(" --kernel_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(kernelOffset))
|
||||
ramdisk?.let {
|
||||
ret.addArgument(" --ramdisk ")
|
||||
ret.addArgument(it)
|
||||
}
|
||||
ret.addArgument(" --ramdisk_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(ramdiskOffset))
|
||||
second?.let {
|
||||
ret.addArgument(" --second ")
|
||||
ret.addArgument(it)
|
||||
}
|
||||
ret.addArgument(" --second_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(secondOffset))
|
||||
if (!board.isBlank()) {
|
||||
ret.addArgument(" --board ")
|
||||
ret.addArgument(board)
|
||||
}
|
||||
if (headerVersion > 0) {
|
||||
dtbo?.let { dtbo ->
|
||||
ret.addArgument(" --recovery_dtbo ")
|
||||
ret.addArgument(dtbo)
|
||||
}
|
||||
ret.addArgument(" --recovery_dtbo_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(dtboOffset))
|
||||
}
|
||||
ret.addArgument(" --pagesize ")
|
||||
ret.addArgument(Integer.toString(pageSize))
|
||||
ret.addArgument(" --cmdline ")
|
||||
ret.addArgument(cmdline, false)
|
||||
if (!osVersion.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_version ")
|
||||
ret.addArgument(osVersion)
|
||||
}
|
||||
if (!osPatchLevel.isNullOrBlank()) {
|
||||
ret.addArgument(" --os_patch_level ")
|
||||
ret.addArgument(osPatchLevel)
|
||||
}
|
||||
ret.addArgument(" --tags_offset ")
|
||||
ret.addArgument("0x" + java.lang.Long.toHexString(tagsOffset))
|
||||
if (id) {
|
||||
ret.addArgument(" --id ")
|
||||
}
|
||||
ret.addArgument(" --output ")
|
||||
ret.addArgument(output + ".google")
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package cfig
|
||||
|
||||
data class ImgInfo(
|
||||
//kernel
|
||||
var kernelPosition: Int = 0,
|
||||
var kernelLength: Int = 0,
|
||||
//ramdisk
|
||||
var ramdiskPosition: Int = 0,
|
||||
var ramdiskLength: Int = 0,
|
||||
//second bootloader
|
||||
var secondBootloaderPosition: Int = 0,
|
||||
var secondBootloaderLength: Int = 0,
|
||||
//dtbo
|
||||
var recoveryDtboPosition: Int = 0,
|
||||
var recoveryDtboLength: Int = 0,
|
||||
|
||||
var headerSize: Int = 0,
|
||||
var hash: ByteArray = ByteArray(0),
|
||||
|
||||
//signature
|
||||
var signature: Any? = null
|
||||
) {
|
||||
data class AvbSignature(
|
||||
var type: String = "avb",
|
||||
var originalImageSize: Int? = null,
|
||||
var imageSize: Int? = null,
|
||||
var partName: String? = null)
|
||||
|
||||
data class VeritySignature(
|
||||
var type: String = "dm-verity",
|
||||
var path: String = "/boot",
|
||||
var verity_pk8: String = "security/verity.pk8",
|
||||
var verity_pem: String = "security/verity.x509.pem",
|
||||
var jarPath: String = "boot_signer/build/libs/boot_signer.jar")
|
||||
}
|
@ -0,0 +1,302 @@
|
||||
package cfig
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import org.apache.commons.exec.DefaultExecutor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.MessageDigest
|
||||
import java.util.regex.Pattern
|
||||
import org.junit.Assert.*
|
||||
|
||||
class Packer {
|
||||
private val log = LoggerFactory.getLogger("Packer")
|
||||
private val workDir = UnifiedConfig.workDir
|
||||
|
||||
@Throws(CloneNotSupportedException::class)
|
||||
private fun hashFileAndSize(vararg inFiles: String?): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA1")
|
||||
for (item in inFiles) {
|
||||
if (null == item) {
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(0)
|
||||
.array())
|
||||
log.debug("update null $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
} else {
|
||||
val currentFile = File(item)
|
||||
FileInputStream(currentFile).use { iS ->
|
||||
var byteRead: Int
|
||||
var dataRead = ByteArray(1024)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
md.update(dataRead, 0, byteRead)
|
||||
}
|
||||
log.debug("update file $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
md.update(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putInt(currentFile.length().toInt())
|
||||
.array())
|
||||
log.debug("update size $item: " + Helper.toHexString((md.clone() as MessageDigest).digest()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
private fun writePaddedFile(inBF: ByteBuffer, srcFile: String, padding: Int) {
|
||||
FileInputStream(srcFile).use { iS ->
|
||||
var byteRead: Int
|
||||
val dataRead = ByteArray(128)
|
||||
while (true) {
|
||||
byteRead = iS.read(dataRead)
|
||||
if (-1 == byteRead) {
|
||||
break
|
||||
}
|
||||
inBF.put(dataRead, 0, byteRead)
|
||||
}
|
||||
padFile(inBF, padding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun padFile(inBF: ByteBuffer, padding: Int) {
|
||||
val pad = padding - (inBF.position() and padding - 1) and padding - 1
|
||||
inBF.put(ByteArray(pad))
|
||||
}
|
||||
|
||||
private fun writeData(inArgs: ImgArgs) {
|
||||
log.info("Writing data ...")
|
||||
val bf = ByteBuffer.allocate(1024 * 1024 * 64)//assume total size small than 64MB
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
writePaddedFile(bf, inArgs.kernel, inArgs.pageSize)
|
||||
inArgs.ramdisk?.let { ramdisk ->
|
||||
writePaddedFile(bf, ramdisk, inArgs.pageSize)
|
||||
}
|
||||
inArgs.second?.let { second ->
|
||||
writePaddedFile(bf, second, inArgs.pageSize)
|
||||
}
|
||||
inArgs.dtbo?.let { dtbo ->
|
||||
writePaddedFile(bf, dtbo, inArgs.pageSize)
|
||||
}
|
||||
//write
|
||||
FileOutputStream(inArgs.output + ".clear", true).use { fos ->
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun packOsVersion(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val pattern = Pattern.compile("^(\\d{1,3})(?:\\.(\\d{1,3})(?:\\.(\\d{1,3}))?)?")
|
||||
val m = pattern.matcher(x)
|
||||
if (m.find()) {
|
||||
val a = Integer.decode(m.group(1))
|
||||
var b = 0
|
||||
var c = 0
|
||||
if (m.groupCount() >= 2) {
|
||||
b = Integer.decode(m.group(2))
|
||||
}
|
||||
if (m.groupCount() == 3) {
|
||||
c = Integer.decode(m.group(3))
|
||||
}
|
||||
assertTrue(a < 128)
|
||||
assertTrue(b < 128)
|
||||
assertTrue(c < 128)
|
||||
return (a shl 14) or (b shl 7) or c
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_version")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseOsPatchLevel(x: String?): Int {
|
||||
if (x.isNullOrBlank()) return 0
|
||||
val ret: Int
|
||||
val pattern = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})")
|
||||
val matcher = pattern.matcher(x)
|
||||
if (matcher.find()) {
|
||||
val y = Integer.parseInt(matcher.group(1), 10) - 2000
|
||||
val m = Integer.parseInt(matcher.group(2), 10)
|
||||
// 7 bits allocated for the year, 4 bits for the month
|
||||
assertTrue(y in 0..127)
|
||||
assertTrue(m in 1..12)
|
||||
ret = (y shl 4) or m
|
||||
} else {
|
||||
throw IllegalArgumentException("invalid os_patch_level")
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun writeHeader(inArgs: ImgArgs): ByteArray {
|
||||
log.info("Writing header ...")
|
||||
val bf = ByteBuffer.allocate(1024 * 32)
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
//header start
|
||||
bf.put("ANDROID!".toByteArray())
|
||||
bf.putInt(File(inArgs.kernel).length().toInt())
|
||||
bf.putInt((inArgs.base + inArgs.kernelOffset).toInt())
|
||||
|
||||
if (null == inArgs.ramdisk) {
|
||||
bf.putInt(0)
|
||||
} else {
|
||||
bf.putInt(File(inArgs.ramdisk).length().toInt())
|
||||
}
|
||||
|
||||
bf.putInt((inArgs.base + inArgs.ramdiskOffset).toInt())
|
||||
|
||||
if (null == inArgs.second) {
|
||||
bf.putInt(0)
|
||||
} else {
|
||||
bf.putInt(File(inArgs.second).length().toInt())
|
||||
}
|
||||
|
||||
bf.putInt((inArgs.base + inArgs.secondOffset).toInt())
|
||||
bf.putInt((inArgs.base + inArgs.tagsOffset).toInt())
|
||||
bf.putInt(inArgs.pageSize)
|
||||
bf.putInt(inArgs.headerVersion)
|
||||
bf.putInt((packOsVersion(inArgs.osVersion) shl 11) or parseOsPatchLevel(inArgs.osPatchLevel))
|
||||
|
||||
if (inArgs.board.isBlank()) {
|
||||
bf.put(ByteArray(16))
|
||||
} else {
|
||||
bf.put(inArgs.board.toByteArray())
|
||||
bf.put(ByteArray(16 - inArgs.board.length))
|
||||
}
|
||||
|
||||
bf.put(inArgs.cmdline.substring(0, minOf(512, inArgs.cmdline.length)).toByteArray())
|
||||
bf.put(ByteArray(512 - minOf(512, inArgs.cmdline.length)))
|
||||
|
||||
//hash
|
||||
val imageId = if (inArgs.headerVersion > 0) {
|
||||
hashFileAndSize(inArgs.kernel, inArgs.ramdisk, inArgs.second, inArgs.dtbo)
|
||||
} else {
|
||||
hashFileAndSize(inArgs.kernel, inArgs.ramdisk, inArgs.second)
|
||||
}
|
||||
bf.put(imageId)
|
||||
bf.put(ByteArray(32 - imageId.size))
|
||||
|
||||
if (inArgs.cmdline.length > 512) {
|
||||
bf.put(inArgs.cmdline.substring(512).toByteArray())
|
||||
bf.put(ByteArray(1024 + 512 - inArgs.cmdline.length))
|
||||
} else {
|
||||
bf.put(ByteArray(1024))
|
||||
}
|
||||
|
||||
if (inArgs.headerVersion > 0) {
|
||||
if (inArgs.dtbo == null) {
|
||||
bf.putInt(0)
|
||||
} else {
|
||||
bf.putInt(File(inArgs.dtbo).length().toInt())
|
||||
}
|
||||
bf.putLong(inArgs.dtboOffset)
|
||||
bf.putInt(1648)
|
||||
}
|
||||
|
||||
//padding
|
||||
padFile(bf, inArgs.pageSize)
|
||||
|
||||
//write
|
||||
FileOutputStream(inArgs.output + ".clear", false).use { fos ->
|
||||
fos.write(bf.array(), 0, bf.position())
|
||||
}
|
||||
|
||||
return imageId
|
||||
}
|
||||
|
||||
fun pack(mkbootimgBin: String) {
|
||||
log.info("Loading config from ${workDir}bootimg.json")
|
||||
val cfg = ObjectMapper().readValue(File(workDir + "bootimg.json"), UnifiedConfig::class.java)
|
||||
val readBack = cfg.toArgs()
|
||||
val args = readBack[0] as ImgArgs
|
||||
val info = readBack[1] as ImgInfo
|
||||
args.mkbootimg = mkbootimgBin
|
||||
log.debug(args.toString())
|
||||
log.debug(info.toString())
|
||||
|
||||
//clean
|
||||
if (File(args.output + ".google").exists()) File(args.output + ".google").delete()
|
||||
if (File(args.output + ".clear").exists()) File(args.output + ".clear").delete()
|
||||
if (File(args.output + ".signed").exists()) File(args.output + ".signed").delete()
|
||||
|
||||
writeHeader(args)
|
||||
writeData(args)
|
||||
|
||||
DefaultExecutor().execute(args.toCommandLine())
|
||||
val ourHash = hashFileAndSize(args.output + ".clear")
|
||||
val googleHash = hashFileAndSize(args.output + ".google")
|
||||
log.info("ours hash ${Helper.toHexString(ourHash)}, google's hash ${Helper.toHexString(googleHash)}")
|
||||
if (ourHash.contentEquals(googleHash)) {
|
||||
log.info("Hash verification passed: ${Helper.toHexString(ourHash)}")
|
||||
} else {
|
||||
log.error("Hash verification failed")
|
||||
throw UnknownError("Do not know why hash verification fails, maybe a bug")
|
||||
}
|
||||
}
|
||||
|
||||
fun sign(avbtool: String, bootSigner: String) {
|
||||
log.info("Loading config from ${workDir}bootimg.json")
|
||||
val cfg = ObjectMapper().readValue(File(workDir + "bootimg.json"), UnifiedConfig::class.java)
|
||||
val readBack = cfg.toArgs()
|
||||
val args = readBack[0] as ImgArgs
|
||||
val info = readBack[1] as ImgInfo
|
||||
|
||||
when (args.verifyType) {
|
||||
ImgArgs.VerifyType.VERIFY -> {
|
||||
log.info("Signing with verified-boot 1.0 style")
|
||||
val sig = ObjectMapper().readValue(
|
||||
mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.VeritySignature::class.java)
|
||||
DefaultExecutor().execute(CommandLine.parse("java -jar $bootSigner " +
|
||||
"${sig.path} ${args.output}.clear ${sig.verity_pk8} ${sig.verity_pem} ${args.output}.signed"))
|
||||
|
||||
}
|
||||
ImgArgs.VerifyType.AVB -> {
|
||||
log.info("Adding hash_footer with verified-boot 2.0 style")
|
||||
val sig = ObjectMapper().readValue(
|
||||
mapToJson(info.signature as LinkedHashMap<*, *>), ImgInfo.AvbSignature::class.java)
|
||||
File(args.output + ".clear").copyTo(File(args.output + ".signed"))
|
||||
DefaultExecutor().execute(CommandLine.parse(
|
||||
"$avbtool add_hash_footer " +
|
||||
"--image ${args.output}.signed " +
|
||||
"--partition_size ${sig.imageSize} " +
|
||||
"--partition_name ${sig.partName}"))
|
||||
verifyAVBIntegrity(args, info, avbtool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapToJson(m: LinkedHashMap<*, *>): String {
|
||||
val sb = StringBuilder()
|
||||
m.forEach { k, v ->
|
||||
if (sb.isNotEmpty()) sb.append(", ")
|
||||
sb.append("\"$k\": \"$v\"")
|
||||
}
|
||||
return "{ $sb }"
|
||||
}
|
||||
|
||||
private fun runCmdList(inCmd: List<String>, inWorkdir: String? = null) {
|
||||
log.info("CMD:$inCmd")
|
||||
val pb = ProcessBuilder(inCmd)
|
||||
.directory(File(inWorkdir ?: "."))
|
||||
.redirectErrorStream(true)
|
||||
val p: Process = pb.start()
|
||||
val br = BufferedReader(InputStreamReader(p.inputStream))
|
||||
while (br.ready()) {
|
||||
log.info(br.readLine())
|
||||
}
|
||||
p.waitFor()
|
||||
assertTrue(0 == p.exitValue())
|
||||
}
|
||||
|
||||
private fun verifyAVBIntegrity(args: ImgArgs, info: ImgInfo, avbtool: String) {
|
||||
val tgt = args.output + ".signed"
|
||||
log.info("Verifying AVB: $tgt")
|
||||
DefaultExecutor().execute(CommandLine.parse("$avbtool verify_image --image $tgt"))
|
||||
log.info("Verifying image passed: $tgt")
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
package cfig
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.exec.CommandLine
|
||||
import org.apache.commons.exec.DefaultExecutor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.lang.IllegalStateException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import org.junit.Assert.*
|
||||
import org.apache.commons.exec.PumpStreamHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class Parser {
|
||||
private val workDir = UnifiedConfig.workDir
|
||||
private fun readInt(iS: InputStream): Int {
|
||||
val bf = ByteBuffer.allocate(128)
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
val data4 = ByteArray(4)
|
||||
assertTrue(4 == iS.read(data4))
|
||||
bf.clear()
|
||||
bf.put(data4)
|
||||
bf.flip()
|
||||
return bf.int
|
||||
}
|
||||
|
||||
private fun readUnsignedAsLong(iS: InputStream): Long {
|
||||
val bf = ByteBuffer.allocate(128)
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
val data4 = ByteArray(4)
|
||||
assertTrue(4 == iS.read(data4))
|
||||
bf.clear()
|
||||
bf.put(data4)
|
||||
bf.put(ByteArray(4)) //complete high bits with 0
|
||||
bf.flip()
|
||||
return bf.long
|
||||
}
|
||||
|
||||
private fun readLong(iS: InputStream): Long {
|
||||
val bf = ByteBuffer.allocate(128)
|
||||
bf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
val data4 = ByteArray(8)
|
||||
assertTrue(8 == iS.read(data4))
|
||||
bf.clear()
|
||||
bf.put(data4)
|
||||
bf.flip()
|
||||
return bf.long
|
||||
}
|
||||
|
||||
private fun readBytes(iS: InputStream, len: Int): ByteArray {
|
||||
val data4 = ByteArray(len)
|
||||
assertTrue(len == iS.read(data4))
|
||||
return data4
|
||||
}
|
||||
|
||||
private fun parseOsVersion(x: Int): String {
|
||||
val a = x shr 14
|
||||
val b = x - (a shl 14) shr 7
|
||||
val c = x and 0x7f
|
||||
|
||||
return String.format("%d.%d.%d", a, b, c)
|
||||
}
|
||||
|
||||
private fun parseOsPatchLevel(x: Int): String {
|
||||
var y = x shr 4
|
||||
val m = x and 0xf
|
||||
y += 2000
|
||||
|
||||
return String.format("%d-%02d-%02d", y, m, 0)
|
||||
}
|
||||
|
||||
private fun getHeaderSize(pageSize: Int): Int {
|
||||
val pad = (pageSize - (1648 and (pageSize - 1))) and (pageSize - 1)
|
||||
return pad + 1648
|
||||
}
|
||||
|
||||
private fun getPaddingSize(position: Int, pageSize: Int): Int {
|
||||
return (pageSize - (position and pageSize - 1)) and (pageSize - 1)
|
||||
}
|
||||
|
||||
private fun parseHeader(args: ImgArgs, info: ImgInfo) {
|
||||
FileInputStream(args.output).use { iS ->
|
||||
assertTrue(readBytes(iS, 8).contentEquals("ANDROID!".toByteArray()))
|
||||
info.kernelLength = readInt(iS)
|
||||
args.kernelOffset = readUnsignedAsLong(iS)
|
||||
info.ramdiskLength = readInt(iS)
|
||||
args.ramdiskOffset = readUnsignedAsLong(iS)
|
||||
info.secondBootloaderLength = readInt(iS)
|
||||
args.secondOffset = readUnsignedAsLong(iS)
|
||||
args.tagsOffset = readUnsignedAsLong(iS)
|
||||
args.pageSize = readInt(iS)
|
||||
args.headerVersion = readInt(iS)
|
||||
|
||||
val osNPatch = readInt(iS)
|
||||
if (0 != osNPatch) { //treated as 'reserved' in this boot image
|
||||
args.osVersion = parseOsVersion(osNPatch shr 11)
|
||||
args.osPatchLevel = parseOsPatchLevel(osNPatch and 0x7ff)
|
||||
}
|
||||
|
||||
args.board = Helper.byteArray2CString(readBytes(iS, 16))
|
||||
if (args.board.isBlank()) {
|
||||
args.board = ""
|
||||
}
|
||||
|
||||
val cmd1 = Helper.byteArray2CString(readBytes(iS, 512))
|
||||
info.hash = readBytes(iS, 32) //hash
|
||||
val cmd2 = Helper.byteArray2CString(readBytes(iS, 1024))
|
||||
args.cmdline = cmd1 + cmd2
|
||||
|
||||
info.recoveryDtboLength = readInt(iS)
|
||||
args.dtboOffset = readLong(iS)
|
||||
info.headerSize = readInt(iS)
|
||||
|
||||
//calc subimg positions
|
||||
info.kernelPosition = getHeaderSize(args.pageSize)
|
||||
info.ramdiskPosition = info.kernelPosition + info.kernelLength + getPaddingSize(info.kernelLength, args.pageSize)
|
||||
info.secondBootloaderPosition = info.ramdiskPosition + info.ramdiskLength + getPaddingSize(info.ramdiskLength, args.pageSize)
|
||||
info.recoveryDtboPosition = info.secondBootloaderPosition + info.secondBootloaderLength + getPaddingSize(info.secondBootloaderLength, args.pageSize)
|
||||
|
||||
//adjust args
|
||||
if (args.kernelOffset > Int.MAX_VALUE) {
|
||||
args.base = Int.MAX_VALUE + 1L
|
||||
args.kernelOffset -= args.base
|
||||
args.ramdiskOffset -= args.base
|
||||
args.secondOffset -= args.base
|
||||
args.tagsOffset -= args.base
|
||||
args.dtboOffset -= args.base
|
||||
}
|
||||
|
||||
if (info.ramdiskLength == 0) args.ramdisk = null
|
||||
if (info.kernelLength == 0) throw IllegalStateException("boot image has no kernel")
|
||||
if (info.secondBootloaderLength == 0) args.second = null
|
||||
if (info.recoveryDtboLength == 0) args.dtbo = null
|
||||
}
|
||||
}//resource-closable
|
||||
|
||||
private fun verifiedWithAVB(args: ImgArgs): Boolean {
|
||||
val expectedBf = "AVBf".toByteArray()
|
||||
FileInputStream(args.output).use { fis ->
|
||||
fis.skip(File(args.output).length() - 64)
|
||||
val bf = ByteArray(4)
|
||||
fis.read(bf)
|
||||
return bf.contentEquals(expectedBf)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyAVBIntegrity(args: ImgArgs, info: ImgInfo, avbtool: String) {
|
||||
DefaultExecutor().execute(CommandLine.parse("$avbtool verify_image --image ${args.output}"))
|
||||
}
|
||||
|
||||
private fun parseAVBInfo(args: ImgArgs, info: ImgInfo, avbtool: String) {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val exec = DefaultExecutor()
|
||||
exec.streamHandler = PumpStreamHandler(outputStream)
|
||||
exec.execute(CommandLine.parse("$avbtool info_image --image ${args.output}"))
|
||||
val lines = outputStream.toString().split("\n")
|
||||
lines.forEach {
|
||||
val m = Pattern.compile("^Original image size:\\s+(\\d+)\\s*bytes").matcher(it)
|
||||
if (m.find()) {
|
||||
(info.signature as ImgInfo.AvbSignature).originalImageSize = Integer.parseInt(m.group(1))
|
||||
}
|
||||
|
||||
val m2 = Pattern.compile("^Image size:\\s+(\\d+)\\s*bytes").matcher(it)
|
||||
if (m2.find()) {
|
||||
(info.signature as ImgInfo.AvbSignature).imageSize = Integer.parseInt(m2.group(1))
|
||||
}
|
||||
|
||||
val m3 = Pattern.compile("^\\s*Partition Name:\\s+(\\S+)$").matcher(it)
|
||||
if (m3.find()) {
|
||||
(info.signature as ImgInfo.AvbSignature).partName = m3.group(1)
|
||||
|
||||
}
|
||||
log.debug("[" + it + "]")
|
||||
}
|
||||
assertNotNull((info.signature as ImgInfo.AvbSignature).imageSize)
|
||||
assertNotNull((info.signature as ImgInfo.AvbSignature).originalImageSize)
|
||||
assertTrue(!(info.signature as ImgInfo.AvbSignature).partName.isNullOrBlank())
|
||||
}
|
||||
|
||||
private fun unpackRamdisk(imgArgs: ImgArgs) {
|
||||
val exe = DefaultExecutor()
|
||||
exe.workingDirectory = File(workDir + "root")
|
||||
if (exe.workingDirectory.exists()) exe.workingDirectory.deleteRecursively()
|
||||
exe.workingDirectory.mkdirs()
|
||||
val ramdiskFile = File(imgArgs.ramdisk!!.removeSuffix(".gz"))
|
||||
exe.execute(CommandLine.parse("cpio -i -m -F " + ramdiskFile.canonicalPath))
|
||||
log.info("extract ramdisk done: $ramdiskFile -> ${exe.workingDirectory.path}")
|
||||
}
|
||||
|
||||
fun parseAndExtract(fileName: String?, avbtool: String) {
|
||||
val imgArgs = ImgArgs(output = fileName ?: "boot.img")
|
||||
val imgInfo = ImgInfo()
|
||||
if (File(workDir).exists()) File(workDir).deleteRecursively()
|
||||
File(workDir).mkdirs()
|
||||
if (!fileName.isNullOrBlank()) {
|
||||
imgArgs.output = fileName!!
|
||||
}
|
||||
|
||||
//parse header
|
||||
parseHeader(imgArgs, imgInfo)
|
||||
|
||||
//parse signature
|
||||
if (verifiedWithAVB(imgArgs)) {
|
||||
imgArgs.verifyType = ImgArgs.VerifyType.AVB
|
||||
imgInfo.signature = ImgInfo.AvbSignature()
|
||||
verifyAVBIntegrity(imgArgs, imgInfo, avbtool)
|
||||
parseAVBInfo(imgArgs, imgInfo, avbtool)
|
||||
} else {
|
||||
imgArgs.verifyType = ImgArgs.VerifyType.VERIFY
|
||||
imgInfo.signature = ImgInfo.VeritySignature()
|
||||
}
|
||||
|
||||
log.info(imgArgs.toString())
|
||||
log.info(imgInfo.toString())
|
||||
|
||||
Helper.extractImageData(imgArgs.output, imgArgs.kernel, imgInfo.kernelPosition.toLong(), imgInfo.kernelLength)
|
||||
log.info("kernel dumped to ${imgArgs.kernel}")
|
||||
imgArgs.ramdisk?.let { ramdisk ->
|
||||
log.info("ramdisk dumped to ${imgArgs.ramdisk}")
|
||||
Helper.extractImageData(imgArgs.output, ramdisk, imgInfo.ramdiskPosition.toLong(), imgInfo.ramdiskLength)
|
||||
Helper.unGnuzipFile(ramdisk, workDir + "ramdisk.img")
|
||||
unpackRamdisk(imgArgs)
|
||||
}
|
||||
imgArgs.second?.let { second ->
|
||||
Helper.extractImageData(imgArgs.output, second, imgInfo.secondBootloaderPosition.toLong(), imgInfo.secondBootloaderLength)
|
||||
log.info("second bootloader dumped to ${imgArgs.second}")
|
||||
}
|
||||
imgArgs.dtbo?.let { dtbo ->
|
||||
Helper.extractImageData(imgArgs.output, dtbo, imgInfo.recoveryDtboPosition.toLong(), imgInfo.recoveryDtboLength)
|
||||
log.info("dtbo dumped to ${imgArgs.dtbo}")
|
||||
}
|
||||
val cfg = UnifiedConfig.fromArgs(imgArgs, imgInfo)
|
||||
log.debug(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(cfg))
|
||||
ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File(imgArgs.cfg), cfg)
|
||||
log.info("image info written to ${imgArgs.cfg}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger("Parser")!!
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cfig
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if ((args.size == 5) && args[0] in setOf("pack", "unpack", "sign")) {
|
||||
when (args[0]) {
|
||||
"unpack" -> {
|
||||
Parser().parseAndExtract(args[1], args[3])
|
||||
}
|
||||
"pack" -> {
|
||||
Packer().pack(args[2])
|
||||
}
|
||||
"sign" -> {
|
||||
Packer().sign(args[3], args[4])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println("Usage: unpack <boot_image_path> <mkbootfs_bin_path> <avbtool_path> <boot_signer_path>")
|
||||
println("Usage: pack <boot_image_path> <mkbootfs_bin_path> <avbtool_path> <boot_signer_path>")
|
||||
println("Usage: sign <boot_image_path> <mkbootfs_bin_path> <avbtool_path> <boot_signer_path>")
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package cfig
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
data class UnifiedConfig(
|
||||
var info: MiscInfo = MiscInfo(),
|
||||
var kernel: CommArgs = CommArgs(),
|
||||
var ramdisk: CommArgs? = null,
|
||||
var secondBootloader: CommArgs? = null,
|
||||
var recoveryDtbo: CommArgs? = null,
|
||||
var signature: Any? = ImgInfo.VeritySignature()
|
||||
) {
|
||||
data class CommArgs(
|
||||
var file: String? = null,
|
||||
var position: String = "0",
|
||||
var size: String = "0",
|
||||
var loadOffset: String = "0")
|
||||
|
||||
data class MiscInfo(
|
||||
var output: String = "",
|
||||
var headerVersion: Int = 0,
|
||||
var headerSize: Int = 0,
|
||||
var loadBase: String = "",
|
||||
var tagsOffset: String = "0",
|
||||
var board: String? = null,
|
||||
var pageSize: Int = 0,
|
||||
var cmdline: String = "",
|
||||
var osVersion: String? = null,
|
||||
var osPatchLevel: String? = null,
|
||||
var hash: String = "",
|
||||
var verify: ImgArgs.VerifyType = ImgArgs.VerifyType.VERIFY)
|
||||
|
||||
fun toArgs(): Array<Any> {
|
||||
val args = ImgArgs()
|
||||
val info = ImgInfo()
|
||||
|
||||
args.output = this.info.output
|
||||
args.kernel = this.kernel.file ?: workDir + "kernel"
|
||||
args.kernelOffset = this.kernel.loadOffset.removePrefix("0x").toLong(16)
|
||||
info.kernelPosition = Integer.decode(this.kernel.position)
|
||||
info.kernelLength = Integer.decode(this.kernel.size)
|
||||
|
||||
if (this.ramdisk == null) {
|
||||
args.ramdisk = null
|
||||
} else {
|
||||
args.ramdisk = this.ramdisk!!.file
|
||||
args.ramdiskOffset = this.ramdisk!!.loadOffset.removePrefix("0x").toLong(16)
|
||||
info.ramdiskPosition = Integer.decode(this.ramdisk!!.position)
|
||||
info.ramdiskLength = Integer.decode(this.ramdisk!!.size)
|
||||
}
|
||||
|
||||
this.secondBootloader?.let {
|
||||
args.second = it.file
|
||||
args.secondOffset = it.loadOffset.removePrefix("0x").toLong(16)
|
||||
info.secondBootloaderPosition = Integer.decode(it.position)
|
||||
info.secondBootloaderLength = Integer.decode(it.size)
|
||||
}
|
||||
if (this.secondBootloader == null) args.second = null
|
||||
|
||||
this.recoveryDtbo?.let {
|
||||
args.dtbo = it.file
|
||||
args.dtboOffset = it.loadOffset.removePrefix("0x").toLong(16)
|
||||
info.recoveryDtboPosition = Integer.decode(it.position)
|
||||
info.recoveryDtboLength = Integer.decode(it.size)
|
||||
}
|
||||
if (this.recoveryDtbo == null) args.dtbo = null
|
||||
|
||||
info.headerSize = this.info.headerSize
|
||||
args.headerVersion = this.info.headerVersion
|
||||
args.base = this.info.loadBase.removePrefix("0x").toLong(16)
|
||||
this.info.board?.let { args.board = it }
|
||||
args.tagsOffset = this.info.tagsOffset.removePrefix("0x").toLong(16)
|
||||
args.cmdline = this.info.cmdline
|
||||
args.osVersion = this.info.osVersion
|
||||
args.osPatchLevel = this.info.osPatchLevel
|
||||
info.hash = Helper.fromHexString(this.info.hash)
|
||||
args.pageSize = this.info.pageSize
|
||||
args.verifyType = this.info.verify
|
||||
info.signature = this.signature
|
||||
|
||||
return arrayOf(args, info)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val workDir = "build/unzip_boot/"
|
||||
private val log = LoggerFactory.getLogger(UnifiedConfig::class.java)
|
||||
fun fromArgs(args: ImgArgs, info: ImgInfo): UnifiedConfig {
|
||||
val ret = UnifiedConfig()
|
||||
ret.kernel.file = args.kernel
|
||||
ret.kernel.loadOffset = "0x${java.lang.Long.toHexString(args.kernelOffset)}"
|
||||
ret.kernel.size = "0x${Integer.toHexString(info.kernelLength)}"
|
||||
ret.kernel.position = "0x${Integer.toHexString(info.kernelPosition)}"
|
||||
|
||||
ret.ramdisk = CommArgs()
|
||||
ret.ramdisk!!.loadOffset = "0x${java.lang.Long.toHexString(args.ramdiskOffset)}"
|
||||
ret.ramdisk!!.size = "0x${Integer.toHexString(info.ramdiskLength)}"
|
||||
ret.ramdisk!!.position = "0x${Integer.toHexString(info.ramdiskPosition)}"
|
||||
args.ramdisk?.let {
|
||||
ret.ramdisk!!.file = args.ramdisk
|
||||
}
|
||||
|
||||
ret.secondBootloader = CommArgs()
|
||||
ret.secondBootloader!!.loadOffset = "0x${java.lang.Long.toHexString(args.secondOffset)}"
|
||||
ret.secondBootloader!!.size = "0x${Integer.toHexString(info.secondBootloaderLength)}"
|
||||
ret.secondBootloader!!.position = "0x${Integer.toHexString(info.secondBootloaderPosition)}"
|
||||
args.second?.let {
|
||||
ret.secondBootloader!!.file = args.second
|
||||
}
|
||||
|
||||
if (args.headerVersion > 0) {
|
||||
ret.recoveryDtbo = CommArgs()
|
||||
args.dtbo?.let {
|
||||
ret.recoveryDtbo!!.file = args.dtbo
|
||||
}
|
||||
ret.recoveryDtbo!!.loadOffset = "0x${java.lang.Long.toHexString(args.dtboOffset)}"
|
||||
ret.recoveryDtbo!!.size = "0x${Integer.toHexString(info.recoveryDtboLength)}"
|
||||
ret.recoveryDtbo!!.position = "0x${Integer.toHexString(info.recoveryDtboPosition)}"
|
||||
}
|
||||
|
||||
ret.info.output = args.output
|
||||
ret.info.headerSize = info.headerSize
|
||||
ret.info.headerVersion = args.headerVersion
|
||||
ret.info.loadBase = "0x${java.lang.Long.toHexString(args.base)}"
|
||||
ret.info.board = if (args.board.isBlank()) null else args.board
|
||||
ret.info.tagsOffset = "0x${java.lang.Long.toHexString(args.tagsOffset)}"
|
||||
ret.info.cmdline = args.cmdline
|
||||
ret.info.osVersion = args.osVersion
|
||||
ret.info.osPatchLevel = args.osPatchLevel
|
||||
ret.info.hash = Helper.toHexString(info.hash)
|
||||
ret.info.pageSize = args.pageSize
|
||||
|
||||
ret.info.verify = args.verifyType
|
||||
ret.signature = info.signature
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue