phoenix_firmware_dumper/utils/kdztools/libexec/dz.py

199 lines
6.0 KiB
Python

#!/usr/bin/env python
"""
Copyright (C) 2016 Elliott Mitchell <ehem+android@m5p.com>
Copyright (C) 2013 IOMonster (thecubed on XDA)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import absolute_import
from __future__ import print_function
import sys
from struct import Struct
from collections import OrderedDict
class DZStruct(object):
"""
Common class for DZ file structures
"""
# Length of the headers in DZ files
_dz_length = 512
def __init__(self, classy):
"""
Common initializations for all DZ structures
"""
# Generate the struct for .unpack()
try:
classy._dzstruct
except AttributeError:
classy._dz_struct = Struct("<" + "".join([x[0] for x in classy._dz_format_dict.values()]))
# Sanity check
if self._dz_struct.size != self._dz_length:
print("[!] Internal error! Chunk format wrong! (computed={:d}, specified={:d})".format(self._dz_struct.size, self._dz_length), file=sys.stderr)
sys.exit(-1)
# Generate list of items that can be collapsed (truncated)
try:
classy._dz_collapsibles
except AttributeError:
classy._dz_collapsibles = [n for n, (y, p) in classy._dz_format_dict.items() if p]
def packdict(self, din):
"""
Pack all the fields from the dict into a returned buffer
"""
dout = dict()
# pad any string keys that need padding
for k in self._dz_format_dict.keys():
if self._dz_format_dict[k][0][-1] == 's':
l = int(self._dz_format_dict[k][0][:-1])
dout[k] = (din[k] if k in din else b"").ljust(l, b'\x00')
elif not k in din and k in self._dz_collapsibles:
dout[k] = 0
else:
dout[k] = din[k]
dout['header'] = self._dz_header
values = [dout[k] for k in self._dz_format_dict.keys()]
buffer = self._dz_struct.pack(*values)
return buffer
def unpackdict(self, buffer):
"""
Unpack data in buffer into a returned dictionary, return None
if magic number/header is absent
"""
d = dict(zip(
self._dz_format_dict.keys(),
self._dz_struct.unpack(buffer)
))
if d['header'] != self._dz_header:
return None
return d
class DZChunk(DZStruct):
"""
Representation of an individual file chunk from a LGE DZ file
"""
_dz_area = "chunk"
_dz_header = b"\x30\x12\x95\x78"
# Format string dict
# itemName is the new dict key for the data to be stored under
# formatString is the Python formatstring for struct.unpack()
# collapse: boolean that controls whether extra \x00 's should be stripped
# for integer types collapse set to True means that the value should always be zero
# Example:
# ('itemName', ('formatString', collapse))
_dz_format_dict = OrderedDict([
('header', ('4s', False)), # magic number
('sliceName', ('32s', True)), # name of our slice
('chunkName', ('64s', True)), # name of our chunk
('targetSize', ('I', False)), # bytes of target area
('dataSize', ('I', False)), # amount of compressed
('md5', ('16s', False)), # MD5 of target image
('targetAddr', ('I', False)), # first block to write
('trimCount', ('I', False)), # blocks to TRIM before
('dev', ('I', False)), # flash device Id
('crc32', ('I', False)), # CRC32 of target image
('pad', ('372s', True)), # currently always zero
])
def __init__(self):
"""
Initializer for DZChunk, gets DZStruct to fill remaining values
"""
super(DZChunk, self).__init__(DZChunk)
class DZFile(DZStruct):
"""
Representation of the data parsed from a LGE DZ file
"""
_dz_area = "file"
_dz_header = b"\x32\x96\x18\x74"
# Format string dict
# itemName is the new dict key for the data to be stored under
# formatString is the Python formatstring for struct.unpack()
# collapse: boolean that controls whether extra \x00 's should be stripped
# for integer types collapse set to True means that the value should always be zero
# Example:
# ('itemName', ('formatString', collapse))
_dz_format_dict = OrderedDict([
('header', ('4s', False)), # magic number
('formatMajor', ('I', False)), # always 2 in LE
('formatMinor', ('I', False)), # always 1 in LE
('reserved0', ('I', True)), # format patchlevel?
('device', ('32s', True)),
('version', ('121s', True)), # "factoryversion"
('unknown9', ('23s', False)), # md5?
('chunkCount', ('I', False)),
('md5', ('16s', False)), # MD5 of chunk headers
('unknown0', ('I', False)), # 256?
('reserved1', ('I', True)), # currently always zero
('reserved4', ('H', True)), # currently always zero
('unknown1', ('16s', False)), # unknown, MD5 of thing?
('unknown2', ('50s', True)), # A##-M##-C##-U##-0 ?
('buildType', ('20s', True)), # "user"???
('unknown3', ('4s', False)), # version code? CRC?
('androidVer', ('10s', True)), # Android ver, optional
#anti-rollback minimum date? absent from Lollipop, "122142720" on all other V10
('oldDateCode', ('10s', True)), # prior firmware date?
('reserved5', ('I', False)), # currently always zero
('unknown4', ('I', False)), # sometimes 256?
('unknown5', ('I', False)), # ???
('unknown6', ('64s', False)), # ???
('unknown7', ('32s', False)), # ???
('unknown8', ('8s', False)), # ???
('pad', ('64s', True)), # currently always zero
])
def __init__(self):
"""
Initializer for DZFile, gets DZStruct to fill remaining values
"""
super(DZFile, self).__init__(DZFile)
if __name__ == "__main__":
print("Sorry, this file is an internal library and doesn't do anything interesting by", file=sys.stderr)
print("itself.", file=sys.stderr)
sys.exit(1)