omnizip 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +754 -0
- data/COPYING +502 -0
- data/Gemfile +17 -0
- data/LICENSE +12 -0
- data/README.adoc +1045 -0
- data/Rakefile +12 -0
- data/benchmark/README.md +260 -0
- data/benchmark/benchmark_suite.rb +125 -0
- data/benchmark/compression_bench.rb +181 -0
- data/benchmark/filter_bench.rb +180 -0
- data/benchmark/models/benchmark_result.rb +59 -0
- data/benchmark/models/comparison_result.rb +69 -0
- data/benchmark/profile_suite.rb +167 -0
- data/benchmark/reporter.rb +150 -0
- data/benchmark/run_benchmarks.rb +66 -0
- data/benchmark/test_data.rb +137 -0
- data/config/formats/rar3_spec.yml +91 -0
- data/config/formats/rar5_spec.yml +102 -0
- data/docs/.github/workflows/docs.yml +142 -0
- data/docs/.gitignore +21 -0
- data/docs/.lychee.toml +67 -0
- data/docs/Gemfile +13 -0
- data/docs/RAR_WRITE_SUPPORT.md +26 -0
- data/docs/README.md +101 -0
- data/docs/_config.yml +112 -0
- data/docs/assets/logo.svg +1 -0
- data/docs/assets/omnizip-logo.pdf +1540 -11
- data/docs/comparison/feature-matrix.adoc +694 -0
- data/docs/comparison/index.adoc +113 -0
- data/docs/comparison/vs-7zip.adoc +309 -0
- data/docs/comparison/vs-peazip.adoc +77 -0
- data/docs/comparison/vs-rubyzip.adoc +342 -0
- data/docs/comparison/vs-winrar.adoc +100 -0
- data/docs/compatibility.adoc +579 -0
- data/docs/concepts/index.adoc +129 -0
- data/docs/developer/architecture.adoc +256 -0
- data/docs/developer/contributing.adoc +158 -0
- data/docs/developer/index.adoc +25 -0
- data/docs/developer/testing.adoc +212 -0
- data/docs/getting-started/basic-usage.adoc +271 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +185 -0
- data/docs/getting-started/your-first-archive.adoc +218 -0
- data/docs/guides/advanced-features/encryption.adoc +300 -0
- data/docs/guides/advanced-features/index.adoc +49 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
- data/docs/guides/advanced-features/streaming.adoc +212 -0
- data/docs/guides/archive-formats/gzip-format.adoc +107 -0
- data/docs/guides/archive-formats/index.adoc +130 -0
- data/docs/guides/archive-formats/rar-format.adoc +104 -0
- data/docs/guides/archive-formats/rar5.adoc +521 -0
- data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
- data/docs/guides/archive-formats/tar-format.adoc +106 -0
- data/docs/guides/archive-formats/xz-format.adoc +118 -0
- data/docs/guides/archive-formats/zip-format.adoc +35 -0
- data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
- data/docs/guides/compression-algorithms/deflate.adoc +319 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +398 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
- data/docs/guides/creating-archives.adoc +354 -0
- data/docs/guides/extracting-archives.adoc +53 -0
- data/docs/guides/format-conversion.adoc +64 -0
- data/docs/guides/index.adoc +49 -0
- data/docs/guides/migration-rubyzip.adoc +217 -0
- data/docs/guides/parity-archives.adoc +605 -0
- data/docs/guides/performance-tuning.adoc +88 -0
- data/docs/index.adoc +218 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +188 -0
- data/docs/reference/cli/compress-command.adoc +114 -0
- data/docs/reference/cli/overview.adoc +140 -0
- data/docs/reference/index.adoc +26 -0
- data/docs/resources/faq.adoc +185 -0
- data/docs/resources/quick-reference.adoc +222 -0
- data/docs/troubleshooting/index.adoc +208 -0
- data/examples/api_comparison.rb +205 -0
- data/examples/deflate64_example.rb +96 -0
- data/examples/par2_demo.rb +121 -0
- data/examples/quick_start_native.rb +150 -0
- data/examples/quick_start_rubyzip.rb +115 -0
- data/examples/rubyzip_compatibility_demo.rb +194 -0
- data/exe/omnizip +27 -0
- data/lib/omnizip/algorithm.rb +130 -0
- data/lib/omnizip/algorithm_registry.rb +86 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
- data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
- data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
- data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
- data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
- data/lib/omnizip/algorithms/bzip2.rb +130 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
- data/lib/omnizip/algorithms/deflate.rb +128 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
- data/lib/omnizip/algorithms/deflate64.rb +109 -0
- data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
- data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
- data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
- data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
- data/lib/omnizip/algorithms/lzma/match.rb +32 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
- data/lib/omnizip/algorithms/lzma.rb +238 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +141 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +116 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +82 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
- data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
- data/lib/omnizip/algorithms/zstandard.rb +138 -0
- data/lib/omnizip/buffer/memory_archive.rb +251 -0
- data/lib/omnizip/buffer/memory_extractor.rb +224 -0
- data/lib/omnizip/buffer.rb +176 -0
- data/lib/omnizip/checksum_registry.rb +114 -0
- data/lib/omnizip/checksums/crc32.rb +100 -0
- data/lib/omnizip/checksums/crc64.rb +101 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/verifier.rb +131 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +120 -0
- data/lib/omnizip/chunked.rb +129 -0
- data/lib/omnizip/cli/output_formatter.rb +104 -0
- data/lib/omnizip/cli.rb +572 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +427 -0
- data/lib/omnizip/commands/archive_extract_command.rb +272 -0
- data/lib/omnizip/commands/archive_list_command.rb +218 -0
- data/lib/omnizip/commands/archive_repair_command.rb +131 -0
- data/lib/omnizip/commands/archive_verify_command.rb +117 -0
- data/lib/omnizip/commands/compress_command.rb +117 -0
- data/lib/omnizip/commands/decompress_command.rb +120 -0
- data/lib/omnizip/commands/list_command.rb +53 -0
- data/lib/omnizip/commands/metadata_command.rb +153 -0
- data/lib/omnizip/commands/parity_create_command.rb +122 -0
- data/lib/omnizip/commands/parity_repair_command.rb +122 -0
- data/lib/omnizip/commands/parity_verify_command.rb +124 -0
- data/lib/omnizip/commands/profile_list_command.rb +56 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/convenience.rb +359 -0
- data/lib/omnizip/converter/conversion_registry.rb +49 -0
- data/lib/omnizip/converter/conversion_strategy.rb +121 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
- data/lib/omnizip/converter.rb +105 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/error.rb +106 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
- data/lib/omnizip/eta/rate_calculator.rb +104 -0
- data/lib/omnizip/eta/sample_history.rb +143 -0
- data/lib/omnizip/eta/time_estimator.rb +106 -0
- data/lib/omnizip/eta.rb +63 -0
- data/lib/omnizip/extraction/filter_chain.rb +177 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
- data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
- data/lib/omnizip/extraction/regex_pattern.rb +50 -0
- data/lib/omnizip/extraction/selective_extractor.rb +240 -0
- data/lib/omnizip/extraction.rb +111 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +113 -0
- data/lib/omnizip/filter.rb +139 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +166 -0
- data/lib/omnizip/filters/bcj.rb +279 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
- data/lib/omnizip/filters/bcj2.rb +99 -0
- data/lib/omnizip/filters/bcj_arm.rb +176 -0
- data/lib/omnizip/filters/bcj_arm64.rb +244 -0
- data/lib/omnizip/filters/bcj_ia64.rb +196 -0
- data/lib/omnizip/filters/bcj_ppc.rb +190 -0
- data/lib/omnizip/filters/bcj_sparc.rb +176 -0
- data/lib/omnizip/filters/bcj_x86.rb +193 -0
- data/lib/omnizip/filters/delta.rb +196 -0
- data/lib/omnizip/filters/filter_base.rb +72 -0
- data/lib/omnizip/filters/registry.rb +123 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/format_detector.rb +162 -0
- data/lib/omnizip/format_registry.rb +59 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +172 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +385 -0
- data/lib/omnizip/formats/cpio/reader.rb +196 -0
- data/lib/omnizip/formats/cpio/writer.rb +234 -0
- data/lib/omnizip/formats/cpio.rb +140 -0
- data/lib/omnizip/formats/format_spec_loader.rb +230 -0
- data/lib/omnizip/formats/gzip.rb +238 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
- data/lib/omnizip/formats/iso/directory_record.rb +152 -0
- data/lib/omnizip/formats/iso/joliet.rb +204 -0
- data/lib/omnizip/formats/iso/path_table.rb +125 -0
- data/lib/omnizip/formats/iso/reader.rb +197 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +530 -0
- data/lib/omnizip/formats/iso.rb +140 -0
- data/lib/omnizip/formats/lzip.rb +175 -0
- data/lib/omnizip/formats/lzma_alone.rb +171 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
- data/lib/omnizip/formats/rar/block_parser.rb +243 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
- data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +82 -0
- data/lib/omnizip/formats/rar/decompressor.rb +238 -0
- data/lib/omnizip/formats/rar/external_writer.rb +312 -0
- data/lib/omnizip/formats/rar/header.rb +192 -0
- data/lib/omnizip/formats/rar/license_validator.rb +109 -0
- data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
- data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
- data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
- data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
- data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
- data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
- data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
- data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
- data/lib/omnizip/formats/rar/reader.rb +366 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
- data/lib/omnizip/formats/rar/writer.rb +431 -0
- data/lib/omnizip/formats/rar.rb +205 -0
- data/lib/omnizip/formats/rar3/compressor.rb +73 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar3/reader.rb +386 -0
- data/lib/omnizip/formats/rar3/writer.rb +219 -0
- data/lib/omnizip/formats/rar5/compressor.rb +73 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar5/reader.rb +342 -0
- data/lib/omnizip/formats/rar5/writer.rb +214 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
- data/lib/omnizip/formats/seven_zip/header.rb +106 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
- data/lib/omnizip/formats/seven_zip.rb +93 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +94 -0
- data/lib/omnizip/formats/tar/header.rb +168 -0
- data/lib/omnizip/formats/tar/reader.rb +121 -0
- data/lib/omnizip/formats/tar/writer.rb +216 -0
- data/lib/omnizip/formats/tar.rb +84 -0
- data/lib/omnizip/formats/xz/reader.rb +116 -0
- data/lib/omnizip/formats/xz.rb +237 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
- data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
- data/lib/omnizip/formats/zip/reader.rb +250 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +375 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
- data/lib/omnizip/formats/zip.rb +50 -0
- data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
- data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
- data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
- data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
- data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
- data/lib/omnizip/io/buffered_input.rb +146 -0
- data/lib/omnizip/io/buffered_output.rb +105 -0
- data/lib/omnizip/io/stream_manager.rb +115 -0
- data/lib/omnizip/link_handler/hard_link.rb +79 -0
- data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
- data/lib/omnizip/link_handler.rb +124 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +146 -0
- data/lib/omnizip/metadata/metadata_editor.rb +171 -0
- data/lib/omnizip/metadata/metadata_registry.rb +64 -0
- data/lib/omnizip/metadata/metadata_validator.rb +99 -0
- data/lib/omnizip/metadata.rb +57 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +73 -0
- data/lib/omnizip/models/compression_options.rb +71 -0
- data/lib/omnizip/models/conversion_options.rb +87 -0
- data/lib/omnizip/models/conversion_result.rb +135 -0
- data/lib/omnizip/models/eta_result.rb +46 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +144 -0
- data/lib/omnizip/models/filter_config.rb +183 -0
- data/lib/omnizip/models/match_result.rb +124 -0
- data/lib/omnizip/models/optimization_suggestion.rb +91 -0
- data/lib/omnizip/models/parallel_options.rb +104 -0
- data/lib/omnizip/models/performance_result.rb +79 -0
- data/lib/omnizip/models/profile_report.rb +82 -0
- data/lib/omnizip/models/progress_options.rb +38 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +81 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +203 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
- data/lib/omnizip/parallel/worker_pool.rb +223 -0
- data/lib/omnizip/parallel.rb +149 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +73 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
- data/lib/omnizip/parity/models/main_packet.rb +128 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +109 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
- data/lib/omnizip/parity/par2_creator.rb +531 -0
- data/lib/omnizip/parity/par2_repairer.rb +407 -0
- data/lib/omnizip/parity/par2_verifier.rb +364 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
- data/lib/omnizip/parity.rb +186 -0
- data/lib/omnizip/password/encryption_registry.rb +65 -0
- data/lib/omnizip/password/encryption_strategy.rb +96 -0
- data/lib/omnizip/password/password_validator.rb +129 -0
- data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
- data/lib/omnizip/password.rb +87 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
- data/lib/omnizip/pipe.rb +121 -0
- data/lib/omnizip/platform/ntfs_streams.rb +201 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +39 -0
- data/lib/omnizip/profile/balanced_profile.rb +33 -0
- data/lib/omnizip/profile/binary_profile.rb +36 -0
- data/lib/omnizip/profile/compression_profile.rb +158 -0
- data/lib/omnizip/profile/custom_profile.rb +157 -0
- data/lib/omnizip/profile/fast_profile.rb +33 -0
- data/lib/omnizip/profile/maximum_profile.rb +33 -0
- data/lib/omnizip/profile/profile_detector.rb +110 -0
- data/lib/omnizip/profile/profile_registry.rb +161 -0
- data/lib/omnizip/profile/text_profile.rb +36 -0
- data/lib/omnizip/profile.rb +190 -0
- data/lib/omnizip/profiler/memory_profiler.rb +66 -0
- data/lib/omnizip/profiler/method_profiler.rb +49 -0
- data/lib/omnizip/profiler/report_generator.rb +169 -0
- data/lib/omnizip/profiler.rb +204 -0
- data/lib/omnizip/progress/callback_reporter.rb +36 -0
- data/lib/omnizip/progress/console_reporter.rb +62 -0
- data/lib/omnizip/progress/log_reporter.rb +91 -0
- data/lib/omnizip/progress/operation_progress.rb +118 -0
- data/lib/omnizip/progress/progress_bar.rb +156 -0
- data/lib/omnizip/progress/progress_reporter.rb +40 -0
- data/lib/omnizip/progress/progress_tracker.rb +190 -0
- data/lib/omnizip/progress/silent_reporter.rb +24 -0
- data/lib/omnizip/progress.rb +127 -0
- data/lib/omnizip/rubyzip_compat.rb +63 -0
- data/lib/omnizip/temp/safe_extract.rb +168 -0
- data/lib/omnizip/temp/temp_file.rb +124 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +181 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +156 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +273 -0
- data/lib/omnizip/zip/output_stream.rb +324 -0
- data/lib/omnizip.rb +156 -0
- data/readme-docs/advanced-features.adoc +515 -0
- data/readme-docs/api-usage.adoc +444 -0
- data/readme-docs/architecture.adoc +449 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +442 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +325 -0
- data/readme-docs/installation.adoc +228 -0
- data/readme-docs/par2-archives.adoc +608 -0
- data/readme-docs/performance-profiler.adoc +389 -0
- data/readme-docs/preprocessing-filters.adoc +280 -0
- data/xz-file-format-1.2.1.txt +1174 -0
- metadata +617 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "header"
|
|
5
|
+
require_relative "parser"
|
|
6
|
+
require_relative "models/stream_info"
|
|
7
|
+
require_relative "models/file_entry"
|
|
8
|
+
require_relative "stream_decompressor"
|
|
9
|
+
require_relative "split_archive_reader"
|
|
10
|
+
require_relative "header_encryptor"
|
|
11
|
+
require_relative "encrypted_header"
|
|
12
|
+
require "fileutils"
|
|
13
|
+
|
|
14
|
+
module Omnizip
|
|
15
|
+
module Formats
|
|
16
|
+
module SevenZip
|
|
17
|
+
# .7z archive reader
|
|
18
|
+
# Provides read-only access to .7z archives
|
|
19
|
+
class Reader
|
|
20
|
+
include Constants
|
|
21
|
+
|
|
22
|
+
attr_reader :file_path, :header, :entries, :stream_info, :split_reader
|
|
23
|
+
|
|
24
|
+
# Initialize reader with file path
|
|
25
|
+
#
|
|
26
|
+
# @param file_path [String] Path to .7z file
|
|
27
|
+
# @param options [Hash] Reader options
|
|
28
|
+
# @option options [String] :password Password for encrypted headers
|
|
29
|
+
def initialize(file_path, options = {})
|
|
30
|
+
@file_path = file_path
|
|
31
|
+
@entries = []
|
|
32
|
+
@stream_info = nil
|
|
33
|
+
@split_reader = nil
|
|
34
|
+
@password = options[:password]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Open and parse .7z archive
|
|
38
|
+
#
|
|
39
|
+
# @raise [RuntimeError] if file cannot be opened or parsed
|
|
40
|
+
def open
|
|
41
|
+
# Check if this is a split archive
|
|
42
|
+
if split_archive?
|
|
43
|
+
@split_reader = SplitArchiveReader.new(@file_path)
|
|
44
|
+
@split_reader.open
|
|
45
|
+
@header = @split_reader.header
|
|
46
|
+
@entries = @split_reader.entries
|
|
47
|
+
@stream_info = @split_reader.stream_info
|
|
48
|
+
else
|
|
49
|
+
File.open(@file_path, "rb") do |io|
|
|
50
|
+
parse_archive(io)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if archive is split
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean] true if split across multiple volumes
|
|
59
|
+
def split?
|
|
60
|
+
@split_reader&.split? || false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get total number of volumes (for split archives)
|
|
64
|
+
#
|
|
65
|
+
# @return [Integer] Number of volumes
|
|
66
|
+
def total_volumes
|
|
67
|
+
@split_reader&.total_volumes || 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get volume size (for split archives)
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer] Volume size in bytes
|
|
73
|
+
def volume_size
|
|
74
|
+
@split_reader&.volume_size || File.size(@file_path)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get list of volumes (for split archives)
|
|
78
|
+
#
|
|
79
|
+
# @return [Array<String>] Volume paths
|
|
80
|
+
def volumes
|
|
81
|
+
@split_reader&.volumes || [@file_path]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# List all files in archive
|
|
85
|
+
#
|
|
86
|
+
# @return [Array<Models::FileEntry>] File entries
|
|
87
|
+
def list_files
|
|
88
|
+
@entries
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Extract file to output path
|
|
92
|
+
#
|
|
93
|
+
# @param entry_name [String] File name to extract
|
|
94
|
+
# @param output_path [String] Destination path
|
|
95
|
+
# @raise [RuntimeError] if entry not found or extraction fails
|
|
96
|
+
def extract_entry(entry_name, output_path)
|
|
97
|
+
# Delegate to split reader if available
|
|
98
|
+
if @split_reader
|
|
99
|
+
@split_reader.extract_entry(entry_name, output_path)
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
entry = @entries.find { |e| e.name == entry_name }
|
|
104
|
+
raise "Entry not found: #{entry_name}" unless entry
|
|
105
|
+
|
|
106
|
+
# Create directory if needed
|
|
107
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
108
|
+
|
|
109
|
+
# Extract file
|
|
110
|
+
if entry.directory?
|
|
111
|
+
FileUtils.mkdir_p(output_path)
|
|
112
|
+
elsif entry.has_stream?
|
|
113
|
+
File.open(@file_path, "rb") do |io|
|
|
114
|
+
data = extract_entry_data(io, entry)
|
|
115
|
+
File.binwrite(output_path, data)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Set timestamp if available
|
|
119
|
+
if entry.mtime
|
|
120
|
+
File.utime(entry.atime || entry.mtime || Time.now,
|
|
121
|
+
entry.mtime || Time.now,
|
|
122
|
+
output_path)
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
# Empty file
|
|
126
|
+
FileUtils.touch(output_path)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Extract all files to directory
|
|
131
|
+
#
|
|
132
|
+
# @param output_dir [String] Destination directory
|
|
133
|
+
# @raise [RuntimeError] on extraction error
|
|
134
|
+
def extract_all(output_dir)
|
|
135
|
+
FileUtils.mkdir_p(output_dir)
|
|
136
|
+
|
|
137
|
+
@entries.each do |entry|
|
|
138
|
+
output_path = File.join(output_dir, entry.name)
|
|
139
|
+
extract_entry(entry.name, output_path)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if archive is valid .7z format
|
|
144
|
+
#
|
|
145
|
+
# @return [Boolean] true if valid
|
|
146
|
+
def valid?
|
|
147
|
+
!@header.nil? && @header.valid?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Check if headers are encrypted
|
|
151
|
+
#
|
|
152
|
+
# @return [Boolean] true if headers are encrypted
|
|
153
|
+
def encrypted?
|
|
154
|
+
!@encrypted_header.nil?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Check if can decrypt headers (password provided)
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] true if password available for encrypted headers
|
|
160
|
+
def can_decrypt?
|
|
161
|
+
encrypted? && !@password.nil?
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
# Parse .7z archive structure
|
|
167
|
+
#
|
|
168
|
+
# @param io [io] Input stream
|
|
169
|
+
def parse_archive(io)
|
|
170
|
+
# Read and validate start header
|
|
171
|
+
@header = Header.read(io)
|
|
172
|
+
|
|
173
|
+
# Read next header metadata
|
|
174
|
+
# NOTE: next_header_offset is from the END of the Start Header (byte 32)
|
|
175
|
+
# NOT from the end of the file
|
|
176
|
+
next_header_pos = Constants::START_HEADER_SIZE + @header.next_header_offset
|
|
177
|
+
io.seek(next_header_pos)
|
|
178
|
+
next_header_data = io.read(@header.next_header_size)
|
|
179
|
+
|
|
180
|
+
# Check if header is encoded (compressed or encrypted)
|
|
181
|
+
# ENCODED_HEADER (0x17) can mean compressed or encrypted
|
|
182
|
+
first_byte = next_header_data.getbyte(0)
|
|
183
|
+
if first_byte == PropertyId::ENCODED_HEADER
|
|
184
|
+
# Try to parse as encrypted first (if enough data and has structure)
|
|
185
|
+
if next_header_data.bytesize >= 54
|
|
186
|
+
begin
|
|
187
|
+
# Check if it's actually an encrypted header
|
|
188
|
+
EncryptedHeader.from_binary(next_header_data)
|
|
189
|
+
# If we got here, it's encrypted - try to decrypt
|
|
190
|
+
next_header_data = decrypt_header(next_header_data)
|
|
191
|
+
rescue RuntimeError => e
|
|
192
|
+
# Re-raise password-related errors
|
|
193
|
+
if e.message.include?("Password required") || e.message.include?("incorrect password")
|
|
194
|
+
raise
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Not encrypted - it's a compressed header
|
|
198
|
+
# Decompress it using the encoded stream info
|
|
199
|
+
next_header_data = decompress_encoded_header(io,
|
|
200
|
+
next_header_data)
|
|
201
|
+
rescue StandardError
|
|
202
|
+
# Parsing error - not an encrypted header, try decompression
|
|
203
|
+
next_header_data = decompress_encoded_header(io,
|
|
204
|
+
next_header_data)
|
|
205
|
+
end
|
|
206
|
+
else
|
|
207
|
+
# Too short for encrypted header, must be compressed
|
|
208
|
+
next_header_data = decompress_encoded_header(io, next_header_data)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Parse metadata
|
|
213
|
+
parser = Parser.new(next_header_data)
|
|
214
|
+
@stream_info, @entries = parse_metadata(parser)
|
|
215
|
+
|
|
216
|
+
# Map entries to their folders/streams
|
|
217
|
+
map_entries_to_streams
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Decrypt encrypted header
|
|
221
|
+
#
|
|
222
|
+
# @param encrypted_data [String] Encrypted header bytes
|
|
223
|
+
# @return [String] Decrypted header data
|
|
224
|
+
# @raise [RuntimeError] if password not provided
|
|
225
|
+
def decrypt_header(encrypted_data)
|
|
226
|
+
# Parse encrypted header structure
|
|
227
|
+
@encrypted_header = EncryptedHeader.from_binary(encrypted_data)
|
|
228
|
+
|
|
229
|
+
unless @password
|
|
230
|
+
raise "Archive headers are encrypted. Password required to access."
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Decrypt using password
|
|
234
|
+
encryptor = HeaderEncryptor.new(@password)
|
|
235
|
+
encryptor.decrypt(
|
|
236
|
+
@encrypted_header.encrypted_data,
|
|
237
|
+
@encrypted_header.salt,
|
|
238
|
+
@encrypted_header.iv,
|
|
239
|
+
)
|
|
240
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
241
|
+
raise "Failed to decrypt headers: incorrect password (#{e.message})"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Decompress encoded (compressed) header
|
|
245
|
+
#
|
|
246
|
+
# @param io [IO] Archive file handle
|
|
247
|
+
# @param encoded_data [String] Encoded header bytes
|
|
248
|
+
# @return [String] Decompressed header data
|
|
249
|
+
def decompress_encoded_header(io, encoded_data)
|
|
250
|
+
# Skip ENCODED_HEADER marker
|
|
251
|
+
parser = Parser.new(encoded_data[1..])
|
|
252
|
+
|
|
253
|
+
# Parse the streams info for the encoded header
|
|
254
|
+
stream_info = Models::StreamInfo.new
|
|
255
|
+
|
|
256
|
+
# Read streams info - can be either MAIN_STREAMS_INFO or direct stream properties
|
|
257
|
+
type = parser.read_byte
|
|
258
|
+
|
|
259
|
+
if type == PropertyId::MAIN_STREAMS_INFO
|
|
260
|
+
parse_streams_info(parser, stream_info)
|
|
261
|
+
elsif type == PropertyId::PACK_INFO
|
|
262
|
+
# Direct PackInfo without MAIN_STREAMS_INFO wrapper
|
|
263
|
+
parser.read_pack_info(stream_info)
|
|
264
|
+
|
|
265
|
+
# Read UNPACK_INFO
|
|
266
|
+
type = parser.read_byte
|
|
267
|
+
if type == PropertyId::UNPACK_INFO
|
|
268
|
+
parser.read_unpack_info(stream_info)
|
|
269
|
+
end
|
|
270
|
+
else
|
|
271
|
+
raise "Unexpected property in encoded header: 0x#{type.to_s(16)}"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Decompress the header using the stream info
|
|
275
|
+
pack_pos = @header.start_pos_after_header + stream_info.pack_pos
|
|
276
|
+
folder = stream_info.folders[0]
|
|
277
|
+
pack_size = stream_info.pack_sizes[0]
|
|
278
|
+
unpack_size = folder.uncompressed_size
|
|
279
|
+
|
|
280
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos, pack_size)
|
|
281
|
+
decompressor.decompress(unpack_size)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Parse archive metadata
|
|
285
|
+
#
|
|
286
|
+
# @param parser [Parser] Parser instance
|
|
287
|
+
# @return [Array<StreamInfo, Array<Models::FileEntry>>] Parsed data
|
|
288
|
+
def parse_metadata(parser)
|
|
289
|
+
stream_info = Models::StreamInfo.new
|
|
290
|
+
entries = []
|
|
291
|
+
|
|
292
|
+
# Read main header
|
|
293
|
+
type = parser.read_byte
|
|
294
|
+
raise "Expected Header, got 0x#{type.to_s(16)}" unless
|
|
295
|
+
type == PropertyId::HEADER
|
|
296
|
+
|
|
297
|
+
# Parse header sections
|
|
298
|
+
until parser.eof?
|
|
299
|
+
prop_type = parser.read_byte
|
|
300
|
+
|
|
301
|
+
case prop_type
|
|
302
|
+
when PropertyId::MAIN_STREAMS_INFO
|
|
303
|
+
parse_streams_info(parser, stream_info)
|
|
304
|
+
when PropertyId::FILES_INFO
|
|
305
|
+
entries = parser.read_files_info
|
|
306
|
+
when PropertyId::K_END
|
|
307
|
+
break
|
|
308
|
+
else
|
|
309
|
+
# Skip unknown properties
|
|
310
|
+
parser.skip_data if !parser.eof? &&
|
|
311
|
+
parser.peek_byte != PropertyId::K_END
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
parser.read_byte if !parser.eof? &&
|
|
316
|
+
parser.peek_byte == PropertyId::K_END
|
|
317
|
+
|
|
318
|
+
[stream_info, entries]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Parse streams info section
|
|
322
|
+
#
|
|
323
|
+
# @param parser [Parser] Parser instance
|
|
324
|
+
# @param stream_info [Models::StreamInfo] Stream info to populate
|
|
325
|
+
def parse_streams_info(parser, stream_info)
|
|
326
|
+
until parser.eof?
|
|
327
|
+
prop_type = parser.read_byte
|
|
328
|
+
|
|
329
|
+
case prop_type
|
|
330
|
+
when PropertyId::PACK_INFO
|
|
331
|
+
parser.read_pack_info(stream_info)
|
|
332
|
+
when PropertyId::UNPACK_INFO
|
|
333
|
+
parser.read_unpack_info(stream_info)
|
|
334
|
+
when PropertyId::SUBSTREAMS_INFO
|
|
335
|
+
parser.read_substreams_info(stream_info)
|
|
336
|
+
when PropertyId::K_END
|
|
337
|
+
break
|
|
338
|
+
else
|
|
339
|
+
# Unknown property within streams_info - skip it
|
|
340
|
+
parser.skip_data if !parser.eof? && parser.peek_byte != PropertyId::K_END
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Consume final K_END for MAIN_STREAMS_INFO section
|
|
345
|
+
parser.read_byte if !parser.eof? && parser.peek_byte == PropertyId::K_END
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Map entries to their folders and streams
|
|
349
|
+
def map_entries_to_streams
|
|
350
|
+
return if @stream_info.nil?
|
|
351
|
+
|
|
352
|
+
stream_idx = 0
|
|
353
|
+
@entries.each_with_index do |entry, i|
|
|
354
|
+
next unless entry.has_stream?
|
|
355
|
+
|
|
356
|
+
# Find which folder this stream belongs to
|
|
357
|
+
folder_idx = 0
|
|
358
|
+
accumulated = 0
|
|
359
|
+
@stream_info.num_unpack_streams_in_folders.each_with_index do |num, fi|
|
|
360
|
+
if stream_idx < accumulated + num
|
|
361
|
+
folder_idx = fi
|
|
362
|
+
break
|
|
363
|
+
end
|
|
364
|
+
accumulated += num
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
entry.folder_index = folder_idx
|
|
368
|
+
entry.file_index = i
|
|
369
|
+
entry.size = @stream_info.unpack_sizes[stream_idx] if @stream_info.unpack_sizes[stream_idx]
|
|
370
|
+
stream_idx += 1
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Extract entry data
|
|
375
|
+
#
|
|
376
|
+
# @param io [IO] Archive file handle
|
|
377
|
+
# @param entry [Models::FileEntry] Entry to extract
|
|
378
|
+
# @return [String] Extracted data
|
|
379
|
+
def extract_entry_data(io, entry)
|
|
380
|
+
return "" unless entry.has_stream?
|
|
381
|
+
return "" unless @stream_info
|
|
382
|
+
|
|
383
|
+
folder = @stream_info.folders[entry.folder_index]
|
|
384
|
+
return "" unless folder
|
|
385
|
+
|
|
386
|
+
# Calculate pack position
|
|
387
|
+
pack_pos = @header.start_pos_after_header +
|
|
388
|
+
@stream_info.pack_pos
|
|
389
|
+
|
|
390
|
+
# Get pack size for this folder
|
|
391
|
+
pack_idx = 0
|
|
392
|
+
entry.folder_index.times do |i|
|
|
393
|
+
num_streams = @stream_info.folders[i].pack_stream_indices.size
|
|
394
|
+
pack_idx += num_streams
|
|
395
|
+
end
|
|
396
|
+
pack_size = @stream_info.pack_sizes[pack_idx] || 0
|
|
397
|
+
|
|
398
|
+
# For solid archives, multiple files share one compressed stream
|
|
399
|
+
# We need to decompress the entire folder and extract the correct portion
|
|
400
|
+
num_files_in_folder = @stream_info.num_unpack_streams_in_folders[entry.folder_index] || 1
|
|
401
|
+
|
|
402
|
+
if num_files_in_folder > 1
|
|
403
|
+
# Solid archive: decompress entire folder and extract this file's portion
|
|
404
|
+
total_unpack_size = folder.uncompressed_size
|
|
405
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos,
|
|
406
|
+
pack_size)
|
|
407
|
+
full_data = decompressor.decompress(total_unpack_size)
|
|
408
|
+
|
|
409
|
+
# Find offset of this file within the uncompressed stream
|
|
410
|
+
file_offset = 0
|
|
411
|
+
@entries.each do |e|
|
|
412
|
+
break if e.file_index == entry.file_index
|
|
413
|
+
|
|
414
|
+
file_offset += e.size if e.has_stream? && e.folder_index == entry.folder_index
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Extract this file's data
|
|
418
|
+
data = full_data[file_offset, entry.size]
|
|
419
|
+
|
|
420
|
+
# Verify CRC if available
|
|
421
|
+
if entry.crc
|
|
422
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
423
|
+
crc.update(data)
|
|
424
|
+
unless crc.value == entry.crc
|
|
425
|
+
raise "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
data
|
|
430
|
+
else
|
|
431
|
+
# Non-solid: each file has its own compressed stream
|
|
432
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos,
|
|
433
|
+
pack_size)
|
|
434
|
+
expected_crc = entry.crc
|
|
435
|
+
decompressor.decompress_and_verify(entry.size, expected_crc)
|
|
436
|
+
end
|
|
437
|
+
rescue StandardError => e
|
|
438
|
+
warn "Extraction failed for #{entry.name}: #{e.message}"
|
|
439
|
+
raise
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Check if file path indicates a split archive
|
|
443
|
+
#
|
|
444
|
+
# @return [Boolean] true if appears to be split
|
|
445
|
+
def split_archive?
|
|
446
|
+
# Numeric pattern: .001, .002, etc.
|
|
447
|
+
return true if /\.\d{3}$/.match?(@file_path)
|
|
448
|
+
|
|
449
|
+
# Alpha pattern: .7z.aa, .tar.ab, etc. (but not .7z, .tar, .zip alone)
|
|
450
|
+
# Must have a known archive extension followed by alpha suffix
|
|
451
|
+
return true if /\.(7z|tar|zip|rar)\.[a-z]{2,}$/.match?(@file_path)
|
|
452
|
+
|
|
453
|
+
false
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|