Skip to content

Commit

Permalink
Merge pull request #30 from doronz88/feature/remotezip
Browse files Browse the repository at this point in the history
Feature/remotezip
  • Loading branch information
doronz88 authored Dec 1, 2024
2 parents 58a369d + 439b66d commit 4bdb0e6
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 14 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# ipsw-parser

[![Python application](https://github.com/doronz88/ipsw_parser/workflows/Python%20application/badge.svg)](https://github.com/doronz88/ipsw_parser/actions/workflows/python-app.yml "Python application action")
[![Pypi version](https://img.shields.io/pypi/v/ipsw_parser.svg)](https://pypi.org/project/ipsw_parser/ "PyPi package")
[![Downloads](https://static.pepy.tech/personalized-badge/ipsw_parser?period=total&units=none&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/ipsw_parser)

# Overview
## Overview

python3 utility for parsing and extracting data from IPSW.

# Installation
## Installation

```shell
python3 -m pip install ipsw-parser
Expand All @@ -15,7 +17,7 @@ python3 -m pip install ipsw-parser
Additionally, if you installed [blacktop/ipsw](https://github.com/blacktop/ipsw), the IPSW extraction will also contain
the split DSC.

# Usage
## Usage

```
Usage: ipsw-parser [OPTIONS] COMMAND [ARGS]...
Expand Down
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 4bdb0e6

Please sign in to comment.