#!/usr/bin/env python
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A tool for interacting with .pak files.
For details on the pak file format, see:
https://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
"""
from __future__
import print_function
import argparse
import gzip
import hashlib
import os
import shutil
import sys
import tempfile
# Import grit first to get local third_party modules.
import grit
# pylint: disable=ungrouped-imports,unused-import
import six
from grit.format
import data_pack
def _RepackMain(args):
output_info_filepath = args.output_pak_file +
'.info'
if args.compress:
# If the file needs to be compressed, call RePack with a tempfile path,
# then compress the tempfile to args.output_pak_file.
temp_outfile = tempfile.NamedTemporaryFile()
out_path = temp_outfile.name
# Strip any non .pak extension from the .info output file path.
splitext = os.path.splitext(args.output_pak_file)
if splitext[1] !=
'.pak':
output_info_filepath = splitext[0] +
'.info'
else:
out_path = args.output_pak_file
data_pack.RePack(out_path, args.input_pak_files, args.whitelist,
args.suppress_removed_key_output,
output_info_filepath=output_info_filepath)
if args.compress:
with open(args.output_pak_file,
'wb')
as out:
with gzip.GzipFile(filename=
'', mode=
'wb', fileobj=out, mtime=0)
as outgz:
shutil.copyfileobj(temp_outfile, outgz)
def _ExtractMain(args):
pak = data_pack.ReadDataPack(args.pak_file)
if args.textual_id:
info_dict = data_pack.ReadGrdInfo(args.pak_file)
for resource_id, payload
in pak.resources.items():
filename = (
info_dict[resource_id].textual_id
if args.textual_id
else str(resource_id))
path = os.path.join(args.output_dir, filename)
with open(path,
'w')
as f:
f.write(payload)
def _CreateMain(args):
pak = {}
for name
in os.listdir(args.input_dir):
try:
resource_id = int(name)
except:
continue
filename = os.path.join(args.input_dir, name)
if os.path.isfile(filename):
with open(filename,
'rb')
as f:
pak[resource_id] = f.read()
data_pack.WriteDataPack(pak, args.output_pak_file, data_pack.UTF8)
def _PrintMain(args):
pak = data_pack.ReadDataPack(args.pak_file)
if args.textual_id:
info_dict = data_pack.ReadGrdInfo(args.pak_file)
output = args.output
encoding =
'binary'
if pak.encoding == 1:
encoding =
'utf-8'
elif pak.encoding == 2:
encoding =
'utf-16'
else:
encoding =
'?' + str(pak.encoding)
output.write(
'version: {}\n'.format(pak.version))
output.write(
'encoding: {}\n'.format(encoding))
output.write(
'num_resources: {}\n'.format(len(pak.resources)))
output.write(
'num_aliases: {}\n'.format(len(pak.aliases)))
breakdown =
', '.join(
'{}: {}'.format(*x)
for x
in pak.sizes)
output.write(
'total_size: {} ({})\n'.format(pak.sizes.total, breakdown))
try_decode = args.decode
and encoding.startswith(
'utf')
# Print IDs in ascending order, since that's the order in which they appear in
# the file (order is lost by Python dict).
for resource_id
in sorted(pak.resources):
data = pak.resources[resource_id]
canonical_id = pak.aliases.get(resource_id, resource_id)
desc =
''
if try_decode:
try:
desc = six.text_type(data, encoding)
if len(desc) > 60:
desc = desc[:60] + u
'...'
desc = desc.replace(
'\n',
'\\n')
except UnicodeDecodeError:
pass
sha1 = hashlib.sha1(data).hexdigest()[:10]
if args.textual_id:
textual_id = info_dict[resource_id].textual_id
canonical_textual_id = info_dict[canonical_id].textual_id
output.write(
u
'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
textual_id, canonical_textual_id, len(data), sha1,
desc).encode(
'utf-8'))
else:
output.write(
u
'Entry(id={}, canonical_id={}, size={}, sha1={}): {}\n'.format(
resource_id, canonical_id, len(data), sha1, desc).encode(
'utf-8'))
def _ListMain(args):
pak = data_pack.ReadDataPack(args.pak_file)
if args.textual_id
or args.path:
info_dict = data_pack.ReadGrdInfo(args.pak_file)
fmt =
''.join([
'{id}',
' = {textual_id}' if args.textual_id
else '',
' @ {path}' if args.path
else '',
'\n'
])
for resource_id
in sorted(pak.resources):
item = info_dict[resource_id]
args.output.write(
fmt.format(textual_id=item.textual_id, id=item.id, path=item.path))
else:
for resource_id
in sorted(pak.resources):
args.output.write(
'%d\n' % resource_id)
def main():
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
# Subparsers are required by default under Python 2. Python 3 changed to
# not required, but didn't include a required option until 3.7. Setting
# the required member works in all versions (and setting dest name).
sub_parsers = parser.add_subparsers(dest=
'action')
sub_parsers.required =
True
sub_parser = sub_parsers.add_parser(
'repack',
help=
'Combines several .pak files into one.')
sub_parser.add_argument(
'output_pak_file', help=
'File to create.')
sub_parser.add_argument(
'input_pak_files', nargs=
'+',
help=
'Input .pak files.')
sub_parser.add_argument(
'--whitelist',
help=
'Path to a whitelist used to filter output pak file resource IDs.')
sub_parser.add_argument(
'--suppress-removed-key-output', action=
'store_true',
help=
'Do not log which keys were removed by the whitelist.')
sub_parser.add_argument(
'--compress', dest=
'compress', action=
'store_true',
default=
False, help=
'Compress output_pak_file using gzip.')
sub_parser.set_defaults(func=_RepackMain)
sub_parser = sub_parsers.add_parser(
'extract', help=
'Extracts pak file')
sub_parser.add_argument(
'pak_file')
sub_parser.add_argument(
'-o',
'--output-dir', default=
'.',
help=
'Directory to extract to.')
sub_parser.add_argument(
'-t',
'--textual-id',
action=
'store_true',
help=
'Use textual resource ID (name) (from .info file) as filenames.')
sub_parser.set_defaults(func=_ExtractMain)
sub_parser = sub_parsers.add_parser(
'create',
help=
'Creates pak file from extracted directory.')
sub_parser.add_argument(
'output_pak_file', help=
'File to create.')
sub_parser.add_argument(
'-i',
'--input-dir', default=
'.',
help=
'Directory to create from.')
sub_parser.set_defaults(func=_CreateMain)
sub_parser = sub_parsers.add_parser(
'print',
help=
'Prints all pak IDs and contents. Useful for diffing.')
sub_parser.add_argument(
'pak_file')
sub_parser.add_argument(
'--output', type=argparse.FileType(
'w'),
default=sys.stdout,
help=
'The resource list path to write (default stdout)')
sub_parser.add_argument(
'--no-decode', dest=
'decode', action=
'store_false',
default=
True, help=
'Do not print entry data.')
sub_parser.add_argument(
'-t',
'--textual-id',
action=
'store_true',
help=
'Print textual ID (name) (from .info file) instead of the ID.')
sub_parser.set_defaults(func=_PrintMain)
sub_parser = sub_parsers.add_parser(
'list-id',
help=
'Outputs all resource IDs to a file.')
sub_parser.add_argument(
'pak_file')
sub_parser.add_argument(
'--output', type=argparse.FileType(
'w'),
default=sys.stdout,
help=
'The resource list path to write (default stdout)')
sub_parser.add_argument(
'-t',
'--textual-id',
action=
'store_true',
help=
'Print the textual resource ID (from .info file).')
sub_parser.add_argument(
'-p',
'--path',
action=
'store_true',
help=
'Print the resource path (from .info file).')
sub_parser.set_defaults(func=_ListMain)
args = parser.parse_args()
args.func(args)
if __name__ ==
'__main__':
main()