Class: Cabriolet::Decompressors::LZSS

Inherits:
Base
  • Object
show all
Defined in:
lib/cabriolet/decompressors/lzss.rb

Overview

LZSS decompressor for LZSS-compressed CAB data

LZSS (Lempel-Ziv-Storer-Szymanski) is a derivative of LZ77 compression. It uses a 4096-byte sliding window with a control byte mechanism to indicate whether the next operation is a literal byte copy or a match from the window history.

Constant Summary collapse

WINDOW_SIZE =

LZSS algorithm constants

4096
WINDOW_FILL =
0x20
MODE_EXPAND =

LZSS modes

0
MODE_MSHELP =
1
MODE_QBASIC =
2

Instance Attribute Summary collapse

Attributes inherited from Base

#buffer_size, #input, #io_system, #output

Instance Method Summary collapse

Methods inherited from Base

#free

Constructor Details

#initialize(io_system, input, output, buffer_size, mode = MODE_EXPAND) ⇒ LZSS

Initialize LZSS decompressor

Parameters:



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/cabriolet/decompressors/lzss.rb', line 30

def initialize(io_system, input, output, buffer_size,
               mode = MODE_EXPAND)
  super(io_system, input, output, buffer_size)
  @mode = mode
  @window = (WINDOW_FILL.chr * WINDOW_SIZE).b
  @window_pos = initialize_window_position
  @input_buffer = ""
  @input_pos = 0
  @invert = mode == MODE_MSHELP ? 0xFF : 0x00
  @output_buffer = String.new(encoding: Encoding::BINARY, capacity: 4096)
end

Instance Attribute Details

#modeObject (readonly)

Returns the value of attribute mode.



21
22
23
# File 'lib/cabriolet/decompressors/lzss.rb', line 21

def mode
  @mode
end

#windowObject (readonly)

Returns the value of attribute window.



21
22
23
# File 'lib/cabriolet/decompressors/lzss.rb', line 21

def window
  @window
end

#window_posObject (readonly)

Returns the value of attribute window_pos.



21
22
23
# File 'lib/cabriolet/decompressors/lzss.rb', line 21

def window_pos
  @window_pos
end

Instance Method Details

#decompress(bytes = nil) ⇒ Integer

Decompress LZSS data

Parameters:

  • bytes (Integer, nil) (defaults to: nil)

    Maximum number of output bytes to write (nil or 0 = until EOF)

Returns:

  • (Integer)

    Number of bytes decompressed



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/cabriolet/decompressors/lzss.rb', line 46

def decompress(bytes = nil)
  bytes_written = 0
  # Only enforce limit if bytes is a positive integer
  enforce_limit = bytes&.positive?

  loop do
    # Check if we've reached the output byte limit (only when limit is enforced)
    break if enforce_limit && bytes_written >= bytes

    # Read control byte
    control_byte = read_input_byte
    break if control_byte.nil?

    control_byte ^= @invert

    # Process each bit in the control byte
    8.times do |bit_index|
      # Check output limit before each operation (only when limit is enforced)
      break if enforce_limit && bytes_written >= bytes

      mask = 1 << bit_index

      if control_byte.anybits?(mask)
        # Bit is 1: literal byte
        literal = read_input_byte
        break if literal.nil?

        @window.setbyte(@window_pos, literal)
        buffer_output_byte(literal)
        bytes_written += 1

        @window_pos = (@window_pos + 1) & (WINDOW_SIZE - 1)
      else
        # Bit is 0: match from window
        offset_low = read_input_byte
        break if offset_low.nil?

        offset_high_and_length = read_input_byte
        break if offset_high_and_length.nil?

        # Decode match position and length
        match_pos = offset_low | ((offset_high_and_length & 0xF0) << 4)
        length = (offset_high_and_length & 0x0F) + 3

        # Copy from window
        length.times do
          # Check if we've reached the limit mid-match
          break if enforce_limit && bytes_written >= bytes

          byte = @window.getbyte(match_pos)
          @window.setbyte(@window_pos, byte)
          buffer_output_byte(byte)
          bytes_written += 1

          @window_pos = (@window_pos + 1) & (WINDOW_SIZE - 1)
          match_pos = (match_pos + 1) & (WINDOW_SIZE - 1)
        end
      end
    end
  end

  flush_output_buffer
  bytes_written
end