Skip to content

Commit

Permalink
add remotezip support together with extract-kernel
Browse files Browse the repository at this point in the history
  • Loading branch information
doronz88 committed Nov 26, 2024
1 parent 2eb1d2a commit fc992da
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 10 deletions.
36 changes: 26 additions & 10 deletions ipsw_parser/__main__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/usr/bin/env python3
import logging
from pathlib import Path
from typing import IO, Optional
from typing import Optional
from zipfile import ZipFile

import click
import coloredlogs
from remotezip import RemoteZip

from ipsw_parser.ipsw import IPSW

Expand All @@ -23,6 +24,14 @@

PEM_DB_ENV_VAR = 'IPSW_PARSER_PEM_DB'


def handle_ipsw_argument(ctx: click.Context, param: click.Argument, value: str) -> IPSW:
if value.startswith('http://') or value.startswith('https://'):
return IPSW(RemoteZip(value))
return IPSW(ZipFile(Path(value).expanduser()))


ipsw_argument = click.argument('ipsw', callback=handle_ipsw_argument)
pem_db_option = click.option('--pem-db', envvar=PEM_DB_ENV_VAR,
help='Path DB file url (can be either a filesystem path or an HTTP URL). '
'Alternatively, use the IPSW_PARSER_PEM_DB envvar.')
Expand All @@ -35,10 +44,9 @@ def cli() -> None:


@cli.command('info')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
def info(file) -> None:
@ipsw_argument
def info(ipsw) -> None:
""" Parse given .ipsw basic info """
ipsw = IPSW(ZipFile(file))
print(f'SupportedProductTypes: {ipsw.build_manifest.supported_product_types}')
print(f'ProductVersion: {ipsw.build_manifest.product_version}')
print(f'ProductBuildVersion: {ipsw.build_manifest.product_build_version}')
Expand All @@ -51,28 +59,36 @@ def info(file) -> None:


@cli.command('extract')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
@ipsw_argument
@click.argument('output', type=click.Path(exists=False))
@pem_db_option
def extract(file: IO, output: str, pem_db: Optional[str]) -> None:
def extract(ipsw: IPSW, output: str, pem_db: Optional[str]) -> None:
""" Extract .ipsw into filesystem layout """
output = Path(output)

if not output.exists():
output.mkdir(parents=True, exist_ok=True)

ipsw = IPSW(ZipFile(file))
ipsw.build_manifest.build_identities[0].extract(output, pem_db=pem_db)
ipsw.archive.extractall(
path=output, members=[f for f in ipsw.archive.filelist if f.filename.startswith('Firmware')])


@cli.command('extract-kernel')
@ipsw_argument
@click.argument('output', type=click.Path(exists=False))
@click.option('--arch', help='Arch name to extract using lipo')
def extract_kernel(ipsw: IPSW, output: str, arch: Optional[str]) -> None:
""" Extract kernelcache from given .ipsw into given output filename """
Path(output).write_bytes(ipsw.build_manifest.build_identities[0].get_kernelcache_payload(arch=arch))


@cli.command('device-support')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
@ipsw_argument
@pem_db_option
def device_support(file: IO, pem_db: Optional[str]) -> None:
def device_support(ipsw: IPSW, pem_db: Optional[str]) -> None:
""" Create DeviceSupport directory """
IPSW(ZipFile(file)).create_device_support(pem_db=pem_db)
ipsw.create_device_support(pem_db=pem_db)


if __name__ == '__main__':
Expand Down
12 changes: 12 additions & 0 deletions ipsw_parser/build_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ def extract_dsc(self, output: Path, pem_db: Optional[str] = None) -> None:
sub_path=Path('System'), pem_db=pem_db)
_split_dsc(output)

def get_kernelcache_payload(self, arch: Optional[str] = None) -> bytes:
im4p = IM4P(self.build_manifest.build_identities[0].get_component('KernelCache').data)
im4p.payload.decompress()
payload = im4p.payload.output().data
if arch is None:
return payload

with TemporaryDirectory() as temp_dir:
kernel_output = Path(temp_dir) / 'kernel'
local['ipsw']('macho', 'lipo', '-a', arch, kernel_output)
return Path(next(kernel_output.parent.glob(f'*.{arch}'))).read_bytes()

def extract(self, output: Path, pem_db: Optional[str] = None) -> None:
logger.info(f'extracting into: {output}')

Expand Down

0 comments on commit fc992da

Please sign in to comment.