Class: Cabriolet::LIT::Decompressor
- Inherits:
-
Object
- Object
- Cabriolet::LIT::Decompressor
- Defined in:
- lib/cabriolet/lit/decompressor.rb
Overview
Decompressor for Microsoft Reader LIT files
Handles complete LIT file extraction including:
-
Parsing complex LIT structure with Parser
-
DataSpace/Storage sections with transform layers
-
LZX decompression with ResetTable
-
Manifest-based filename restoration
-
Section caching for efficiency
Based on the openclit/SharpLit reference implementation.
NOTE: DES encryption (DRM) is not supported.
Constant Summary collapse
- DEFAULT_BUFFER_SIZE =
Default buffer size for decompression
8192
Instance Attribute Summary collapse
-
#buffer_size ⇒ Object
Returns the value of attribute buffer_size.
-
#io_system ⇒ Object
readonly
Returns the value of attribute io_system.
-
#parser ⇒ Object
readonly
Returns the value of attribute parser.
Instance Method Summary collapse
-
#close(_lit_file) ⇒ void
Close a LIT file (no-op for compatibility).
-
#extract(lit_file, file, output_path) ⇒ Integer
Extract a file from LIT archive (wrapper for extract_file).
-
#extract_all(lit_file, output_dir, use_manifest: true) ⇒ Integer
Extract all files from LIT archive.
-
#extract_file(lit_file, internal_name, output_path) ⇒ Integer
Extract a file by name from LIT archive.
-
#initialize(io_system = nil, algorithm_factory = nil) ⇒ Decompressor
constructor
A new instance of Decompressor.
-
#list_files(lit_file, use_manifest: true) ⇒ Array<Hash>
List all files in LIT archive.
-
#open(filename) ⇒ Models::LITFile
Open and parse a LIT file.
Constructor Details
#initialize(io_system = nil, algorithm_factory = nil) ⇒ Decompressor
29 30 31 32 33 34 35 |
# File 'lib/cabriolet/lit/decompressor.rb', line 29 def initialize(io_system = nil, algorithm_factory = nil) @io_system = io_system || System::IOSystem.new @algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory @parser = Parser.new(@io_system) @section_cache = {} @buffer_size = DEFAULT_BUFFER_SIZE end |
Instance Attribute Details
#buffer_size ⇒ Object
Returns the value of attribute buffer_size.
24 25 26 |
# File 'lib/cabriolet/lit/decompressor.rb', line 24 def buffer_size @buffer_size end |
#io_system ⇒ Object (readonly)
Returns the value of attribute io_system.
23 24 25 |
# File 'lib/cabriolet/lit/decompressor.rb', line 23 def io_system @io_system end |
#parser ⇒ Object (readonly)
Returns the value of attribute parser.
23 24 25 |
# File 'lib/cabriolet/lit/decompressor.rb', line 23 def parser @parser end |
Instance Method Details
#close(_lit_file) ⇒ void
This method returns an undefined value.
Close a LIT file (no-op for compatibility)
63 64 65 66 67 68 |
# File 'lib/cabriolet/lit/decompressor.rb', line 63 def close(_lit_file) # No resources to free in the file object itself # File handles are managed separately during extraction @section_cache.clear nil end |
#extract(lit_file, file, output_path) ⇒ Integer
Extract a file from LIT archive (wrapper for extract_file)
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/cabriolet/lit/decompressor.rb', line 79 def extract(lit_file, file, output_path) raise ArgumentError, "Header must not be nil" unless lit_file raise ArgumentError, "File must not be nil" unless file raise ArgumentError, "Output path must not be nil" unless output_path # Check for encryption if lit_file.encrypted? raise NotImplementedError, "Encrypted sections not yet supported. " \ "DRM level: #{lit_file.drm_level}" end # Use extract_file with file name internal_name = file.respond_to?(:name) ? file.name : file.to_s extract_file(lit_file, internal_name, output_path) end |
#extract_all(lit_file, output_dir, use_manifest: true) ⇒ Integer
Extract all files from LIT archive
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/cabriolet/lit/decompressor.rb', line 145 def extract_all(lit_file, output_dir, use_manifest: true) raise ArgumentError, "Header must not be nil" unless lit_file unless output_dir raise ArgumentError, "Output directory must not be nil" end ::FileUtils.mkdir_p(output_dir) extracted = 0 # Extract each directory entry lit_file.directory.entries.each do |entry| # Skip root entry and directories (ending with /) next if entry.root? || entry.name.end_with?("/") # Determine output filename if use_manifest && lit_file.manifest mapping = lit_file.manifest.find_by_internal(entry.name) filename = mapping ? mapping.original_name : entry.name else filename = entry.name end # Sanitize filename and convert path separators # Replace :: prefix and convert / to proper path separator filename = sanitize_path(filename) # Create output path (join with output_dir) output_path = ::File.join(output_dir, filename) # Create subdirectories if needed file_dir = ::File.dirname(output_path) ::FileUtils.mkdir_p(file_dir) unless ::File.directory?(file_dir) # Extract file extract_file(lit_file, entry.name, output_path) extracted += 1 end extracted end |
#extract_file(lit_file, internal_name, output_path) ⇒ Integer
Extract a file by name from LIT archive
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/lit/decompressor.rb', line 103 def extract_file(lit_file, internal_name, output_path) raise ArgumentError, "LIT file required" unless lit_file raise ArgumentError, "Internal name required" unless internal_name raise ArgumentError, "Output path required" unless output_path # Find directory entry entry = lit_file.directory.find(internal_name) unless entry raise Cabriolet::DecompressionError, "File not found: #{internal_name}" end # Get section data (cached or decompressed) section_data = get_section_data(lit_file, entry.section) # Extract file from section file_data = section_data[entry.offset, entry.size] # Check if extraction was successful unless file_data raise Cabriolet::DecompressionError, "Failed to extract file #{entry.name}: " \ "offset=#{entry.offset}, size=#{entry.size}, section_data_size=#{section_data&.bytesize || 0}" end # Write to output output_handle = @io_system.open(output_path, Constants::MODE_WRITE) begin @io_system.write(output_handle, file_data) ensure @io_system.close(output_handle) end file_data.bytesize end |
#list_files(lit_file, use_manifest: true) ⇒ Array<Hash>
List all files in LIT archive
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/cabriolet/lit/decompressor.rb', line 194 def list_files(lit_file, use_manifest: true) raise ArgumentError, "LIT file required" unless lit_file lit_file.directory.entries.reject(&:root?).map do |entry| info = { internal_name: entry.name, section: entry.section, offset: entry.offset, size: entry.size, } if use_manifest && lit_file.manifest mapping = lit_file.manifest.find_by_internal(entry.name) if mapping info[:original_name] = mapping.original_name info[:content_type] = mapping.content_type end end info end end |
#open(filename) ⇒ Models::LITFile
Open and parse a LIT file
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/cabriolet/lit/decompressor.rb', line 43 def open(filename) lit_file = @parser.parse(filename) # Store filename for later extraction lit_file.instance_variable_set(:@filename, filename) # Check for DRM if lit_file.encrypted? raise NotImplementedError, "DES-encrypted LIT files not supported. " \ "DRM level: #{lit_file.drm_level}" end lit_file end |