├── .gitignore ├── restruct.py ├── requirements.txt ├── .gitmodules ├── LICENSE.md ├── README.md ├── nvram.py ├── img4.py ├── macho.py └── dt.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /restruct.py: -------------------------------------------------------------------------------- 1 | ext/restruct/restruct.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto 2 | -e ./ext/pylzfse 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/restruct"] 2 | path = ext/restruct 3 | url = https://github.com/Shizmob/restruct 4 | [submodule "ext/pylzfse"] 5 | path = ext/pylzfse 6 | url = https://github.com/dimkr/pylzfse 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 Shiz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # img4 2 | 3 | Image4 dumper tool. 4 | 5 | ## Requirements 6 | 7 | - `asn1crypto` 8 | - `pylzfse` (bundled in `./ext/pylzfse`) 9 | - Using `git clone --recursive` 10 | 11 | ## Usage 12 | 13 | ``` 14 | usage: img4.py [-h] [-r] infile [outfile] 15 | 16 | positional arguments: 17 | infile input .img4/.im4m/.im4p file 18 | outfile output data file for payload 19 | 20 | optional arguments: 21 | -h, --help show this help message and exit 22 | -r, --raw print raw parsed data 23 | ``` 24 | 25 | # dt 26 | 27 | Apple device tree dumper tool. 28 | 29 | ## Usage 30 | 31 | ``` 32 | usage: dt.py [-h] {dump,find,to-fdt,to-adt,to-src,diff,regs} ... 33 | 34 | process Apple (ADT) and Flattened (FDT) device tree files 35 | 36 | positional arguments: 37 | {dump,find,to-fdt,to-adt,to-src,diff,regs} 38 | subcommand 39 | dump visually show device tree 40 | find find node in device tree 41 | to-fdt convert to flattened device tree 42 | to-adt convert to Apple device tree 43 | to-src convert to device tree source 44 | diff visually show the difference between two device trees 45 | regs show calculated register ranges for given path 46 | 47 | optional arguments: 48 | -h, --help show this help message and exit 49 | ``` 50 | 51 | # macho 52 | 53 | Mach-O dumper tool. 54 | 55 | ## Usage 56 | 57 | ``` 58 | usage: macho.py infile 59 | 60 | positional arguments: 61 | infile input device tree file 62 | ``` 63 | 64 | # nvram 65 | 66 | Raw NVRAM data tool. 67 | 68 | ## Usage 69 | 70 | ``` 71 | usage: nvram.py [-h] {dump} ... 72 | 73 | work with raw NVRAM data 74 | 75 | positional arguments: 76 | {dump} subcommand 77 | dump show all data 78 | 79 | optional arguments: 80 | -h, --help show this help message and exit 81 | ``` 82 | 83 | # License 84 | 85 | See `LICENSE.md`. 86 | -------------------------------------------------------------------------------- /nvram.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Greetings to: 4 | # - hexdump tool of choice 5 | 6 | import restruct 7 | 8 | 9 | def escape_nvram_value(v: bytes) -> bytes: 10 | res = bytearray() 11 | 12 | i = 0 13 | while i < len(v): 14 | if v[i] in (0, 0xff): 15 | nzeros = len(v[i:]) - len(v[i:].lstrip(b'\x00')) 16 | nmax = len(v[i:]) - len(v[i:].lstrip(b'\xff')) 17 | i += nzeros + nmax 18 | 19 | while nzeros > 0: 20 | res.append(0xff) 21 | res.append(nzeros % 0x80) 22 | nzeros -= 0x7F 23 | while nmax > 0: 24 | res.append(0xff) 25 | res.append(0x80 + nmax % 0x80) 26 | nmax -= 0x7F 27 | else: 28 | res.append(v[i]) 29 | i += 1 30 | 31 | return res 32 | 33 | def unescape_nvram_value(v: bytes) -> bytes: 34 | res = bytearray() 35 | 36 | i = 0 37 | while i < len(v): 38 | if v[i] == 0xff: 39 | i += 1 40 | l = v[i] 41 | if l < 0x80: 42 | res.extend(b'\x00' * l) 43 | else: 44 | l -= 0x80 45 | res.extend(b'\xFF' * l) 46 | else: 47 | res.append(v[i]) 48 | i += 1 49 | 50 | return res 51 | 52 | 53 | class NVRAMKeyValue(restruct.Struct): 54 | key: Str(terminator=b'=') 55 | value: Processed(Data(), parse=unescape_nvram_value, emit=escape_nvram_value) 56 | 57 | NVRAMKeyValues = restruct.Arr(NVRAMKeyValue, separator=b'\x00') 58 | 59 | class NVRAMHeader(restruct.Struct): 60 | unk1: Data(4) 61 | name: Str(type='raw', length=12, exact=True) 62 | 63 | class NVRAMSection(restruct.Struct): 64 | header: NVRAMHeader 65 | values: NVRAMKeyValues 66 | 67 | class NVRAM(restruct.Struct): 68 | header: NVRAMHeader 69 | unk1: NVRAMHeader 70 | sections: Arr(AlignTo(NVRAMSection, 0x4000)) # total guess 71 | 72 | 73 | if __name__ == '__main__': 74 | import argparse 75 | 76 | parser = argparse.ArgumentParser(description='work with raw NVRAM data') 77 | parser.set_defaults(func=None) 78 | subparsers = parser.add_subparsers(help='subcommand') 79 | 80 | def do_dump(args): 81 | nvram = restruct.parse(NVRAM, args.infile) 82 | 83 | for section in nvram.sections: 84 | name = section.header.name.rstrip('\x00') 85 | if not name: 86 | continue 87 | print(name + ':') 88 | for val in section.values: 89 | v = val.value 90 | if all(0x20 <= b <= 0x7E for b in v): 91 | v = repr(v.decode('ascii')) 92 | print(' ' + val.key + ': ' + restruct.format_value(v, str)) 93 | dump_parser = subparsers.add_parser('dump', help='show all data') 94 | dump_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 95 | dump_parser.set_defaults(func=do_dump) 96 | 97 | args = parser.parse_args() 98 | if not args.func: 99 | parser.error('a subcommand must be provided') 100 | args.func(args) 101 | -------------------------------------------------------------------------------- /img4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Greetings to: 4 | # - https://www.theiphonewiki.com/wiki/IMG4_File_Format 5 | # - https://github.com/tihmstar/img4tool/ 6 | # - https://lapo.it/asn1js/ 7 | # - hexdump tool of choice 8 | 9 | import functools 10 | from asn1crypto.core import ( 11 | Enumerated, Choice, Sequence, SequenceOf, SetOf, 12 | Integer, IA5String, OctetString, ParsableOctetString, Integer, 13 | Any 14 | ) 15 | from asn1crypto.x509 import Certificate 16 | import restruct 17 | 18 | 19 | def ascii2int(s): 20 | return int.from_bytes(s.encode('ascii'), byteorder='big') 21 | 22 | class any_tag(tuple): 23 | """ highly cursed tuple subtype to bully asn1crypto into accepting any tag """ 24 | def __contains__(self, o): 25 | return True 26 | 27 | 28 | class IMG4KeyBag(Sequence): 29 | _fields = [ 30 | ('id', Integer), 31 | ('iv', OctetString), 32 | ('key', OctetString), 33 | ] 34 | 35 | class IMG4KeyBagSequence(SequenceOf): 36 | _child_spec = IMG4KeyBag 37 | 38 | class IMG4CompressionAlgorithm(Integer): 39 | _map = { 40 | 1: 'lzfse', 41 | } 42 | 43 | class IMG4Compression(Sequence): 44 | _fields = [ 45 | ('algorithm', IMG4CompressionAlgorithm), 46 | ('original_size', Integer), 47 | ] 48 | 49 | class IMG4Payload(Sequence): 50 | _fields = [ 51 | ('magic', IA5String), # "IM4P" 52 | ('type', IA5String), 53 | ('description', IA5String), 54 | ('data', OctetString, {'optional': True}), 55 | ('keybags', ParsableOctetString, {'optional': True}), 56 | ('compression', IMG4Compression, {'optional': True}), 57 | ] 58 | 59 | 60 | class AnyValueInner(Sequence): 61 | _fields = [ 62 | ('key', IA5String), 63 | ('value', Any, {'optional': True}), 64 | ] 65 | 66 | class AnyValue(Sequence): 67 | _fields = [ 68 | ('value', AnyValueInner), 69 | ] 70 | class_ = 3 71 | _bad_tag = any_tag() 72 | 73 | class AnySet(SetOf): 74 | _child_spec = AnyValue 75 | 76 | class IMG4ManifestProperties(Sequence): 77 | _fields = [ 78 | ('type', IA5String), # "MANP", 79 | ('values', AnySet) 80 | ] 81 | 82 | class IMG4ManifestCategory(Sequence): 83 | _fields = [ 84 | ('category', IMG4ManifestProperties) 85 | ] 86 | class_ = 3 87 | _bad_tag = any_tag() 88 | 89 | class IMG4ManifestCategorySet(SetOf): 90 | _child_spec = IMG4ManifestCategory 91 | 92 | class IMG4ManifestBody(Sequence): 93 | _fields = [ 94 | ('type', IA5String), # "MANB" 95 | ('categories', IMG4ManifestCategorySet), 96 | ] 97 | 98 | class IMG4ManifestContent(Choice): 99 | _alternatives = [ 100 | ('category', IMG4ManifestBody, {'explicit': ('private', ascii2int('MANB'))}), 101 | ] 102 | 103 | class IMG4ManifestContentSet(SetOf): 104 | _child_spec = IMG4ManifestContent 105 | 106 | class IMG4CertificateSequence(SequenceOf): 107 | _child_spec = Certificate 108 | 109 | class IMG4Manifest(Sequence): 110 | _fields = [ 111 | ('magic', IA5String), # "IM4M" 112 | ('version', Integer), 113 | ('contents', IMG4ManifestContentSet), 114 | ('signature', OctetString), 115 | ('certificates', IMG4CertificateSequence), 116 | ] 117 | 118 | 119 | class IMG4(Sequence): 120 | _fields = [ 121 | ('magic', IA5String), # "IMG4", 122 | ('payload', IMG4Payload), 123 | ('manifest', IMG4Manifest, {'explicit': 0}), 124 | ] 125 | 126 | 127 | if __name__ == '__main__': 128 | import argparse 129 | 130 | parser = argparse.ArgumentParser() 131 | parser.add_argument('-r', '--raw', action='store_true', help='print raw parsed data') 132 | parser.add_argument('infile', type=argparse.FileType('rb'), help='input .img4/.im4m/.im4p file') 133 | parser.add_argument('outfile', type=argparse.FileType('wb'), nargs='?', help='output data file for payload') 134 | args = parser.parse_args() 135 | 136 | contents = args.infile.read() 137 | errors = {} 138 | for p in (IMG4, IMG4Manifest, IMG4Payload): 139 | try: 140 | img4 = p.load(contents) 141 | img4.native # trigger parsing 142 | break 143 | except Exception as e: 144 | errors[p] = e 145 | else: 146 | print('Could not parse file {}:'.format(args.infile.name)) 147 | for (p, e) in errors.items(): 148 | print(' - As {}: {}'.format(p.__name__, e)) 149 | sys.exit(1) 150 | 151 | if isinstance(img4, IMG4): 152 | payload = img4['payload'] 153 | manifest = img4['manifest'] 154 | elif isinstance(img4, IMG4Manifest): 155 | payload = None 156 | manifest = img4 157 | elif isinstance(img4, IMG4Payload): 158 | payload = img4 159 | manifest = None 160 | 161 | if payload: 162 | p = payload.native 163 | if args.raw: 164 | print(restruct.format_value(p, str)) 165 | else: 166 | print('payload:') 167 | print(' type:', p['type']) 168 | print(' desc:', p['description']) 169 | if p['keybags']: 170 | print(' keybags:') 171 | keybags = payload['keybags'].parse(IMG4KeyBagSequence).native 172 | for kb in keybags: 173 | print(' id: ', kb['id']) 174 | print(' iv: ', restruct.format_value(kb['iv'], str)) 175 | print(' key:', restruct.format_value(kb['key'], str)) 176 | print() 177 | if p['compression']: 178 | print(' compression:') 179 | print(' algo:', p['compression']['algorithm']) 180 | print(' size:', p['compression']['original_size']) 181 | algo = p['compression']['algorithm'] 182 | else: 183 | algo = None 184 | print() 185 | 186 | if args.outfile: 187 | if algo == 'lzfse': 188 | import lzfse 189 | data = lzfse.decompress(p['data']) 190 | elif algo: 191 | raise ValueError('unknown algorithm: {}'.format(algo)) 192 | else: 193 | data = p['data'] 194 | args.outfile.write(data) 195 | if manifest: 196 | m = manifest.native 197 | if args.raw: 198 | print(restruct.format_value(m, str)) 199 | else: 200 | print('manifest:') 201 | for p in m['contents']: 202 | print(' body:') 203 | if p['type'] == 'MANB': 204 | for c in p['categories']: 205 | cname = c['category']['type'] 206 | for v in c['category']['values']: 207 | print(' {}.{}: {}'.format(cname, v['value']['key'], restruct.format_value(v['value']['value'], str))) 208 | print() 209 | -------------------------------------------------------------------------------- /macho.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Greetings to: XNU source code 4 | 5 | import enum 6 | import uuid 7 | import restruct 8 | from restruct import Processed, Type, Struct, Data, Arr, Generic 9 | 10 | 11 | # Forward declaration 12 | MachO_ = Generic() 13 | 14 | 15 | class ULEB128(Type): 16 | def parse(self, io, context): 17 | value = 0 18 | n = 0 19 | while True: 20 | v = io.read(1)[0] 21 | value |= (v & 0b1111111) << n 22 | if not (v & 0b10000000): 23 | break 24 | n += 7 25 | return value 26 | 27 | 28 | class Protection(enum.Flag): 29 | Read = 1 30 | Write = 2 31 | Exec = 4 32 | 33 | 34 | class CPUType(enum.Enum): 35 | VAX = 1 36 | MC68k = 6 37 | X86 = 7 38 | MIPS = 8 39 | MC98k = 10 40 | HPPA = 11 41 | ARM = 12 42 | MC88k = 13 43 | SPARC = 14 44 | I860 = 15 45 | Alpha = 16 46 | PowerPC = 18 47 | 48 | class LoadCommandType(enum.Enum): 49 | Segment = 1 50 | SymbolTable = 2 51 | SymbolDebugInfo = 3 52 | Thread = 4 53 | UnixThread = 5 54 | LoadFixedLibrary = 6 55 | FixedLibraryID = 7 56 | ID = 8 57 | IncludeFixed = 9 58 | Prepage = 10 59 | DynamicSymbolTable = 11 60 | LoadDynamicLibrary = 12 61 | DynamicLibraryID = 13 62 | LoadDynamicLinker = 14 63 | DynamicLinkerID = 15 64 | PreboundDynamicLibrary = 16 65 | Routines = 17 66 | SubFramework = 18 67 | SubUmbrella = 19 68 | SubClient = 20 69 | SubLibrary = 21 70 | TwoLevelHints = 22 71 | PreboundChecksum = 23 72 | LoadWeakDynamicLibrary = 24 73 | Segment64 = 25 74 | Routines64 = 26 75 | UUID = 27 76 | RunPath = 28 77 | CodeSignature = 29 78 | SegmentSplitInfo = 30 79 | ReExportDynamicLibrary = 31 80 | LazyLoadDynamicLibrary = 32 81 | EncryptionInfo = 33 82 | DynamicLinkerInfo = 34 83 | LoadUpwardDynamicLibrary = 35 84 | MinMacOSVersion = 36 85 | MinIPhoneOSVersion = 37 86 | FunctionStartAddresses = 38 87 | DynamicLinkerEnvironment = 39 88 | Main = 40 89 | DataInCode = 41 90 | SourceVersion = 42 91 | DynamicLibraryCodeSignatureDR = 43 92 | EncryptionInfo64 = 44 93 | LinkerOption = 45 94 | LinkerOptimizationHint = 46 95 | MinTVOSVersion = 47 96 | MinWatchOSVersion = 48 97 | Note = 49 98 | BuildVersion = 50 99 | DynamicLibraryExportsTrie = 51 100 | ChainedFixups = 52 101 | FileSetEntry = 53 102 | 103 | 104 | LOAD_COMMANDS = {} 105 | 106 | def lc(i): 107 | def inner(v): 108 | LOAD_COMMANDS[i] = v 109 | return v 110 | return inner 111 | 112 | 113 | lc(LoadCommandType.UUID)( 114 | Processed(Data(16), lambda x: uuid.UUID(bytes=x), lambda x: x.bytes) 115 | ) 116 | 117 | class Version(Struct): 118 | patch: UInt(8) 119 | minor: UInt(8) 120 | major: UInt(16) 121 | 122 | class BuildTool(enum.Enum): 123 | Clang = 1 124 | Swift = 2 125 | LD = 3 126 | 127 | class BuildToolVersion(Struct): 128 | tool: Enum(BuildTool, UInt(32)) 129 | version: Version 130 | 131 | class Platform(enum.Enum): 132 | Unknown = 0 133 | macOS = 1 134 | iOS = 2 135 | tvOS = 3 136 | watchOS = 4 137 | bridgeOS = 5 138 | Catalyst = 6 139 | iOSSimulator = 7 140 | tvOSSimulator = 8 141 | watchOSSimulator = 9 142 | DriverKit = 10 143 | 144 | @lc(LoadCommandType.BuildVersion) 145 | class BuildVersion(Struct, partials={'C'}): 146 | platform: Enum(Platform, UInt(32)) 147 | min_os: Version 148 | sdk: Version 149 | tool_count: UInt(32) @ C.count 150 | tools: Arr(BuildToolVersion) @ C 151 | 152 | 153 | class ThreadFlavor(enum.Enum): 154 | Thread = 1 155 | FloatingPoint = 2 156 | Exception = 3 157 | Debug = 4 158 | No = 5 159 | Thread64 = 6 160 | Exception64 = 7 161 | Thread32 = 9 162 | 163 | THREAD_TYPES = {} 164 | 165 | def thread(cpu, id): 166 | def inner(v): 167 | THREAD_TYPES[cpu, id] = v 168 | return v 169 | return inner 170 | 171 | 172 | @thread(CPUType.ARM, ThreadFlavor.Thread) 173 | class ARMThreadState32(Struct): 174 | gp: Arr(UInt(32), count=13) 175 | sp: UInt(32) 176 | lr: UInt(32) 177 | pc: UInt(32) 178 | cpsr: UInt(32) 179 | 180 | @thread(CPUType.ARM, ThreadFlavor.Thread64) 181 | class ARMThreadState64(Struct): 182 | gp: Arr(UInt(64), count=29) 183 | fp: UInt(64) 184 | lr: UInt(64) 185 | sp: UInt(64) 186 | pc: UInt(64) 187 | cpsr: UInt(32) 188 | flags: UInt(32) 189 | 190 | @thread(CPUType.ARM, ThreadFlavor.FloatingPoint) 191 | class ARMFloatingPointState(Struct): 192 | fp: Arr(UInt(32), count=64) 193 | fpscr: UInt(32) 194 | 195 | @thread(CPUType.ARM, ThreadFlavor.Exception) 196 | class ARMExceptionState32(Struct): 197 | id: UInt(32) 198 | status: UInt(32) 199 | addr: UInt(32) 200 | 201 | @thread(CPUType.ARM, ThreadFlavor.Exception64) 202 | class ARMExceptionState64(Struct): 203 | addr: UInt(64) 204 | status: UInt(32) 205 | id: UInt(32) 206 | 207 | @lc(LoadCommandType.Thread) 208 | @lc(LoadCommandType.UnixThread) 209 | class Thread(Struct, partials={'S'}): 210 | flavor: Enum(ThreadFlavor, UInt(32)) 211 | size: Processed(UInt(32), lambda x: x * 4, lambda x: x // 4) @ S.limit 212 | state: Sized(Switch(fallback=Data(), options=THREAD_TYPES), exact=True) @ S 213 | 214 | def on_parse_flavor(self, spec, context): 215 | spec.state.type.selector = (context.user.cpu_type, self.flavor) 216 | 217 | 218 | class LinkEntry(Struct, generics={'T'}, partials={'R', 'S'}): 219 | offset: UInt(32) @ R.point 220 | size: UInt(32) @ S.limit 221 | data: Ref(Sized(T) @ S) @ R 222 | 223 | lc(LoadCommandType.ChainedFixups)(LinkEntry[Data()]) 224 | lc(LoadCommandType.FunctionStartAddresses)(LinkEntry[Arr(ULEB128(), stop_value=0)]) 225 | 226 | 227 | class Section(Struct, generics={'AddrSize'}): 228 | name: Str(length=16, exact=True) 229 | segment_name: Str(length=16, exact=True) 230 | vm_offset: UInt(AddrSize) 231 | vm_size: UInt(AddrSize) 232 | file_offset: UInt(32) 233 | alignment: UInt(32) 234 | reloc_offset: UInt(32) 235 | reloc_count: UInt(32) 236 | flags: UInt(32) 237 | _reserved: Data(12) 238 | 239 | class Segment(Struct, generics={'AddrSize'}, partials={'C', 'DS', 'DR'}): 240 | name: Str(length=16, exact=True) 241 | vm_offset: UInt(AddrSize) @ DR.point 242 | vm_size: UInt(AddrSize) @ DS.size 243 | file_offset: UInt(AddrSize) 244 | file_size: UInt(AddrSize) 245 | max_prot: Enum(Protection, UInt(32)) 246 | init_prot: Enum(Protection, UInt(32)) 247 | section_count: UInt(32) @ C.count 248 | flags: UInt(32) 249 | sections: Arr(Section[AddrSize]) 250 | 251 | 252 | lc(LoadCommandType.Segment)(Segment[32]) 253 | lc(LoadCommandType.Segment64)(Segment[64]) 254 | 255 | 256 | class SymbolType(enum.Enum): 257 | Undefined = 0 258 | Absolute = 1 259 | Indirect = 5 260 | Prebound = 6 261 | InSection = 7 262 | 263 | class SymbolEntry(Struct, generics={'AddrSize'}): 264 | string_index: UInt(32) 265 | external: Bool(Bits(1)) 266 | type: Enum(SymbolType, Bits(3)) 267 | priv_external:Bool(Bits(1)) 268 | stab: Bits(3) 269 | section: UInt(8) 270 | desc: UInt(16) 271 | value: UInt(AddrSize) 272 | 273 | @lc(LoadCommandType.SymbolTable) 274 | class SymbolTable(Struct, partials={'SyR', 'SyA', 'StL', 'StR', 'StC'}): 275 | symbol_offset: UInt(32) @ SyR.point 276 | symbol_count: UInt(32) @ SyA.count 277 | string_offset: UInt(32) @ StR.point 278 | string_size: UInt(32) @ StC.limit @ StL.size 279 | #symbols: Ref(Arr(SymbolEntry[64]) @ SyA) @ SyR 280 | #strings: Ref(Lazy(Sized(Arr(Str(), stop_value='')) @ StC) @ StL) @ StR 281 | 282 | @lc(LoadCommandType.DynamicSymbolTable) 283 | class DynamicSymbolTable(Struct): 284 | local_index: UInt(32) 285 | local_count: UInt(32) 286 | external_index: UInt(32) 287 | external_count: UInt(32) 288 | undefined_index: UInt(32) 289 | undefined_count: UInt(32) 290 | toc_offset: UInt(32) 291 | toc_count: UInt(32) 292 | module_table_offset: UInt(32) 293 | module_table_count: UInt(32) 294 | external_table_offset: UInt(32) 295 | external_table_count: UInt(32) 296 | indirect_table_offset: UInt(32) 297 | indirect_table_count: UInt(32) 298 | external_reloc_offset: UInt(32) 299 | external_reloc_count: UInt(32) 300 | reloc_offset: UInt(32) 301 | reloc_count: UInt(32) 302 | 303 | @lc(LoadCommandType.SourceVersion) 304 | class SourceVersion(Struct): 305 | level: Bits(10) 306 | stage: Bits(10) 307 | revision: Bits(10) 308 | minor: Bits(10) 309 | major: UInt(24) 310 | 311 | @lc(LoadCommandType.FileSetEntry) 312 | class FileSetEntry(Struct, partials={'R'}): 313 | vm_offset: UInt(64) 314 | file_offset: UInt(64) @ R.point 315 | entry_id: UInt(64) 316 | name: Str() 317 | data: Ref(MachO_) @ R 318 | 319 | 320 | class CPUTypeFlags(Struct): 321 | is_64_bit: Bool(Bits(1)) 322 | is_64_32_bit: Bool(Bits(1)) 323 | _pad2: Bits(6) 324 | 325 | class ARMSubType(enum.Enum): 326 | All = 0 327 | V4T = 5 328 | V6 = 6 329 | V5TEJ = 7 330 | XScale = 8 331 | V7 = 9 332 | V7F = 10 333 | V7S = 11 334 | V7K = 12 335 | V8 = 13 336 | V6M = 14 337 | V7M = 15 338 | V7EM = 16 339 | V8M = 17 340 | 341 | class ARM64SubType(enum.Enum): 342 | All = 0 343 | V8 = 1 344 | E = 2 345 | 346 | class FileType(enum.Enum): 347 | Object = 1 348 | Executable = 2 349 | FixedLibrary = 3 350 | CoreDump = 4 351 | Preloaded = 5 352 | DynamicLibrary = 6 353 | DynamicLinker = 7 354 | Bundle = 8 355 | DynamicLibraryStub = 9 356 | DSym = 10 357 | KernelExtensionBundle = 11 358 | FileSet = 12 359 | 360 | class LoadCommand(Struct, partials={'T', 'S'}): 361 | type: Enum(LoadCommandType, Bits(31)) @ T.selector 362 | vital: Bool(Bits(1)) 363 | size: Processed(UInt(32), lambda x: x - 8, lambda x: x + 8) @ S.limit 364 | data: Sized(Switch(fallback=Data(), options=LOAD_COMMANDS) @ T, exact=True) @ S 365 | 366 | class MachO(Struct, partials={'C', 'S'}): 367 | magic: UInt(32) 368 | cpu_type: Enum(CPUType, UInt(24)) 369 | cpu_type_flags: CPUTypeFlags 370 | cpu_sub_type: Switch(fallback=UInt(24), options={ 371 | (CPUType.ARM, False): Enum(ARMSubType, UInt(24)), 372 | (CPUType.ARM, True): Enum(ARM64SubType, UInt(24)) 373 | }) 374 | cpu_sub_type_flags: UInt(8) 375 | file_type: Enum(FileType, UInt(32)) 376 | command_count: UInt(32) @ C.count 377 | command_size: UInt(32) @ S.limit 378 | flags: UInt(32) 379 | reserved: UInt(32) 380 | commands: Sized(Arr(LoadCommand) @ C) @ S 381 | 382 | def on_parse_cpu_type_flags(self, spec, context): 383 | context.user.cpu_type = self.cpu_type 384 | spec.cpu_sub_type.selector = (self.cpu_type, self.cpu_type_flags.is_64_bit) 385 | 386 | MachO_.resolve(MachO) 387 | 388 | 389 | if __name__ == '__main__': 390 | import sys, os, os.path 391 | f = open(sys.argv[1], 'rb') 392 | m = restruct.parse(MachO, f) 393 | print(m) 394 | -------------------------------------------------------------------------------- /dt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Greetings to: 4 | # - hexdump tool of choice 5 | 6 | import enum 7 | import restruct 8 | 9 | 10 | class DeviceTreeType(enum.Enum): 11 | Empty = enum.auto() 12 | U32 = enum.auto() 13 | U64 = enum.auto() 14 | Handle = enum.auto() 15 | String = enum.auto() 16 | StringList = enum.auto() 17 | Opaque = enum.auto() 18 | 19 | PROPERTY_TYPES = { 20 | 'compatible': DeviceTreeType.StringList, 21 | 'model': DeviceTreeType.String, 22 | 'phandle': DeviceTreeType.Handle, 23 | 'AAPL,phandle': DeviceTreeType.Handle, 24 | 'linux,phandle': DeviceTreeType.Handle, 25 | 'status': DeviceTreeType.String, 26 | '#size-cells': DeviceTreeType.U32, 27 | '#address-cells': DeviceTreeType.U32, 28 | 29 | 'name': DeviceTreeType.String, 30 | } 31 | 32 | def isprint(x): 33 | return all(0x7E >= b >= 0x20 for b in x) 34 | 35 | def determine_type(k, v): 36 | if k in PROPERTY_TYPES: 37 | return PROPERTY_TYPES[k] 38 | if v and v[-1] == 0 and isprint(v[:-1]): 39 | return DeviceTreeType.String 40 | if len(v) == 4: 41 | # not a fan of this heuristic 42 | return DeviceTreeType.U32 43 | if not v: 44 | # or this one 45 | return DeviceTreeType.Empty 46 | return DeviceTreeType.Opaque 47 | 48 | def determine_reverse_type(k, v): 49 | if k in PROPERTY_TYPES: 50 | return PROPERTY_TYPES[k] 51 | return { 52 | type(None): DeviceTreeType.Empty, 53 | int: DeviceTreeType.U32, 54 | str: DeviceTreeType.String, 55 | list: DeviceTreeType.StringList, 56 | bytes: DeviceTreeType.Opaque, 57 | }[type(v)] 58 | 59 | 60 | FDT_PROPERTY_TYPES = { 61 | DeviceTreeType.Empty: restruct.Nothing(), 62 | DeviceTreeType.U32: restruct.UInt(32, order='be'), 63 | DeviceTreeType.U64: restruct.UInt(64, order='be'), 64 | DeviceTreeType.Handle: restruct.UInt(32, order='be'), 65 | DeviceTreeType.String: restruct.Str(), 66 | DeviceTreeType.StringList: restruct.Arr(restruct.Str(type='c')), 67 | DeviceTreeType.Opaque: restruct.Data(), 68 | } 69 | 70 | class FDTMemoryReservation(restruct.Struct): 71 | address: UInt(64, order='be') 72 | size: UInt(64, order='be') 73 | 74 | FDTMemoryArray = restruct.Arr(restruct.AlignedTo(FDTMemoryReservation, 8)) 75 | 76 | class FDTToken(enum.Enum): 77 | NodeBegin = 1 78 | NodeEnd = 2 79 | Property = 3 80 | Ignore = 4 81 | End = 9 82 | 83 | class FDTProperty(restruct.Struct, partials={'DataSize'}): 84 | size: UInt(32, order='be') @ DataSize.limit 85 | name_offset: UInt(32, order='be') 86 | name: Ref(Str()) 87 | data: Sized(Data()) @ DataSize 88 | 89 | def on_parse_name_offset(self, spec, context): 90 | spec.name.point = context.user.strings_offset + self.name_offset 91 | 92 | def on_parse_data(self, spec, context): 93 | self.data = restruct.parse(FDT_PROPERTY_TYPES[determine_type(self.name, self.data)], self.data) 94 | 95 | class FDTNode(restruct.Struct, partials={'DataType'}): 96 | token: Enum(FDTToken, UInt(32, order='be')) @ DataType.selector 97 | data: Switch(fallback=Nothing(), options={ 98 | FDTToken.NodeBegin: Str(), 99 | FDTToken.Property: FDTProperty, 100 | }) @ DataType 101 | 102 | FDTNodeArray = restruct.Arr(restruct.AlignedTo(FDTNode, 4), stop_value=FDTNode(token=FDTToken.End, data=None)) 103 | 104 | class FlattenedDeviceTree(restruct.Struct, partials={'MemOff', 'StruOff', 'StruSize', 'StriOff', 'StriSize'}): 105 | magic: Fixed(b'\xd0\x0d\xfe\xed') 106 | size: UInt(32, order='be') 107 | structs_offset: UInt(32, order='be') @ StruOff.point 108 | strings_offset: UInt(32, order='be') @ StriOff.point 109 | mem_offset: UInt(32, order='be') @ MemOff.point 110 | version: UInt(32, order='be') 111 | compat_version: UInt(32, order='be') 112 | boot_cpu_id: UInt(32, order='be') 113 | strings_size: UInt(32, order='be') @ StriSize.limit 114 | structs_size: UInt(32, order='be') @ StruSize.limit 115 | 116 | strings: Ref(Sized(Data()) @ StriSize) @ StriOff 117 | structs: Ref(Sized(FDTNodeArray) @ StruSize) @ StruOff 118 | memory: Ref(Sized(FDTMemoryArray)) @ MemOff 119 | 120 | def on_parse_structs_size(self, spec, context): 121 | spec.memory.type.limit = self.size - self.structs_size - self.strings_size - 40 122 | 123 | def on_parse_strings(self, spec, context): 124 | context.user.strings_offset = self.strings_offset 125 | 126 | 127 | 128 | ADT_PROPERTY_TYPES = { 129 | DeviceTreeType.Empty: restruct.Nothing(), 130 | DeviceTreeType.U32: restruct.UInt(32, order='le'), 131 | DeviceTreeType.U64: restruct.UInt(64, order='le'), 132 | DeviceTreeType.Handle: restruct.UInt(32, order='le'), 133 | DeviceTreeType.String: restruct.Str(), 134 | DeviceTreeType.StringList: restruct.Arr(restruct.Str(type='c')), 135 | DeviceTreeType.Opaque: restruct.Data(), 136 | } 137 | 138 | class ADTProperty(restruct.Struct, partials={'DataSize'}): 139 | name: Str(length=32, exact=True) 140 | size: Bits(31, byteswap=True) @ DataSize.limit 141 | template: Bool(Bits(1)) 142 | value: Sized(Data()) @ DataSize 143 | 144 | def on_parse_value(self, spec, context): 145 | self.value = restruct.parse(ADT_PROPERTY_TYPES[determine_type(self.name, self.value)], self.value) 146 | 147 | def on_emit_value(self, spec, context): 148 | self.value = restruct.emit(ADT_PROPERTY_TYPES[determine_reverse_type(self.name, self.value)], self.value).getvalue() 149 | 150 | class ADTNode(restruct.Struct, recursive=True, partials={'PropArr', 'ChildArr'}): 151 | property_count: UInt(32) @ PropArr.count 152 | child_count: UInt(32) @ ChildArr.count 153 | properties: Arr(AlignTo(ADTProperty, 4)) @ PropArr 154 | children: Arr(Self) @ ChildArr 155 | 156 | AppleDeviceTree = ADTNode 157 | 158 | 159 | 160 | class DeviceTreeRange(restruct.Struct, generics={'ChildAddrSize', 'ParentAddrSize', 'LengthSize'}): 161 | child_address: UInt(ChildAddrSize) 162 | parent_address: UInt(ParentAddrSize) 163 | length: UInt(LengthSize) 164 | 165 | class DeviceTreeRegister(restruct.Struct, generics={'AddrSize', 'LengthSize'}): 166 | address: UInt(AddrSize) 167 | length: UInt(LengthSize) 168 | 169 | 170 | 171 | def dump_value(n, v): 172 | return restruct.format_value(v, str) 173 | 174 | def dump(node, depth=0, last=True): 175 | space = ' ' * (depth * 2) 176 | if last and not node.children: 177 | leader = ' ' 178 | else: 179 | leader = '| ' 180 | 181 | props = {p.name: p.value for p in node.properties} 182 | 183 | name = props.get('name', '') 184 | s = space + '+- [' + name + ']\n' 185 | 186 | for k, v in props.items(): 187 | s += space + leader + ' ' + k + ': ' + dump_value(k, v) + '\n' 188 | 189 | if node.children: 190 | s += space + '\\_,\n' 191 | for i, child in enumerate(node.children, start=1): 192 | s += dump(child, depth=depth + 1, last=i == len(node.children)) 193 | 194 | return s 195 | 196 | 197 | def get(node, path): 198 | results = [] 199 | 200 | if len(path) == 1: 201 | props = {p.name: p for p in node.properties} 202 | if path[0] in props: 203 | results.append(props[path[0]]) 204 | 205 | for c in node.children: 206 | props = {p.name: p.value for p in c.properties} 207 | if props['name'] == path[0]: 208 | if len(path) == 1: 209 | results.append(c) 210 | else: 211 | results.extend(get(c, path[1:])) 212 | 213 | return results 214 | 215 | 216 | def from_fdt(nodes, depth=0): 217 | node = ADTNode(property_count=0, properties=[], child_count=0, children=[]) 218 | 219 | assert nodes[0].token == FDTToken.NodeBegin 220 | if depth == 0 and not nodes[0].data: 221 | name = 'device-tree' 222 | else: 223 | name = nodes[0].data 224 | node.properties.append(ADTProperty(name='name', template=False, value=name)) 225 | 226 | i = 1 227 | while True: 228 | if nodes[i].token == FDTToken.NodeEnd: 229 | break 230 | elif nodes[i].token == FDTToken.NodeBegin: 231 | n, child = from_fdt(nodes[i:], depth=depth + 1) 232 | i += n 233 | node.children.append(child) 234 | elif nodes[i].token == FDTToken.Property: 235 | k = nodes[i].data.name 236 | v = nodes[i].data.data 237 | node.properties.append(ADTProperty(name=k, template=False, value=v)) 238 | i += 1 239 | 240 | node.property_count = len(node.properties) 241 | node.child_count = len(node.children) 242 | return i, node 243 | 244 | 245 | def to_fdt(node, depth=0): 246 | raise NotImplementedError 247 | 248 | 249 | def value_to_dts(val): 250 | if val is None: 251 | return None 252 | if isinstance(val, int): 253 | return '<' + hex(val) + '>' 254 | if isinstance(val, str): 255 | return '"' + val.replace('"', '\\"') + '"' 256 | if isinstance(val, list): 257 | return ','.join(value_to_dts(x) for x in val) 258 | if isinstance(val, bytes): 259 | return '[' + val.hex() + ']' 260 | 261 | def to_dts(node, depth=0): 262 | s = '' 263 | props = {p.name: p.value for p in node.properties} 264 | spacing = ' ' * (depth * 2) 265 | 266 | if not depth: 267 | s += '/dts-v1/;\n\n' 268 | 269 | name = props.pop('name') 270 | if not depth: 271 | name = '/' 272 | s += spacing + name + ' {\n' 273 | 274 | for k, v in props.items(): 275 | p = k 276 | sv = value_to_dts(v) 277 | if sv is not None: 278 | p += ' = ' + sv 279 | s += restruct.indent(p + ';', count=(depth + 1) * 2, start=True) + '\n' 280 | 281 | for c in node.children: 282 | s += '\n' + to_dts(c, depth=depth + 1) 283 | 284 | s += spacing + '};\n' 285 | 286 | return s 287 | 288 | 289 | def diff(a, b, path=[]): 290 | diffs = {tuple(path): None} 291 | a_props = {p.name: p.value for p in a.properties} if a else {} 292 | b_props = {p.name: p.value for p in b.properties} if b else {} 293 | 294 | premoved = [] 295 | padded = [] 296 | for k in a_props: 297 | if k not in b_props: 298 | premoved.append((k, a_props[k])) 299 | else: 300 | if a_props[k] != b_props[k]: 301 | premoved.append((k, a_props[k])) 302 | padded.append((k, b_props[k])) 303 | del b_props[k] 304 | 305 | for k in b_props: 306 | padded.append((k, b_props[k])) 307 | 308 | b_children = {} 309 | for c in (b.children if b else []): 310 | name = next(p.value for p in c.properties if p.name == 'name') 311 | b_children.setdefault(name, []).append(c) 312 | 313 | cremoved = [] 314 | cadded = [] 315 | for c in (a.children if a else []): 316 | props = {p.name: p.value for p in c.properties} 317 | name = props['name'] 318 | if name not in b_children: 319 | cremoved.append(c) 320 | else: 321 | diffs.update(diff(c, b_children[name].pop(0), path + [name])) 322 | 323 | for name, cs in b_children.items(): 324 | for c in cs: 325 | cadded.append(c) 326 | 327 | diffs[tuple(path)] = (premoved, padded, cremoved, cadded) 328 | return diffs 329 | 330 | 331 | def find(node, pname, pvalue, path=[]): 332 | results = [] 333 | props = {p.name: p.value for p in node.properties} 334 | 335 | nname = props['name'] 336 | if props.get(pname, None) == pvalue: 337 | results.append(path + [nname]) 338 | 339 | for c in node.children: 340 | results.extend(find(c, pname, pvalue, path + [nname])) 341 | 342 | return results 343 | 344 | 345 | def regs(node, path): 346 | path = path[:] 347 | addrspaces = [] 348 | last_addr_size = None 349 | last_size_size = None 350 | 351 | while True: 352 | props = {p.name: p.value for p in node.properties} 353 | 354 | if '#address-cells' in props and '#size-cells' in props: 355 | this_addr_size = props['#address-cells'] 356 | this_size_size = props['#size-cells'] 357 | if 'ranges' in props: 358 | range_spec = DeviceTreeRange[this_addr_size * 32, last_addr_size * 32, this_size_size * 32] 359 | ranges = restruct.parse(restruct.Arr(range_spec), props['ranges']) 360 | addrspaces.append(ranges) 361 | last_addr_size = this_addr_size 362 | last_size_size = this_size_size 363 | 364 | if not path: 365 | if 'reg' in props: 366 | reg_spec = DeviceTreeRegister[last_addr_size * 32, last_size_size * 32] 367 | regs = restruct.parse(restruct.Arr(reg_spec), props['reg']) 368 | else: 369 | regs = [] 370 | break 371 | 372 | for child in node.children: 373 | cprops = {p.name: p.value for p in child.properties} 374 | cname = cprops['name'] 375 | if cname == path[0]: 376 | path.pop(0) 377 | node = child 378 | break 379 | else: 380 | raise ValueError('404') 381 | 382 | rs = [] 383 | for reg in regs: 384 | addr = reg.address 385 | 386 | for addrspace in reversed(addrspaces): 387 | for r in addrspace: 388 | if addr >= r.child_address and addr + reg.length <= r.child_address + r.length: 389 | addr = r.parent_address + (addr - r.child_address) 390 | break 391 | else: 392 | if addrspace: 393 | raise ValueError('could not map child to parent address space') 394 | 395 | rs.append((addr, reg.length)) 396 | 397 | return rs 398 | 399 | 400 | if __name__ == '__main__': 401 | def get_adt(infile): 402 | try: 403 | fdt = restruct.parse(FlattenedDeviceTree, infile) 404 | _, adt = from_fdt(fdt.structs) 405 | return adt 406 | except: 407 | infile.seek(0) 408 | return restruct.parse(AppleDeviceTree, infile) 409 | 410 | import sys 411 | import argparse 412 | 413 | parser = argparse.ArgumentParser(description='process Apple (ADT) and Flattened (FDT) device tree files') 414 | parser.set_defaults(func=None) 415 | subparsers = parser.add_subparsers(help='subcommand') 416 | 417 | def do_dump(args): 418 | dt = get_adt(args.infile) 419 | args.outfile.write(dump(dt)) 420 | dump_parser = subparsers.add_parser('dump', help='visually show device tree') 421 | dump_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 422 | dump_parser.add_argument('outfile', type=argparse.FileType('w'), nargs='?', default=sys.stdout, help='output file') 423 | dump_parser.set_defaults(func=do_dump) 424 | 425 | def do_show(args): 426 | dt = get_adt(args.infile) 427 | path = args.path.lstrip('/').split('/') 428 | for value in get(dt, path): 429 | if isinstance(value, ADTProperty): 430 | print(dump_value(value.name, value.value)) 431 | else: 432 | for p in value.properties: 433 | print(p.name + ': ' + dump_value(p.name, p.value)) 434 | 435 | show_parser = subparsers.add_parser('show', help='get value of property or node in device tree') 436 | show_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 437 | show_parser.add_argument('path', help='path to get') 438 | show_parser.set_defaults(func=do_show) 439 | 440 | def do_find(args): 441 | dt = get_adt(args.infile) 442 | if '=' in args.property: 443 | pn, pv = args.property.split('=') 444 | else: 445 | pn = 'name' 446 | pv = args.property 447 | for c in dt.children: 448 | for p in find(c, pn, pv): 449 | print('/' + '/'.join(p)) 450 | find_parser = subparsers.add_parser('find', help='find node in device tree') 451 | find_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 452 | find_parser.add_argument('property', help='name or property of node to find') 453 | find_parser.set_defaults(func=do_find) 454 | 455 | def do_diff_props(added, removed, path, label=None): 456 | p = '/' + '/'.join(path) 457 | print('--- ' + p) 458 | print('+++ ' + p + (' (' + label + ')' if label else '')) 459 | for (k, v) in removed: 460 | print('-' + k + ': ' + dump_value(k, v)) 461 | for (k, v) in added: 462 | print('+' + k + ': ' + dump_value(k, v)) 463 | print() 464 | def do_diff_node(node, path, removed, recursive=True): 465 | props = {p.name: p.value for p in node.properties} 466 | name = props['name'] 467 | 468 | if removed: 469 | do_diff_props({}, props.items(), path + (name,), label='removed') 470 | else: 471 | do_diff_props(props.items(), {}, path + (name,), label='added') 472 | 473 | if recursive: 474 | for child in node.children: 475 | do_diff_node(child, path + (name,), removed=removed) 476 | def do_diff(args): 477 | a = get_adt(args.a) 478 | b = get_adt(args.b) 479 | if args.path: 480 | path = args.path.lstrip('/').split('/') 481 | ap = get(a, path) 482 | bp = get(b, path) 483 | a = ap[0] if ap else None 484 | b = bp[0] if bp else None 485 | 486 | diffs = diff(a, b) 487 | for path, (premoved, padded, cremoved, cadded) in diffs.items(): 488 | if premoved or padded: 489 | do_diff_props(padded, premoved, path) 490 | for child in cremoved: 491 | do_diff_node(child, path, removed=True) 492 | for child in cadded: 493 | do_diff_node(child, path, removed=False) 494 | diff_parser = subparsers.add_parser('diff', help='show the difference between two device trees') 495 | diff_parser.add_argument('a', type=argparse.FileType('rb'), help='first file') 496 | diff_parser.add_argument('b', type=argparse.FileType('rb'), help='second file') 497 | diff_parser.add_argument('path', nargs='?', help='path to show differences for') 498 | diff_parser.set_defaults(func=do_diff) 499 | 500 | def do_regs(args): 501 | dt = get_adt(args.infile) 502 | path = args.path.lstrip('/').split('/') 503 | for (addr, size) in regs(dt, path): 504 | print(hex(addr), size) 505 | regs_parser = subparsers.add_parser('regs', help='show calculated register ranges for given path') 506 | regs_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 507 | regs_parser.add_argument('path', help='path to the device node, nodes separated by \'/\' (example: arm-io/i2c2/audio-codec-output)') 508 | regs_parser.set_defaults(func=do_regs) 509 | 510 | def do_conv_fdt(args): 511 | adt = restruct.parse(AppleDeviceTree, args.infile) 512 | n, dt = to_fdt(adt) 513 | restruct.emit(FlattenedDeviceTree, dt, args.outfile) 514 | conv_fdt_parser = subparsers.add_parser('to-fdt', help='convert to flattened device tree') 515 | conv_fdt_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 516 | conv_fdt_parser.add_argument('outfile', type=argparse.FileType('w+b'), nargs='?', default=sys.stdout.buffer, help='output file') 517 | conv_fdt_parser.set_defaults(func=do_conv_fdt) 518 | 519 | def do_conv_adt(args): 520 | dt = get_adt(args.infile) 521 | restruct.emit(AppleDeviceTree, dt, args.outfile) 522 | conv_adt_parser = subparsers.add_parser('to-adt', help='convert to Apple device tree') 523 | conv_adt_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 524 | conv_adt_parser.add_argument('outfile', type=argparse.FileType('w+b'), nargs='?', default=sys.stdout.buffer, help='output file') 525 | conv_adt_parser.set_defaults(func=do_conv_adt) 526 | 527 | def do_conv_src(args): 528 | dt = get_adt(args.infile) 529 | dts = to_dts(dt) 530 | args.outfile.write(dts) 531 | conv_src_parser = subparsers.add_parser('to-src', help='convert to device tree source') 532 | conv_src_parser.add_argument('infile', type=argparse.FileType('rb'), help='input file') 533 | conv_src_parser.add_argument('outfile', type=argparse.FileType('w'), nargs='?', default=sys.stdout, help='output file') 534 | conv_src_parser.set_defaults(func=do_conv_src) 535 | 536 | args = parser.parse_args() 537 | if not args.func: 538 | parser.error('a subcommand must be provided') 539 | args.func(args) 540 | --------------------------------------------------------------------------------