|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +#==================================================== |
| 4 | +# FILE: sdat2img.py |
| 5 | +# AUTHORS: xpirt - luxi78 - howellzhu |
| 6 | +# DATE: 2018-10-27 10:33:21 CEST |
| 7 | +#==================================================== |
| 8 | + |
| 9 | +from __future__ import print_function |
| 10 | +import sys, os, errno |
| 11 | + |
| 12 | +def main(TRANSFER_LIST_FILE, NEW_DATA_FILE, OUTPUT_IMAGE_FILE): |
| 13 | + __version__ = '1.2' |
| 14 | + |
| 15 | + if sys.hexversion < 0x02070000: |
| 16 | + print >> sys.stderr, "Python 2.7 or newer is required." |
| 17 | + try: |
| 18 | + input = raw_input |
| 19 | + except NameError: pass |
| 20 | + input('Press ENTER to exit...') |
| 21 | + sys.exit(1) |
| 22 | + else: |
| 23 | + print('sdat2img binary - version: {}\n'.format(__version__)) |
| 24 | + |
| 25 | + def rangeset(src): |
| 26 | + src_set = src.split(',') |
| 27 | + num_set = [int(item) for item in src_set] |
| 28 | + if len(num_set) != num_set[0]+1: |
| 29 | + print('Error on parsing following data to rangeset:\n{}'.format(src), file=sys.stderr) |
| 30 | + sys.exit(1) |
| 31 | + |
| 32 | + return tuple ([ (num_set[i], num_set[i+1]) for i in range(1, len(num_set), 2) ]) |
| 33 | + |
| 34 | + def parse_transfer_list_file(path): |
| 35 | + trans_list = open(TRANSFER_LIST_FILE, 'r') |
| 36 | + |
| 37 | + # First line in transfer list is the version number |
| 38 | + version = int(trans_list.readline()) |
| 39 | + |
| 40 | + # Second line in transfer list is the total number of blocks we expect to write |
| 41 | + new_blocks = int(trans_list.readline()) |
| 42 | + |
| 43 | + if version >= 2: |
| 44 | + # Third line is how many stash entries are needed simultaneously |
| 45 | + trans_list.readline() |
| 46 | + # Fourth line is the maximum number of blocks that will be stashed simultaneously |
| 47 | + trans_list.readline() |
| 48 | + |
| 49 | + # Subsequent lines are all individual transfer commands |
| 50 | + commands = [] |
| 51 | + for line in trans_list: |
| 52 | + line = line.split(' ') |
| 53 | + cmd = line[0] |
| 54 | + if cmd in ['erase', 'new', 'zero']: |
| 55 | + commands.append([cmd, rangeset(line[1])]) |
| 56 | + else: |
| 57 | + # Skip lines starting with numbers, they are not commands anyway |
| 58 | + if not cmd[0].isdigit(): |
| 59 | + print('Command "{}" is not valid.'.format(cmd), file=sys.stderr) |
| 60 | + trans_list.close() |
| 61 | + sys.exit(1) |
| 62 | + |
| 63 | + trans_list.close() |
| 64 | + return version, new_blocks, commands |
| 65 | + |
| 66 | + BLOCK_SIZE = 4096 |
| 67 | + |
| 68 | + version, new_blocks, commands = parse_transfer_list_file(TRANSFER_LIST_FILE) |
| 69 | + |
| 70 | + if version == 1: |
| 71 | + print('Android Lollipop 5.0 detected!\n') |
| 72 | + elif version == 2: |
| 73 | + print('Android Lollipop 5.1 detected!\n') |
| 74 | + elif version == 3: |
| 75 | + print('Android Marshmallow 6.x detected!\n') |
| 76 | + elif version == 4: |
| 77 | + print('Android Nougat 7.x / Oreo 8.x detected!\n') |
| 78 | + else: |
| 79 | + print('Unknown Android version!\n') |
| 80 | + |
| 81 | + # Don't clobber existing files to avoid accidental data loss |
| 82 | + try: |
| 83 | + output_img = open(OUTPUT_IMAGE_FILE, 'wb') |
| 84 | + except IOError as e: |
| 85 | + if e.errno == errno.EEXIST: |
| 86 | + print('Error: the output file "{}" already exists'.format(e.filename), file=sys.stderr) |
| 87 | + print('Remove it, rename it, or choose a different file name.', file=sys.stderr) |
| 88 | + sys.exit(e.errno) |
| 89 | + else: |
| 90 | + raise |
| 91 | + |
| 92 | + new_data_file = open(NEW_DATA_FILE, 'rb') |
| 93 | + all_block_sets = [i for command in commands for i in command[1]] |
| 94 | + max_file_size = max(pair[1] for pair in all_block_sets)*BLOCK_SIZE |
| 95 | + |
| 96 | + for command in commands: |
| 97 | + if command[0] == 'new': |
| 98 | + for block in command[1]: |
| 99 | + begin = block[0] |
| 100 | + end = block[1] |
| 101 | + block_count = end - begin |
| 102 | + print('Copying {} blocks into position {}...'.format(block_count, begin)) |
| 103 | + |
| 104 | + # Position output file |
| 105 | + output_img.seek(begin*BLOCK_SIZE) |
| 106 | + |
| 107 | + # Copy one block at a time |
| 108 | + while(block_count > 0): |
| 109 | + output_img.write(new_data_file.read(BLOCK_SIZE)) |
| 110 | + block_count -= 1 |
| 111 | + else: |
| 112 | + print('Skipping command {}...'.format(command[0])) |
| 113 | + |
| 114 | + # Make file larger if necessary |
| 115 | + if(output_img.tell() < max_file_size): |
| 116 | + output_img.truncate(max_file_size) |
| 117 | + |
| 118 | + output_img.close() |
| 119 | + new_data_file.close() |
| 120 | + print('Done! Output image: {}'.format(os.path.realpath(output_img.name))) |
| 121 | + |
| 122 | +if __name__ == '__main__': |
| 123 | + try: |
| 124 | + TRANSFER_LIST_FILE = str(sys.argv[1]) |
| 125 | + NEW_DATA_FILE = str(sys.argv[2]) |
| 126 | + except IndexError: |
| 127 | + print('\nUsage: sdat2img.py <transfer_list> <system_new_file> [system_img]\n') |
| 128 | + print(' <transfer_list>: transfer list file') |
| 129 | + print(' <system_new_file>: system new dat file') |
| 130 | + print(' [system_img]: output system image\n\n') |
| 131 | + print('Visit xda thread for more information.\n') |
| 132 | + try: |
| 133 | + input = raw_input |
| 134 | + except NameError: pass |
| 135 | + input('Press ENTER to exit...') |
| 136 | + sys.exit() |
| 137 | + |
| 138 | + try: |
| 139 | + OUTPUT_IMAGE_FILE = str(sys.argv[3]) |
| 140 | + except IndexError: |
| 141 | + OUTPUT_IMAGE_FILE = 'system.img' |
| 142 | + |
| 143 | + main(TRANSFER_LIST_FILE, NEW_DATA_FILE, OUTPUT_IMAGE_FILE) |
0 commit comments