HLP Format Guide
Purpose
This guide provides comprehensive documentation for working with Microsoft Windows Help (HLP) files using Cabriolet. HLP is Microsoft’s proprietary format for delivering help documentation in Windows 3.x and Windows 9x systems.
Concepts
What is an HLP File?
HLP files are Microsoft’s help system format used in older Windows and DOS systems. Cabriolet supports two distinct HLP formats:
- QuickHelp (DOS)
-
-
Used in DOS-based Microsoft development tools (QuickC, QuickBASIC, early Visual C++)
-
Signature:
0x4C 0x4E("LN") -
Uses Huffman coding and LZSS MODE_MSHELP compression
-
Topic-based organization with context strings
-
- Windows Help (WinHelp)
-
-
Used in Windows 3.x through Windows XP
-
Signature:
0x35F3(version 3.x) or0x5F3F/0x3F5F(version 4.x) -
Uses Zeck LZ77 compression (4KB sliding window)
-
Internal file system with
|SYSTEM,|TOPIC, and B-tree indexes
-
Common sources of HLP files:
-
Windows 3.x/9x application help
-
Legacy software documentation
-
Microsoft development tools
-
Technical manuals and guides
HLP File Structure
An HLP file contains multiple internal files organized hierarchically:
┌─────────────────────────┐
│ HLP Header │ File signature and metadata
├─────────────────────────┤
│ Internal File System │
│ ┌─────────────────┐ │
│ │ |SYSTEM │ │ System information
│ ├─────────────────┤ │
│ │ |TOPIC │ │ Topic content
│ ├─────────────────┤ │
│ │ |Phrases │ │ Phrase compression
│ ├─────────────────┤ │
│ │ |CONTEXT │ │ Context mappings
│ ├─────────────────┤ │
│ │ Other files │ │ Additional resources
│ └─────────────────┘ │
└─────────────────────────┘HLP Header: Contains file version and internal file directory.
Internal Files: Each HLP file contains multiple embedded files, each potentially compressed.
System Files: Control help behavior and appearance (starting with |).
Content Files: Topic text, graphics, and other resources.
Compression Support
HLP files use different compression depending on format:
- QuickHelp (DOS)
-
-
LZSS MODE_MSHELP - Specialized LZSS variant for help files
-
Huffman coding - Optional statistical encoding
-
Fixed 4KB sliding window optimized for help text
-
- Windows Help (WinHelp)
-
-
Zeck LZ77 - Custom LZ77 variant specific to WinHelp
-
4KB sliding window
-
Variable-length matches (3-271 bytes)
-
Flag-based token control (8 tokens per flag byte)
-
For detailed algorithm information, see LZSS MODE_MSHELP.
Automatic Format Detection
Cabriolet automatically detects the HLP format variant:
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
case header
when Cabriolet::Models::HLPHeader
puts "QuickHelp format (DOS)"
puts "Topics: #{header.topics.size}"
when Cabriolet::Models::WinHelpHeader
puts "Windows Help format"
puts "Version: #{header.version}" # :winhelp3 or :winhelp4
puts "Internal files: #{header.internal_files.size}"
endBasic Operations
Listing HLP Contents
View internal files without extracting:
cabriolet list help.hlp
# For explicit format specification:
cabriolet list --format hlp help.hlprequire 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
puts "HLP File: help.hlp"
puts "Version: #{header.version}"
puts "Files: #{header.files.size}"
puts ""
header.files.each do |file|
compression = file.compressed? ? "LZSS" : "none"
puts "#{file.filename}"
puts " Uncompressed: #{file.length} bytes"
puts " Compressed: #{file.compressed_length} bytes (#{compression})"
end
decompressor.close(header)HLP File Information
==================================================
Filename: help.hlp
Version: 3
Files: 8
Files:
|SYSTEM
Uncompressed: 2,048 bytes
Compressed: 1,024 bytes (LZSS)
|TOPIC
Uncompressed: 32,768 bytes
Compressed: 16,384 bytes (LZSS)
|Phrases
Uncompressed: 4,096 bytes
Compressed: 2,048 bytes (LZSS)Extracting HLP Files
Extract all internal files:
# Extract to current directory
cabriolet extract help.hlp
# Extract to specific directory
cabriolet extract help.hlp output/
# For explicit format specification:
cabriolet extract --format hlp help.hlp output/require 'cabriolet'
require 'fileutils'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
FileUtils.mkdir_p('output')
count = decompressor.extract_all(header, 'output')
decompressor.close(header)
puts "Extracted #{count} files to output/"Getting HLP Information
Display detailed HLP information:
cabriolet info help.hlp
# For explicit format specification:
cabriolet info --format hlp help.hlprequire 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
puts "Version: #{header.version}"
puts "Files: #{header.files.size}"
# Count compressed vs uncompressed
compressed = header.files.count { |f| f.compressed? }
uncompressed = header.files.size - compressed
puts "Compressed files: #{compressed}"
puts "Uncompressed files: #{uncompressed}"
decompressor.close(header)Creating HLP Files
Build new HLP files from source files:
# Create with compression (default)
cabriolet create help.hlp system.dat topic.dat
# Create without compression
cabriolet create --no-compress help.hlp file1.dat file2.dat
# For explicit format specification:
cabriolet create --format hlp --no-compress help.hlp file1.dat file2.datrequire 'cabriolet'
compressor = Cabriolet::HLP::Compressor.new
# Add files with compression
compressor.add_file('system.dat', '|SYSTEM', compress: true)
compressor.add_file('topic.dat', '|TOPIC', compress: true)
compressor.add_file('phrases.dat', '|Phrases', compress: true)
# Add uncompressed file
compressor.add_file('context.dat', '|CONTEXT', compress: false)
bytes = compressor.generate('help.hlp')
puts "Created help.hlp (#{bytes} bytes)"Advanced Features
System Files
Work with HLP system files (starting with |):
require 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
# Find system files
system_files = header.files.select { |f| f.filename.start_with?('|') }
system_files.each do |file|
puts "System file: #{file.filename}"
puts " Size: #{file.length} bytes"
puts " Compressed: #{file.compressed?}"
end
decompressor.close(header)Common system files include:
-
|SYSTEM- Help system configuration -
|TOPIC- Topic content and text -
|Phrases- Phrase compression dictionary -
|CONTEXT- Context ID mappings -
|FONT- Font table -
|CTXOMAP- Context offset map
Compression Control
Control which files are compressed:
require 'cabriolet'
compressor = Cabriolet::HLP::Compressor.new
# Compress text files for better ratio
compressor.add_file('topics.dat', '|TOPIC', compress: true)
compressor.add_file('phrases.dat', '|Phrases', compress: true)
# Don't compress already-compressed or small files
compressor.add_file('image.bmp', 'image.bmp', compress: false)
compressor.add_file('tiny.dat', 'tiny.dat', compress: false)
compressor.generate('help.hlp')File Metadata
Access internal file metadata:
require 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
header.files.each do |file|
puts "#{file.filename}:"
puts " Uncompressed size: #{file.length}"
puts " Compressed size: #{file.compressed_length}"
if file.compressed?
ratio = (1 - file.compressed_length.to_f / file.length) * 100
puts " Compression ratio: #{ratio.round(1)}%"
end
end
decompressor.close(header)Version Detection
Detect HLP file version:
require 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
case header.version
when 3
puts "Windows 3.x Help format"
when 4
puts "Windows 95 Help format"
else
puts "Unknown HLP version: #{header.version}"
end
decompressor.close(header)Performance Optimization
Selective Compression
Compress only files that benefit:
| File Type | Compress? |
|---|---|
Text content (` | TOPIC`, ` |
Phrases`) | Yes - good compression ratio |
System files (` | SYSTEM`, ` |
CONTEXT`) | Yes - moderate compression |
Graphics (BMP, icon) | No - already optimized |
Very small files (<256 bytes) | No - overhead too high |
Extraction Performance
Extract efficiently:
require 'cabriolet'
require 'fileutils'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
# Efficient: Extract all at once
count = decompressor.extract_all(header, 'output/')
# Less efficient: Extract one by one
# header.files.each do |file|
# decompressor.extract_file(file, "output/#{file.filename}")
# end
decompressor.close(header)Common Use Cases
Converting Legacy Help
Extract and convert old help files:
require 'cabriolet'
require 'fileutils'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('oldapp.hlp')
# Extract all content
FileUtils.mkdir_p('help-conversion')
decompressor.extract_all(header, 'help-conversion/')
# Process topic file
topic_file = header.files.find { |f| f.filename == '|TOPIC' }
if topic_file
puts "Found topics: #{topic_file.length} bytes"
# Convert to modern format...
end
decompressor.close(header)Analyzing Help Structure
Examine help file organization:
require 'cabriolet'
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
# Categorize files
system = header.files.select { |f| f.filename.start_with?('|') }
content = header.files.reject { |f| f.filename.start_with?('|') }
puts "System files: #{system.count}"
system.each { |f| puts " - #{f.filename}" }
puts "\nContent files: #{content.count}"
content.each { |f| puts " - #{f.filename}" }
# Calculate total sizes
total_uncompressed = header.files.sum { |f| f.length }
total_compressed = header.files.sum { |f| f.compressed_length }
puts "\nTotal uncompressed: #{total_uncompressed} bytes"
puts "Total compressed: #{total_compressed} bytes"
puts "Overall ratio: #{((1 - total_compressed.to_f / total_uncompressed) * 100).round(1)}%"
decompressor.close(header)Creating Custom Help
Build custom help files:
require 'cabriolet'
compressor = Cabriolet::HLP::Compressor.new
# Create minimal help structure
compressor.add_file('my-system.dat', '|SYSTEM', compress: true)
compressor.add_file('my-topics.dat', '|TOPIC', compress: true)
compressor.add_file('my-context.dat', '|CONTEXT', compress: true)
# Add custom content
compressor.add_file('custom.dat', 'CustomFile', compress: false)
bytes = compressor.generate('custom.hlp')
puts "Created custom help file: #{bytes} bytes"Troubleshooting
Common Errors
"Invalid HLP signature"
The file is not a valid HLP file:
# Check file type
file help.hlp
# Should identify as Windows Help file"Unsupported HLP version"
The HLP version is not supported:
cabriolet info help.hlp # Shows version"Decompression failed"
Internal file is corrupted:
# Try extracting uncompressed files only
cabriolet extract help.hlp output/
# Check which files failed"Cannot find internal file"
Specified internal file doesn’t exist. List files first:
cabriolet info help.hlpValidation
Verify HLP file integrity:
require 'cabriolet'
begin
decompressor = Cabriolet::HLP::Decompressor.new
header = decompressor.open('help.hlp')
puts "HLP file is valid"
puts "Version: #{header.version}"
puts "Files: #{header.files.size}"
# Verify all files can be read
header.files.each do |file|
if file.compressed? && file.compressed_length == 0
puts "Warning: #{file.filename} has zero compressed size"
end
end
decompressor.close(header)
rescue Cabriolet::FormatError => e
puts "Invalid HLP file: #{e.message}"
rescue Cabriolet::CorruptionError => e
puts "Corrupted HLP: #{e.message}"
endBest practices
-
Preserve system files: Keep all
|files for proper help functionality -
Compress text files: Always compress
|TOPIC,|Phrases, and text content -
Don’t compress graphics: Leave BMP and other images uncompressed
-
Verify version: Check HLP version for compatibility
-
Test extraction: Verify all files extract correctly
-
Document structure: Keep track of internal file organization
Format Specifications
File Signature
HLP files use a proprietary format without a clear magic number. The file starts with:
Offset Bytes Description
0x0000 4 File size (little-endian)
0x0004 2 Version (usually 3 or 4)
0x0006 ... Internal file directoryInternal File Structure
Each internal file has:
-
Filename: Internal name (may start with
|) -
Uncompressed size: Original file size
-
Compressed size: Size after compression (0 if uncompressed)
-
Offset: Position in HLP file
-
Compression flag: Whether file is compressed
Compression Method
-
Algorithm: LZSS MODE_MSHELP
-
Window size: 4096 bytes
-
Specialized: Optimized for help file text
For complete format specifications, see Format Specifications.
Next steps
-
Learn about LZSS MODE_MSHELP compression
-
Review extracting files
-
Study Ruby API