Class: Cabriolet::HLP::Compressor

Inherits:
Object
  • Object
show all
Defined in:
lib/cabriolet/hlp/compressor.rb

Overview

Compressor creates HLP (Windows Help) compressed archives

HLP files contain an internal file system where files can be compressed using LZSS MODE_MSHELP compression. The compressor builds the archive structure and compresses files as needed.

NOTE: This implementation is based on the knowledge that HLP files use LZSS compression with MODE_MSHELP, but cannot be fully validated due to lack of test fixtures and incomplete libmspack implementation.

Constant Summary collapse

DEFAULT_BUFFER_SIZE =

Default buffer size for I/O operations

2048

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_system = nil) ⇒ Compressor

Initialize a new HLP compressor

Parameters:

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

    Custom I/O system or nil for default



24
25
26
27
# File 'lib/cabriolet/hlp/compressor.rb', line 24

def initialize(io_system = nil)
  @io_system = io_system || System::IOSystem.new
  @files = []
end

Instance Attribute Details

#io_systemObject (readonly)

Returns the value of attribute io_system.



15
16
17
# File 'lib/cabriolet/hlp/compressor.rb', line 15

def io_system
  @io_system
end

Instance Method Details

#add_data(data, hlp_path, compress: true) ⇒ void

This method returns an undefined value.

Add data from memory to the HLP archive

Parameters:

  • data (String)

    Data to add

  • hlp_path (String)

    Path within HLP archive

  • compress (Boolean) (defaults to: true)

    Whether to compress the data



49
50
51
52
53
54
55
# File 'lib/cabriolet/hlp/compressor.rb', line 49

def add_data(data, hlp_path, compress: true)
  @files << {
    data: data,
    hlp_path: hlp_path,
    compress: compress,
  }
end

#add_file(source_path, hlp_path, compress: true) ⇒ void

This method returns an undefined value.

Add a file to the HLP archive

Parameters:

  • source_path (String)

    Path to source file

  • hlp_path (String)

    Path within HLP archive

  • compress (Boolean) (defaults to: true)

    Whether to compress the file



35
36
37
38
39
40
41
# File 'lib/cabriolet/hlp/compressor.rb', line 35

def add_file(source_path, hlp_path, compress: true)
  @files << {
    source: source_path,
    hlp_path: hlp_path,
    compress: compress,
  }
end

#generate(output_file, **options) ⇒ Integer

Generate HLP archive

Parameters:

  • output_file (String)

    Path to output HLP file

  • options (Hash)

    Compression options

Options Hash (**options):

  • :version (Integer)

    HLP format version (default: 1)

Returns:

  • (Integer)

    Bytes written to output file

Raises:

  • (Errors::CompressionError)

    if compression fails



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
# File 'lib/cabriolet/hlp/compressor.rb', line 64

def generate(output_file, **options)
  version = options.fetch(:version, 1)

  output_handle = @io_system.open(output_file, Constants::MODE_WRITE)

  begin
    # Compress all files and collect metadata
    compressed_files = compress_all_files

    # Calculate directory size first
    directory_size = calculate_directory_size(compressed_files)

    # Calculate offsets
    header_size = 18 # Header structure size
    directory_offset = header_size
    data_offset = header_size + directory_size

    # Assign file offsets
    current_offset = data_offset
    compressed_files.each do |file_info|
      file_info[:offset] = current_offset
      current_offset += file_info[:compressed_data].bytesize
    end

    # Write header
    header_bytes = write_header(
      output_handle,
      version,
      compressed_files.size,
      directory_offset,
    )

    # Write directory
    directory_bytes = write_directory(output_handle, compressed_files)

    # Write file data
    data_bytes = write_file_data(output_handle, compressed_files)

    header_bytes + directory_bytes + data_bytes
  ensure
    @io_system.close(output_handle) if output_handle
  end
end