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