blob: 943b3bba2cfcb7ed6d6fbf5c4a30a4649d0f7582 [file] [log] [blame]
Chirayu Desai7119eb62021-12-07 03:07:13 +05301#!/usr/bin/env python3
2#
3# Copyright 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Chirayu Desai7119eb62021-12-07 03:07:13 +053016import argparse
17import logging
Michael Bestas0382f302023-06-12 02:22:51 +030018import os
19import sys
20from lxml.etree import XMLParser
21import yaml
Chirayu Desai7119eb62021-12-07 03:07:13 +053022
Michael Bestas0382f302023-06-12 02:22:51 +030023#from google3.third_party.devsite.androidsource.en.docs.core.architecture.bootloader.tools.pixel.fw_unpack import fbpack
24import fbpack
Chirayu Desai7119eb62021-12-07 03:07:13 +053025
26def bytes_to_str(bstr):
Michael Bestas0382f302023-06-12 02:22:51 +030027 return bstr.decode().rstrip('\x00')
Chirayu Desai7119eb62021-12-07 03:07:13 +053028
29
30def print_pack_header(pack):
Michael Bestas0382f302023-06-12 02:22:51 +030031 print('magic: {:#x}'.format(pack.magic))
32 print('version: {}'.format(pack.version))
33 print('header size: {}'.format(pack.header_size))
34 print('entry header size: {}'.format(pack.entry_header_size))
35 platform = bytes_to_str(pack.platform)
36 print('platform: {}'.format(platform))
37 pack_version = bytes_to_str(pack.pack_version)
38 print('pack version: {}'.format(pack_version))
39 print('slock type: {}'.format(pack.slot_type))
40 print('data align: {}'.format(pack.data_align))
41 print('total entries: {}'.format(pack.total_entries))
42 print('total size: {}'.format(pack.total_size))
Chirayu Desai7119eb62021-12-07 03:07:13 +053043
44
45def print_pack_entry(entry, prefix):
Michael Bestas0382f302023-06-12 02:22:51 +030046 name = bytes_to_str(entry.name)
47 print('{}name: {}'.format(prefix, name))
48 etype = 'unknown'
49 if entry.type == fbpack.FBPACK_PARTITION_TABLE:
50 etype = 'partiton table'
51 elif entry.type == fbpack.FBPACK_PARTITION_DATA:
52 etype = 'partition'
53 elif entry.type == fbpack.FBPACK_SIDELOAD_DATA:
54 etype = 'sideload'
55 else:
56 print('entry else')
57 print('{}type: {}'.format(prefix, etype))
58 product = bytes_to_str(entry.product)
59 print('{}product: {}'.format(prefix, product))
60 print('{}offset: {:#x} ({})'.format(prefix, entry.offset, entry.offset))
61 print('{}size: {:#x} ({})'.format(prefix, entry.size, entry.size))
62 print('{}slotted: {}'.format(entry.size, bool(entry.slotted)))
63 print('{}crc32: {:#08x}'.format(prefix, entry.crc32))
Chirayu Desai7119eb62021-12-07 03:07:13 +053064
65
66def cmd_info(args):
Michael Bestas0382f302023-06-12 02:22:51 +030067 with open(args.file, 'rb') as f:
68 pack = fbpack.PackHeader.from_bytes(f.read(len(fbpack.PackHeader())))
Chirayu Desai7119eb62021-12-07 03:07:13 +053069
Michael Bestas0382f302023-06-12 02:22:51 +030070 if pack.version != fbpack.FBPACK_VERSION:
71 raise NotImplementedError('unsupported version {}'.format(pack.version))
Chirayu Desai7119eb62021-12-07 03:07:13 +053072
Michael Bestas0382f302023-06-12 02:22:51 +030073 print('Header:')
74 print_pack_header(pack)
Chirayu Desai7119eb62021-12-07 03:07:13 +053075
Michael Bestas0382f302023-06-12 02:22:51 +030076 print('\nEntries:')
77 for i in range(1, pack.total_entries + 1):
78 entry = fbpack.PackEntry.from_bytes(f.read(len(fbpack.PackEntry())))
79 print('Entry {}: {{'.format(i))
80 print_pack_entry(entry, ' ')
81 print('}')
Chirayu Desai7119eb62021-12-07 03:07:13 +053082
83
84def align_up(val, align):
Michael Bestas0382f302023-06-12 02:22:51 +030085 return (val + align - 1) & ~(align - 1)
Chirayu Desai7119eb62021-12-07 03:07:13 +053086
87
88def create_pack_file(file_name, in_dir_name, pack):
Michael Bestas0382f302023-06-12 02:22:51 +030089 pack.total_entries = len(pack.entries)
90 offset = pack.header_size + pack.total_entries * pack.entry_header_size
91 with open(file_name, 'wb') as f:
92 # write entries data
93 for entry in pack.entries:
94 # align data
95 offset = align_up(offset, pack.data_align)
96 entry.offset = offset
97 f.seek(offset)
98 fin_name = os.path.join(in_dir_name, entry.filepath)
99 with open(fin_name, 'rb') as fin:
100 data = fin.read()
101 entry.size = len(data)
102 f.write(data)
103 offset += len(data)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530104
Michael Bestas0382f302023-06-12 02:22:51 +0300105 pack.total_size = offset
106 f.seek(0)
107 # write pack header
108 f.write(bytes(pack))
109 # iterate over entries again to write entry header
110 for entry in pack.entries:
111 f.write(bytes(entry))
Chirayu Desai7119eb62021-12-07 03:07:13 +0530112
113
114def cmd_create(args):
Michael Bestas0382f302023-06-12 02:22:51 +0300115 if not (args.file.lower().endswith('.xml') or
116 args.file.lower().endswith('.yaml')):
117 raise NotImplementedError('{} type not supported'.format(args.file))
Chirayu Desai7119eb62021-12-07 03:07:13 +0530118
Michael Bestas0382f302023-06-12 02:22:51 +0300119 pack = None
120 if args.file.lower().endswith('.yaml'):
121 pack = yaml.parse(args.file)
122 else:
123 pack = XMLParser.parse(args.file)
124 pack.pack_version = bytes(str(args.pack_version).encode('ascii'))
125 pack.header_size = len(pack)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530126
Michael Bestas0382f302023-06-12 02:22:51 +0300127 # create output directory if missing
128 if not os.path.isdir(args.out_dir):
129 os.makedirs(args.out_dir, 0o755)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530130
Michael Bestas0382f302023-06-12 02:22:51 +0300131 file_name = os.path.join(args.out_dir, pack.name + '.img')
Chirayu Desai7119eb62021-12-07 03:07:13 +0530132
Michael Bestas0382f302023-06-12 02:22:51 +0300133 create_pack_file(file_name, args.in_dir, pack)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530134
135
136def product_match(products, product):
Michael Bestas0382f302023-06-12 02:22:51 +0300137 return product in products.split(b'|')
Chirayu Desai7119eb62021-12-07 03:07:13 +0530138
139
140def copyfileobj(src, dst, file_size):
Michael Bestas0382f302023-06-12 02:22:51 +0300141 while file_size > 0:
142 buf = src.read(min(128 * 1024, file_size))
143 dst.write(buf)
144 file_size -= len(buf)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530145
146
147def cmd_unpack(args):
Michael Bestas0382f302023-06-12 02:22:51 +0300148 with open(args.file, 'rb') as f:
149 pack = fbpack.PackHeader.from_bytes(f.read(len(fbpack.PackHeader())))
Chirayu Desai7119eb62021-12-07 03:07:13 +0530150
Michael Bestas0382f302023-06-12 02:22:51 +0300151 if pack.version != fbpack.FBPACK_VERSION:
152 raise NotImplementedError('unsupported version {}'.format(pack.version))
Chirayu Desai7119eb62021-12-07 03:07:13 +0530153
Michael Bestas0382f302023-06-12 02:22:51 +0300154 entries = []
155 # create list of entries we want to extact
156 for _ in range(pack.total_entries):
157 entry = fbpack.PackEntry.from_bytes(f.read(len(fbpack.PackEntry())))
158 name = bytes_to_str(entry.name)
159 if not args.partitions or name in args.partitions:
160 # if both product are valid then match product name too
161 if not args.product or not entry.product or product_match(
162 entry.product, args.product):
163 entries.append(entry)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530164
Michael Bestas0382f302023-06-12 02:22:51 +0300165 if not entries and not args.unpack_ver:
166 raise RuntimeError('no images to unpack')
Chirayu Desai7119eb62021-12-07 03:07:13 +0530167
Michael Bestas0382f302023-06-12 02:22:51 +0300168 # create output directory if it does not exist
169 if not os.path.isdir(args.out_dir):
170 os.makedirs(args.out_dir, 0o755)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530171
Michael Bestas0382f302023-06-12 02:22:51 +0300172 out_files = {}
173 # write file per entry
174 for entry in entries:
175 name = bytes_to_str(entry.name)
176 logging.info('Unpacking {} (size: {}, offset: {})'.format(
177 name, entry.size, entry.offset))
178 f.seek(entry.offset)
179 entry_filename = os.path.join(args.out_dir, name + '.img')
180 instance = out_files.get(entry_filename, 0) + 1
181 out_files[entry_filename] = instance
182 if instance > 1:
183 entry_filename = os.path.join(args.out_dir,
184 name + '({}).img'.format(instance - 1))
185 with open(entry_filename, 'wb') as entry_file:
186 copyfileobj(f, entry_file, entry.size)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530187
Michael Bestas0382f302023-06-12 02:22:51 +0300188 if args.unpack_ver:
189 ver_file_path = os.path.join(args.out_dir, 'version.txt')
190 with open(ver_file_path, 'w') as ver_file:
191 ver_file.write(bytes_to_str(pack.pack_version))
Chirayu Desai7119eb62021-12-07 03:07:13 +0530192
Michael Bestas0382f302023-06-12 02:22:51 +0300193 logging.info('Done')
Chirayu Desai7119eb62021-12-07 03:07:13 +0530194
195
196def parse_args():
Michael Bestas0382f302023-06-12 02:22:51 +0300197 parser = argparse.ArgumentParser(
198 description='Tool to create/modify/inspect fastboot packed images')
199 parser.add_argument(
200 '-v',
201 '--verbosity',
202 action='count',
203 default=0,
204 help='increase output verbosity')
Chirayu Desai7119eb62021-12-07 03:07:13 +0530205
Michael Bestas0382f302023-06-12 02:22:51 +0300206 subparsers = parser.add_subparsers()
Chirayu Desai7119eb62021-12-07 03:07:13 +0530207
Michael Bestas0382f302023-06-12 02:22:51 +0300208 # info command
209 info = subparsers.add_parser('info')
210 info.add_argument('file', help='packed image file')
211 info.set_defaults(func=cmd_info)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530212
Michael Bestas0382f302023-06-12 02:22:51 +0300213 # create command
214 create = subparsers.add_parser('create')
215 create.add_argument(
216 '-d', '--in_dir', help='directory to search for data files', default='.')
217 create.add_argument(
218 '-o',
219 '--out_dir',
220 help='output directory for the packed image',
221 default='.')
222 create.add_argument(
223 '-v', '--pack_version', help='Packed image version ', default='')
224 create.add_argument(
225 'file', help='config file describing packed image (yaml/xml)')
226 create.set_defaults(func=cmd_create)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530227
Michael Bestas0382f302023-06-12 02:22:51 +0300228 # unpack command
229 unpack = subparsers.add_parser('unpack')
230 unpack.add_argument(
231 '-o', '--out_dir', help='directory to store unpacked images', default='.')
232 unpack.add_argument(
233 '-p', '--product', help='filter images by product', default='')
234 unpack.add_argument(
235 '-v',
236 '--unpack_ver',
237 help='Unpack version to a file',
238 action='store_true')
239 unpack.add_argument('file', help='packed image file')
240 unpack.add_argument(
241 'partitions',
242 metavar='PART',
243 type=str,
244 nargs='*',
245 help='Partition names to extract (default all).')
246 unpack.set_defaults(func=cmd_unpack)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530247
Michael Bestas0382f302023-06-12 02:22:51 +0300248 args = parser.parse_args()
249 # make sure a command was passed
250 if not hasattr(args, 'func'):
251 parser.print_usage()
252 print('fbpacktool.py: error: no command was passed')
253 sys.exit(2)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530254
Michael Bestas0382f302023-06-12 02:22:51 +0300255 return args
Chirayu Desai7119eb62021-12-07 03:07:13 +0530256
257
258def main():
Michael Bestas0382f302023-06-12 02:22:51 +0300259 args = parse_args()
Chirayu Desai7119eb62021-12-07 03:07:13 +0530260
Michael Bestas0382f302023-06-12 02:22:51 +0300261 if args.verbosity >= 2:
Chirayu Desai7119eb62021-12-07 03:07:13 +0530262 log_level = logging.DEBUG
Michael Bestas0382f302023-06-12 02:22:51 +0300263 elif args.verbosity == 1:
264 log_level = logging.INFO
265 else:
266 log_level = logging.WARNING
Chirayu Desai7119eb62021-12-07 03:07:13 +0530267
Michael Bestas0382f302023-06-12 02:22:51 +0300268 logging.basicConfig(level=log_level)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530269
Michael Bestas0382f302023-06-12 02:22:51 +0300270 # execute command
271 args.func(args)
Chirayu Desai7119eb62021-12-07 03:07:13 +0530272
273
274if __name__ == '__main__':
Michael Bestas0382f302023-06-12 02:22:51 +0300275 main()