me7_update_parser.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. #!/usr/bin/env python3
  2. """ME7 Update binary parser."""
  3. # Copyright (C) 2020 Tom Hiller <thrilleratplay@gmail.com>
  4. # Copyright (C) 2016-2018 Nicola Corna <nicola@corna.info>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # Based on the amazing me_cleaner, https://github.com/corna/me_cleaner, parses
  17. # the required signed partition from an ME update file to generate a valid
  18. # flashable ME binary.
  19. #
  20. # This was written for Heads ROM, https://github.com/osresearch/heads
  21. # to allow continuous integration reproducible builds for Lenovo xx20 models
  22. # (X220, T420, T520, etc).
  23. #
  24. # A full model list can be found:
  25. # https://download.lenovo.com/ibmdl/pub/pc/pccbbs/mobiles/83rf46ww.txt
  26. from struct import pack, unpack
  27. from typing import List
  28. import argparse
  29. import sys
  30. import hashlib
  31. import binascii
  32. import os.path
  33. #############################################################################
  34. FTPR_END = 0x76000
  35. MINIFIED_FTPR_OFFSET = 0x400 # offset start of Factory Partition (FTPR)
  36. ORIG_FTPR_OFFSET = 0xCC000
  37. PARTITION_HEADER_OFFSET = 0x30 # size of partition header
  38. DEFAULT_OUTPUT_FILE_NAME = "flashregion_2_intel_me.bin"
  39. #############################################################################
  40. class EntryFlags:
  41. """EntryFlag bitmap values."""
  42. ExclBlockUse = 8192
  43. WOPDisable = 4096
  44. Logical = 2048
  45. Execute = 1024
  46. Write = 512
  47. Read = 256
  48. DirectAccess = 128
  49. Type = 64
  50. def generateHeader() -> bytes:
  51. """Generate Header."""
  52. ROM_BYPASS_INSTR_0 = binascii.unhexlify("2020800F")
  53. ROM_BYPASS_INSTR_1 = binascii.unhexlify("40000010")
  54. ROM_BYPASS_INSTR_2 = pack("<I", 0)
  55. ROM_BYPASS_INSTR_3 = pack("<I", 0)
  56. # $FPT Partition table header
  57. HEADER_TAG = "$FPT".encode()
  58. HEADER_NUM_PARTITIONS = pack("<I", 1)
  59. HEADER_VERSION = b"\x20" # version 2.0
  60. HEADER_ENTRY_TYPE = b"\x10"
  61. HEADER_LENGTH = b"\x30"
  62. HEADER_CHECKSUM = pack("<B", 0)
  63. HEADER_FLASH_CYCLE_LIFE = pack("<H", 7)
  64. HEADER_FLASH_CYCLE_LIMIT = pack("<H", 100)
  65. HEADER_UMA_SIZE = pack("<H", 32)
  66. HEADER_FLAGS = binascii.unhexlify("000000FCFFFF")
  67. HEADER_FITMAJOR = pack("<H", 0)
  68. HEADER_FITMINOR = pack("<H", 0)
  69. HEADER_FITHOTFIX = pack("<H", 0)
  70. HEADER_FITBUILD = pack("<H", 0)
  71. FTPR_header_layout = bytearray(
  72. ROM_BYPASS_INSTR_0
  73. + ROM_BYPASS_INSTR_1
  74. + ROM_BYPASS_INSTR_2
  75. + ROM_BYPASS_INSTR_3
  76. + HEADER_TAG
  77. + HEADER_NUM_PARTITIONS
  78. + HEADER_VERSION
  79. + HEADER_ENTRY_TYPE
  80. + HEADER_LENGTH
  81. + HEADER_CHECKSUM
  82. + HEADER_FLASH_CYCLE_LIFE
  83. + HEADER_FLASH_CYCLE_LIMIT
  84. + HEADER_UMA_SIZE
  85. + HEADER_FLAGS
  86. + HEADER_FITMAJOR
  87. + HEADER_FITMINOR
  88. + HEADER_FITHOTFIX
  89. + HEADER_FITBUILD
  90. )
  91. # Update checksum
  92. FTPR_header_layout[27] = (0x100 - sum(FTPR_header_layout) & 0xFF) & 0xFF
  93. return FTPR_header_layout
  94. def generateFtpPartition() -> bytes:
  95. """Partition table entry."""
  96. ENTRY_NAME = binascii.unhexlify("46545052")
  97. ENTRY_OWNER = binascii.unhexlify("FFFFFFFF") # "None"
  98. ENTRY_OFFSET = binascii.unhexlify("00040000")
  99. ENTRY_LENGTH = binascii.unhexlify("00600700")
  100. ENTRY_START_TOKENS = pack("<I", 1)
  101. ENTRY_MAX_TOKENS = pack("<I", 1)
  102. ENTRY_SCRATCH_SECTORS = pack("<I", 0)
  103. ENTRY_FLAGS = pack(
  104. "<I",
  105. (
  106. EntryFlags.ExclBlockUse
  107. + EntryFlags.Execute
  108. + EntryFlags.Write
  109. + EntryFlags.Read
  110. + EntryFlags.DirectAccess
  111. ),
  112. )
  113. partition = (
  114. ENTRY_NAME
  115. + ENTRY_OWNER
  116. + ENTRY_OFFSET
  117. + ENTRY_LENGTH
  118. + ENTRY_START_TOKENS
  119. + ENTRY_MAX_TOKENS
  120. + ENTRY_SCRATCH_SECTORS
  121. + ENTRY_FLAGS
  122. )
  123. # offset of the partition - length of partition entry -length of header
  124. pad_len = MINIFIED_FTPR_OFFSET - (len(partition) + PARTITION_HEADER_OFFSET)
  125. padding = b""
  126. for i in range(0, pad_len):
  127. padding += b"\xFF"
  128. return partition + padding
  129. ############################################################################
  130. class OutOfRegionException(Exception):
  131. """Out of Region Exception."""
  132. pass
  133. class clean_ftpr:
  134. """Clean Factory Parition (FTPR)."""
  135. UNREMOVABLE_MODULES = ("ROMP", "BUP")
  136. COMPRESSION_TYPE_NAME = ("uncomp.", "Huffman", "LZMA")
  137. def __init__(self, ftpr: bytes):
  138. """Init."""
  139. self.orig_ftpr = ftpr
  140. self.ftpr = ftpr
  141. self.mod_headers: List[bytes] = []
  142. self.check_and_clean_ftpr()
  143. #####################################################################
  144. # tilities
  145. #####################################################################
  146. def slice(self, offset: int, size: int) -> bytes:
  147. """Copy data of a given size from FTPR starting from offset."""
  148. offset_end = offset + size
  149. return self.ftpr[offset:offset_end]
  150. def unpack_next_int(self, offset: int) -> int:
  151. """Sugar syntax for unpacking a little-endian UINT at offset."""
  152. return self.unpack_val(self.slice(offset, 4))
  153. def unpack_val(self, data: bytes) -> int:
  154. """Sugar syntax for unpacking a little-endian unsigned integer."""
  155. return unpack("<I", data)[0]
  156. def bytes_to_ascii(self, data: bytes) -> str:
  157. """Decode bytes into ASCII."""
  158. return data.rstrip(b"\x00").decode("ascii")
  159. def clear_ftpr_data(self, start: int, end: int) -> None:
  160. """Replace values in range with 0xFF."""
  161. empty_data = bytes()
  162. for i in range(0, end - start):
  163. empty_data += b"\xff"
  164. self.write_ftpr_data(start, empty_data)
  165. def write_ftpr_data(self, start: int, data: bytes) -> None:
  166. """Replace data in FTPR starting at a given offset."""
  167. end = len(data) + start
  168. new_partition = self.ftpr[:start]
  169. new_partition += data
  170. if end != FTPR_END:
  171. new_partition += self.ftpr[end:]
  172. self.ftpr = new_partition
  173. ######################################################################
  174. # FTPR cleanig/checking functions
  175. ######################################################################
  176. def get_chunks_offsets(self, llut: bytes):
  177. """Calculate Chunk offsets from LLUT."""
  178. chunk_count = self.unpack_val(llut[0x04:0x08])
  179. huffman_stream_end = sum(unpack("<II", llut[0x10:0x18]))
  180. nonzero_offsets = [huffman_stream_end]
  181. offsets = []
  182. for i in range(0, chunk_count):
  183. llut_start = 0x40 + (i * 4)
  184. llut_end = 0x44 + (i * 4)
  185. chunk = llut[llut_start:llut_end]
  186. offset = 0
  187. if chunk[3] != 0x80:
  188. offset = self.unpack_val(chunk[0:3] + b"\x00")
  189. offsets.append([offset, 0])
  190. if offset != 0:
  191. nonzero_offsets.append(offset)
  192. nonzero_offsets.sort()
  193. for i in offsets:
  194. if i[0] != 0:
  195. i[1] = nonzero_offsets[nonzero_offsets.index(i[0]) + 1]
  196. return offsets
  197. def relocate_partition(self) -> int:
  198. """Relocate partition."""
  199. new_offset = MINIFIED_FTPR_OFFSET
  200. name = self.bytes_to_ascii(self.slice(PARTITION_HEADER_OFFSET, 4))
  201. old_offset, partition_size = unpack(
  202. "<II", self.slice(PARTITION_HEADER_OFFSET + 0x8, 0x8)
  203. )
  204. llut_start = 0
  205. for mod_header in self.mod_headers:
  206. if (self.unpack_val(mod_header[0x50:0x54]) >> 4) & 7 == 0x01:
  207. llut_start = self.unpack_val(mod_header[0x38:0x3C])
  208. llut_start += old_offset
  209. break
  210. if self.mod_headers and llut_start != 0:
  211. # Bytes 0x9:0xb of the LLUT (bytes 0x1:0x3 of the AddrBase) are
  212. # added to the SpiBase (bytes 0xc:0x10 of the LLUT) to compute the
  213. # final start of the LLUT. Since AddrBase is not modifiable, we can
  214. # act only on SpiBase and here we compute the minimum allowed
  215. # new_offset.
  216. llut_start_corr = unpack("<H", self.slice(llut_start + 0x9, 2))[0]
  217. new_offset = max(
  218. new_offset, llut_start_corr - llut_start - 0x40 + old_offset
  219. )
  220. new_offset = ((new_offset + 0x1F) // 0x20) * 0x20
  221. offset_diff = new_offset - old_offset
  222. print(
  223. "Relocating {} from {:#x} - {:#x} to {:#x} - {:#x}...".format(
  224. name,
  225. old_offset,
  226. old_offset + partition_size,
  227. new_offset,
  228. new_offset + partition_size,
  229. )
  230. )
  231. print(" Adjusting FPT entry...")
  232. self.write_ftpr_data(
  233. PARTITION_HEADER_OFFSET + 0x08,
  234. pack("<I", new_offset),
  235. )
  236. if self.mod_headers:
  237. if llut_start != 0:
  238. if self.slice(llut_start, 4) == b"LLUT":
  239. print(" Adjusting LUT start offset...")
  240. llut_offset = pack(
  241. "<I", llut_start + offset_diff + 0x40 - llut_start_corr
  242. )
  243. self.write_ftpr_data(llut_start + 0x0C, llut_offset)
  244. print(" Adjusting Huffman start offset...")
  245. old_huff_offset = self.unpack_next_int(llut_start + 0x14)
  246. ftpr_offset_diff = MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET
  247. self.write_ftpr_data(
  248. llut_start + 0x14,
  249. pack("<I", old_huff_offset + ftpr_offset_diff),
  250. )
  251. print(" Adjusting chunks offsets...")
  252. chunk_count = self.unpack_next_int(llut_start + 0x4)
  253. offset = llut_start + 0x40
  254. offset_end = chunk_count * 4
  255. chunks = bytearray(self.slice(offset, offset_end))
  256. for i in range(0, offset_end, 4):
  257. i_plus_3 = i + 3
  258. if chunks[i_plus_3] != 0x80:
  259. chunks[i:i_plus_3] = pack(
  260. "<I",
  261. self.unpack_val(chunks[i:i_plus_3] + b"\x00")
  262. + (MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET),
  263. )[0:3]
  264. self.write_ftpr_data(offset, bytes(chunks))
  265. else:
  266. sys.exit("Huffman modules present but no LLUT found!")
  267. else:
  268. print(" No Huffman modules found")
  269. print(" Moving data...")
  270. partition_size = min(partition_size, FTPR_END - old_offset)
  271. if (
  272. old_offset + partition_size <= FTPR_END
  273. and new_offset + partition_size <= FTPR_END
  274. ):
  275. for i in range(0, partition_size, 4096):
  276. block_length = min(partition_size - i, 4096)
  277. block = self.slice(old_offset + i, block_length)
  278. self.clear_ftpr_data(old_offset + i, len(block))
  279. self.write_ftpr_data(new_offset + i, block)
  280. else:
  281. raise OutOfRegionException()
  282. return new_offset
  283. def remove_modules(self) -> int:
  284. """Remove modules."""
  285. unremovable_huff_chunks = []
  286. chunks_offsets = []
  287. base = 0
  288. chunk_size = 0
  289. end_addr = 0
  290. for mod_header in self.mod_headers:
  291. name = self.bytes_to_ascii(mod_header[0x04:0x14])
  292. offset = self.unpack_val(mod_header[0x38:0x3C])
  293. size = self.unpack_val(mod_header[0x40:0x44])
  294. flags = self.unpack_val(mod_header[0x50:0x54])
  295. comp_type = (flags >> 4) & 7
  296. comp_type_name = self.COMPRESSION_TYPE_NAME[comp_type]
  297. print(" {:<16} ({:<7}, ".format(name, comp_type_name), end="")
  298. # If compresion type uncompressed or LZMA
  299. if comp_type == 0x00 or comp_type == 0x02:
  300. offset_end = offset + size
  301. range_msg = "0x{:06x} - 0x{:06x} ): "
  302. print(range_msg.format(offset, offset_end), end="")
  303. if name in self.UNREMOVABLE_MODULES:
  304. end_addr = max(end_addr, offset + size)
  305. print("NOT removed, essential")
  306. else:
  307. offset_end = min(offset + size, FTPR_END)
  308. self.clear_ftpr_data(offset, offset_end)
  309. print("removed")
  310. # Else if compression type huffman
  311. elif comp_type == 0x01:
  312. if not chunks_offsets:
  313. # Check if Local Look Up Table (LLUT) is present
  314. if self.slice(offset, 4) == b"LLUT":
  315. llut = self.slice(offset, 0x40)
  316. chunk_count = self.unpack_val(llut[0x4:0x8])
  317. base = self.unpack_val(llut[0x8:0xC]) + 0x10000000
  318. chunk_size = self.unpack_val(llut[0x30:0x34])
  319. llut = self.slice(offset, (chunk_count * 4) + 0x40)
  320. # calculate offsets of chunks from LLUT
  321. chunks_offsets = self.get_chunks_offsets(llut)
  322. else:
  323. no_llut_msg = "Huffman modules found,"
  324. no_llut_msg += "but LLUT is not present."
  325. sys.exit(no_llut_msg)
  326. module_base = self.unpack_val(mod_header[0x34:0x38])
  327. module_size = self.unpack_val(mod_header[0x3C:0x40])
  328. first_chunk_num = (module_base - base) // chunk_size
  329. last_chunk_num = first_chunk_num + module_size // chunk_size
  330. huff_size = 0
  331. chunk_length = last_chunk_num + 1
  332. for chunk in chunks_offsets[first_chunk_num:chunk_length]:
  333. huff_size += chunk[1] - chunk[0]
  334. size_in_kiB = "~" + str(int(round(huff_size / 1024))) + " KiB"
  335. print(
  336. "fragmented data, {:<9}): ".format(size_in_kiB),
  337. end="",
  338. )
  339. # Check if module is in the unremovable list
  340. if name in self.UNREMOVABLE_MODULES:
  341. print("NOT removed, essential")
  342. # add to list of unremovable chunks
  343. for x in chunks_offsets[first_chunk_num:chunk_length]:
  344. if x[0] != 0:
  345. unremovable_huff_chunks.append(x)
  346. else:
  347. print("removed")
  348. # Else unknown compression type
  349. else:
  350. unkwn_comp_msg = " 0x{:06x} - 0x{:06x}): "
  351. unkwn_comp_msg += "unknown compression, skipping"
  352. print(unkwn_comp_msg.format(offset, offset + size), end="")
  353. if chunks_offsets:
  354. removable_huff_chunks = []
  355. for chunk in chunks_offsets:
  356. # if chunk is not in a unremovable chunk, it must be removable
  357. if all(
  358. not (
  359. unremovable_chk[0] <= chunk[0] < unremovable_chk[1]
  360. or unremovable_chk[0] < chunk[1] <= unremovable_chk[1]
  361. )
  362. for unremovable_chk in unremovable_huff_chunks
  363. ):
  364. removable_huff_chunks.append(chunk)
  365. for removable_chunk in removable_huff_chunks:
  366. if removable_chunk[1] > removable_chunk[0]:
  367. chunk_start = removable_chunk[0] - ORIG_FTPR_OFFSET
  368. chunk_end = removable_chunk[1] - ORIG_FTPR_OFFSET
  369. self.clear_ftpr_data(chunk_start, chunk_end)
  370. end_addr = max(
  371. end_addr, max(unremovable_huff_chunks, key=lambda x: x[1])[1]
  372. )
  373. end_addr -= ORIG_FTPR_OFFSET
  374. return end_addr
  375. def find_mod_header_size(self) -> None:
  376. """Find module header size."""
  377. self.mod_header_size = 0
  378. data = self.slice(0x290, 0x84)
  379. # check header size
  380. if data[0x0:0x4] == b"$MME":
  381. if data[0x60:0x64] == b"$MME" or self.num_modules == 1:
  382. self.mod_header_size = 0x60
  383. elif data[0x80:0x84] == b"$MME":
  384. self.mod_header_size = 0x80
  385. def find_mod_headers(self) -> None:
  386. """Find module headers."""
  387. data = self.slice(0x290, self.mod_header_size * self.num_modules)
  388. for i in range(0, self.num_modules):
  389. header_start = i * self.mod_header_size
  390. header_end = (i + 1) * self.mod_header_size
  391. self.mod_headers.append(data[header_start:header_end])
  392. def resize_partition(self, end_addr: int) -> None:
  393. """Resize partition."""
  394. spared_blocks = 4
  395. if end_addr > 0:
  396. end_addr = (end_addr // 0x1000 + 1) * 0x1000
  397. end_addr += spared_blocks * 0x1000
  398. # partition header not added yet
  399. # remove trailing data the same size as the header.
  400. end_addr -= MINIFIED_FTPR_OFFSET
  401. me_size_msg = "The ME minimum size should be {0} "
  402. me_size_msg += "bytes ({0:#x} bytes)"
  403. print(me_size_msg.format(end_addr))
  404. print("Truncating file at {:#x}...".format(end_addr))
  405. self.ftpr = self.ftpr[:end_addr]
  406. def check_and_clean_ftpr(self) -> None:
  407. """Check and clean FTPR (factory partition)."""
  408. self.num_modules = self.unpack_next_int(0x20)
  409. self.find_mod_header_size()
  410. if self.mod_header_size != 0:
  411. self.find_mod_headers()
  412. # ensure all of the headers begin with b'$MME'
  413. if all(hdr.startswith(b"$MME") for hdr in self.mod_headers):
  414. end_addr = self.remove_modules()
  415. new_offset = self.relocate_partition()
  416. end_addr += new_offset
  417. self.resize_partition(end_addr)
  418. # flip bit
  419. # XXX: I have no idea why this works and passes RSA signiture
  420. self.write_ftpr_data(0x39, b"\x00")
  421. else:
  422. sys.exit(
  423. "Found less modules than expected in the FTPR "
  424. "partition; skipping modules removal and exiting."
  425. )
  426. else:
  427. sys.exit(
  428. "Can't find the module header size; skipping modules"
  429. "removal and exiting."
  430. )
  431. ##########################################################################
  432. def check_partition_signature(f, offset) -> bool:
  433. """check_partition_signature copied/shamelessly stolen from me_cleaner."""
  434. f.seek(offset)
  435. header = f.read(0x80)
  436. modulus = int(binascii.hexlify(f.read(0x100)[::-1]), 16)
  437. public_exponent = unpack("<I", f.read(4))[0]
  438. signature = int(binascii.hexlify(f.read(0x100)[::-1]), 16)
  439. header_len = unpack("<I", header[0x4:0x8])[0] * 4
  440. manifest_len = unpack("<I", header[0x18:0x1C])[0] * 4
  441. f.seek(offset + header_len)
  442. sha256 = hashlib.sha256()
  443. sha256.update(header)
  444. tmp = f.read(manifest_len - header_len)
  445. sha256.update(tmp)
  446. decrypted_sig = pow(signature, public_exponent, modulus)
  447. return "{:#x}".format(decrypted_sig).endswith(sha256.hexdigest()) # FIXME
  448. ##########################################################################
  449. def generate_me_blob(input_file: str, output_file: str) -> None:
  450. """Generate ME blob."""
  451. print("Starting ME 7.x Update parser.")
  452. orig_f = open(input_file, "rb")
  453. cleaned_ftpr = clean_ftpr(orig_f.read(FTPR_END))
  454. orig_f.close()
  455. fo = open(output_file, "wb")
  456. fo.write(generateHeader())
  457. fo.write(generateFtpPartition())
  458. fo.write(cleaned_ftpr.ftpr)
  459. fo.close()
  460. def verify_output(output_file: str) -> None:
  461. """Verify Generated ME file."""
  462. file_verifiy = open(output_file, "rb")
  463. if check_partition_signature(file_verifiy, MINIFIED_FTPR_OFFSET):
  464. print(output_file + " is VALID")
  465. file_verifiy.close()
  466. else:
  467. print(output_file + " is INVALID!!")
  468. file_verifiy.close()
  469. sys.exit("The FTPR partition signature is not valid.")
  470. if __name__ == "__main__":
  471. parser = argparse.ArgumentParser(
  472. description="Tool to remove as much code "
  473. "as possible from Intel ME/TXE 7.x firmware "
  474. "update and create paratition for a flashable ME parition."
  475. )
  476. parser.add_argument("file", help="ME/TXE image or full dump")
  477. parser.add_argument(
  478. "-O",
  479. "--output",
  480. metavar="output_file",
  481. help="save "
  482. "save file name other than the default '" + DEFAULT_OUTPUT_FILE_NAME + "'",
  483. )
  484. args = parser.parse_args()
  485. output_file_name = DEFAULT_OUTPUT_FILE_NAME if not args.output else args.output
  486. # Check if output file exists, ask to overwrite or exit
  487. if os.path.isfile(output_file_name):
  488. input_msg = output_file_name
  489. input_msg += " exists. Do you want to overwrite? [y/N]: "
  490. if not str(input(input_msg)).lower().startswith("y"):
  491. sys.exit("Not overwriting file. Exiting.")
  492. generate_me_blob(args.file, output_file_name)
  493. verify_output(output_file_name)