├── .gitignore ├── .gitmodules ├── README.md ├── apply_patch.sh ├── extract_rom.py ├── patches └── extract_android_ota_payload │ └── 0001-Update-include-for-use-as-utils-module.patch └── utils └── split_boot /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | boot/ 4 | system/ 5 | vendor/ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "utils/extract_android_ota_payload"] 2 | path = utils/extract_android_ota_payload 3 | url = git@github.com:cyxx/extract_android_ota_payload.git 4 | [submodule "utils/sdat2img"] 5 | path = utils/sdat2img 6 | url = git@github.com:xpirt/sdat2img.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### First usage ### 2 | ``` 3 | pip3 install protobuf 4 | git submodule update --recursive --init 5 | ./apply_patch.sh 6 | ``` 7 | 8 | ### How to use ### 9 | ``` 10 | ./extract_rom.py [out/dir] 11 | ``` 12 | -------------------------------------------------------------------------------- /apply_patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd utils/extract_android_ota_payload/ 4 | git am ../../patches/extract_android_ota_payload/0001-Update-include-for-use-as-utils-module.patch 5 | cd ../.. 6 | -------------------------------------------------------------------------------- /extract_rom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | import os 6 | import pathlib 7 | import shutil 8 | import sys 9 | import tempfile 10 | import zipfile 11 | from utils.sdat2img import sdat2img 12 | from utils.extract_android_ota_payload import extract_android_ota_payload 13 | 14 | ### start http://thesmithfam.org/blog/2012/10/25/temporarily-suppress-console-output-in-python/ 15 | from contextlib import contextmanager 16 | 17 | @contextmanager 18 | def suppress_stdout(): 19 | with open(os.devnull, "w") as devnull: 20 | old_stdout = sys.stdout 21 | sys.stdout = devnull 22 | try: 23 | yield 24 | finally: 25 | sys.stdout = old_stdout 26 | ### end http://thesmithfam.org/blog/2012/10/25/temporarily-suppress-console-output-in-python/ 27 | 28 | def extract(partitions): 29 | if len(sys.argv) == 1: 30 | print("Usage: python3 extract_rom.py path/to/rom.zip") 31 | exit(1) 32 | 33 | path = os.getcwd() 34 | original_package = os.path.abspath(str(sys.argv[1])) 35 | sparse = True 36 | 37 | with tempfile.TemporaryDirectory() as out, zipfile.ZipFile(original_package, 'r') as package: 38 | print(f'Unpacking {original_package}') 39 | print() 40 | package.extractall(out) 41 | for i in pathlib.Path(out).rglob('*.zip'): 42 | print(f'Unpacking {i}') 43 | zipfile.ZipFile(i).extractall(out) 44 | if "payload.bin" in os.listdir(out): 45 | extract_android_ota_payload.main(f'{out}/payload.bin', f'{out}') 46 | sparse = False 47 | for partition in partitions: 48 | if partition in os.listdir(path): 49 | shutil.rmtree(f'{path}/{partition}') 50 | 51 | if partition == "boot": 52 | if "boot.img" in os.listdir(out): 53 | print("unpacking boot.img") 54 | print() 55 | os.system(f'{os.path.dirname(os.path.realpath(__file__))}/utils/split_boot {out}/{partition}.img') 56 | else: 57 | print(f'Can not get {partition}.img out of {original_package}') 58 | print() 59 | return 60 | if f'{partition}.new.dat.br' in os.listdir(out): 61 | print(f'Decompressing {partition}.new.dat.br') 62 | print() 63 | os.system(f'brotli -d {out}/{partition}.new.dat.br -o {out}/{partition}.new.dat') 64 | 65 | if f'{partition}.new.dat' in os.listdir(out): 66 | print(f'Decompressing {partition}.new.dat') 67 | print() 68 | with suppress_stdout(): 69 | sdat2img.main(f'{out}/{partition}.transfer.list', f'{out}/{partition}.new.dat', f'{out}/{partition}.img') 70 | 71 | sparse = False 72 | 73 | if f'{partition}.img' in os.listdir(out): 74 | img = f'{partition}.img' 75 | if sparse: 76 | print(f'Decompressing sparse image {partition}.img') 77 | print() 78 | simg = os.system(f'simg2img {out}/{partition}.img {out}/raw.{partition}.img') 79 | if int(simg) == 0: 80 | img = f'raw.{partition}.img' 81 | 82 | os.mkdir(f'{path}/{partition}') 83 | if os.system(f'7z x {out}/{img} -o{path}/{partition} > /dev/null') != 0: 84 | shutil.rmtree(f'{path}/{partition}') 85 | os.mkdir(f'{path}/{partition}') 86 | print(f'Failed to extract {partition} using 7z. Trying to mount') 87 | os.mkdir(f'{out}/{partition}_mount') 88 | if os.system(f'sudo mount -o loop {out}/{img} {out}/{partition}_mount') == 0: 89 | print(f'Successfully mounted {partition}') 90 | os.system(f'sudo cp -rf {out}/{partition}_mount/* {path}/{partition}') 91 | os.system(f'sudo umount {out}/{partition}_mount/') 92 | os.system(f'sudo chown -R $(whoami) {path}/{partition}/') 93 | os.rmdir(f'{out}/{partition}_mount') 94 | else: 95 | os.rmdir(f'{out}/{partition}_mount') 96 | print(f'Failed to mount {partition}') 97 | continue 98 | 99 | print(f'Extracted {partition} to {path}/{partition}') 100 | print() 101 | else: 102 | print(f'Can not get {partition}.img out of {original_package}') 103 | print() 104 | 105 | 106 | 107 | partitions = ["odm", "product", "system", "system_ext", "vendor", "vendor_dlkm", "boot"] 108 | extract(partitions) 109 | -------------------------------------------------------------------------------- /patches/extract_android_ota_payload/0001-Update-include-for-use-as-utils-module.patch: -------------------------------------------------------------------------------- 1 | From 96ac6fb066b3653cc83213f0a48f68076839ddf3 Mon Sep 17 00:00:00 2001 2 | From: Arian 3 | Date: Mon, 27 Dec 2021 12:22:47 +0000 4 | Subject: [PATCH] Update include for use as utils module 5 | 6 | --- 7 | extract_android_ota_payload.py | 2 +- 8 | 1 file changed, 1 insertion(+), 1 deletion(-) 9 | 10 | diff --git a/extract_android_ota_payload.py b/extract_android_ota_payload.py 11 | index 8844715..051df71 100644 12 | --- a/extract_android_ota_payload.py 13 | +++ b/extract_android_ota_payload.py 14 | @@ -10,7 +10,7 @@ import sys 15 | import zipfile 16 | 17 | # from https://android.googlesource.com/platform/system/update_engine/+/refs/heads/master/scripts/update_payload/ 18 | -import update_metadata_pb2 19 | +from utils.extract_android_ota_payload import update_metadata_pb2 20 | 21 | PROGRAMS = [ 'bzcat', 'xzcat' ] 22 | 23 | -- 24 | 2.34.1 25 | 26 | -------------------------------------------------------------------------------- /utils/split_boot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | ###################################################################### 3 | # 4 | # File : split_boot 5 | # Author(s) : William Enck 6 | # Modified by CNexus to split the .img to a separate folder 7 | # and to display the BASE address so that the files can be repacked correctly. 8 | # 9 | # Description : Split appart an Android boot image created 10 | # with mkbootimg. The format can be found in 11 | # android-src/system/core/mkbootimg/bootimg.h 12 | # 13 | # Thanks to alansj on xda-developers.com for 14 | # identifying the format in bootimg.h and 15 | # describing initial instructions for splitting 16 | # the boot.img file. 17 | # 18 | # Last Modified : Tue Dec 2 23:36:25 EST 2008 19 | # By : William Enck 20 | # 21 | # Copyright (c) 2008 The Pennsylvania State University 22 | # Systems and Internet Infrastructure Security Laboratory 23 | # 24 | # Licensed under the Apache License, Version 2.0 (the "License"); 25 | # you may not use this file except in compliance with the License. 26 | # You may obtain a copy of the License at 27 | # 28 | # http://www.apache.org/licenses/LICENSE-2.0 29 | # 30 | # Unless required by applicable law or agreed to in writing, software 31 | # distributed under the License is distributed on an "AS IS" BASIS, 32 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | # See the License for the specific language governing permissions and 34 | # limitations under the License. 35 | # 36 | ###################################################################### 37 | 38 | use strict; 39 | use warnings; 40 | 41 | # Turn on print flushing 42 | $|++; 43 | 44 | ###################################################################### 45 | ## Global Variables and Constants 46 | 47 | my $SCRIPT = __FILE__; 48 | my $IMAGE_FN = undef; 49 | 50 | # Constants (from bootimg.h) 51 | use constant BOOT_MAGIC => 'ANDROID!'; 52 | use constant BOOT_MAGIC_SIZE => 8; 53 | use constant BOOT_NAME_SIZE => 16; 54 | use constant BOOT_ARGS_SIZE => 512; 55 | 56 | # Unsigned integers are 4 bytes 57 | use constant UNSIGNED_SIZE => 4; 58 | 59 | # Parsed Values 60 | my $PAGE_SIZE = undef; 61 | my $KERNEL_SIZE = undef; 62 | my $RAMDISK_SIZE = undef; 63 | my $SECOND_SIZE = undef; 64 | 65 | ###################################################################### 66 | ## Main Code 67 | 68 | &parse_cmdline(); 69 | &parse_header($IMAGE_FN); 70 | 71 | =format (from bootimg.h) 72 | ** +-----------------+ 73 | ** | boot header | 1 page 74 | ** +-----------------+ 75 | ** | kernel | n pages 76 | ** +-----------------+ 77 | ** | ramdisk | m pages 78 | ** +-----------------+ 79 | ** | second stage | o pages 80 | ** +-----------------+ 81 | ** 82 | ** n = (kernel_size + page_size - 1) / page_size 83 | ** m = (ramdisk_size + page_size - 1) / page_size 84 | ** o = (second_size + page_size - 1) / page_size 85 | =cut 86 | 87 | my $n = int(($KERNEL_SIZE + $PAGE_SIZE - 1) / $PAGE_SIZE); 88 | my $m = int(($RAMDISK_SIZE + $PAGE_SIZE - 1) / $PAGE_SIZE); 89 | my $o = int(($SECOND_SIZE + $PAGE_SIZE - 1) / $PAGE_SIZE); 90 | 91 | my $k_offset = $PAGE_SIZE; 92 | my $r_offset = $k_offset + ($n * $PAGE_SIZE); 93 | my $s_offset = $r_offset + ($m * $PAGE_SIZE); 94 | 95 | (my $base = $IMAGE_FN) =~ s/.*\/(.*)$/$1/; 96 | (my $base2 = $base) =~ s/\.[^.]+$//; 97 | 98 | my $k_file = $base2. "/" . $base . "-kernel"; 99 | my $r_file = $base2. "/" . $base . "-ramdisk.cpio.gz"; 100 | my $s_file = $base2. "/" . $base . "-second.gz"; 101 | 102 | mkdir $base2; 103 | mkdir $base2 . "/ramdisk"; 104 | 105 | # The kernel is always there 106 | print "Writing $k_file ..."; 107 | &dump_file($IMAGE_FN, $k_file, $k_offset, $KERNEL_SIZE); 108 | print " complete.\n"; 109 | 110 | # The ramdisk is always there 111 | print "Writing $r_file ..."; 112 | &dump_file($IMAGE_FN, $r_file, $r_offset, $RAMDISK_SIZE); 113 | print " complete.\n"; 114 | 115 | # The Second stage bootloader is optional 116 | unless ($SECOND_SIZE == 0) { 117 | print "Writing $s_file ..."; 118 | &dump_file($IMAGE_FN, $s_file, $s_offset, $SECOND_SIZE); 119 | print " complete.\n"; 120 | } 121 | 122 | print "Unpacking ramdisk..."; 123 | print " complete.\n"; 124 | system("cd $base2/ramdisk && gunzip -c ../$base-ramdisk.cpio.gz | cpio -i > /dev/null 2>&1"); 125 | 126 | ###################################################################### 127 | ## Supporting Subroutines 128 | 129 | =header_format (from bootimg.h) 130 | struct boot_img_hdr 131 | { 132 | unsigned char magic[BOOT_MAGIC_SIZE]; 133 | 134 | unsigned kernel_size; /* size in bytes */ 135 | unsigned kernel_addr; /* physical load addr */ 136 | 137 | unsigned ramdisk_size; /* size in bytes */ 138 | unsigned ramdisk_addr; /* physical load addr */ 139 | 140 | unsigned second_size; /* size in bytes */ 141 | unsigned second_addr; /* physical load addr */ 142 | 143 | unsigned tags_addr; /* physical addr for kernel tags */ 144 | unsigned page_size; /* flash page size we assume */ 145 | unsigned unused[2]; /* future expansion: should be 0 */ 146 | 147 | unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */ 148 | 149 | unsigned char cmdline[BOOT_ARGS_SIZE]; 150 | 151 | unsigned id[8]; /* timestamp / checksum / sha1 / etc */ 152 | }; 153 | =cut 154 | sub parse_header { 155 | my ($fn) = @_; 156 | my $buf = undef; 157 | 158 | open INF, $fn or die "Could not open $fn: $!\n"; 159 | binmode INF; 160 | 161 | # Read the Magic 162 | read(INF, $buf, BOOT_MAGIC_SIZE); 163 | unless ($buf eq BOOT_MAGIC) { 164 | die "Android Magic not found in $fn. Giving up.\n"; 165 | } 166 | 167 | # Read kernel size and address (assume little-endian) 168 | read(INF, $buf, UNSIGNED_SIZE * 2); 169 | my ($k_size, $k_addr) = unpack("VV", $buf); 170 | 171 | # Read ramdisk size and address (assume little-endian) 172 | read(INF, $buf, UNSIGNED_SIZE * 2); 173 | my ($r_size, $r_addr) = unpack("VV", $buf); 174 | 175 | # Read second size and address (assume little-endian) 176 | read(INF, $buf, UNSIGNED_SIZE * 2); 177 | my ($s_size, $s_addr) = unpack("VV", $buf); 178 | 179 | # Read tags_addr 180 | read(INF, $buf, UNSIGNED_SIZE); 181 | my ($tags_addr) = unpack("V", $buf); 182 | 183 | # get the page size (assume little-endian) 184 | read(INF, $buf, UNSIGNED_SIZE); 185 | my ($p_size) = unpack("V", $buf); 186 | 187 | # Ignore unused 188 | read(INF, $buf, UNSIGNED_SIZE * 2); 189 | 190 | # Read the name (board name) 191 | read(INF, $buf, BOOT_NAME_SIZE); 192 | my $name = $buf; 193 | 194 | # Read the command line 195 | read(INF, $buf, BOOT_ARGS_SIZE); 196 | my $cmdline = $buf; 197 | 198 | # Ignore the id 199 | read(INF, $buf, UNSIGNED_SIZE * 8); 200 | 201 | # Close the file 202 | close INF; 203 | 204 | # Print important values 205 | printf "Page size: %d (0x%08x)\n", $p_size, $p_size; 206 | printf "Kernel size: %d (0x%08x)\n", $k_size, $k_size; 207 | printf "Ramdisk size: %d (0x%08x)\n", $r_size, $r_size; 208 | printf "Second size: %d (0x%08x)\n", $s_size, $s_size; 209 | printf "Board name: $name\n"; 210 | printf "Command line: \'$cmdline\'\n"; 211 | printf "Base address: (0x%08x)\n", $tags_addr - 0x00000100; 212 | print "\n"; 213 | 214 | # Save the values 215 | $PAGE_SIZE = $p_size; 216 | $KERNEL_SIZE = $k_size; 217 | $RAMDISK_SIZE = $r_size; 218 | $SECOND_SIZE = $s_size; 219 | } 220 | 221 | sub dump_file { 222 | my ($infn, $outfn, $offset, $size) = @_; 223 | my $buf = undef; 224 | 225 | open INF, $infn or die "Could not open $infn: $!\n"; 226 | open OUTF, ">$outfn" or die "Could not open $outfn: $!\n"; 227 | 228 | binmode INF; 229 | binmode OUTF; 230 | 231 | seek(INF, $offset, 0) or die "Could not seek in $infn: $!\n"; 232 | read(INF, $buf, $size) or die "Could not read $infn: $!\n"; 233 | print OUTF $buf or die "Could not write $outfn: $!\n"; 234 | 235 | close INF; 236 | close OUTF; 237 | } 238 | 239 | ###################################################################### 240 | ## Configuration Subroutines 241 | 242 | sub parse_cmdline { 243 | unless ($#ARGV == 0) { 244 | die "Usage: $SCRIPT boot.img\n"; 245 | } 246 | $IMAGE_FN = $ARGV[0]; 247 | } 248 | 249 | 250 | 251 | --------------------------------------------------------------------------------