# v2 pbzx stream handler
# My personal writeup on the differences here: https://gist.github.com/pudquick/29fcfe09c326a9b96cf5
#
# Pure python reimplementation of .cpio.xz content extraction from pbzx file payload originally here:
# http://www.tonymacx86.com/general-help/135458-pbzx-stream-parser.html
#
# Cleaned up C version (as the basis for my code) here, thanks to Pepijn Bruienne / @bruienne
# https://gist.github.com/bruienne/029494bbcfb358098b41

import struct
import sys


def seekread(f, offset = None, length = 0, relative = True):
    if offset is not None:
        # offset provided, let's seek
        f.seek(offset, [0, 1, 2][relative])
    if length:
        return f.read(length)
    return None


def parse_pbzx(pbzx_path):
    section = 0
    xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
    with open(pbzx_path, 'rb') as f:
        # pbzx = f.read()
        # f.close()
        magic = seekread(f, length = 4)
        if magic != 'pbzx':
            raise RuntimeError("Error: Not a pbzx file")
        # Read 8 bytes for initial flags
        flags = seekread(f, length = 8)
        # Interpret the flags as a 64-bit big-endian unsigned int
        flags = struct.unpack('>Q', flags)[0]
        while flags & (1 << 24):
            with open(xar_out_path, 'wb') as xar_f:
                xar_f.seek(0, os.SEEK_END)
                # Read in more flags
                flags = seekread(f, length = 8)
                flags = struct.unpack('>Q', flags)[0]
                # Read in length
                f_length = seekread(f, length = 8)
                f_length = struct.unpack('>Q', f_length)[0]
                xzmagic = seekread(f, length = 6)
                if xzmagic != '\xfd7zXZ\x00':
                    # This isn't xz content, this is actually _raw decompressed cpio_ chunk of 16MB in size...
                    # Let's back up ...
                    seekread(f, offset = -6, length = 0)
                    # ... and split it out ...
                    f_content = seekread(f, length = f_length)
                    section += 1
                    decomp_out = '%s.part%02d.cpio' % (pbzx_path, section)
                    with open(decomp_out, 'wb') as g:
                        g.write(f_content)
                    # Now to start the next section, which should hopefully be .xz (we'll just assume it is ...)
                    section += 1
                    xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
                else:
                    f_length -= 6
                    # This part needs buffering
                    f_content = seekread(f, length = f_length)
                    tail = seekread(f, offset = -2, length = 2)
                    xar_f.write(xzmagic)
                    xar_f.write(f_content)
                    if tail != 'YZ':
                        raise RuntimeError("Error: Footer is not xar file footer")


def main():
    parse_pbzx(sys.argv[1])
    print("Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file")


if __name__ == '__main__':
    main()
