diff --git a/build/tools/extract_utils.sh b/build/tools/extract_utils.sh index cea42192..a6239cee 100644 --- a/build/tools/extract_utils.sh +++ b/build/tools/extract_utils.sh @@ -778,7 +778,7 @@ function fix_xml() { # extract: # # $1: file containing the list of items to extract -# $2: path to extracted system folder, or "adb" to extract from device +# $2: path to extracted system folder, an ota zip file, or "adb" to extract from device # function extract() { if [ -z "$OUTDIR" ]; then @@ -802,6 +802,41 @@ function extract() { init_adb_connection fi + if [ -f "$SRC" ] && [ "${SRC##*.}" == "zip" ]; then + DUMPDIR="$CM_ROOT"/system_dump + + # Check if we're working with the same zip that was passed last time. + # If so, let's just use what's already extracted. + MD5=`md5sum "$SRC"| awk '{print $1}'` + OLDMD5=`cat "$DUMPDIR"/zipmd5.txt` + + if [ "$MD5" != "$OLDMD5" ]; then + rm -rf "$DUMPDIR" + mkdir "$DUMPDIR" + unzip "$SRC" -d "$DUMPDIR" + echo "$MD5" > "$DUMPDIR"/zipmd5.txt + + # Stop if an A/B OTA zip is detected. We cannot extract these. + if [ -a "$DUMPDIR"/payload.bin ]; then + echo "A/B style OTA zip detected. This is not supported at this time. Stopping..." + exit 1 + # If OTA is block based, extract it. + elif [ -a "$DUMPDIR"/system.new.dat ]; then + echo "Converting system.new.dat to system.img" + python "$CM_ROOT"/vendor/cm/build/tools/sdat2img.py "$DUMPDIR"/system.transfer.list "$DUMPDIR"/system.new.dat "$DUMPDIR"/system.img 2>&1 + rm -rf "$DUMPDIR"/system.new.dat "$DUMPDIR"/system + mkdir "$DUMPDIR"/system "$DUMPDIR"/tmp + echo "Requesting sudo access to mount the system.img" + sudo mount -o loop "$DUMPDIR"/system.img "$DUMPDIR"/tmp + cp -r "$DUMPDIR"/tmp/* "$DUMPDIR"/system/ + sudo umount "$DUMPDIR"/tmp + rm -rf "$DUMPDIR"/tmp "$DUMPDIR"/system.img + fi + fi + + SRC="$DUMPDIR" + fi + if [ "$VENDOR_STATE" -eq "0" ]; then echo "Cleaning output directory ($OUTPUT_ROOT).." rm -rf "${OUTPUT_TMP:?}" diff --git a/build/tools/sdat2img.py b/build/tools/sdat2img.py new file mode 100755 index 00000000..3efb2f78 --- /dev/null +++ b/build/tools/sdat2img.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#==================================================== +# FILE: sdat2img.py +# AUTHORS: xpirt - luxi78 - howellzhu +# DATE: 2016-11-23 16:20:11 CST +#==================================================== + +import sys, os, errno + +__version__ = '1.0' + +if sys.hexversion < 0x02070000: + print >> sys.stderr, "Python 2.7 or newer is required." + try: + input = raw_input + except NameError: pass + input('Press ENTER to exit...') + sys.exit(1) +else: + print('sdat2img binary - version: %s\n' % __version__) + +try: + TRANSFER_LIST_FILE = str(sys.argv[1]) + NEW_DATA_FILE = str(sys.argv[2]) +except IndexError: + print('\nUsage: sdat2img.py [system_img]\n') + print(' : transfer list file') + print(' : system new dat file') + print(' [system_img]: output system image\n\n') + print('Visit xda thread for more information.\n') + try: + input = raw_input + except NameError: pass + input('Press ENTER to exit...') + sys.exit() + +try: + OUTPUT_IMAGE_FILE = str(sys.argv[3]) +except IndexError: + OUTPUT_IMAGE_FILE = 'system.img' + +BLOCK_SIZE = 4096 + +def rangeset(src): + src_set = src.split(',') + num_set = [int(item) for item in src_set] + if len(num_set) != num_set[0]+1: + print('Error on parsing following data to rangeset:\n%s' % src) + sys.exit(1) + + return tuple ([ (num_set[i], num_set[i+1]) for i in range(1, len(num_set), 2) ]) + +def parse_transfer_list_file(path): + trans_list = open(TRANSFER_LIST_FILE, 'r') + + # First line in transfer list is the version number + version = int(trans_list.readline()) + + # Second line in transfer list is the total number of blocks we expect to write + new_blocks = int(trans_list.readline()) + + if version >= 2: + # Third line is how many stash entries are needed simultaneously + trans_list.readline() + # Fourth line is the maximum number of blocks that will be stashed simultaneously + trans_list.readline() + + # Subsequent lines are all individual transfer commands + commands = [] + for line in trans_list: + line = line.split(' ') + cmd = line[0] + if cmd in ['erase', 'new', 'zero']: + commands.append([cmd, rangeset(line[1])]) + else: + # Skip lines starting with numbers, they are not commands anyway + if not cmd[0].isdigit(): + print('Command "%s" is not valid.' % cmd) + trans_list.close() + sys.exit(1) + + trans_list.close() + return version, new_blocks, commands + +def main(argv): + version, new_blocks, commands = parse_transfer_list_file(TRANSFER_LIST_FILE) + + if version == 1: + print('Android Lollipop 5.0 detected!\n') + elif version == 2: + print('Android Lollipop 5.1 detected!\n') + elif version == 3: + print('Android Marshmallow 6.0 detected!\n') + elif version == 4: + print('Android Nougat 7.0 detected!\n') + else: + print('Unknown Android version!\n') + + # Don't clobber existing files to avoid accidental data loss + try: + output_img = open(OUTPUT_IMAGE_FILE, 'wb') + except IOError as e: + if e.errno == errno.EEXIST: + print('Error: the output file "{}" already exists'.format(e.filename)) + print('Remove it, rename it, or choose a different file name.') + sys.exit(e.errno) + else: + raise + + new_data_file = open(NEW_DATA_FILE, 'rb') + all_block_sets = [i for command in commands for i in command[1]] + max_file_size = max(pair[1] for pair in all_block_sets)*BLOCK_SIZE + + for command in commands: + if command[0] == 'new': + for block in command[1]: + begin = block[0] + end = block[1] + block_count = end - begin + print('Copying {} blocks into position {}...'.format(block_count, begin)) + + # Position output file + output_img.seek(begin*BLOCK_SIZE) + + # Copy one block at a time + while(block_count > 0): + output_img.write(new_data_file.read(BLOCK_SIZE)) + block_count -= 1 + else: + print('Skipping command %s...' % command[0]) + + # Make file larger if necessary + if(output_img.tell() < max_file_size): + output_img.truncate(max_file_size) + + output_img.close() + new_data_file.close() + print('Done! Output image: %s' % os.path.realpath(output_img.name)) + +if __name__ == '__main__': + main(sys.argv)