omnizip 0.3.8 → 0.3.10
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 +4 -4
- data/.rubocop_todo.yml +51 -50
- data/Gemfile +2 -0
- data/Rakefile +3 -0
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +91 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +82 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/lib/omnizip/algorithm.rb +0 -2
- data/lib/omnizip/algorithm_registry.rb +29 -57
- data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
- data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
- data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
- data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
- data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
- data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
- data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8.rb +0 -2
- data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
- data/lib/omnizip/algorithms.rb +1 -0
- data/lib/omnizip/archive_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
- data/lib/omnizip/archive_handlers.rb +11 -0
- data/lib/omnizip/checksum_registry.rb +23 -96
- data/lib/omnizip/cli/shared.rb +31 -0
- data/lib/omnizip/cli.rb +6 -43
- data/lib/omnizip/commands/archive_create_command.rb +0 -6
- data/lib/omnizip/commands/archive_extract_command.rb +0 -4
- data/lib/omnizip/commands/archive_list_command.rb +0 -4
- data/lib/omnizip/commands/archive_repair_command.rb +0 -2
- data/lib/omnizip/commands/archive_verify_command.rb +0 -2
- data/lib/omnizip/commands/compress_command.rb +0 -2
- data/lib/omnizip/commands/decompress_command.rb +0 -2
- data/lib/omnizip/commands/list_command.rb +0 -2
- data/lib/omnizip/commands/metadata_command.rb +0 -5
- data/lib/omnizip/commands/parity_create_command.rb +0 -2
- data/lib/omnizip/commands/parity_repair_command.rb +0 -2
- data/lib/omnizip/commands/parity_verify_command.rb +0 -2
- data/lib/omnizip/commands/profile_list_command.rb +0 -2
- data/lib/omnizip/commands/profile_show_command.rb +0 -2
- data/lib/omnizip/convenience.rb +111 -234
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
- data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
- data/lib/omnizip/error.rb +22 -18
- data/lib/omnizip/filter.rb +5 -3
- data/lib/omnizip/filter_registry.rb +74 -98
- data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
- data/lib/omnizip/filters/bcj2.rb +0 -2
- data/lib/omnizip/filters/bcj_x86.rb +0 -2
- data/lib/omnizip/filters/filter_base.rb +4 -70
- data/lib/omnizip/filters/registry.rb +0 -2
- data/lib/omnizip/format_detector.rb +21 -12
- data/lib/omnizip/format_registry.rb +79 -38
- data/lib/omnizip/formats/bzip2_file.rb +0 -2
- data/lib/omnizip/formats/cpio/entry.rb +0 -2
- data/lib/omnizip/formats/cpio/reader.rb +0 -1
- data/lib/omnizip/formats/cpio/writer.rb +0 -1
- data/lib/omnizip/formats/cpio.rb +0 -1
- data/lib/omnizip/formats/format_spec_loader.rb +1 -1
- data/lib/omnizip/formats/gzip.rb +0 -2
- data/lib/omnizip/formats/iso/directory_record.rb +6 -3
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso.rb +0 -1
- data/lib/omnizip/formats/lzip.rb +5 -25
- data/lib/omnizip/formats/lzma_alone.rb +7 -25
- data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
- data/lib/omnizip/formats/msi/constants.rb +157 -0
- data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
- data/lib/omnizip/formats/msi/entry.rb +128 -0
- data/lib/omnizip/formats/msi/reader.rb +301 -0
- data/lib/omnizip/formats/msi/string_pool.rb +246 -0
- data/lib/omnizip/formats/msi/table_parser.rb +244 -0
- data/lib/omnizip/formats/msi.rb +84 -0
- data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
- data/lib/omnizip/formats/ole/dirent.rb +20 -3
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
- data/lib/omnizip/formats/ole/storage.rb +19 -4
- data/lib/omnizip/formats/ole.rb +0 -2
- data/lib/omnizip/formats/rar/block_parser.rb +0 -1
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
- data/lib/omnizip/formats/rar/decompressor.rb +38 -1
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +2 -2
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +3 -8
- data/lib/omnizip/formats/rpm/lead.rb +37 -26
- data/lib/omnizip/formats/rpm/tag.rb +0 -1
- data/lib/omnizip/formats/rpm/writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
- data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
- data/lib/omnizip/formats/seven_zip.rb +63 -5
- data/lib/omnizip/formats/tar/entry.rb +0 -2
- data/lib/omnizip/formats/tar/header.rb +0 -2
- data/lib/omnizip/formats/tar/reader.rb +0 -2
- data/lib/omnizip/formats/tar/writer.rb +0 -2
- data/lib/omnizip/formats/tar.rb +0 -1
- data/lib/omnizip/formats/xar/entry.rb +0 -1
- data/lib/omnizip/formats/xar/header.rb +0 -1
- data/lib/omnizip/formats/xar/reader.rb +0 -1
- data/lib/omnizip/formats/xar/toc.rb +3 -3
- data/lib/omnizip/formats/xar/writer.rb +11 -3
- data/lib/omnizip/formats/xar.rb +0 -1
- data/lib/omnizip/formats/xz/reader.rb +7 -3
- data/lib/omnizip/formats/xz.rb +3 -40
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
- data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
- data/lib/omnizip/formats/zip/reader.rb +12 -2
- data/lib/omnizip/formats/zip/writer.rb +73 -1
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
- data/lib/omnizip/formats/zip.rb +0 -1
- data/lib/omnizip/formats.rb +9 -22
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
- data/lib/omnizip/io/source.rb +136 -0
- data/lib/omnizip/io.rb +2 -0
- data/lib/omnizip/link_handler.rb +7 -3
- data/lib/omnizip/metadata/metadata_editor.rb +1 -1
- data/lib/omnizip/models/algorithm_metadata.rb +33 -60
- data/lib/omnizip/models/compression_options.rb +40 -53
- data/lib/omnizip/models/conversion_options.rb +9 -18
- data/lib/omnizip/models/parallel_options.rb +25 -0
- data/lib/omnizip/optimization_registry.rb +8 -44
- data/lib/omnizip/parallel/job_scheduler.rb +26 -8
- data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
- data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
- data/lib/omnizip/parity/par2_repairer.rb +5 -5
- data/lib/omnizip/parity/par2_verifier.rb +27 -22
- data/lib/omnizip/parity.rb +4 -4
- data/lib/omnizip/password/encryption_registry.rb +11 -50
- data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
- data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
- data/lib/omnizip/profile/archive_profile.rb +0 -2
- data/lib/omnizip/profile/binary_profile.rb +0 -2
- data/lib/omnizip/profile/compression_profile.rb +0 -1
- data/lib/omnizip/profile/profile_detector.rb +0 -2
- data/lib/omnizip/profile/text_profile.rb +0 -2
- data/lib/omnizip/profile.rb +0 -2
- data/lib/omnizip/registry.rb +112 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +20 -9
- data/lib/omnizip.rb +26 -55
- data/lib/tasks/msi_test_files.rake +66 -0
- metadata +57 -6
|
@@ -33,7 +33,7 @@ module Omnizip
|
|
|
33
33
|
# extract the original bit values. It maintains a code value
|
|
34
34
|
# that represents the current position within the range.
|
|
35
35
|
class RangeDecoder < RangeCoder
|
|
36
|
-
attr_reader :code
|
|
36
|
+
attr_reader :code, :init_bytes_remaining, :range, :stream
|
|
37
37
|
|
|
38
38
|
# Initialize the range decoder
|
|
39
39
|
#
|
|
@@ -48,13 +48,6 @@ module Omnizip
|
|
|
48
48
|
|
|
49
49
|
# Update the input stream (for LZMA2 multi-chunk streams)
|
|
50
50
|
#
|
|
51
|
-
# When processing LZMA2 chunks, we need to update the stream
|
|
52
|
-
# reference for each new chunk while preserving the range decoder
|
|
53
|
-
# state (range, code) across chunks.
|
|
54
|
-
#
|
|
55
|
-
# XZ Utils pattern: The range coder uses a buffer pointer that's
|
|
56
|
-
# updated for each chunk, while rc_reset() resets range/code.
|
|
57
|
-
#
|
|
58
51
|
# @param new_stream [IO] New input stream
|
|
59
52
|
# @return [void]
|
|
60
53
|
def update_stream(new_stream)
|
|
@@ -63,167 +56,77 @@ module Omnizip
|
|
|
63
56
|
|
|
64
57
|
# Decode a single bit using a probability model
|
|
65
58
|
#
|
|
66
|
-
#
|
|
67
|
-
# and
|
|
68
|
-
# the actual bit value.
|
|
69
|
-
#
|
|
70
|
-
# XZ Utils pattern (rc_if_0): normalize BEFORE bound calculation
|
|
71
|
-
# See: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:181-184
|
|
59
|
+
# This is the hottest method (~5 billion calls for a 600MB file).
|
|
60
|
+
# normalize() and model.update() are inlined to eliminate method dispatch.
|
|
72
61
|
#
|
|
73
62
|
# @param model [BitModel] The probability model for this bit
|
|
74
63
|
# @return [Integer] The decoded bit value (0 or 1)
|
|
75
64
|
def decode_bit(model)
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# DEBUG: Trace is_rep bit decoding
|
|
85
|
-
trace_is_rep = ENV.fetch("TRACE_IS_REP_BITS",
|
|
86
|
-
nil) && (bound > 1_000_000)
|
|
87
|
-
|
|
88
|
-
if trace_is_rep
|
|
89
|
-
puts " [RangeDecoder.decode_bit] BEFORE: range=#{@range}, code=#{@code}, bound=#{bound}, prob=#{model.probability}"
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# DEBUG: Trace model selection at dict_full=227
|
|
93
|
-
if ENV["TRACE_MODEL_SELECTION"]
|
|
94
|
-
begin
|
|
95
|
-
ObjectSpace.each_object(Omnizip::Algorithms::XzUtilsDecoder) do |decoder|
|
|
96
|
-
dict_full = decoder.instance_variable_get(:@dict_full)
|
|
97
|
-
if dict_full && dict_full >= 220 && dict_full <= 235
|
|
98
|
-
pos = decoder.instance_variable_get(:@pos)
|
|
99
|
-
state = decoder.instance_variable_get(:@state)
|
|
100
|
-
puts " [decode_bit] dict_full=#{dict_full}, pos=#{pos}, state=#{state}"
|
|
101
|
-
puts " [decode_bit] model.object_id=#{model.object_id}, prob=#{model.probability}"
|
|
102
|
-
puts " [decode_bit] range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, bound=0x#{bound.to_s(16)}"
|
|
103
|
-
$stderr.flush
|
|
104
|
-
end
|
|
105
|
-
break
|
|
106
|
-
end
|
|
107
|
-
rescue StandardError => e
|
|
108
|
-
# Context not available
|
|
109
|
-
puts " [decode_bit] ERROR: #{e.message}"
|
|
110
|
-
$stderr.flush
|
|
65
|
+
# Inline normalize: only the hot path (range < TOP check)
|
|
66
|
+
# Init bytes are handled eagerly in reset(), not here
|
|
67
|
+
if @range < 0x01000000
|
|
68
|
+
@range <<= 8
|
|
69
|
+
byte = @stream.getbyte
|
|
70
|
+
if byte.nil?
|
|
71
|
+
raise Omnizip::DecompressionError,
|
|
72
|
+
"Truncated LZMA stream during range decoder normalization"
|
|
111
73
|
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# DEBUG: Trace decode_bit for lit_state=96 literal decoding
|
|
115
|
-
if ENV["TRACE_DECODE_BIT_LIT96"]
|
|
116
|
-
puts " decode_bit: range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, prob=#{model.probability}, bound=0x#{bound.to_s(16)}, code<bound?=#{@code < bound}"
|
|
117
|
-
end
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
if ENV.fetch("TRACE_SPECIFIC_DECODE",
|
|
121
|
-
nil) && @range == 0x40000000 && @code == 0x21407d82
|
|
122
|
-
puts " === CRITICAL DECODE_BIT (MATCHED LITERAL) ==="
|
|
123
|
-
puts " BEFORE: range=0x#{@range.to_s(16)} (#{@range})"
|
|
124
|
-
puts " BEFORE: code=0x#{@code.to_s(16)} (#{@code})"
|
|
125
|
-
puts " probability=#{model.probability}"
|
|
126
|
-
puts " bound=0x#{bound.to_s(16)} (#{bound})"
|
|
127
|
-
puts " range >> 11 = 0x#{(@range >> 11).to_s(16)} (#{@range >> 11})"
|
|
128
|
-
puts " (range >> 11) * probability = 0x#{((@range >> 11) * model.probability).to_s(16)} (#{(@range >> 11) * model.probability})"
|
|
129
|
-
puts " code < bound? #{@code < bound}"
|
|
130
|
-
puts " result should be: #{@code < bound ? 0 : 1}"
|
|
131
|
-
puts " =========================================="
|
|
75
|
+
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
132
76
|
end
|
|
133
77
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# We need to know which model is being used
|
|
137
|
-
# Unfortunately, we don't have direct access to the model_index here
|
|
138
|
-
puts " [decode_bit] range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, prob=#{model.probability}, bound=0x#{bound.to_s(16)}, code<bound?=#{@code < bound}, result=#{@code < bound ? 0 : 1}"
|
|
139
|
-
end
|
|
78
|
+
prob = model.probability
|
|
79
|
+
bound = (@range >> 11) * prob
|
|
140
80
|
|
|
141
81
|
if @code < bound
|
|
142
82
|
@range = bound
|
|
143
|
-
model.update(0)
|
|
144
|
-
|
|
145
|
-
puts " [decode_bit] model UPDATE: #{prob_before} -> #{model.probability} (bit=0, object_id=#{model.object_id})"
|
|
146
|
-
end
|
|
147
|
-
if trace_is_rep
|
|
148
|
-
puts " [RangeDecoder.decode_bit] AFTER (bit=0): range=#{@range}, code=#{@code}"
|
|
149
|
-
end
|
|
83
|
+
# Inline model.update(0): prob += (2048 - prob) >> 5
|
|
84
|
+
model.probability = prob + ((2048 - prob) >> 5)
|
|
150
85
|
0
|
|
151
86
|
else
|
|
152
87
|
@code -= bound
|
|
153
88
|
@range -= bound
|
|
154
|
-
model.update(1)
|
|
155
|
-
|
|
156
|
-
puts " [decode_bit] model UPDATE: #{prob_before} -> #{model.probability} (bit=1, object_id=#{model.object_id})"
|
|
157
|
-
end
|
|
158
|
-
if trace_is_rep
|
|
159
|
-
puts " [RangeDecoder.decode_bit] AFTER (bit=1): range=#{@range}, code=#{@code}"
|
|
160
|
-
end
|
|
89
|
+
# Inline model.update(1): prob -= prob >> 5
|
|
90
|
+
model.probability = prob - (prob >> 5)
|
|
161
91
|
1
|
|
162
92
|
end
|
|
163
93
|
end
|
|
164
94
|
|
|
165
95
|
# Decode bits directly without using probability model
|
|
166
96
|
#
|
|
167
|
-
# This is used for decoding values with uniform distribution
|
|
168
|
-
# where all bit values are equally likely.
|
|
169
|
-
#
|
|
170
97
|
# @param num_bits [Integer] Number of bits to decode
|
|
171
98
|
# @return [Integer] The decoded value
|
|
172
99
|
def decode_direct_bits(num_bits)
|
|
173
100
|
result = 0
|
|
174
|
-
trace_this = (num_bits == 25)
|
|
175
|
-
iteration = 0
|
|
176
|
-
|
|
177
|
-
if trace_this
|
|
178
|
-
begin
|
|
179
|
-
warn " decode_direct_bits START: num_bits=#{num_bits}"
|
|
180
|
-
warn " BEFORE: range=#{@range.inspect}, code=#{@code.inspect}"
|
|
181
|
-
$stderr.flush
|
|
182
|
-
rescue StandardError => e
|
|
183
|
-
warn " ERROR in trace: #{e.message}"
|
|
184
|
-
$stderr.flush
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
begin
|
|
189
|
-
num_bits.downto(1) do |_i|
|
|
190
|
-
iteration += 1
|
|
191
|
-
normalize
|
|
192
|
-
@range >>= 1
|
|
193
101
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
102
|
+
num_bits.downto(1) do |_i|
|
|
103
|
+
# Inline normalize
|
|
104
|
+
if @range < 0x01000000
|
|
105
|
+
@range <<= 8
|
|
106
|
+
byte = @stream.getbyte
|
|
107
|
+
if byte.nil?
|
|
108
|
+
raise Omnizip::DecompressionError,
|
|
109
|
+
"Truncated LZMA stream during range decoder normalization"
|
|
198
110
|
end
|
|
199
111
|
|
|
200
|
-
|
|
201
|
-
@code -= @range
|
|
202
|
-
result = (result << 1) | 1
|
|
203
|
-
else
|
|
204
|
-
result = (result << 1) | 0
|
|
205
|
-
end
|
|
112
|
+
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
206
113
|
end
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
$stderr.flush
|
|
211
|
-
raise
|
|
212
|
-
end
|
|
114
|
+
@range >>= 1
|
|
115
|
+
|
|
116
|
+
bit = @code >= @range ? 1 : 0
|
|
213
117
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
118
|
+
if bit == 1
|
|
119
|
+
@code -= @range
|
|
120
|
+
result = (result << 1) | 1
|
|
121
|
+
else
|
|
122
|
+
result = (result << 1) | 0
|
|
123
|
+
end
|
|
217
124
|
end
|
|
218
125
|
|
|
219
126
|
result
|
|
220
127
|
end
|
|
221
128
|
|
|
222
|
-
# Decode a cumulative frequency value
|
|
223
|
-
#
|
|
224
|
-
# This is used by PPMd for decoding symbols based on their
|
|
225
|
-
# frequency distribution. Returns the cumulative frequency
|
|
226
|
-
# that can be mapped back to a symbol.
|
|
129
|
+
# Decode a cumulative frequency value (PPMd)
|
|
227
130
|
#
|
|
228
131
|
# @param total_freq [Integer] Total frequency of all symbols in context
|
|
229
132
|
# @return [Integer] The cumulative frequency value
|
|
@@ -235,9 +138,6 @@ module Omnizip
|
|
|
235
138
|
|
|
236
139
|
# Normalize after decoding a symbol with frequency
|
|
237
140
|
#
|
|
238
|
-
# After using decode_freq to get the cumulative frequency,
|
|
239
|
-
# call this to update the range decoder state.
|
|
240
|
-
#
|
|
241
141
|
# @param cum_freq [Integer] Cumulative frequency of decoded symbol
|
|
242
142
|
# @param freq [Integer] Frequency of decoded symbol
|
|
243
143
|
# @param total_freq [Integer] Total frequency of all symbols
|
|
@@ -253,144 +153,84 @@ module Omnizip
|
|
|
253
153
|
|
|
254
154
|
# Decode bits directly using a base value (XZ Utils rc_direct pattern)
|
|
255
155
|
#
|
|
256
|
-
# This method implements the XZ Utils rc_direct macro which is used
|
|
257
|
-
# for decoding distance values in slots 14+. The pattern matches
|
|
258
|
-
# XZ Utils' implementation in rangecoder/range_decoder.h:366-375.
|
|
259
|
-
#
|
|
260
|
-
# XZ Utils rc_direct behavior (from C macro):
|
|
261
|
-
# - dest = (dest << 1) + 1 (unconditionally)
|
|
262
|
-
# - Normalize range, halve it, subtract from code
|
|
263
|
-
# - bound = 0 - (code >> 31) extracts sign bit
|
|
264
|
-
# - If code >= range (bit=1): sign=0, bound=0, dest stays at (dest << 1) + 1
|
|
265
|
-
# - If code < range (bit=0): sign=1, bound=-1, dest = (dest << 1) + 1 - 1 = dest << 1
|
|
266
|
-
# - dest += bound
|
|
267
|
-
# - code += range & bound (restore code if bit=0)
|
|
268
|
-
#
|
|
269
|
-
# In Ruby (without unsigned wraparound), we explicitly check if code >= range
|
|
270
|
-
# and undo the +1 if the bit is 0.
|
|
271
|
-
#
|
|
272
156
|
# @param num_bits [Integer] Number of bits to decode
|
|
273
157
|
# @param base [Integer] Base value to start from (2 or 3 for distances)
|
|
274
158
|
# @return [Integer] The decoded value
|
|
275
159
|
def decode_direct_bits_with_base(num_bits, base)
|
|
276
160
|
result = base
|
|
277
|
-
|
|
278
|
-
if ENV["TRACE_DIRECT_BITS_SLOT40"]
|
|
279
|
-
puts " [decode_direct_bits_with_base] START: base=#{base}, num_bits=#{num_bits}"
|
|
280
|
-
puts " BEFORE: range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}"
|
|
281
|
-
end
|
|
282
|
-
num_bits.times do |i|
|
|
161
|
+
num_bits.times do |_i|
|
|
283
162
|
result = (result << 1) + 1
|
|
284
|
-
normalize
|
|
163
|
+
# Inline normalize
|
|
164
|
+
if @range < 0x01000000
|
|
165
|
+
@range <<= 8
|
|
166
|
+
byte = @stream.getbyte
|
|
167
|
+
if byte.nil?
|
|
168
|
+
raise Omnizip::DecompressionError,
|
|
169
|
+
"Truncated LZMA stream during range decoder normalization"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
173
|
+
end
|
|
285
174
|
@range >>= 1
|
|
286
175
|
|
|
287
|
-
# Check if bit is 1 before modifying @code
|
|
288
|
-
# If code >= range, bit is 1; otherwise bit is 0
|
|
289
176
|
bit = @code >= @range ? 1 : 0
|
|
290
177
|
|
|
291
|
-
if ENV["TRACE_DIRECT_BITS_SLOT40"] && i < 15
|
|
292
|
-
puts " [#{i + 1}/#{num_bits}] bit=#{bit}, result after this step = #{result - (bit.zero? ? 1 : 0)}, range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}"
|
|
293
|
-
end
|
|
294
|
-
|
|
295
178
|
if bit == 1
|
|
296
|
-
# Bit is 1: result stays at (result << 1) + 1
|
|
297
179
|
@code -= @range
|
|
298
180
|
else
|
|
299
|
-
# Bit is 0: undo the +1, result = (result << 1) + 1 - 1 = result << 1
|
|
300
181
|
result -= 1
|
|
301
182
|
end
|
|
302
183
|
end
|
|
303
|
-
if ENV["TRACE_DIRECT_BITS_SLOT40"]
|
|
304
|
-
puts " [decode_direct_bits_with_base] END: result=#{result}"
|
|
305
|
-
end
|
|
306
184
|
result
|
|
307
185
|
end
|
|
308
186
|
|
|
309
187
|
# Reset the range decoder for a new chunk
|
|
310
188
|
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
# - Reset code to 0
|
|
314
|
-
# - Set init_bytes_remaining to 5 (lazy initialization)
|
|
315
|
-
# - Let normalize() read the initialization bytes during actual decoding
|
|
316
|
-
#
|
|
317
|
-
# Called during state reset (control >= 0xA0) to reset the range decoder
|
|
318
|
-
# for the new chunk's compressed data.
|
|
319
|
-
#
|
|
320
|
-
# XZ Utils reference: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:181
|
|
189
|
+
# Resets state only. Call read_init_bytes after the stream is set
|
|
190
|
+
# to the correct input.
|
|
321
191
|
#
|
|
322
192
|
# @return [void]
|
|
323
193
|
def reset
|
|
324
|
-
if ENV["LZMA_DEBUG"]
|
|
325
|
-
stream_pos = begin
|
|
326
|
-
@stream.pos
|
|
327
|
-
rescue StandardError
|
|
328
|
-
"N/A"
|
|
329
|
-
end
|
|
330
|
-
warn " RangeDecoder.reset: BEFORE reset, range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, stream.pos=#{stream_pos}, init_bytes_remaining=#{@init_bytes_remaining}"
|
|
331
|
-
end
|
|
332
194
|
@range = 0xFFFFFFFF
|
|
333
195
|
@code = 0
|
|
334
|
-
# Lazy initialization: set remaining bytes but don't read yet
|
|
335
|
-
# normalize() will read these bytes during actual decoding
|
|
336
196
|
@init_bytes_remaining = 5
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Eagerly read the 5 initialization bytes from the current stream.
|
|
200
|
+
# Must be called after the stream is set to the correct input.
|
|
201
|
+
#
|
|
202
|
+
# @return [void]
|
|
203
|
+
def read_init_bytes
|
|
204
|
+
while @init_bytes_remaining.positive?
|
|
205
|
+
byte = @stream.getbyte
|
|
206
|
+
if byte.nil?
|
|
207
|
+
raise Omnizip::DecompressionError,
|
|
208
|
+
"Truncated LZMA stream during range decoder initialization"
|
|
342
209
|
end
|
|
343
|
-
|
|
210
|
+
|
|
211
|
+
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
212
|
+
@init_bytes_remaining -= 1
|
|
344
213
|
end
|
|
345
214
|
end
|
|
346
215
|
|
|
347
216
|
# Normalize the range when it becomes too small
|
|
348
217
|
#
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
#
|
|
352
|
-
# XZ Utils pattern (rc_normalize): uses IF, not WHILE!
|
|
353
|
-
# Each normalize call shifts in at most ONE byte.
|
|
354
|
-
# See: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:143-149
|
|
355
|
-
#
|
|
356
|
-
# XZ Utils lazy initialization (range_decoder.h:146-149):
|
|
357
|
-
# If init_bytes_remaining > 0, read byte for code initialization
|
|
358
|
-
# Otherwise, read byte for range normalization
|
|
218
|
+
# Still needed for decode_freq() and other non-hot paths.
|
|
219
|
+
# The hot-path methods inline normalize directly.
|
|
359
220
|
#
|
|
360
221
|
# @return [void]
|
|
361
222
|
def normalize
|
|
362
|
-
#
|
|
223
|
+
# Handle lazy initialization if needed (for non-hot paths)
|
|
363
224
|
if @init_bytes_remaining.positive?
|
|
364
|
-
|
|
365
|
-
@stream.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
@stream.size
|
|
371
|
-
rescue StandardError
|
|
372
|
-
"N/A"
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
# XZ Utils: Handle lazy initialization first
|
|
377
|
-
# IMPORTANT: Read ALL initialization bytes in a loop, not just one!
|
|
378
|
-
# XZ Utils rc_normalize reads one byte per call, but decode_bit only calls
|
|
379
|
-
# normalize once at the start, so we need to loop to read all 5 bytes.
|
|
380
|
-
while @init_bytes_remaining.positive?
|
|
381
|
-
byte = @stream.getbyte
|
|
382
|
-
byte ||= 0
|
|
383
|
-
code_before = @code
|
|
384
|
-
@code = ((code_before << 8) | byte) & 0xFFFFFFFF
|
|
385
|
-
@init_bytes_remaining -= 1
|
|
225
|
+
while @init_bytes_remaining.positive?
|
|
226
|
+
byte = @stream.getbyte
|
|
227
|
+
if byte.nil?
|
|
228
|
+
raise Omnizip::DecompressionError,
|
|
229
|
+
"Truncated LZMA stream during range decoder initialization"
|
|
230
|
+
end
|
|
386
231
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
puts " stream_pos_before=#{stream_pos_before}, stream_size=#{stream_size}"
|
|
390
|
-
puts " byte=0x#{byte.to_s(16).upcase}, code_before=0x#{code_before.to_s(16).upcase}"
|
|
391
|
-
puts " (code_before << 8) = 0x#{(code_before << 8).to_s(16).upcase}"
|
|
392
|
-
puts " ((code_before << 8) | byte) = 0x#{((code_before << 8) | byte).to_s(16).upcase}"
|
|
393
|
-
puts " code_after=0x#{@code.to_s(16).upcase}"
|
|
232
|
+
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
233
|
+
@init_bytes_remaining -= 1
|
|
394
234
|
end
|
|
395
235
|
end
|
|
396
236
|
|
|
@@ -398,15 +238,6 @@ module Omnizip
|
|
|
398
238
|
byte = read_byte
|
|
399
239
|
@range <<= 8
|
|
400
240
|
@code = ((@code << 8) | byte) & 0xFFFFFFFF
|
|
401
|
-
if ENV["RANGE_DECODER_TRACE"]
|
|
402
|
-
pos = begin
|
|
403
|
-
@stream.pos
|
|
404
|
-
rescue StandardError
|
|
405
|
-
"N/A"
|
|
406
|
-
end
|
|
407
|
-
warn " NORMALIZE: pos=#{pos}, byte=0x#{byte.to_s(16).upcase}, code=0x#{@code.to_s(16).upcase}, range=0x#{@range.to_s(16).upcase}"
|
|
408
|
-
$stderr.flush
|
|
409
|
-
end
|
|
410
241
|
end
|
|
411
242
|
end
|
|
412
243
|
|
|
@@ -414,11 +245,6 @@ module Omnizip
|
|
|
414
245
|
|
|
415
246
|
# Initialize the decoder by reading the first bytes
|
|
416
247
|
#
|
|
417
|
-
# XZ Utils rc_read_init (range_decoder.h:160-167):
|
|
418
|
-
# - Read 5 bytes and construct code value
|
|
419
|
-
# - code is uint32_t, so it's automatically masked to 32 bits
|
|
420
|
-
# - In Ruby, we need to explicitly mask to ensure 32-bit value
|
|
421
|
-
#
|
|
422
248
|
# @return [void]
|
|
423
249
|
def init_decoder
|
|
424
250
|
5.times do
|
|
@@ -435,29 +261,11 @@ module Omnizip
|
|
|
435
261
|
def read_byte
|
|
436
262
|
byte = @stream.getbyte
|
|
437
263
|
|
|
438
|
-
# During normal decoding (after initialization), if we run out of input,
|
|
439
|
-
# this indicates corrupted data - the compressed stream ended prematurely
|
|
440
264
|
if byte.nil? && @initialization_complete && @init_bytes_remaining.zero?
|
|
441
265
|
raise Omnizip::DecompressionError,
|
|
442
266
|
"LZMA compressed data exhausted prematurely. The file may be corrupted or the uncompressed size field may be incorrect."
|
|
443
267
|
end
|
|
444
268
|
|
|
445
|
-
# Only track as data byte if initialization is complete
|
|
446
|
-
if @initialization_complete && @init_bytes_remaining.zero?
|
|
447
|
-
pos = begin
|
|
448
|
-
@stream.pos
|
|
449
|
-
rescue StandardError
|
|
450
|
-
"N/A"
|
|
451
|
-
end
|
|
452
|
-
if ENV["RANGE_DECODER_TRACE"]
|
|
453
|
-
warn " READ_BYTE: pos=#{pos.inspect}, byte=0x#{byte.to_s(16).upcase}"
|
|
454
|
-
$stderr.flush
|
|
455
|
-
end
|
|
456
|
-
if ENV["LZMA_DEBUG"]
|
|
457
|
-
warn " READ_BYTE: pos=#{pos.inspect}, byte=0x#{byte.to_s(16).upcase}, @code now=0x#{@code.to_s(16)}"
|
|
458
|
-
end
|
|
459
|
-
end
|
|
460
|
-
|
|
461
269
|
byte || 0
|
|
462
270
|
end
|
|
463
271
|
end
|
|
@@ -61,11 +61,6 @@ module Omnizip
|
|
|
61
61
|
|
|
62
62
|
prob = model.probability
|
|
63
63
|
|
|
64
|
-
# DEBUG: Trace is_rep bit encoding
|
|
65
|
-
if ENV["TRACE_IS_REP_BITS"] && bit.zero?
|
|
66
|
-
puts " [RangeEncoder.encode_bit] BEFORE: range=#{@range}, low=#{@low}, prob=#{prob}, bit=#{bit}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
64
|
if bit.zero?
|
|
70
65
|
# RC_BIT_0: shrink range to lower portion
|
|
71
66
|
# rc->range = (rc->range >> 11) * prob
|
|
@@ -81,10 +76,6 @@ module Omnizip
|
|
|
81
76
|
@range = (@range - bound) & 0xFFFFFFFF
|
|
82
77
|
end
|
|
83
78
|
|
|
84
|
-
if ENV["TRACE_IS_REP_BITS"] && bit.zero?
|
|
85
|
-
puts " [RangeEncoder.encode_bit] AFTER: range=#{@range}, low=#{@low}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
79
|
# Update probability model based on the bit value
|
|
89
80
|
# This matches the decoder's update behavior (proper OOP symmetry)
|
|
90
81
|
model.update(bit)
|
|
@@ -128,7 +128,6 @@ module Omnizip
|
|
|
128
128
|
|
|
129
129
|
# Queue flush operation (matches XZ Utils rc_flush)
|
|
130
130
|
def flush
|
|
131
|
-
puts "[FLUSH] Adding 5 RC_FLUSH symbols, @count before=#{@count}" if ENV["DEBUG"]
|
|
132
131
|
5.times do
|
|
133
132
|
raise "Symbol buffer overflow" if @count >= RC_SYMBOLS_MAX
|
|
134
133
|
|
|
@@ -136,7 +135,6 @@ module Omnizip
|
|
|
136
135
|
@probs[@count] = nil
|
|
137
136
|
@count += 1
|
|
138
137
|
end
|
|
139
|
-
puts "[FLUSH] @count after=#{@count}" if ENV["DEBUG"]
|
|
140
138
|
end
|
|
141
139
|
alias queue_flush flush
|
|
142
140
|
|
|
@@ -163,8 +161,6 @@ module Omnizip
|
|
|
163
161
|
def encode(out, out_pos, out_size)
|
|
164
162
|
raise "Symbol buffer overflow" if @count > RC_SYMBOLS_MAX
|
|
165
163
|
|
|
166
|
-
puts "[ENCODE] Start: @count=#{@count} @pos=#{@pos} @out_total=#{@out_total}" if ENV["DEBUG"]
|
|
167
|
-
|
|
168
164
|
skip_increment = false
|
|
169
165
|
|
|
170
166
|
while @pos < @count
|
|
@@ -206,20 +202,11 @@ module Omnizip
|
|
|
206
202
|
# Prevent further normalizations (XZ Utils behavior)
|
|
207
203
|
@range = 0xFFFFFFFF
|
|
208
204
|
|
|
209
|
-
puts "[ENCODE] RC_FLUSH: @pos=#{@pos} @count=#{@count}" if ENV["DEBUG"]
|
|
210
|
-
|
|
211
|
-
iteration = 0
|
|
212
205
|
# Flush the last five bytes (see rc_flush)
|
|
213
206
|
begin
|
|
214
|
-
iteration += 1
|
|
215
|
-
puts "[ENCODE] RC_FLUSH iteration #{iteration}: @pos=#{@pos}" if ENV["DEBUG"]
|
|
216
207
|
return true if shift_low(out, out_pos, out_size)
|
|
217
|
-
|
|
218
|
-
puts "[ENCODE] After shift_low: @pos=#{@pos}" if ENV["DEBUG"]
|
|
219
208
|
end while (@pos += 1) < @count
|
|
220
209
|
|
|
221
|
-
puts "[ENCODE] After RC_FLUSH loop: total #{iteration} iterations" if ENV["DEBUG"]
|
|
222
|
-
|
|
223
210
|
# Reset the range encoder (matches XZ Utils)
|
|
224
211
|
reset
|
|
225
212
|
# CRITICAL: Skip the @pos increment at loop end because do-while already did it
|
|
@@ -233,8 +220,6 @@ module Omnizip
|
|
|
233
220
|
@pos += 1 unless skip_increment
|
|
234
221
|
end
|
|
235
222
|
|
|
236
|
-
puts "[ENCODE] End: @count=#{@count} @pos=#{@pos} @out_total=#{@out_total}" if ENV["DEBUG"]
|
|
237
|
-
|
|
238
223
|
@count = 0
|
|
239
224
|
@pos = 0
|
|
240
225
|
|
|
@@ -35,7 +35,6 @@ module Omnizip
|
|
|
35
35
|
# Update state after encoding literal
|
|
36
36
|
# Matches XZ Utils update_literal() macro
|
|
37
37
|
def update_literal
|
|
38
|
-
old_value = @value
|
|
39
38
|
@value = if @value <= STATE_SHORTREP_LIT_LIT
|
|
40
39
|
STATE_LIT_LIT
|
|
41
40
|
elsif @value <= STATE_LIT_SHORTREP
|
|
@@ -43,19 +42,12 @@ module Omnizip
|
|
|
43
42
|
else
|
|
44
43
|
@value - 6
|
|
45
44
|
end
|
|
46
|
-
if ENV["LZMA_DEBUG"]
|
|
47
|
-
warn "DEBUG: update_literal - state: #{old_value} → #{@value}"
|
|
48
|
-
end
|
|
49
45
|
end
|
|
50
46
|
|
|
51
47
|
# Update state after encoding match
|
|
52
48
|
# Matches XZ Utils update_match() macro
|
|
53
49
|
def update_match
|
|
54
|
-
old_value = @value
|
|
55
50
|
@value = @value < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH
|
|
56
|
-
if ENV["LZMA_DEBUG"]
|
|
57
|
-
warn "DEBUG: update_match - state: #{old_value} → #{@value}"
|
|
58
|
-
end
|
|
59
51
|
end
|
|
60
52
|
|
|
61
53
|
# Update state after encoding long rep match
|