@@ -651,14 +651,21 @@ def readall(self) -> bytes:
651651class CompressedStream (AlignedStream ):
652652 """Create a stream from a file-like object which is compresssed. Decompressing it with the supplied decompressor.
653653
654+ This is useful for compressed file formats that store the compressed data in chunks like:
655+ * Windows Imaging (WIM) archives (LZXPRESS + Huffman, LZX)
656+ * Windows Overlay Filter (WOF) compressed files (LZXPRESS + Huffman, LZNT1, LZX) .
657+ * Hiberfil.sys files (LZXPRESS).
658+
654659 Args:
655660 fh: The source file-like object.
656661 offset: The offset in the source file-like object to start decompressing from.
657662 compressed_size: The size of the compressed data.
658663 original_size: The size of the decompressed data.
659664 decompressor: A function that decompresses a chunk of data.
660665 chunk_size: The size of each chunk in bytes. Default is 32 KiB.
666+ chunks: A tuple of offsets to each chunk of compressed data.
661667 """
668+
662669 def __init__ (
663670 self ,
664671 fh : BinaryIO ,
@@ -667,44 +674,33 @@ def __init__(
667674 original_size : int ,
668675 decompressor : Callable [[bytes ], bytes ],
669676 chunk_size : int = 32 * 1024 ,
677+ chunks : tuple [int , ...] | None = None ,
670678 ):
671679 self .fh = fh
672680 self .offset = offset
673681 self .compressed_size = compressed_size
674682 self .original_size = original_size
675683 self .decompressor = decompressor
676684 self .chunk_size = chunk_size
677-
678- # Read the chunk table in advance
679- if chunk_size :
680- fh .seek (self .offset )
681- num_chunks = (original_size + self .chunk_size - 1 ) // self .chunk_size - 1
682- if num_chunks == 0 :
683- self ._chunks = (0 ,)
684- else :
685- entry_size = "Q" if original_size > 0xFFFFFFFF else "I"
686- pattern = f"<{ num_chunks } { entry_size } "
687- self ._chunks = (0 , * struct .unpack (pattern , fh .read (struct .calcsize (pattern ))))
688-
689- self ._data_offset = fh .tell ()
685+ self .chunks = chunks or (offset ,)
690686
691687 self ._read_chunk = lru_cache (32 )(self ._read_chunk )
692688 super ().__init__ (self .original_size )
693689
694690 def _read (self , offset : int , length : int ) -> bytes :
695691 result = []
696692
697- num_chunks = len (self ._chunks )
693+ num_chunks = len (self .chunks )
698694 chunk , offset_in_chunk = divmod (offset , self .chunk_size )
699695
700696 while length :
701697 if chunk >= num_chunks :
702698 # We somehow requested more data than we have runs for
703699 break
704700
705- chunk_offset = self ._chunks [chunk ]
701+ chunk_offset = self .chunks [chunk ]
706702 if chunk < num_chunks - 1 :
707- next_chunk_offset = self ._chunks [chunk + 1 ]
703+ next_chunk_offset = self .chunks [chunk + 1 ]
708704 chunk_remaining = self .chunk_size - offset_in_chunk
709705 else :
710706 next_chunk_offset = self .compressed_size
@@ -722,6 +718,6 @@ def _read(self, offset: int, length: int) -> bytes:
722718 return b"" .join (result )
723719
724720 def _read_chunk (self , offset : int , size : int ) -> bytes :
725- self .fh .seek (self . _data_offset + offset )
721+ self .fh .seek (offset )
726722 buf = self .fh .read (size )
727723 return self .decompressor (buf )
0 commit comments