Class: Cabriolet::SZDD::Decompressor

Inherits:
Object
  • Object
show all
Defined in:
lib/cabriolet/szdd/decompressor.rb

Overview

Decompressor is the main interface for SZDD file operations

SZDD files use LZSS compression and are decompressed using the Decompressors::LZSS class with appropriate mode settings.

Constant Summary collapse

DEFAULT_BUFFER_SIZE =

Input buffer size for decompression

2048

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_system = nil) ⇒ Decompressor

Initialize a new SZDD decompressor

Parameters:

  • io_system (System::IOSystem, nil) (defaults to: nil)

    Custom I/O system or nil for default



20
21
22
23
24
# File 'lib/cabriolet/szdd/decompressor.rb', line 20

def initialize(io_system = nil)
  @io_system = io_system || System::IOSystem.new
  @parser = Parser.new(@io_system)
  @buffer_size = DEFAULT_BUFFER_SIZE
end

Instance Attribute Details

#buffer_sizeObject

Returns the value of attribute buffer_size.



11
12
13
# File 'lib/cabriolet/szdd/decompressor.rb', line 11

def buffer_size
  @buffer_size
end

#io_systemObject (readonly)

Returns the value of attribute io_system.



10
11
12
# File 'lib/cabriolet/szdd/decompressor.rb', line 10

def io_system
  @io_system
end

#parserObject (readonly)

Returns the value of attribute parser.



10
11
12
# File 'lib/cabriolet/szdd/decompressor.rb', line 10

def parser
  @parser
end

Instance Method Details

#auto_output_filename(input_path, header) ⇒ String

Generate output filename from input filename and header

Parameters:

Returns:

  • (String)

    Suggested output filename



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/cabriolet/szdd/decompressor.rb', line 171

def auto_output_filename(input_path, header)
  # Get base filename without directory
  base = ::File.basename(input_path)

  # Use header's suggested filename method
  suggested = header.suggested_filename(base)

  # Combine with original directory
  dir = ::File.dirname(input_path)
  ::File.join(dir, suggested)
end

#close(_header) ⇒ void

This method returns an undefined value.

Close an SZDD file (no-op for compatibility)

Parameters:



41
42
43
44
45
# File 'lib/cabriolet/szdd/decompressor.rb', line 41

def close(_header)
  # No resources to free in the header itself
  # File handles are managed separately during extraction
  nil
end

#decompress(input_path, output_path = nil) ⇒ Integer

One-shot decompression from input file to output file

This method combines open(), extract(), and close() for convenience. Similar to MS-DOS EXPAND.EXE behavior.

Parameters:

  • input_path (String)

    Path to compressed SZDD file

  • output_path (String, nil) (defaults to: nil)

    Path to output file, or nil to auto-detect

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (Errors::ParseError)

    if input is not valid SZDD

  • (Errors::DecompressionError)

    if decompression fails



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/cabriolet/szdd/decompressor.rb', line 150

def decompress(input_path, output_path = nil)
  # Parse header
  header = self.open(input_path)

  # Auto-detect output filename if not provided
  output_path ||= auto_output_filename(input_path, header)

  # Extract
  bytes_written = extract(header, output_path)

  # Close (no-op but kept for API consistency)
  close(header)

  bytes_written
end

#extract(header, output_path) ⇒ Integer

Extract an SZDD file to output

Parameters:

  • header (Models::SZDDHeader)

    SZDD header from open()

  • output_path (String)

    Where to write the decompressed file

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (Errors::DecompressionError)

    if decompression fails



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
# File 'lib/cabriolet/szdd/decompressor.rb', line 53

def extract(header, output_path)
  raise ArgumentError, "Header must not be nil" unless header
  raise ArgumentError, "Output path must not be nil" unless output_path

  input_handle = @io_system.open(header.filename, Constants::MODE_READ)
  output_handle = @io_system.open(output_path, Constants::MODE_WRITE)

  begin
    # Seek to compressed data start
    data_offset = @parser.data_offset(header.format)
    @io_system.seek(input_handle, data_offset, Constants::SEEK_START)

    # Determine LZSS mode based on format
    lzss_mode = if header.normal_format?
                  Decompressors::LZSS::MODE_EXPAND
                else
                  Decompressors::LZSS::MODE_QBASIC
                end

    # Create LZSS decompressor
    decompressor = Decompressors::LZSS.new(
      @io_system,
      input_handle,
      output_handle,
      @buffer_size,
      lzss_mode,
    )

    # Decompress
    bytes_written = decompressor.decompress(header.length)

    # Verify decompressed size matches expected
    if bytes_written != header.length && Cabriolet.verbose && Cabriolet.verbose
      warn "[Cabriolet] WARNING; decompressed #{bytes_written} bytes, " \
           "expected #{header.length} bytes"
    end

    bytes_written
  ensure
    @io_system.close(input_handle) if input_handle
    @io_system.close(output_handle) if output_handle
  end
end

#extract_to_memory(header) ⇒ String

Extract SZDD file to memory

Parameters:

Returns:

  • (String)

    Decompressed data

Raises:

  • (Errors::DecompressionError)

    if decompression fails



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/cabriolet/szdd/decompressor.rb', line 102

def extract_to_memory(header)
  raise ArgumentError, "Header must not be nil" unless header

  input_handle = @io_system.open(header.filename, Constants::MODE_READ)
  output_handle = System::MemoryHandle.new("", Constants::MODE_WRITE)

  begin
    # Seek to compressed data start
    data_offset = @parser.data_offset(header.format)
    @io_system.seek(input_handle, data_offset, Constants::SEEK_START)

    # Determine LZSS mode based on format
    lzss_mode = if header.normal_format?
                  Decompressors::LZSS::MODE_EXPAND
                else
                  Decompressors::LZSS::MODE_QBASIC
                end

    # Create LZSS decompressor
    decompressor = Decompressors::LZSS.new(
      @io_system,
      input_handle,
      output_handle,
      @buffer_size,
      lzss_mode,
    )

    # Decompress
    decompressor.decompress(header.length)

    # Return the decompressed data
    output_handle.data
  ensure
    @io_system.close(input_handle) if input_handle
  end
end

#open(filename) ⇒ Models::SZDDHeader

Open and parse an SZDD file

Parameters:

  • filename (String)

    Path to the SZDD file

Returns:

Raises:

  • (Errors::ParseError)

    if the file is not a valid SZDD



31
32
33
34
35
# File 'lib/cabriolet/szdd/decompressor.rb', line 31

def open(filename)
  header = @parser.parse(filename)
  header.filename = filename
  header
end