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 Dec 1, 2024
1 parent 2eb1d2a commit 439b66d
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 11 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 remotezip2 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
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ coloredlogs
cached_property
plumbum
pyimg4>=0.8.6
requests
requests
remotezip2

0 comments on commit 439b66d

Please sign in to comment.