-
-
Save CrackerCat/729d936ddb1e32adf5711aa9a9e2846c to your computer and use it in GitHub Desktop.
Fast Android Sparse Image unpacker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import sys | |
| import struct | |
| SPARSE_HEADER_SIZE = 28 | |
| SPARSE_HEADER_MAGIC = 0xED26FF3A | |
| SPARSE_HEADER_MAJOR_VER = 1 | |
| SPARSE_CHUNK_SIZE = 12 | |
| SPARSE_CHUNK_TYPE_RAW = 0xCAC1 | |
| SPARSE_CHUNK_TYPE_FILL = 0xCAC2 | |
| SPARSE_CHUNK_TYPE_DONT_CARE = 0xCAC3 | |
| SPARSE_CHUNK_TYPE_CRC32 = 0xCAC4 | |
| class SparseHeader: | |
| def __init__(self, buf): | |
| ( | |
| self.magic, | |
| self.major_version, | |
| self.minor_version, | |
| self.file_hdr_sz, | |
| self.chunk_hdr_sz, | |
| self.blk_sz, | |
| self.total_blks, | |
| self.total_chunks, | |
| self.image_checksum, | |
| ) = struct.unpack("<IHHHHIIII", buf) | |
| class SparseChunkHeader: | |
| def __init__(self, buf): | |
| (self.chunk_type, _, self.chunk_sz, self.total_sz) = struct.unpack("<HHII", buf) | |
| # Not perfect but it should suffice. https://stackoverflow.com/a/1094933 | |
| def sizeof_fmt(num, suffix="B"): | |
| for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"): | |
| if abs(num) < 1024.0: | |
| return f"{num:3.1f}{unit}{suffix}" | |
| num /= 1024.0 | |
| return f"{num:.1f}Yi{suffix}" | |
| def stream_sparse_zero(strm, size): | |
| strm.seek(size - 1, 1) | |
| strm.write(b"\x00") | |
| # Default chunk_size is 4MiB | |
| def stream_read_chunked(strm, size, chunk_size=0x400000): | |
| while size > 0: | |
| read_size = min(chunk_size, size) | |
| data = strm.read(read_size) | |
| if not data: | |
| break | |
| yield data | |
| size -= len(data) | |
| def main(argv): | |
| in_path = argv[1] | |
| out_path = in_path + ".raw" | |
| if len(argv) > 2: | |
| out_path = argv[2] | |
| with open(in_path, "rb") as in_file: | |
| file_header = SparseHeader(in_file.read(SPARSE_HEADER_SIZE)) | |
| if file_header.magic != SPARSE_HEADER_MAGIC: | |
| raise Exception("Not an Android sparse image") | |
| if file_header.major_version > SPARSE_HEADER_MAJOR_VER: | |
| raise Exception("File major version mismatch") | |
| if file_header.blk_sz % 4 != 0: | |
| raise Exception("Block size is not a multiple of 4") | |
| print(f"Processing {file_header.total_chunks} chunks...") | |
| with open(out_path, "wb") as out_file: | |
| for _ in range(file_header.total_chunks): | |
| chunk_header = SparseChunkHeader(in_file.read(SPARSE_CHUNK_SIZE)) | |
| chunk_size_in_bytes = chunk_header.chunk_sz * file_header.blk_sz | |
| pretty_size = sizeof_fmt(chunk_size_in_bytes) | |
| if chunk_header.chunk_type == SPARSE_CHUNK_TYPE_RAW: | |
| print(f"[Chunk] raw | size={pretty_size}") | |
| for data_chunk in stream_read_chunked(in_file, chunk_size_in_bytes): | |
| out_file.write(data_chunk) | |
| elif chunk_header.chunk_type == SPARSE_CHUNK_TYPE_FILL: | |
| fill_value = in_file.read(4) | |
| print(f"[Chunk] fill | value={fill_value} | size={pretty_size}") | |
| if fill_value == b"\x00\x00\x00\x00": | |
| stream_sparse_zero(out_file, chunk_size_in_bytes) | |
| else: | |
| for _ in range(chunk_size_in_bytes // 4): | |
| out_file.write(fill_value) | |
| elif chunk_header.chunk_type == SPARSE_CHUNK_TYPE_DONT_CARE: | |
| print(f"[Chunk] dont_care | size={pretty_size}") | |
| stream_sparse_zero(out_file, chunk_size_in_bytes) | |
| elif chunk_header.chunk_type == SPARSE_CHUNK_TYPE_CRC32: | |
| crc32_value = int.from_bytes(in_file.read(4), "little") | |
| print(f"[Chunk] crc32 | value=0x{crc32_value:08X}") | |
| else: | |
| raise Exception("Invalid chunk type") | |
| print(f'Wrote result to "{out_path}"') | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2: | |
| print(f"Usage: {sys.argv[0]} input_file [output_file]") | |
| else: | |
| try: | |
| main(sys.argv) | |
| except (OSError, IOError) as e: | |
| print(f"[IO Error] {e.strerror}: {e.filename}") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"[Error] {e}") | |
| sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment