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