Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/remotezip #30

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading