Added support for some firmware images, minor bugs fixed
[unME11.git] / unME11.py
1 #!/usr/bin/env python
2 import os, sys, struct, hashlib, platform
3 import subprocess
4
5 try:
6   import HuffDec11
7   HuffDecoder11 = HuffDec11.HuffDecoder()
8 except:
9   HuffDecoder11 = None
10
11 try:
12   import HuffDec12
13   HuffDecoder12 = HuffDec12.HuffDecoder()
14 except:
15   HuffDecoder12 = None
16
17 class Globals(object):
18   HuffDecoder = HuffDecoder11
19   dumpManifest = True # Dump CPD manifest
20   dumpMeta = True # Dump modules metadata
21   dumpRaw = False # Dump raw modules data (compressed/encrypted)
22   dumpChunks = False # Dump HUFF chunks
23 g = Globals()
24
25 class Error(Exception): pass
26
27 def BitFields(obj, val, bitDef):
28  def bitf(val, lo, hi):  return (val & ((2<<hi)-1)) >> lo
29  for name, lo, hi in bitDef: setattr(obj, name, bitf(val, lo, hi))
30
31 def ListTrueBools(obj, bitDef):
32   return [v[0] for v in filter(lambda x: x[1] == x[2] and getattr(obj, x[0]), bitDef)]
33
34 class StructReader(object):
35   def __init__(self, ab, base=0, isLE=True):
36     self.ab = ab
37     self.base = base
38     self.o = self.base
39     self.cE = "<" if isLE else ">"
40
41   def sizeLeft(self):
42     return len(self.ab) - self.o
43
44   def getData(self, o, cb):
45     o += self.base
46     if o < len(self.ab) and cb >= 0 and o + cb <= len(self.ab):
47       return self.ab[o:o+cb]
48
49   def read(self, obj, stDef, o=None):
50     if o is None: o = self.o
51     self.o += self.base
52     for fldDef in stDef: # Walk field definitions
53       name = fldDef[0]
54       fmt = self.cE + fldDef[1]
55       val, = struct.unpack_from(fmt, self.ab, o)
56       if 3 == len(fldDef):
57         expected = fldDef[2]
58         if isinstance(expected, (list, tuple)):
59           if not val in expected:
60             print >>sys.stderr, "- %s.%s: not %s in %s" % (obj.__class__.__name__, name, val, expected)
61         else:
62           if val != expected:
63             print >>sys.stderr, "- %s.%s:" % (obj.__class__.__name__, name),
64             if isinstance(val, str): print >>sys.stderr, "Got %s, expected %s" % (val.encode("hex"), expected.encode("hex"))
65             else: print >>sys.stderr, "Got [%s], expected [%s]" % (repr(val), repr(expected))
66           else: assert val == expected
67       setattr(obj, name, val)
68       o += struct.calcsize(fmt)
69     self.o = o
70
71   def done(self):
72     assert len(self.ab) == self.o
73
74 aPubKeyHash = [v.decode("hex") for v in (
75   "EA6FA86514FA887C9044218EDB4D70BB3BCC7C2D37587EA8F760BAFBE158C587",
76   "A24E0682EDC8870DCA947C01603D19818AF714BEE9F39D2872D79B8C422F3890",
77   "EA3E9C34C8FD6BDEA277F0A8C6AC5A37E8E39256469C89D279FA86A7317B21AE",
78   "C8E7AA2C5F691F63A892BC044CD3935C5E77C6CB71C8E8627BE4987DFB730856",
79   "3D512A6DB7C855E9F6328DB8B2C259A2C0F291BB6E3EC74A2FB811AD84C5D404",
80   "04A6F35B14628879050AB0B3459326DDF946AE4E5EFD7BB1930883F57F68D084",
81   "980F9572AC1B5BDC9A5F3E89F2503A624C9C5BDF97B72D9031DCCDAB11A9F7A8",
82   "C468E6BA739856797BAF70910861BDE4C3BA95C956B1DCE24B738D614F1211BA",
83 )]
84
85 #***************************************************************************
86 #***************************************************************************
87 #***************************************************************************
88
89 args_lzma = {
90   "Windows": ["lzma", "d", "-si", "-so"],
91   "Linux":   ["lzma", "-d"],
92   "Darwin":  ["lzma", "-d"], # "brew install xz" or "sudo port install xz"
93 }[platform.system()]
94
95 def LZMA_decompress(compdata):
96   process = subprocess.Popen(args_lzma, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97   output, errout = process.communicate(compdata)
98   retcode = process.poll()
99   if retcode: raise Error(errout)
100   return output
101
102 def decompress(data, compType, length):
103   if compType is None:
104     return data
105   elif "lzma" == compType:
106     if not data.startswith("36004000".decode("hex")):
107       print >>sys.stderr, "- Bad LZMA[0x%X] header %s" % (len(data), data[:17].encode("hex"))
108       return None
109     assert data.startswith("36004000".decode("hex"))
110     assert '\0\0\0' == data[14:17]
111     return LZMA_decompress(data[:14] + data[17:])
112   elif "huff" == compType:
113     return g.HuffDecoder.decompress(data, length) if g.HuffDecoder else None
114   else:
115     raise Error("Invalid compType %s" % compType)
116
117
118 RESTART_NOT_ALLOWED = 0
119 RESTART_IMMEDIATLY = 1
120 RESTART_ON_NEXT_BOOT = 2
121
122 # MODULE_TYPES
123 PROCESS_TYPE            = 0
124 SHARED_LIBRARY_TYPE     = 1
125 DATA_TYPE               = 2
126 IUNIT_TYPE              = 3 # v12
127
128 # PARTITION_TYPES
129 FPT_AREATYPE_GENERIC    = 1
130 FPT_AREATYPE_CODE       = 0
131 FPT_AREATYPE_ROM        = 1
132 FPT_AREATYPE_DATA       = 1
133
134 # COMPRESSION_TYPE
135 COMP_TYPE_NOT_COMPRESSED = 0
136 COMP_TYPE_HUFFMAN = 1
137 COMP_TYPE_LZMA = 2
138 dCompType = {
139   COMP_TYPE_NOT_COMPRESSED : None,
140   COMP_TYPE_HUFFMAN: "huff",
141   COMP_TYPE_LZMA: "lzma",
142 }
143
144 #***************************************************************************
145 #***************************************************************************
146 #***************************************************************************
147
148 class Extension(object):
149   NAME = None
150   TYPE = None
151   LIST = None
152   def Banner(self, noSize=False):
153     nItems = "" if noSize or (self.LIST is None) else "[%d]" % len(getattr(self, self.LIST))
154     return ". Ext#%d %s%s:" % (self.TYPE, self.NAME, nItems)
155
156   def PrintItems(self, flog):
157     for i,e in enumerate(getattr(self, self.LIST)): print >>flog, "%6d: %s" % (i+1, e)
158
159   def LoadItems(self, stR, cls, cnt=None):
160     if cnt is None:
161       lst = []
162       while stR.o < len(stR.ab): lst.append(cls(stR))
163     else:
164       lst = [cls(stR) for i in xrange(cnt)]
165     stR.done()
166     setattr(self, self.LIST, lst)
167
168 #***************************************************************************
169 #***************************************************************************
170 #***************************************************************************
171
172 #***************************************************************************
173 #***************************************************************************
174 #***************************************************************************
175
176 class System_Info_Ext(Extension): # 0 : used in Mainfist
177   NAME = "SystemInfo"
178   TYPE = 0 # for system info extension
179   LIST = "indParts"
180   SYSTEM_INFO_EXTENSION = (
181     ("uma_size",                "L",    ),              # Minimum UMA size required for this SKU in bytes
182     ("chipset_version",         "L",    ),              # Chipset version
183     ("img_default_hash",        "32s",  ),              # SHA2 hash of a 'defaults' file added to the image. (/intel.cfg). The load manager is responsible for verifying the hash of this file and creating the default files at the first system boot.
184     ("pageable_uma_size",       "L",    ),              # Size of pageable space within UMA in bytes. Must be divisible by 4K.
185     ("reserved_0",              "Q",    0),             #
186     ("reserved_1",              "L",    0),             #
187     # INDEPENDENT_PARTITION_ENTRY[]
188   )
189   def __init__(self, ab):
190     stR = StructReader(ab)
191     stR.read(self, self.SYSTEM_INFO_EXTENSION)
192     self.img_default_hash = self.img_default_hash[::-1] # Reverse?
193     self.LoadItems(stR, Independent_Partition_Entry)
194
195   def dump(self, flog=sys.stdout):
196     print >>flog, "%s uma_size:0x%X, chipset_version:0x%X, pageable_uma_size:0x%X defaults_h:%s" % (self.Banner(), self.uma_size, self.chipset_version, self.pageable_uma_size, self.img_default_hash.encode("hex"))
197     self.PrintItems(flog)
198
199 #***************************************************************************
200
201 class Independent_Partition_Entry:
202   INDEPENDENT_PARTITION_ENTRY = (
203     ("name",                    "4s",   ),              #
204     ("version",                 "L",    ),              #
205     ("user_id",                 "H",    ),              #
206     ("reserved",                "H",    ),              #
207   )
208   def __init__(self, stR):
209     stR.read(self, self.INDEPENDENT_PARTITION_ENTRY)
210     self.name = self.name.rstrip('\0')
211
212   def __str__(self):
213     return "[%-4s] user_id:0x%04X ver:0x%08X %X" % (self.name, self.user_id, self.version, self.reserved)
214
215 #***************************************************************************
216 #***************************************************************************
217 #***************************************************************************
218
219 class Init_Script_Ext(Extension): # 1 : used in Mainfist
220   NAME = "InitScript"
221   TYPE = 1 # for initialization script extension
222   LIST = "scripts"
223   # length: In bytes; equals (16 + 52*n) for this version where n is the number of modules in the initialization script
224   INIT_SCRIPT = (
225     ("reserved",                "L",    0),             # Reserved for future use.
226     ("number_of_modules",       "L",    ),              # Number of modules in this initialization script. Cannot be more than MAX_MODULES (this is a configuration parameter defining the maximum number of modules supported by the system set at system build time).
227     # INIT_SCRIPT_ENTRY[] # initialization script extension entries
228   )
229   def __init__(self, ab):
230     stR = StructReader(ab)
231     stR.read(self, self.INIT_SCRIPT)
232
233     clsSize, remainder = divmod(stR.sizeLeft(), self.number_of_modules)
234     if remainder: raise Error("Init_Script_Ext data size == %d is not miltiple of nItems == %d" % stR.sizeLeft(), self.number_of_modules)
235     cls = {24: Init_Script_Entry, 28: Init_Script_Entry_v12}[clsSize]
236     self.LoadItems(stR, cls, self.number_of_modules)
237
238   def dump(self, flog=sys.stdout):
239     print >>flog, self.Banner()
240     self.PrintItems(flog)
241
242 #***************************************************************************
243
244 class Init_Script_Entry:
245   INIT_SCRIPT_ENTRY = (
246     ("partition_name",          "4s",   ),              # Manifest Partition Name. This field identifies the manifest in which this module's hash will be found irregardles of manifest's physical location (i.e. FTP manifest may be physically located in NFTP flash partition during FW update).
247     ("name",                    "12s",  ),              # Module Name
248     ("bf_init_flags",           "L",    ),              # Flags used govern initialization flow.
249     ("bf_boot_type",            "L",    ),              # Boot path flag bits to indicate which boot path(s) this module is applicable to. Bit 0 - Normal Bit 1 - HAP Bit 2 - HMRFPO Bit 3 - Temp Disable Bit 4 - Recovery Bit 5 - Safe Mode Bit 6 - FW Update Bits 7:31 - Reserved
250   )
251   def __init__(self, stR):
252     self.unk = None
253     stR.read(self, self.INIT_SCRIPT_ENTRY)
254     self.partition_name = self.partition_name.rstrip('\0')
255     self.name = self.name.rstrip('\0')
256     self.init_flags = Init_Script_Flags(self.bf_init_flags)
257     self.boot_type = Init_Script_Boot_Type(self.bf_boot_type)
258
259   def __str__(self):
260     return "%4s:%-12s Init: %08X (%s) Boot: %08X (%s)" % (self.partition_name, self.name, self.bf_init_flags, self.init_flags, self.bf_boot_type, self.boot_type)
261
262 #***************************************************************************
263
264 class Init_Script_Entry_v12:
265   INIT_SCRIPT_ENTRY = (
266     ("partition_name",          "4s",   ),              # Manifest Partition Name. This field identifies the manifest in which this module's hash will be found irregardles of manifest's physical location (i.e. FTP manifest may be physically located in NFTP flash partition during FW update).
267     ("name",                    "12s",  ),              # Module Name
268     ("bf_init_flags",           "L",    ),              # Flags used govern initialization flow.
269     ("bf_boot_type",            "L",    ),              # Boot path flag bits to indicate which boot path(s) this module is applicable to. Bit 0 - Normal Bit 1 - HAP Bit 2 - HMRFPO Bit 3 - Temp Disable Bit 4 - Recovery Bit 5 - Safe Mode Bit 6 - FW Update Bits 7:31 - Reserved
270     ("unk",                     "L",    ),              #
271   )
272   def __init__(self, stR):
273     stR.read(self, self.INIT_SCRIPT_ENTRY)
274     self.partition_name = self.partition_name.rstrip('\0')
275     self.name = self.name.rstrip('\0')
276     self.init_flags = Init_Script_Flags(self.bf_init_flags)
277     self.boot_type = Init_Script_Boot_Type(self.bf_boot_type)
278
279   def __str__(self):
280     return "%4s:%-12s Init: %08X (%s) Boot: %08X (%s) Unk: %X" % (self.partition_name, self.name, self.bf_init_flags, self.init_flags, self.bf_boot_type, self.boot_type, self.unk)
281
282 #***************************************************************************
283
284 class Init_Script_Flags: # !!! Not sure...
285   dRestart = {
286     RESTART_NOT_ALLOWED : "Not Allowed",        # 0
287     RESTART_IMMEDIATLY  : "Immediatly",         # 1
288     RESTART_ON_NEXT_BOOT: "On Next Boot",       # 2
289   }
290   INIT_SCRIPT_FLAGS = ( # BitFields
291     ("Ibl",                     0,      0),
292     ("IsRemovable",             1,      1),
293     ("InitImmediately",         2,      2),
294     ("RestartPolicy",           3,      15),
295     ("Cm0_u",                   16,     16),
296     ("Cm0_nu",                  17,     17),
297     ("Cm3",                     18,     18),
298 #    ("reserved",               19,     31),
299   )
300   def __init__(self, dw):
301     BitFields(self, dw, self.INIT_SCRIPT_FLAGS)
302
303   def __str__(self):
304     r = ListTrueBools(self, self.INIT_SCRIPT_FLAGS)
305     if self.RestartPolicy: r.append("Restart %s" % self.dRestart[self.RestartPolicy])
306     return ", ".join(r)
307
308 #***************************************************************************
309
310 class Init_Script_Boot_Type:
311   INIT_SCRIPT_BOOT_TYPE = ( # BitFields
312     ("Normal",                  0,      0),
313     ("HAP",                     1,      1),
314     ("HMRFPO",                  2,      2),
315     ("TmpDisable",              3,      3),
316     ("Recovery",                4,      4),
317     ("SafeMode",                5,      5),
318     ("FWUpdate",                6,      6),
319 #    ("reserved",               7,      31),
320   )
321   def __init__(self, dw):
322     BitFields(self, dw, self.INIT_SCRIPT_BOOT_TYPE)
323
324   def __str__(self):
325     return ", ".join(ListTrueBools(self, self.INIT_SCRIPT_BOOT_TYPE))
326
327 #***************************************************************************
328 #***************************************************************************
329 #***************************************************************************
330
331 class Feature_Permissions_Ext(Extension): # 2 : used in Mainfist
332   NAME = "FeaturePermissions"
333   TYPE = 2 # for feature permission extension
334   LIST = "permissions"
335   # length: In bytes; equals (12 + 2*n) for this version where n is the number of features in this extension
336   FEATURE_PERMISSIONS_EXTENSION = (
337     ("num_of_features",         "L",    ),              # Number of features feature numbering always starts from 0.
338     # FEATURE_PERMISSION_ENTRY[] # feature permission extension entries
339   )
340   def __init__(self, ab):
341     stR = StructReader(ab)
342     stR.read(self, self.FEATURE_PERMISSIONS_EXTENSION)
343     self.LoadItems(stR, Feature_Permission_Entry, self.num_of_features)
344
345   def dump(self, flog=sys.stdout):
346     print >>flog, "%s [%s]" % (self.Banner(), ", ".join("0x%04X" % e.user_id for e in self.permissions))
347
348 #***************************************************************************
349
350 class Feature_Permission_Entry:
351   FEATURE_PERMISSION_ENTRY = (
352     ("user_id",                 "H",    ),              # User ID that may change feature state for feature 0.
353     ("reserved",                "H",    0),             #
354   )
355   def __init__(self, stR):
356     stR.read(self, self.FEATURE_PERMISSION_ENTRY)
357
358 #***************************************************************************
359 #***************************************************************************
360 #***************************************************************************
361
362 class Partition_Info_Ext(Extension): # 3 : used in Mainfist
363   NAME = "PartitionInfo"
364   TYPE = 3 # for partition info extension
365   LIST = "modules"
366   # length: In bytes; equals (92 + 52*n) for this version where n is the number of modules in the manifest
367   MANIFEST_PARTITION_INFO_EXT = (
368     ("partition_name",          "4s",   ),              # Name of the partition
369     ("partition_length",        "L",    ),              # Length of complete partition before any process have been removed by the OEM or the firmware update process
370     ("partition_hash",          "32s",  ),              # SHA256 hash of the original complete partition covering everything in the partition except for the manifest (directory binaries and LUT)
371     ("version_control_number",  "L",    ),              # The version control number (VCN) is incremented whenever a change is made to the FW that makes it incompatible from an update perspective with previously released versions of the FW.
372     ("partition_version",       "L",    ),              # minor number
373     ("data_format_version",     "L",    ),              #
374     ("instance_id",             "L",    ),              #
375     ("flags",                   "L",    ),              # Support multiple instances Y/N. Used for independently updated partitions that may have multiple instances (such as WLAN uCode or Localization)
376     ("reserved",                "16s",  ('\0'*16, '\xFF'*16,)), # set to 0xff
377     ("unknown0",                "l",    (0, 1, 3, -1)), # Was 0xffffffff
378     # MANIFEST_MODULE_INFO_EXT[] # Module info extension entries
379   )
380   def __init__(self, ab):
381     stR = StructReader(ab)
382     stR.read(self, self.MANIFEST_PARTITION_INFO_EXT)
383     self.partition_name = self.partition_name.rstrip('\0')
384     self.partition_hash = self.partition_hash[::-1] # Reverse?
385     self.LoadItems(stR, Module_Info)
386
387   def dump(self, flog=sys.stdout):
388     print >>flog, self.Banner(True)
389     print >>flog, "  Name: [%s]" % self.partition_name
390     print >>flog, "  Length: %08X" % self.partition_length
391     print >>flog, "  Hash: %s" % self.partition_hash.encode("hex")
392     print >>flog, "  VCN: %d" % self.version_control_number
393     print >>flog, "  Ver: %X, %X" % (self.partition_version, self.data_format_version)
394     print >>flog, "  Instance ID: %d" % self.instance_id
395     print >>flog, "  Flags: %d" % self.flags
396     print >>flog, "  Unknown: %d" % self.unknown0
397     print >>flog, "  Modules[%d]:" % len(self.modules)
398     self.PrintItems(flog)
399
400 #***************************************************************************
401
402 class Module_Info:
403   dModType = {
404     PROCESS_TYPE:        "Proc",        # 0
405     SHARED_LIBRARY_TYPE: "Lib ",        # 1
406     DATA_TYPE:           "Data",        # 2
407     IUNIT_TYPE:          "iUnt",        # 3 v12
408   }
409   MANIFEST_MODULE_INFO_EXT = (
410     ("name",                    "12s",  ),              # Character array. If name length is shorter than field size the name is padded with 0 bytes
411     ("type",                    "B",    (0,1,2,3)),     # 0 - Process; 1 - Shared Library; 2 - Data; 3 - iUnit
412     ("reserved0",               "B",    ),              #
413     ("reserved1",               "H",    (0, 0xFFFF)),   # set to 0xffff
414     ("metadata_size",           "L",    ),              #
415     ("metadata_hash",           "32s"   ),              # For a process/shared library this is the SHA256 of the module metadata file; for a data module this is the SHA256 hash of the module binary itself
416   )
417   def __init__(self, stR):
418     stR.read(self, self.MANIFEST_MODULE_INFO_EXT)
419     self.name = self.name.rstrip('\0')
420     self.metadata_hash = self.metadata_hash[::-1] # Reverse
421
422   def __str__(self):
423     return "%-4s, Meta cb:%4X h:%s %s" % (self.dModType[self.type], self.metadata_size, self.metadata_hash.encode("hex"), self.name)
424
425 #***************************************************************************
426 #***************************************************************************
427 #***************************************************************************
428
429 class Shared_Lib_Ext(Extension): # 4 : used in Metadata
430   NAME = "SharedLib"
431   TYPE = 4 # for shared library extension
432   # length: In bytes equals 52 for this version
433   SHARED_LIB_EXTENSION = (
434     ("context_size",            "L",    ),              # Size in bytes of the shared library context
435     ("total_alloc_virtual_space", "L",  ),              # Including padding pages for library growth. Currently set to a temporary value. This needs to be updated once the SHARED_CONTEXT_SIZE symbol is defined in the build process.
436     ("code_base_address",       "L",    ),              # Base address for the library private code in VAS. Must be 4KB aligned.
437     ("tls_size",                "L",    ),              # Size of Thread-Local-Storage used by the shared library.
438     ("reserved",                "L",    ),              # reserved bytes set to 0xffffffff
439   )
440   def __init__(self, ab):
441     stR = StructReader(ab)
442     stR.read(self, self.SHARED_LIB_EXTENSION)
443     stR.done()
444
445   def dump(self, flog=sys.stdout):
446     print >>flog, "%s context_size:0x%X, total_alloc_virtual_space:0x%X, code_base_address:0x%X, tls_size:0x%x" % (self.Banner(), self.context_size, self.total_alloc_virtual_space, self.code_base_address, self.tls_size)
447
448 #***************************************************************************
449 #***************************************************************************
450 #***************************************************************************
451
452 class Man_Process_Ext(Extension): # 5 : used in Metadata
453   NAME = "Process"
454   TYPE = 5 # for process attribute extension
455   # length: In bytes equals 160 + 2*n for this version where n is the number of group IDs entries in the extension
456   MAN_PROCESS_EXTENSION = (
457     ("bf_flags",                "L",    ),              # Flags
458     ("main_thread_id",          "L",    ),              # TID for main thread. Optional for IBL processes only. Must be 0 for other processes.
459     ("priv_code_base_address",  "L",    ),              # Base address for code. Address is in LAS for Bringup/Kernel VAS for other processes. Must be 4KB aligned
460     ("uncompressed_priv_code_size","L", ),              # Size of uncompressed process code. Does not include code for shared library.
461     ("cm0_heap_size",           "L",    ),              # Size of Thread-Local-Storage for the process
462     ("bss_size",                "L",    ),              #
463     ("default_heap_size",       "L",    ),              #
464     ("main_thread_entry",       "L",    ),              # VAS of entry point function for the process main thread
465     ("allowed_sys_calls",       "12s",  ),              # Bitmask of allowed system calls by the process
466     ("user_id",                 "H",    ),              # Runtime User ID for process
467     ("reserved_0",              "L",    ),              # Temporary placeholder for thread base
468     ("reserved_1",              "H",    0),             # Must be 0
469     ("reserved_2",              "Q",    ),              #
470     # group_ids['H'] # Group ID for process
471   )
472   def __init__(self, ab):
473     stR = StructReader(ab)
474     stR.read(self, self.MAN_PROCESS_EXTENSION)
475     abGIDs = stR.ab[stR.o:]
476     self.group_ids = list(struct.unpack("<%dH" % (len(abGIDs) / 2), abGIDs))
477     self.flags = Man_Process_Flags(self.bf_flags)
478
479   def dump(self, flog=sys.stdout):
480     print >>flog, self.Banner()
481     print >>flog, "    flags: %s" % self.flags
482     print >>flog, "    main_thread_id: 0x%X" % self.main_thread_id
483     print >>flog, "    priv_code_base_address: 0x%08X" % self.priv_code_base_address
484     print >>flog, "    uncompressed_priv_code_size: 0x%X" % self.uncompressed_priv_code_size
485     print >>flog, "    cm0_heap_size: 0x%X" % self.cm0_heap_size
486     print >>flog, "    bss_size: 0x%X" % self.bss_size
487     print >>flog, "    default_heap_size: 0x%X" % self.default_heap_size
488     print >>flog, "    main_thread_entry: 0x%08X" % self.main_thread_entry
489     print >>flog, "    allowed_sys_calls: %s" % self.allowed_sys_calls.encode("hex")
490     print >>flog, "    user_id: 0x%04X" % self.user_id
491     print >>flog, "    group_ids[%d]: [%s]" % (len(self.group_ids), ", ".join("0x%04X" % gid for gid in self.group_ids))
492
493 #***************************************************************************
494
495 class Man_Process_Flags:
496   MAN_PROCESS_FLAGS = ( # BitFields
497     ("fault_tolerant",          0,      0),     # Kernel exception policy: 0 - Reset System, 1 - Terminate Process
498     ("permanent_process",       1,      1),     # permanent process Y/N. A permanent process' code/rodata sections are not removed from RAM when it terminates normally in order to optimize its reload flow.
499     ("single_instance",         2,      2),     # Single Instance Y/N. When the process is spawned if it is already running in the system the spawn will fail.
500     ("trusted_snd_rev_sender",  3,      3),     # Trusted SendReceive Sender Y/N. If set this process is allowed to send IPC_SendReceive messages to any process (not only public).
501     ("trusted_notify_sender",   4,      4),     # Trusted Notify Sender Y/N. If set this process is allowed to send IPC_Notify notifications to any process (not only public).
502     ("public_snd_rev_receiver", 5,      5),     # Public SendReceive Receiver Y/N. If set any other process is allowed to send IPC_SendReceive messages to it (not only trusted).
503     ("public_notify_receiver",  6,      6),     # Public Notify Receiver Y/N. If set any other process is allowed to IPC_Notify notifications messages to it (not only trusted).
504     #("reserved",               7,      31),    # reserved. Set to 0
505   )
506   def __init__(self, dw):
507     BitFields(self, dw, self.MAN_PROCESS_FLAGS)
508
509   def __str__(self):
510     return ", ".join(ListTrueBools(self, self.MAN_PROCESS_FLAGS))
511
512 #***************************************************************************
513 #***************************************************************************
514 #***************************************************************************
515
516 class Threads_Ext(Extension): # 6 : used in Metadata
517   NAME = "Threads"
518   TYPE = 6 # for threads extension
519   LIST = "threads"
520   def __init__(self, ab):
521     self.LoadItems(StructReader(ab), Thread_Entry)
522
523   def dump(self, flog=sys.stdout):
524     print >>flog, self.Banner()
525     self.PrintItems(flog)
526
527 #***************************************************************************
528
529 class Thread_Entry:
530   THREAD_ENTRY = (
531     ("stack_size",      "L",    ),              # Size of main thread stack in bytes (not including guard page including space reserved for TLS). Must be divisible by 4K with the following exception: if the default heap size is smaller than 4K the last thread's stack size may have any size.
532     ("flags",           "L",    ),              # Bit0 - set to 0 for live thread 1 for CM0-U-only thread; Bits 1-31 - reserved must be 0
533     ("scheduling_policy", "L",  ),              # Bits 0-7: Scheduling Policy, 0 -> fixed priority; Bits 8-31: Scheduling attributes. For a fixed priority policy this is the scheduling priority of the thread.
534     ("reserved",        "L",    ),              #
535   )
536   def __init__(self, stR):
537     stR.read(self, self.THREAD_ENTRY)
538
539   def __str__(self):
540     return "stack_size:0x%08X, flags:%X, scheduling_policy:%08X" % (self.stack_size, self.flags, self.scheduling_policy)
541
542 #***************************************************************************
543 #***************************************************************************
544 #***************************************************************************
545
546 class Device_Ids_Ext(Extension): # 7 : used in Metadata
547   NAME = "DeviceIds"
548   TYPE = 7 # for device ids extension
549   LIST = "device_id_group"
550   def __init__(self, ab):
551     self.LoadItems(StructReader(ab), Device_Entry)
552
553   def dump(self, flog=sys.stdout):
554     print >>flog, "%s [%s]" % (self.Banner(), ", ".join("%08X" % v.device_id for v in self.device_id_group))
555
556 #***************************************************************************
557
558 class Device_Entry:
559   DEVICE_ENTRY = (
560     ("device_id",       "L",    ),              #
561     ("reserved",        "L",    ),              #
562   )
563   def __init__(self, stR):
564     stR.read(self, self.DEVICE_ENTRY)
565
566 #***************************************************************************
567 #***************************************************************************
568 #***************************************************************************
569
570 class Mmio_Ranges_Ext(Extension): # 8 : used in Metadata
571   NAME = "MmioRanges"
572   TYPE = 8 # for mmio ranges extension
573   LIST = "mmio_range_defs"
574   def __init__(self, ab):
575     self.LoadItems(StructReader(ab), Mmio_Range_Def)
576
577   def dump(self, flog=sys.stdout):
578     print >>flog, self.Banner()
579 #    self.PrintItems(flog)
580     for i,e in enumerate(getattr(self, self.LIST)): print >>flog, "    %s" % (e)
581
582 #***************************************************************************
583
584 class Mmio_Range_Def:
585   MMIO_RANGE_DEF = (
586     ("base",            "L",    ),              # Base address of the MMIO range
587     ("size",            "L",    ),              # Limit in bytes of the MMIO range
588     ("flags",           "L",    ),              # Read access Y/N
589   )
590   def __init__(self, stR):
591     stR.read(self, self.MMIO_RANGE_DEF)
592
593   def __str__(self):
594     return "base:%08X, size:%08X, flags:%08X" % (self.base, self.size, self.flags)
595
596 #***************************************************************************
597 #***************************************************************************
598 #***************************************************************************
599
600 class Special_File_Producer_Ext(Extension): # 9 : used in Metadata
601   NAME = "SpecialFileProducer"
602   TYPE = 9 # for special file producer extension
603   LIST = "files"
604   SPECIAL_FILE_PRODUCER_EXTENSION = (
605     ("major_number",            "H",    ),              #
606     ("flags",                   "H",    ),              #
607     # SPECIAL_FILE_DEF[]
608   )
609   def __init__(self, ab):
610     stR = StructReader(ab)
611     stR.read(self, self.SPECIAL_FILE_PRODUCER_EXTENSION)
612     self.LoadItems(stR, Special_File_Def)
613
614   def dump(self, flog=sys.stdout):
615     print >>flog, "%s major_number=0x%04X" % (self.Banner(), self.major_number)
616     self.PrintItems(flog)
617
618 #***************************************************************************
619
620 class Special_File_Def:
621   SPECIAL_FILE_DEF = (
622     ("name",                    "12s",  ),              #
623     ("access_mode",             "H",    ),              #
624     ("user_id",                 "H",    ),              #
625     ("group_id",                "H",    ),              #
626     ("minor_number",            "B",    ),              #
627     ("reserved0",               "B",    ),              #
628     ("reserved1",               "L",    ),              #
629   )
630   def __init__(self, stR):
631     stR.read(self, self.SPECIAL_FILE_DEF)
632     self.name = self.name.rstrip('\0')
633
634   def __str__(self):
635     return "%-12s access_mode:%04o, user_id:0x%04X group_id:0x%04X minor_number:%02X" % (self.name, self.access_mode, self.user_id, self.group_id, self.minor_number)
636
637 #***************************************************************************
638 #***************************************************************************
639 #***************************************************************************
640
641 class Mod_Attr_Ext(Extension): # 10 : used in Metadata
642   NAME = "ModAttr"
643   TYPE = 10 # for this module attribute extension
644   # length: In bytes; equals 56 for this version
645   dCompType = {
646     COMP_TYPE_NOT_COMPRESSED:"    ",
647     COMP_TYPE_HUFFMAN:"Huff",
648     COMP_TYPE_LZMA:"LZMA",
649   }
650   MOD_ATTR_EXTENSION = (
651     ("compression_type",        "B",    (0,1,2,)),      # 0 - Uncompressed; 1 - Huffman Compressed; 2 - LZMA Compressed
652     ("encrypted",               "B",    (0,1)),         # Used as "encrypted" flag
653     ("reserved1",               "B",    0),             # Must be 0
654     ("reserved2",               "B",    0),             # Must be 0
655     ("uncompressed_size",       "L",    ),              # Uncompressed image size must be divisible by 4K
656     ("compressed_size",         "L",    ),              # Compressed image size. This is applicable for LZMA compressed modules only. For other modules should be the same as uncompressed_size field.
657     ("module_number",           "H",    ),              # Module number unique in the scope of the vendor.
658     ("vendor_id",               "H",    0x8086),        # Vendor ID (PCI style). For Intel modules must be 0x8086.
659     ("image_hash",              "32s",  ),              # SHA2 Hash of uncompressed image
660   )
661   def __init__(self, ab):
662     stR = StructReader(ab)
663     stR.read(self, self.MOD_ATTR_EXTENSION)
664     self.image_hash = self.image_hash[::-1] # Reverse
665     stR.done()
666
667   def dump(self, flog=sys.stdout):
668     print >>flog, "%s %s enc=%d %08X->%08X id:%04X.%04X h:%s" % (self.Banner(), self.dCompType[self.compression_type], self.encrypted, self.compressed_size, self.uncompressed_size, self.module_number, self.vendor_id, self.image_hash.encode("hex"))
669
670 #***************************************************************************
671 #***************************************************************************
672 #***************************************************************************
673
674 class Locked_Ranges_Ext(Extension): # 11 : used in Metadata
675   NAME = "LockedRanges"
676   TYPE = 11 # for unknown 11 extension
677   LIST = "ranges"
678   def __init__(self, ab):
679     self.LoadItems(StructReader(ab), Locked_Range)
680
681   def dump(self, flog=sys.stdout):
682     print >>flog, self.Banner()
683     self.PrintItems(flog)
684
685 #***************************************************************************
686
687 class Locked_Range:
688   LOCKED_RANGE = (
689     ("base",            "L",    ),              # Base address in VAS of range to be locked. Must be divisible in 4KB.
690     ("size",            "L",    ),              # Size of range to be locked. Must be divisible in 4KB.
691   )
692   def __init__(self, stR):
693     stR.read(self, self.LOCKED_RANGE)
694
695   def __str__(self):
696     return "base:0x%08X, size:%X" % (self.base, self.size)
697
698 #***************************************************************************
699 #***************************************************************************
700 #***************************************************************************
701
702 class Client_System_Info_Ext(Extension): # 12 : used in Manifest
703   NAME = "ClientSystemInfo"
704   TYPE = 12 # for client system info extension
705   CLIENT_SYSTEM_INFO_EXTENSION = (
706     ("fw_sku_caps",             "L",    ),              #
707     ("fw_sku_caps_reserved",    "28s",  '\xFF'*28),     #
708     ("bf_fw_sku_attributes",    "Q",    ),              # Bits 0:3 - CSE region size in multiples of 0.5 MB Bits 4:6 - firmware sku; 0 for 5.0MB 1 for 1.5MB 2 for slim sku. Bit 7 - Patsberg support Y/N Bit 8 - M3 support Y/N Bit 9 - M0 support Y/N Bits 10:11 - reserved Bits 12:15 - Si class (all H M L) Bits 16:63 - reserved
709   )
710
711   def __init__(self, ab):
712     stR = StructReader(ab)
713     stR.read(self, self.CLIENT_SYSTEM_INFO_EXTENSION)
714     self.attr = Client_System_Sku_Attributes(self.bf_fw_sku_attributes)
715     stR.done()
716
717   def dump(self, flog=sys.stdout):
718     print >>flog, self.Banner()
719     print >>flog, "    fw_sku_caps: %x" % self.fw_sku_caps
720     print >>flog, "    fw_sku_attributes: %s" % self.attr
721
722 #***************************************************************************
723
724 class Client_System_Sku_Attributes:
725   dFirmwareSKU = {
726     0: "5.0MB",
727     1: "1.5MB",
728     2: "Slim",
729     3: "SPS",
730   }
731   CLIENT_SYSTEM_SKU_ATTRIBUTES = ( # BitFields
732     ("CSE_region_size",         0,      3),             # Bits 0:3 - CSE region size in multiples of 0.5 MB
733     ("firmware_sku",            4,      6),             # Bits 4:6 - firmware sku; 0 for 5.0MB 1 for 1.5MB 2 for slim sku.
734     ("Patsberg",                7,      7),             # Bit 7 - Patsberg support Y/N
735     ("M3",                      8,      8),             # Bit 8 - M3 support Y/N
736     ("M0",                      9,      9),             # Bit 9 - M0 support Y/N
737 #    ("reserved0",              10,     11),            # Bits 10:11 - reserved
738     ("Si_class",                12,     15),            # Bits 12:15 - Si class (all H M L)
739 #    ("reserved1",              16,     63),            # Bits 16:63 - reserved
740   )
741   def __init__(self, qw):
742     BitFields(self, qw, self.CLIENT_SYSTEM_SKU_ATTRIBUTES)
743
744   def __str__(self):
745     return "CSE region size: %.2f, firmware sku: %s, Si class: %X, %s" % (0.5*self.CSE_region_size, self.dFirmwareSKU[self.firmware_sku], self.Si_class, ", ".join(ListTrueBools(self, self.CLIENT_SYSTEM_SKU_ATTRIBUTES)))
746
747 #***************************************************************************
748 #***************************************************************************
749 #***************************************************************************
750
751 class User_Info_Ext(Extension): # 13 : used in Manifest
752   NAME = "UserInfo"
753   TYPE = 13 # for user info extension
754   LIST = "users"
755   def __init__(self, ab):
756     try:
757       self.LoadItems(StructReader(ab), User_Info_Entry)
758     except:
759       self.LoadItems(StructReader(ab), User_Info_Entry_new)
760
761   def dump(self, flog=sys.stdout):
762     print >>flog, self.Banner()
763     self.PrintItems(flog)
764
765 #***************************************************************************
766
767 class User_Info_Entry:
768   USER_INFO_ENTRY = (
769     ("user_id",                 "H",    ),              # User ID.
770     ("reserved",                "H",    (0,1)),         # Must be 0.
771     ("non_volatile_storage_quota","L",  ),              # Maximum size of non-volatile storage area.
772     ("ram_storage_quota",       "L",    ),              # Maximum size of RAM storage area.
773     ("wop_quota",               "L",    ),              # Quota to use in wear-out prevention algorithm; in most cases this should match the non-volatile storage quota; however it is possible to virtually add quota to a user to allow it to perform more write operations on expense of another user. At build time the build system will check that the sum of all users WOP quota is not more than the sum of all users non-volatile storage quota.
774     ("working_dir",             "36s",  ),              # Starting directory for the user. Used when accessing files with a relative path. Character array; if name length is shorter than field size the name is padded with 0 bytes.
775   )
776   def __init__(self, stR):
777     stR.read(self, self.USER_INFO_ENTRY)
778     self.working_dir = self.working_dir.rstrip('\0')
779     assert self.working_dir.find('\0') < 0
780
781   def __str__(self):
782     return "user id:0x%04X, NV quota:%8X, RAM quota:%8X, WOP quota:%8X, working dir: [%s]" % (self.user_id, self.non_volatile_storage_quota, self.ram_storage_quota, self.wop_quota, self.working_dir)
783
784 #***************************************************************************
785
786 class User_Info_Entry_new:
787   USER_INFO_ENTRY = (
788     ("user_id",                 "H",    ),              # User ID.
789     ("reserved",                "H",    0),             # Must be 0.
790     ("non_volatile_storage_quota","L",  ),              # Maximum size of non-volatile storage area.
791     ("ram_storage_quota",       "L",    ),              # Maximum size of RAM storage area.
792     ("wop_quota",               "L",    ),              # Quota to use in wear-out prevention algorithm; in most cases this should match the non-volatile storage quota; however it is possible to virtually add quota to a user to allow it to perform more write operations on expense of another user. At build time the build system will check that the sum of all users WOP quota is not more than the sum of all users non-volatile storage quota.
793   )
794   def __init__(self, stR):
795     stR.read(self, self.USER_INFO_ENTRY)
796
797   def __str__(self):
798     return "user id:0x%04X, NV quota:%8X, RAM quota:%8X, WOP quota:%8X" % (self.user_id, self.non_volatile_storage_quota, self.ram_storage_quota, self.wop_quota)
799
800 #***************************************************************************
801 #***************************************************************************
802 #***************************************************************************
803
804 class Package_Info_Ext(Extension): # 15 : used in TXE Mainfist
805   NAME = "PackageInfo"
806   TYPE = 15 # for partition info extension
807   LIST = "modules"
808   SIGNED_PACKAGE_INFO_EXT = (
809     ("package_name",            "4s",   ),              # Name of the partition
810     ("version_control_number",  "L",    ),              # The version control number (VCN) is incremented whenever a change is made to the FW that makes it incompatible from an update perspective with previously released versions of the FW.
811     ("usage_bitmap",            "16s",  ),              # Bitmap of usages depicted by this manifest, indicating which key is used to sign the manifest
812     ("svn",                     "L",    ),              # Secure Version Number
813     ("unknown",                 "L",    ),              #
814     ("reserved",                "12s",  '\x00'*12),     # Must be 0
815     # SIGNED_PACKAGE_INFO_EXT_ENTRY[] # Module info extension entries
816   )
817   def __init__(self, ab):
818     stR = StructReader(ab)
819     stR.read(self, self.SIGNED_PACKAGE_INFO_EXT)
820     self.package_name = self.package_name.rstrip('\0')
821     self.LoadItems(stR, Package_Info_Ext_Entry)
822
823   def dump(self, flog=sys.stdout):
824     print >>flog, self.Banner(True)
825     print >>flog, "  Name: [%s]" % self.package_name
826     print >>flog, "  VCN: %d" % self.version_control_number
827     print >>flog, "  Usage Bitmap: %s" % self.usage_bitmap.encode("hex")
828     print >>flog, "  svn: %d" % self.svn
829     print >>flog, "  unknown: 0x%X" % self.unknown
830     print >>flog, "  Modules[%d]:" % len(self.modules)
831     self.PrintItems(flog)
832
833 #***************************************************************************
834
835 class Package_Info_Ext_Entry:
836   dModType = {
837     PROCESS_TYPE:        "Proc",        # 0
838     SHARED_LIBRARY_TYPE: "Lib ",        # 1
839     DATA_TYPE:           "Data",        # 2
840     IUNIT_TYPE:          "iUnt",        # 3 v12
841   }
842   dHashAlgorithm = {
843     1: "SHA1",
844     2: "SHA256",
845   }
846   SIGNED_PACKAGE_INFO_EXT_ENTRY = (
847     ("name",                    "12s",  ),              # Character array. If name length is shorter than field size the name is padded with 0 bytes
848     ("type",                    "B",    (0,1,2,3)),     # 0 - Process; 1 - Shared Library; 2 - Data; 3 - iUnit
849     ("hash_algorithm",          "B",    2),             # 0 - Reserved; 1 - SHA1; 2 - SHA256
850     ("hash_size",               "H",    32),            # Size of Hash in bytes = N; BXT to support only SHA256. So N=32.
851     ("metadata_size",           "L",    ),              # Size of metadata file
852     ("metadata_hash",           "32s"   ),              # The SHA2 of the module metadata file
853   )
854   def __init__(self, stR):
855     stR.read(self, self.SIGNED_PACKAGE_INFO_EXT_ENTRY)
856     self.name = self.name.rstrip('\0')
857     self.metadata_hash = self.metadata_hash[::-1] # Reverse
858
859   def __str__(self):
860     return "%-4s, Meta cb:%4X h=%s[%d]:%s %s" % (self.dModType[self.type], self.metadata_size, self.dHashAlgorithm[self.hash_algorithm], self.hash_size, self.metadata_hash.encode("hex"), self.name)
861
862 #***************************************************************************
863 #***************************************************************************
864 #***************************************************************************
865
866 class Unk_16_Ext(Extension): # 16 : used in Manifest (for iUnit)
867   NAME = "Unk_iUnit_16"
868   TYPE = 16 # for iUnit extension
869   UNK_IUNIT_16_EXT = (
870     ("v0_1",                    "L",    1),             #
871     ("unk16",                   "16s",  '\0'*16),       #
872     ("v2_3",                    "L",    3),             #
873     ("v3",                      "L",    ),              #
874     ("v4_1",                    "L",    1),             #
875     ("h",                       "32s",  ),              #
876     ("reserved",                "24s",  '\0'*24),       #
877   )
878   def __init__(self, ab):
879     stR = StructReader(ab)
880     stR.read(self, self.UNK_IUNIT_16_EXT)
881 #    self.h = self.h[::-1] # Reverse?
882     stR.done()
883
884   def dump(self, flog=sys.stdout):
885     print >>flog, self.Banner()
886     print >>flog, "  %X %X %X %X h=%s" % (self.v0_1, self.v2_3, self.v3, self.v4_1, self.h.encode("hex"))
887
888 #***************************************************************************
889 #***************************************************************************
890 #***************************************************************************
891
892 class Unk_18_Ext(Extension): # 18 : used in Manifest
893   NAME = "Unk_18"
894   TYPE = 18 # for user info extension
895   LIST = "records"
896   UNK_18_EXT = (
897     ("items",                   "L",    ),              #
898     ("unk",                     "16s",  ),              #
899   )
900   def __init__(self, ab):
901     stR = StructReader(ab)
902     stR.read(self, self.UNK_18_EXT)
903     self.LoadItems(stR, Unk_18_Ext_Entry)
904
905   def dump(self, flog=sys.stdout):
906     print >>flog, self.Banner()
907     print >>flog, "  Records[%d] %s:" % (self.items, self.unk.encode("hex"))
908     self.PrintItems(flog)
909
910 #***************************************************************************
911
912 class Unk_18_Ext_Entry:
913   UNK_18_EXT_ENTRY = (
914     ("ab",                      "56s",  ),              #
915   )
916   def __init__(self, stR):
917     stR.read(self, self.UNK_18_EXT_ENTRY)
918
919   def __str__(self):
920     return self.ab.encode("hex")
921
922 #***************************************************************************
923 #***************************************************************************
924 #***************************************************************************
925
926 class Unk_22_Ext(Extension): # 22 : used in Manifest (v12)
927   NAME = "Unk_22"
928   TYPE = 22 # for v12 extension
929   UNK_22_EXT = (
930     ("name",                    "4s",   ),              #
931     ("unk24",                   "24s",  ),              #
932     ("h",                       "32s",  ),              #
933     ("reserved",                "20s",  '\0'*20),       #
934   )
935   def __init__(self, ab):
936     stR = StructReader(ab)
937     stR.read(self, self.UNK_22_EXT)
938 #    self.h = self.h[::-1] # Reverse?
939     stR.done()
940
941   def dump(self, flog=sys.stdout):
942     print >>flog, "%s [%s] u=%s h=%s" % (self.Banner(), self.name, self.unk24.encode("hex"), self.h.encode("hex"))
943
944 #***************************************************************************
945 #***************************************************************************
946 #***************************************************************************
947
948 class Unk_50_Ext(Extension): # 50 : used in Manifest (HP)
949   NAME = "Unk_50"
950   TYPE = 50 # for iUnit extension
951   UNK_50_EXT = (
952     ("name",                    "4s",   ),              #
953     ("dw0",                     "L",    0),             #
954   )
955   def __init__(self, ab):
956     stR = StructReader(ab)
957     stR.read(self, self.UNK_50_EXT)
958     stR.done()
959
960   def dump(self, flog=sys.stdout):
961     print >>flog, "%s [%s]" % (self.Banner(), self.name)
962
963 #***************************************************************************
964 #***************************************************************************
965 #***************************************************************************
966
967 aExtHandlers = (
968   System_Info_Ext,              # 0
969   Init_Script_Ext,              # 1
970   Feature_Permissions_Ext,      # 2
971   Partition_Info_Ext,           # 3
972   Shared_Lib_Ext,               # 4
973   Man_Process_Ext,              # 5
974   Threads_Ext,                  # 6
975   Device_Ids_Ext,               # 7
976   Mmio_Ranges_Ext,              # 8
977   Special_File_Producer_Ext,    # 9
978   Mod_Attr_Ext,                 # 10
979   Locked_Ranges_Ext,            # 11
980   Client_System_Info_Ext,       # 12
981   User_Info_Ext,                # 13
982 #  None,                        # 14
983   Package_Info_Ext,             # 15
984   Unk_16_Ext,                   # 16
985   Unk_18_Ext,                   # 18
986   Unk_22_Ext,                   # 22
987   Unk_50_Ext,                   # 50
988 )
989
990 dExtHandlers = {ext.TYPE: ext for ext in aExtHandlers}
991
992 def Ext_ParseAll(obj, ab, o=0):
993   def EnumTags(ab, o=0):
994     while o < len(ab):
995       tag, cb = struct.unpack_from("<LL", ab, o)
996       assert cb >= 8
997       assert o+cb <= len(ab)
998       yield tag, ab[o+8:o+cb]
999       o += cb
1000
1001   obj.extList = []
1002   for extType, extData in EnumTags(ab, o):
1003     ext = dExtHandlers.get(extType, None)
1004     if ext is not None:
1005       extObj = ext(extData)
1006       setattr(obj, ext.NAME, extObj)
1007       obj.extList.append(extObj)
1008 #    else: raise Error("Extension #%d[%d] not supported" % (extType, len(extData)))
1009     else:
1010       parent = obj.name + (".met" if isinstance(obj, CPD_Entry) else "")
1011       print >>sys.stderr, "- %s: Unknown extType#%d[%d] %s" % (parent, extType, len(extData), extData.encode("hex"))
1012
1013 def Ext_DumpAll(obj, flog=sys.stdout):
1014   if hasattr(obj, "extList"):
1015     for extObj in obj.extList: extObj.dump(flog)
1016 #  for ext in aExtHandlers:
1017 #    if hasattr(obj, ext.NAME): getattr(obj, ext.NAME).dump(flog)
1018
1019 #***************************************************************************
1020 #***************************************************************************
1021 #***************************************************************************
1022
1023 class CPD_Manifest:
1024   MARKER = "$MN2"
1025   MANIFEST_HEADER = (
1026     ("type",                    "L",    4),             # Must be 0x4
1027     ("length",                  "L",    161),           # in Dwords equals 161 for this version
1028     ("version",                 "L",    0x10000),       # 0x1000 for this version
1029     ("flags",                   "L",    ),              # Debug intel owned
1030     ("vendor",                  "L",    0x8086),        # 0x8086 for intel
1031     ("date",                    "L",    ),              # yyymmdd in BCD format
1032     ("size",                    "L",    ),              # in Dwords size of the entire manifest. Maximum size is 2K DWORDS (8KB)
1033     ("header_id",               "4s",   MARKER),        # Magic number. Equals $MN2 for this version
1034     ("reserved0",               "L",    ),              # Must be 0x4 [not True: it is 0!]
1035     ("version_major",           "H",    ),              # Major Version [== 11]
1036     ("version_minor",           "H",    ),              # Minor Version
1037     ("version_hotfix",          "H",    ),              # Hotfix
1038     ("version_build",           "H",    ),              # Build number
1039     ("svn",                     "L",    ),              # Secure Version Number
1040     ("reserved1",               "Q",    0),             # must be 0
1041     ("reserved2",               "64s",  '\0'*64),       # will be set to 0
1042     ("modulus_size",            "L",    64),            # In DWORDs; 64 for pkcs 1.5-2048
1043     ("exponent_size",           "L",    1),             # In DWORDs; 1 for pkcs 1.5-2048
1044   )
1045   CRYPTO_BLOCK = (
1046     ("public_key",              "256s", ),              # Public Key
1047     ("exponent",                "L",    ),              # Exponent [== 17]
1048     ("signature",               "256s"  ),              # RSA signature of manifest
1049   )
1050   def __init__(self, ab, name):
1051     self.name = name
1052     self.stR = StructReader(ab)
1053     self.stR.read(self, self.MANIFEST_HEADER)
1054     self.stR.read(self, self.CRYPTO_BLOCK)
1055     assert 4*self.size == len(ab)
1056     Ext_ParseAll(self, ab, 4*self.length) # Parse all Extensions
1057
1058     if 12 == self.version_major: g.HuffDecoder = HuffDecoder12
1059
1060   def dump(self, flog=sys.stdout):
1061     print >>flog, "CPD Manifest"
1062     print >>flog, "  Date: %08X" % self.date
1063     print >>flog, "  Version: %d.%d.%d.%d" % (self.version_major, self.version_minor, self.version_hotfix, self.version_build)
1064     print >>flog, "  SVN: %08X" % self.svn
1065
1066     # Validate RSA Public Key hash
1067     h = hashlib.sha256(self.public_key + struct.pack("<L", self.exponent)).digest()
1068     try:
1069       print >>flog, "  RSA Modulus: known[%d]," % aPubKeyHash.index(h),
1070     except:
1071       print >>flog, "- RSA Modulus is unknown,",
1072
1073     # Validate RSA Signatire
1074     h = hashlib.sha256(self.stR.ab[:0x80] + self.stR.ab[4*self.length:]).digest()
1075     modulus = int(self.public_key[::-1].encode("hex"), 16)
1076     sign = int(self.signature[::-1].encode("hex"), 16)
1077     decoded = ("%0512X" % pow(sign, self.exponent, modulus)).decode("hex")
1078     expected = "\x00\x01" + "\xFF"*202 + "003031300D060960864801650304020105000420".decode("hex") + h
1079     print >>flog, "Exponent:%d, Verification:%s" % (self.exponent, "OK" if decoded == expected else "FAILED")
1080     Ext_DumpAll(self, flog) # Dump all extensions
1081
1082 #***************************************************************************
1083 #***************************************************************************
1084 #***************************************************************************
1085
1086 class CPD_Entry:
1087   CPD_ENTRY_OFFSET = ( # BitFields
1088     ("address",                 0,      24),
1089     ("compress_flag",           25,     25),
1090 #    ("offset_reserved",                26,     31),
1091   )
1092   CPD_ENTRY = (
1093     ("name",                    "12s",  ),
1094     ("bf_offset",               "L",    ),
1095     ("length",                  "L",    ),
1096     ("reserved",                "L",    ),
1097   )
1098   def __init__(self, parent):
1099     self.cpd = parent
1100     self.cpd.stR.read(self, self.CPD_ENTRY)
1101     self.name = self.name.rstrip('\0')
1102     BitFields(self, self.bf_offset, self.CPD_ENTRY_OFFSET)
1103     self.mod = None
1104     self.metadata = None
1105
1106   def __str__(self):
1107     return "%-12s %s %8X %-8X" % (self.name, "HUFF" if self.compress_flag else "    ", self.address, self.length)
1108
1109   def getData(self):
1110     return self.cpd.stR.getData(self.address, self.ModAttr.compressed_size if self.compress_flag else self.length)
1111
1112   def saveRaw(self, baseDir, name=None):
1113     if name is None: name = self.name
1114     with open (os.path.join(baseDir, name), "wb") as fo: fo.write(self.getData())
1115
1116 #***************************************************************************
1117 #***************************************************************************
1118 #***************************************************************************
1119
1120 class CPD: # Code Partition Directory
1121   MARKER = "$CPD"
1122   CPD_HEADER = (
1123     ("marker",                  "4s",   MARKER),
1124     ("entries",                 "L",    ),
1125     ("header_version",          "B",    0x01),
1126     ("entry_version",           "B",    0x01),
1127     ("header_length",           "B",    0x10),
1128     ("checksum",                "B",    ),
1129     ("partition_name",          "4s",   ),
1130   )
1131   def __init__(self, ab, base):
1132     self.stR = StructReader(ab, base)
1133     self.stR.read(self, self.CPD_HEADER) # Read header
1134     self.partition_name = self.partition_name.rstrip('\0')
1135     self.files = [CPD_Entry(self) for i in xrange(self.entries)] # Read directory entries
1136     self.d = {e.name:e for e in self.files} # Dict maps name CPD_Entry
1137
1138     e = self.files[0] # Access Manifest (very first entry in lookup table)
1139     self.Manifest = CPD_Manifest(e.getData(), e.name) if e.name.endswith(".man") else None
1140 #    assert self.partition_name + ".man" == e.name
1141
1142     self.modules = None
1143     if self.Manifest:
1144       if hasattr(self.Manifest, "PackageInfo"):
1145         self.modules = self.Manifest.PackageInfo.modules
1146       elif hasattr(self.Manifest, "PartitionInfo"):
1147         self.modules = self.Manifest.PartitionInfo.modules
1148         if len(self.files) != 1 + 2*len(self.modules): # Manfest + nFiles * (Data + Metadata)
1149           print >>sys.stderr, "- Partition holds %d files but only %d module[s] (%d expected)" % (len(self.files), len(self.modules), (len(self.files)-1)/2)
1150
1151     if self.modules: # Try to attach Module Info and Metadata to Entry
1152       for i,mod in enumerate(self.modules): # Walk through modules listed in partion manifest
1153         e = self.d[mod.name] # Access CPD_Entry by module name
1154         e.mod = mod # Attach Module Info to entry
1155         metaName = e.name if e.name.endswith(".met") else e.name + ".met"
1156         e.metadata = self.d[metaName].getData() # Get Metadata content
1157         assert len(e.metadata) == e.mod.metadata_size # Check Metadata length
1158 #        assert hashlib.sha256(e.metadata).digest() == mod.metadata_hash # Check Metadata hash
1159         if hashlib.sha256(e.metadata).digest() != e.mod.metadata_hash: # Check Metadata hash
1160           print >>sys.stderr, "MetaHash %s[%d]: %s != %s" % (e.name, e.mod.metadata_size, hashlib.sha256(e.metadata).hexdigest(), e.mod.metadata_hash.encode("hex"))
1161         Ext_ParseAll(e, e.metadata) # Parse all Metadata Extensions and store them in CPD_Entry
1162
1163   def dump(self, flog=sys.stdout, baseDir=None):
1164     print >>flog, "%08X: CPD %-4s.%02X [%d]" % (self.stR.base, self.partition_name, self.checksum, self.entries)
1165     if baseDir is not None:
1166       baseDir = os.path.join(baseDir, "%08X.%s" % (self.stR.base, self.partition_name))
1167       if not os.path.exists(baseDir): os.makedirs(baseDir)
1168       if self.Manifest:
1169         with open(os.path.join(baseDir, "Manifest.txt"), "wt") as fo: self.Manifest.dump(fo)
1170     if self.Manifest: self.Manifest.dump(flog)
1171
1172     print >>flog, "\nCPD Files[%d]:" % len(self.files)
1173     for i,e in enumerate(self.files):
1174       print >>flog, "=================================\n%4d: %s" % (i+1, e)
1175       Ext_DumpAll(e, flog) # Dump all Metadata Extensions
1176       if baseDir is None: continue
1177
1178       fileName = e.name
1179       fileExt = os.path.splitext(fileName)[1]
1180       bSaveRaw = False
1181       if ".man" == fileExt: # CPD Manifest
1182         if g.dumpManifest: bSaveRaw = True
1183       elif ".met" == fileExt: # Module Metadata
1184         if g.dumpMeta: bSaveRaw = True
1185       else: # Module
1186         if e.metadata: # Module (with metadata)
1187           if g.dumpRaw:
1188             bSaveRaw = True
1189             fileName += ".raw"
1190         else: bSaveRaw = True # Not a module (without metadata) - always dump "as is"
1191       if bSaveRaw: e.saveRaw(baseDir, fileName) # Save raw file data (compressed/encrypted)
1192
1193       if e.metadata: # Only for modules with metadata
1194         with open(os.path.join(baseDir, "%s.txt" % e.name), "wt") as fo: # Dump module info
1195           print >>fo, "%4d: %s" % (i+1, e)
1196           Ext_DumpAll(e, fo) # Dump all Metadata Extensions
1197
1198         if not hasattr(e, "ModAttr"):
1199           e.saveRaw(baseDir)
1200           continue
1201
1202         compType = dCompType[e.ModAttr.compression_type]
1203         data = e.getData()
1204         if e.ModAttr.encrypted:
1205           print >>sys.stderr, "- Module %s is encrypted" % e.name
1206
1207         if "huff" == compType:
1208           assert e.length == e.ModAttr.uncompressed_size
1209           nChunks, left = divmod(e.length, 0x1000)
1210           assert 0 == left
1211
1212         plain = decompress(data, compType, e.length)
1213
1214         hashChecked = False
1215         if "huff" != compType and hashlib.sha256(data).digest() == e.ModAttr.image_hash:
1216 #          print "%8s: data" % e.name
1217           hashChecked = True
1218
1219         if plain:
1220           if not hashChecked:
1221 #            assert hashlib.sha256(plain).digest() == e.ModAttr.image_hash
1222             if hashlib.sha256(plain).digest() == e.ModAttr.image_hash:
1223               hashChecked = True
1224 #              print "%8s: plain" % e.name
1225
1226           with open(os.path.join(baseDir, "%s.mod" % e.name), "wb") as fo: fo.write(plain)
1227         else:
1228           if "huff" == compType:
1229             chunks = HUFF_chunks(nChunks, data)
1230             chunks.save(os.path.join(baseDir, "%s.%s" % (e.name, compType)))
1231             if g.dumpChunks: chunks.dump(os.path.join(baseDir, "%s" % e.name))
1232           else:
1233             with open(os.path.join(baseDir, "%s.%s" % (e.name, compType)), "wb") as fo:
1234               fo.write(data)
1235         if not hashChecked: print >>sys.stderr, "- hash %s.%s[%s]: %s" % (self.partition_name, e.name, compType, e.ModAttr.image_hash.encode("hex"))
1236 #    print
1237
1238 #***************************************************************************
1239 #***************************************************************************
1240 #***************************************************************************
1241
1242 class HUFF_chunks:
1243   def __init__(self, nChunks, data=None):
1244     if isinstance(nChunks, str):
1245       fn = nChunks
1246       with open(fn, "rb") as f:
1247         nChunks = struct.unpack("<L", f.read(4))
1248         data = f.read()
1249
1250     self.nChunks = nChunks
1251     self.data = data
1252
1253     self.a = []
1254     base = 4 * self.nChunks
1255     for v in struct.unpack_from("<%dL" % self.nChunks, self.data):
1256       opt, offs = divmod(v, 0x40000000)
1257       if len(self.a): self.a[-1].append(base + offs) # curr.offs is prev.end
1258       self.a.append([base + offs, opt])
1259     self.a[-1].append(len(data)) # Add End-Of-Data as last.end
1260
1261     for iChunk in xrange(self.nChunks): # Verify chunks
1262       offs, opt, end = self.a[iChunk]
1263       assert opt in (1, 3)
1264       assert offs <= end
1265
1266   def save(self, fn):
1267     with open(fn, "wb") as fo:
1268       fo.write(struct.pack("<L", self.nChunks))
1269       fo.write(self.data)
1270
1271   def __len__(self): return self.nChunks
1272   def __getitem__(self, iChunk):
1273     offs, opt, end = self.a[iChunk]
1274     return opt, self.data[offs:end]
1275
1276   def dump(self, baseName):
1277     chunksDir = baseName + ".chunks"
1278     if not os.path.exists(chunksDir): os.makedirs(chunksDir)
1279     for iChunk in xrange(self.nChunks): # Verify chunks
1280       opt, ab = self[iChunk]
1281       with open(os.path.join(chunksDir, "%d.%08X" % (opt, iChunk*0x1000)), "wb") as fo: fo.write(ab)
1282
1283
1284 #***************************************************************************
1285 #***************************************************************************
1286 #***************************************************************************
1287
1288 class FPT_Entry_Attributes:
1289   dAreaType = {
1290     FPT_AREATYPE_CODE: "Code",
1291     FPT_AREATYPE_DATA: "Data",
1292   }
1293
1294   FPT_ENTRY_ATTRIBUTES = ( # BitFields
1295     ("type",                    0,      6),
1296 #    ("rsvd0",                  7,      14),
1297     ("bwl0",                    15,     15),
1298     ("bwl1",                    16,     16),
1299 #    ("rsvd1",                  17,     23),
1300     ("entry_invalid",           24,     31),
1301   )
1302   def __init__(self, dw): BitFields(self, dw, self.FPT_ENTRY_ATTRIBUTES)
1303
1304   def __str__(self):
1305     r = [self.dAreaType[self.type]] + ListTrueBools(self, self.FPT_ENTRY_ATTRIBUTES)
1306     if self.entry_invalid: r.append("entry_invalid")
1307     return ", ".join(r)
1308 #    return "%s bwl0=%d, bwl1=%d, entry_valid=%02X" % (, self.bwl0, self.bwl1, self.entry_valid)
1309
1310 #***************************************************************************
1311 #***************************************************************************
1312 #***************************************************************************
1313
1314 class FPT_Entry:
1315   FPT_ENTRY = (
1316     ("name",                    "4s",   ),
1317     ("reserved",                "L",    ),
1318     ("offset",                  "L",    ),
1319     ("length",                  "L",    ),
1320     ("reserved1",               "L",    ),
1321     ("reserved2",               "L",    ),
1322     ("reserved3",               "L",    ),
1323     ("bf_attributes",           "L",    ), # class:FPT_ENTRY_ATTRIBUTES
1324   )
1325   def __init__(self, parent):
1326     self.fpt = parent
1327     self.fpt.stR.read(self, self.FPT_ENTRY)
1328     self.name = self.name.rstrip('\0')
1329     self.attributes = FPT_Entry_Attributes(self.bf_attributes)
1330
1331   def getData(self):
1332     return self.fpt.stR.getData(self.offset, self.length)
1333
1334   def __str__(self):
1335     return "[%-4s] %8X:%-8X %s" % (self.name, self.offset, self.length, self.attributes)
1336
1337 #***************************************************************************
1338 #***************************************************************************
1339 #***************************************************************************
1340
1341 class FPT: # Flash Partition Table
1342   MARKER = '$FPT'
1343   FPT_HEADER = (
1344     ("RomBypass",               "16s",  ),
1345     ("HeaderMarker",            "4s",   MARKER),
1346     ("NumFptEntries",           "L",    ),
1347     ("HeaderVersion",           "B",    0x20),
1348     ("EntryVersion",            "B",    0x10),
1349     ("HeaderLength",            "B",    0x20),
1350     ("HeaderChecksum",          "B",    ),
1351     ("TicksToAdd",              "H",    ),
1352     ("TokensToAdd",             "H",    ),
1353     ("reserved",                "L",    ),
1354     ("FlashLayout",             "L",    ),
1355     ("FitcMajor",               "H",    ),
1356     ("FitcMinor",               "H",    ),
1357     ("FitcHotfix",              "H",    ),
1358     ("FitcBuild",               "H",    ),
1359   )
1360   def __init__(self, ab, base=0):
1361     self.stR = StructReader(ab, base)
1362     self.stR.read(self, self.FPT_HEADER) # Read header
1363     self.partitions = [FPT_Entry(self) for i in xrange(self.NumFptEntries)] # Read entries
1364
1365   def dump(self, flog=sys.stdout, baseDir=None):
1366     print >>flog, "NumFptEntries:  %d" % self.NumFptEntries
1367     print >>flog, "HeaderVersion:  %d.%d" % divmod(self.HeaderVersion, 16)
1368     print >>flog, "EntryVersion:   %d.%d" % divmod(self.EntryVersion, 16)
1369     print >>flog, "HeaderLength:   0x%02X" % self.HeaderLength
1370     print >>flog, "HeaderChecksum: 0x%02X" % self.HeaderChecksum
1371     print >>flog, "TicksToAdd:     0x%04X" % self.TicksToAdd
1372     print >>flog, "TokensToAdd:    0x%04X" % self.TokensToAdd
1373     print >>flog, "FlashLayout:    0x%X" % self.FlashLayout
1374     print >>flog, "Fitc:           %d.%d.%d.%d" % (self.FitcMajor, self.FitcMinor, self.FitcHotfix, self.FitcBuild)
1375     print >>flog, "ROM Bypass instruction: %s" % (self.RomBypass.encode("hex") if self.RomBypass.rstrip('\0') else "<None>")
1376     for i,e in enumerate(self.partitions):
1377       print >>flog, "%4d: %s" % (i+1, e)
1378       if baseDir is None: continue # Do not write files
1379       data = e.getData()
1380       if data:
1381         with open(os.path.join(baseDir, "%08X.%d.%s.part" % (e.offset, e.attributes.type, e.name)), "wb") as fo:
1382           fo.write(data)
1383
1384 #***************************************************************************
1385 #***************************************************************************
1386 #***************************************************************************
1387
1388 class ME11:
1389   def __init__(self, fn):
1390     self.fn = fn
1391     with open (self.fn, "rb") as f: self.ab = f.read()
1392
1393     for o in xrange(0, len(self.ab), 0x1000): # Search for FPT
1394       if not self.ab[o+16:o+16+4] == FPT.MARKER: continue
1395       self.fpt = FPT(self.ab, o)
1396       break
1397     else:
1398       print "FPT not found"
1399       self.fpt = None
1400 #      raise Error("FPT not found")
1401
1402     o = 0
1403     self.CPDs = []
1404     while True: # Search for CPDs
1405       o = self.ab.find(CPD.MARKER, o)
1406       if o < 0: break
1407       if "\x01\x01\x10" == self.ab[o+8:o+11]:
1408 #        print "%s at %08X" % (CPD.MARKER, o)
1409         print ". Processing CPD at 0x%X" % o
1410         self.CPDs.append(CPD(self.ab, o))
1411 #        try: except: pass
1412       o += 4
1413
1414   def dump(self):
1415     baseDir = os.path.splitext(self.fn)[0]
1416 #    baseDir = None
1417     if baseDir:
1418       if not os.path.exists(baseDir): os.makedirs(baseDir)
1419       flog = open(baseDir + ".txt", "wt")
1420     else: flog = sys.stdout
1421
1422     if self.fpt: self.fpt.dump(flog, baseDir)
1423
1424     for cpd in self.CPDs:
1425       print >>flog
1426       cpd.dump(flog, baseDir)
1427
1428     if flog != sys.stdout: flog.close()
1429
1430 #***************************************************************************
1431 #***************************************************************************
1432 #***************************************************************************
1433
1434 def main(argv):
1435   for fn in argv[1:]:
1436     me = ME11(fn)
1437     me.dump()
1438
1439 if __name__=="__main__": main(sys.argv)