mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-23 19:23:37 +00:00
333 lines
16 KiB
Python
333 lines
16 KiB
Python
"""LZX data stream parser.
|
|
|
|
Also includes a decompression function (slow!!) which can decompress
|
|
LZX data stored in a Hachoir stream.
|
|
|
|
Author: Robert Xiao
|
|
Creation date: July 18, 2007
|
|
"""
|
|
from hachoir.parser import Parser
|
|
from hachoir.field import (FieldSet,
|
|
UInt32, Bit, Bits, PaddingBits,
|
|
RawBytes, ParserError)
|
|
from hachoir.core.endian import MIDDLE_ENDIAN, LITTLE_ENDIAN
|
|
from hachoir.core.tools import paddingSize
|
|
from hachoir.parser.archive.zlib import build_tree, HuffmanCode, extend_data
|
|
import struct
|
|
|
|
|
|
class LZXPreTreeEncodedTree(FieldSet):
|
|
|
|
def __init__(self, parent, name, num_elements, *args, **kwargs):
|
|
FieldSet.__init__(self, parent, name, *args, **kwargs)
|
|
self.num_elements = num_elements
|
|
|
|
def createFields(self):
|
|
for i in range(20):
|
|
yield Bits(self, "pretree_lengths[]", 4)
|
|
pre_tree = build_tree(
|
|
[self['pretree_lengths[%d]' % x].value for x in range(20)])
|
|
if not hasattr(self.root, "lzx_tree_lengths_" + self.name):
|
|
self.lengths = [0] * self.num_elements
|
|
setattr(self.root, "lzx_tree_lengths_" + self.name, self.lengths)
|
|
else:
|
|
self.lengths = getattr(self.root, "lzx_tree_lengths_" + self.name)
|
|
i = 0
|
|
while i < self.num_elements:
|
|
field = HuffmanCode(self, "tree_code[]", pre_tree)
|
|
if field.realvalue <= 16:
|
|
self.lengths[i] = (self.lengths[i] - field.realvalue) % 17
|
|
field._description = "Literal tree delta length %i (new length value %i for element %i)" % (
|
|
field.realvalue, self.lengths[i], i)
|
|
i += 1
|
|
yield field
|
|
elif field.realvalue == 17:
|
|
field._description = "Tree Code 17: Zeros for 4-19 elements"
|
|
yield field
|
|
extra = Bits(self, "extra[]", 4)
|
|
zeros = 4 + extra.value
|
|
extra._description = "Extra bits: zeros for %i elements (elements %i through %i)" % (
|
|
zeros, i, i + zeros - 1)
|
|
yield extra
|
|
self.lengths[i:i + zeros] = [0] * zeros
|
|
i += zeros
|
|
elif field.realvalue == 18:
|
|
field._description = "Tree Code 18: Zeros for 20-51 elements"
|
|
yield field
|
|
extra = Bits(self, "extra[]", 5)
|
|
zeros = 20 + extra.value
|
|
extra._description = "Extra bits: zeros for %i elements (elements %i through %i)" % (
|
|
zeros, i, i + zeros - 1)
|
|
yield extra
|
|
self.lengths[i:i + zeros] = [0] * zeros
|
|
i += zeros
|
|
elif field.realvalue == 19:
|
|
field._description = "Tree Code 19: Same code for 4-5 elements"
|
|
yield field
|
|
extra = Bits(self, "extra[]", 1)
|
|
run = 4 + extra.value
|
|
extra._description = "Extra bits: run for %i elements (elements %i through %i)" % (
|
|
run, i, i + run - 1)
|
|
yield extra
|
|
newfield = HuffmanCode(self, "tree_code[]", pre_tree)
|
|
assert newfield.realvalue <= 16
|
|
newfield._description = "Literal tree delta length %i (new length value %i for elements %i through %i)" % (
|
|
newfield.realvalue, self.lengths[i], i, i + run - 1)
|
|
self.lengths[
|
|
i:i + run] = [(self.lengths[i] - newfield.realvalue) % 17] * run
|
|
i += run
|
|
yield newfield
|
|
|
|
|
|
class LZXBlock(FieldSet):
|
|
WINDOW_SIZE = {15: 30,
|
|
16: 32,
|
|
17: 34,
|
|
18: 36,
|
|
19: 38,
|
|
20: 42,
|
|
21: 50}
|
|
POSITION_SLOTS = {0: (0, 0, 0),
|
|
1: (1, 1, 0),
|
|
2: (2, 2, 0),
|
|
3: (3, 3, 0),
|
|
4: (4, 5, 1),
|
|
5: (6, 7, 1),
|
|
6: (8, 11, 2),
|
|
7: (12, 15, 2),
|
|
8: (16, 23, 3),
|
|
9: (24, 31, 3),
|
|
10: (32, 47, 4),
|
|
11: (48, 63, 4),
|
|
12: (64, 95, 5),
|
|
13: (96, 127, 5),
|
|
14: (128, 191, 6),
|
|
15: (192, 255, 6),
|
|
16: (256, 383, 7),
|
|
17: (384, 511, 7),
|
|
18: (512, 767, 8),
|
|
19: (768, 1023, 8),
|
|
20: (1024, 1535, 9),
|
|
21: (1536, 2047, 9),
|
|
22: (2048, 3071, 10),
|
|
23: (3072, 4095, 10),
|
|
24: (4096, 6143, 11),
|
|
25: (6144, 8191, 11),
|
|
26: (8192, 12287, 12),
|
|
27: (12288, 16383, 12),
|
|
28: (16384, 24575, 13),
|
|
29: (24576, 32767, 13),
|
|
30: (32768, 49151, 14),
|
|
31: (49152, 65535, 14),
|
|
32: (65536, 98303, 15),
|
|
33: (98304, 131071, 15),
|
|
34: (131072, 196607, 16),
|
|
35: (196608, 262143, 16),
|
|
36: (262144, 393215, 17),
|
|
37: (393216, 524287, 17),
|
|
38: (524288, 655359, 17),
|
|
39: (655360, 786431, 17),
|
|
40: (786432, 917503, 17),
|
|
41: (917504, 1048575, 17),
|
|
42: (1048576, 1179647, 17),
|
|
43: (1179648, 1310719, 17),
|
|
44: (1310720, 1441791, 17),
|
|
45: (1441792, 1572863, 17),
|
|
46: (1572864, 1703935, 17),
|
|
47: (1703936, 1835007, 17),
|
|
48: (1835008, 1966079, 17),
|
|
49: (1966080, 2097151, 17),
|
|
}
|
|
|
|
def createFields(self):
|
|
yield Bits(self, "block_type", 3)
|
|
yield Bits(self, "block_size", 24)
|
|
self.uncompressed_size = self["block_size"].value
|
|
self.compression_level = self.root.compr_level
|
|
self.window_size = self.WINDOW_SIZE[self.compression_level]
|
|
self.block_type = self["block_type"].value
|
|
curlen = len(self.parent.uncompressed_data)
|
|
intel_started = False # Do we perform Intel jump fixups on this block?
|
|
|
|
if self.block_type in (1, 2): # Verbatim or aligned offset block
|
|
if self.block_type == 2:
|
|
for i in range(8):
|
|
yield Bits(self, "aligned_len[]", 3)
|
|
aligned_tree = build_tree(
|
|
[self['aligned_len[%d]' % i].value for i in range(8)])
|
|
yield LZXPreTreeEncodedTree(self, "main_tree_start", 256)
|
|
yield LZXPreTreeEncodedTree(self, "main_tree_rest", self.window_size * 8)
|
|
main_tree = build_tree(
|
|
self["main_tree_start"].lengths + self["main_tree_rest"].lengths)
|
|
if self["main_tree_start"].lengths[0xE8]:
|
|
intel_started = True
|
|
yield LZXPreTreeEncodedTree(self, "length_tree", 249)
|
|
length_tree = build_tree(self["length_tree"].lengths)
|
|
current_decoded_size = 0
|
|
while current_decoded_size < self.uncompressed_size:
|
|
if (curlen + current_decoded_size) % 32768 == 0 and (curlen + current_decoded_size) != 0:
|
|
padding = paddingSize(self.address + self.current_size, 16)
|
|
if padding:
|
|
yield PaddingBits(self, "padding[]", padding)
|
|
field = HuffmanCode(self, "main_code[]", main_tree)
|
|
if field.realvalue < 256:
|
|
field._description = "Literal value %r" % chr(
|
|
field.realvalue)
|
|
current_decoded_size += 1
|
|
self.parent._lzx_window.append(field.realvalue)
|
|
yield field
|
|
continue
|
|
position_header, length_header = divmod(
|
|
field.realvalue - 256, 8)
|
|
info = self.POSITION_SLOTS[position_header]
|
|
if info[2] == 0:
|
|
if info[0] == 0:
|
|
position = self.parent.r0
|
|
field._description = "Position Slot %i, Position [R0] (%i)" % (
|
|
position_header, position)
|
|
elif info[0] == 1:
|
|
position = self.parent.r1
|
|
self.parent.r1 = self.parent.r0
|
|
self.parent.r0 = position
|
|
field._description = "Position Slot %i, Position [R1] (%i)" % (
|
|
position_header, position)
|
|
elif info[0] == 2:
|
|
position = self.parent.r2
|
|
self.parent.r2 = self.parent.r0
|
|
self.parent.r0 = position
|
|
field._description = "Position Slot %i, Position [R2] (%i)" % (
|
|
position_header, position)
|
|
else:
|
|
position = info[0] - 2
|
|
self.parent.r2 = self.parent.r1
|
|
self.parent.r1 = self.parent.r0
|
|
self.parent.r0 = position
|
|
field._description = "Position Slot %i, Position %i" % (
|
|
position_header, position)
|
|
else:
|
|
field._description = "Position Slot %i, Positions %i to %i" % (
|
|
position_header, info[0] - 2, info[1] - 2)
|
|
if length_header == 7:
|
|
field._description += ", Length Values 9 and up"
|
|
yield field
|
|
length_field = HuffmanCode(
|
|
self, "length_code[]", length_tree)
|
|
length = length_field.realvalue + 9
|
|
length_field._description = "Length Code %i, total length %i" % (
|
|
length_field.realvalue, length)
|
|
yield length_field
|
|
else:
|
|
field._description += ", Length Value %i (Huffman Code %i)" % (
|
|
length_header + 2, field.value)
|
|
yield field
|
|
length = length_header + 2
|
|
if info[2]:
|
|
if self.block_type == 1 or info[2] < 3: # verbatim
|
|
extrafield = Bits(
|
|
self, "position_extra[%s" % field.name.split('[')[1], info[2])
|
|
position = extrafield.value + info[0] - 2
|
|
extrafield._description = "Position Extra Bits (%i), total position %i" % (
|
|
extrafield.value, position)
|
|
yield extrafield
|
|
else: # aligned offset
|
|
position = info[0] - 2
|
|
if info[2] > 3:
|
|
extrafield = Bits(
|
|
self, "position_verbatim[%s" % field.name.split('[')[1], info[2] - 3)
|
|
position += extrafield.value * 8
|
|
extrafield._description = "Position Verbatim Bits (%i), added position %i" % (
|
|
extrafield.value, extrafield.value * 8)
|
|
yield extrafield
|
|
if info[2] >= 3:
|
|
extrafield = HuffmanCode(
|
|
self, "position_aligned[%s" % field.name.split('[')[1], aligned_tree)
|
|
position += extrafield.realvalue
|
|
extrafield._description = "Position Aligned Bits (%i), total position %i" % (
|
|
extrafield.realvalue, position)
|
|
yield extrafield
|
|
self.parent.r2 = self.parent.r1
|
|
self.parent.r1 = self.parent.r0
|
|
self.parent.r0 = position
|
|
extend_data(self.parent._lzx_window, length, position)
|
|
current_decoded_size += length
|
|
elif self.block_type == 3: # Uncompressed block
|
|
padding = paddingSize(self.address + self.current_size, 16)
|
|
if padding:
|
|
yield PaddingBits(self, "padding[]", padding)
|
|
else:
|
|
yield PaddingBits(self, "padding[]", 16)
|
|
self.endian = LITTLE_ENDIAN
|
|
intel_started = True # apparently intel fixup may be needed on uncompressed blocks?
|
|
yield UInt32(self, "r[]", "New value of R0")
|
|
yield UInt32(self, "r[]", "New value of R1")
|
|
yield UInt32(self, "r[]", "New value of R2")
|
|
self.parent.r0 = self["r[0]"].value
|
|
self.parent.r1 = self["r[1]"].value
|
|
self.parent.r2 = self["r[2]"].value
|
|
yield RawBytes(self, "data", self.uncompressed_size)
|
|
self.parent._lzx_window += self["data"].value
|
|
if self["block_size"].value % 2:
|
|
yield PaddingBits(self, "padding", 8)
|
|
else:
|
|
raise ParserError("Unknown block type %d!" % self.block_type)
|
|
|
|
# Fixup Intel jumps if necessary (fixups are only applied to the final output, not to the LZX window)
|
|
self.parent.uncompressed_data += self.parent._lzx_window[-self.uncompressed_size:]
|
|
self.parent._lzx_window = self.parent._lzx_window[-(1 << self.root.compr_level):]
|
|
|
|
if (
|
|
intel_started
|
|
and self.parent["filesize_indicator"].value
|
|
and self.parent["filesize"].value > 0
|
|
):
|
|
# Note that we're decoding a block-at-a-time instead of a frame-at-a-time,
|
|
# so we need to handle the frame boundaries carefully.
|
|
filesize = self.parent["filesize"].value
|
|
start_pos = max(0, curlen - 10) # We may need to correct something from the last block
|
|
end_pos = len(self.parent.uncompressed_data) - 10
|
|
while 1:
|
|
jmp_pos = self.parent.uncompressed_data.find(b"\xE8", start_pos, end_pos)
|
|
if jmp_pos == -1:
|
|
break
|
|
if (jmp_pos % 32768) >= (32768 - 10):
|
|
# jumps at the end of frames are not fixed up
|
|
start_pos = jmp_pos + 1
|
|
continue
|
|
abs_off, = struct.unpack("<i", self.parent.uncompressed_data[jmp_pos + 1:jmp_pos + 5])
|
|
if -jmp_pos <= abs_off < filesize:
|
|
if abs_off < 0:
|
|
rel_off = abs_off + filesize
|
|
else:
|
|
rel_off = abs_off - jmp_pos
|
|
self.parent.uncompressed_data[jmp_pos + 1:jmp_pos + 5] = struct.pack("<i", rel_off)
|
|
start_pos = jmp_pos + 5
|
|
|
|
|
|
class LZXStream(Parser):
|
|
endian = MIDDLE_ENDIAN
|
|
|
|
def createFields(self):
|
|
self.uncompressed_data = bytearray()
|
|
self._lzx_window = bytearray()
|
|
self.r0 = 1
|
|
self.r1 = 1
|
|
self.r2 = 1
|
|
yield Bit(self, "filesize_indicator")
|
|
if self["filesize_indicator"].value:
|
|
yield UInt32(self, "filesize")
|
|
while self.current_size < self.size:
|
|
block = LZXBlock(self, "block[]")
|
|
yield block
|
|
if self.size - self.current_size < 16:
|
|
padding = paddingSize(self.address + self.current_size, 16)
|
|
if padding:
|
|
yield PaddingBits(self, "padding[]", padding)
|
|
break
|
|
|
|
|
|
def lzx_decompress(stream, window_bits):
|
|
data = LZXStream(stream)
|
|
data.compr_level = window_bits
|
|
for _ in data:
|
|
pass
|
|
return data.uncompressed_data
|