cabriolet 0.1.2 → 0.2.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +703 -38
  3. data/lib/cabriolet/algorithm_factory.rb +250 -0
  4. data/lib/cabriolet/base_compressor.rb +206 -0
  5. data/lib/cabriolet/binary/bitstream.rb +167 -16
  6. data/lib/cabriolet/binary/bitstream_writer.rb +150 -21
  7. data/lib/cabriolet/binary/chm_structures.rb +2 -2
  8. data/lib/cabriolet/binary/hlp_structures.rb +258 -37
  9. data/lib/cabriolet/binary/lit_structures.rb +231 -65
  10. data/lib/cabriolet/binary/oab_structures.rb +17 -1
  11. data/lib/cabriolet/cab/command_handler.rb +226 -0
  12. data/lib/cabriolet/cab/compressor.rb +108 -84
  13. data/lib/cabriolet/cab/decompressor.rb +16 -20
  14. data/lib/cabriolet/cab/extractor.rb +142 -66
  15. data/lib/cabriolet/cab/file_compression_work.rb +52 -0
  16. data/lib/cabriolet/cab/file_compression_worker.rb +89 -0
  17. data/lib/cabriolet/checksum.rb +49 -0
  18. data/lib/cabriolet/chm/command_handler.rb +227 -0
  19. data/lib/cabriolet/chm/compressor.rb +7 -3
  20. data/lib/cabriolet/chm/decompressor.rb +39 -21
  21. data/lib/cabriolet/chm/parser.rb +5 -2
  22. data/lib/cabriolet/cli/base_command_handler.rb +127 -0
  23. data/lib/cabriolet/cli/command_dispatcher.rb +140 -0
  24. data/lib/cabriolet/cli/command_registry.rb +83 -0
  25. data/lib/cabriolet/cli.rb +356 -607
  26. data/lib/cabriolet/collections/file_collection.rb +175 -0
  27. data/lib/cabriolet/compressors/base.rb +1 -1
  28. data/lib/cabriolet/compressors/lzx.rb +241 -54
  29. data/lib/cabriolet/compressors/mszip.rb +35 -3
  30. data/lib/cabriolet/compressors/quantum.rb +36 -95
  31. data/lib/cabriolet/decompressors/base.rb +1 -1
  32. data/lib/cabriolet/decompressors/lzss.rb +13 -3
  33. data/lib/cabriolet/decompressors/lzx.rb +70 -33
  34. data/lib/cabriolet/decompressors/mszip.rb +126 -39
  35. data/lib/cabriolet/decompressors/quantum.rb +83 -53
  36. data/lib/cabriolet/errors.rb +3 -0
  37. data/lib/cabriolet/extraction/base_extractor.rb +88 -0
  38. data/lib/cabriolet/extraction/extractor.rb +171 -0
  39. data/lib/cabriolet/extraction/file_extraction_work.rb +60 -0
  40. data/lib/cabriolet/extraction/file_extraction_worker.rb +106 -0
  41. data/lib/cabriolet/file_entry.rb +156 -0
  42. data/lib/cabriolet/file_manager.rb +144 -0
  43. data/lib/cabriolet/format_base.rb +79 -0
  44. data/lib/cabriolet/hlp/command_handler.rb +282 -0
  45. data/lib/cabriolet/hlp/compressor.rb +28 -238
  46. data/lib/cabriolet/hlp/decompressor.rb +107 -147
  47. data/lib/cabriolet/hlp/parser.rb +52 -101
  48. data/lib/cabriolet/hlp/quickhelp/compression_stream.rb +138 -0
  49. data/lib/cabriolet/hlp/quickhelp/compressor.rb +151 -0
  50. data/lib/cabriolet/hlp/quickhelp/decompressor.rb +558 -0
  51. data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
  52. data/lib/cabriolet/hlp/quickhelp/huffman_stream.rb +74 -0
  53. data/lib/cabriolet/hlp/quickhelp/huffman_tree.rb +167 -0
  54. data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -0
  55. data/lib/cabriolet/hlp/quickhelp/parser.rb +274 -0
  56. data/lib/cabriolet/hlp/quickhelp/structure_builder.rb +93 -0
  57. data/lib/cabriolet/hlp/quickhelp/topic_builder.rb +52 -0
  58. data/lib/cabriolet/hlp/quickhelp/topic_compressor.rb +83 -0
  59. data/lib/cabriolet/hlp/winhelp/btree_builder.rb +289 -0
  60. data/lib/cabriolet/hlp/winhelp/compressor.rb +400 -0
  61. data/lib/cabriolet/hlp/winhelp/decompressor.rb +192 -0
  62. data/lib/cabriolet/hlp/winhelp/parser.rb +484 -0
  63. data/lib/cabriolet/hlp/winhelp/zeck_lz77.rb +271 -0
  64. data/lib/cabriolet/huffman/encoder.rb +15 -12
  65. data/lib/cabriolet/huffman/tree.rb +85 -1
  66. data/lib/cabriolet/kwaj/command_handler.rb +213 -0
  67. data/lib/cabriolet/kwaj/compressor.rb +7 -3
  68. data/lib/cabriolet/kwaj/decompressor.rb +18 -12
  69. data/lib/cabriolet/lit/command_handler.rb +221 -0
  70. data/lib/cabriolet/lit/compressor.rb +119 -168
  71. data/lib/cabriolet/lit/content_encoder.rb +76 -0
  72. data/lib/cabriolet/lit/content_type_detector.rb +50 -0
  73. data/lib/cabriolet/lit/decompressor.rb +518 -152
  74. data/lib/cabriolet/lit/directory_builder.rb +153 -0
  75. data/lib/cabriolet/lit/guid_generator.rb +16 -0
  76. data/lib/cabriolet/lit/header_writer.rb +124 -0
  77. data/lib/cabriolet/lit/parser.rb +670 -0
  78. data/lib/cabriolet/lit/piece_builder.rb +74 -0
  79. data/lib/cabriolet/lit/structure_builder.rb +252 -0
  80. data/lib/cabriolet/models/hlp_file.rb +130 -29
  81. data/lib/cabriolet/models/hlp_header.rb +105 -17
  82. data/lib/cabriolet/models/lit_header.rb +212 -25
  83. data/lib/cabriolet/models/szdd_header.rb +10 -2
  84. data/lib/cabriolet/models/winhelp_header.rb +127 -0
  85. data/lib/cabriolet/oab/command_handler.rb +257 -0
  86. data/lib/cabriolet/oab/compressor.rb +17 -8
  87. data/lib/cabriolet/oab/decompressor.rb +41 -10
  88. data/lib/cabriolet/offset_calculator.rb +81 -0
  89. data/lib/cabriolet/plugin.rb +233 -0
  90. data/lib/cabriolet/plugin_manager.rb +453 -0
  91. data/lib/cabriolet/plugin_validator.rb +422 -0
  92. data/lib/cabriolet/quantum_shared.rb +105 -0
  93. data/lib/cabriolet/system/io_system.rb +3 -0
  94. data/lib/cabriolet/system/memory_handle.rb +17 -4
  95. data/lib/cabriolet/szdd/command_handler.rb +217 -0
  96. data/lib/cabriolet/szdd/compressor.rb +15 -11
  97. data/lib/cabriolet/szdd/decompressor.rb +18 -9
  98. data/lib/cabriolet/version.rb +1 -1
  99. data/lib/cabriolet.rb +181 -20
  100. metadata +69 -4
  101. data/lib/cabriolet/auto.rb +0 -173
  102. data/lib/cabriolet/parallel.rb +0 -333
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ module LIT
5
+ # Builds LIT directory structure with AOLL chunks
6
+ class DirectoryBuilder
7
+ # Chunk size for directory entries (8KB)
8
+ DEFAULT_CHUNK_SIZE = 0x2000
9
+
10
+ attr_reader :chunk_size, :entries
11
+
12
+ # Initialize directory builder
13
+ #
14
+ # @param chunk_size [Integer] Chunk size for directory entries
15
+ def initialize(chunk_size: DEFAULT_CHUNK_SIZE)
16
+ @chunk_size = chunk_size
17
+ @entries = []
18
+ end
19
+
20
+ # Add an entry to the directory
21
+ #
22
+ # @param name [String] Entry name
23
+ # @param section [Integer] Section number
24
+ # @param offset [Integer] Offset within section
25
+ # @param size [Integer] Size in bytes
26
+ def add_entry(name:, section:, offset:, size:)
27
+ @entries << {
28
+ name: name,
29
+ section: section,
30
+ offset: offset,
31
+ size: size,
32
+ }
33
+ end
34
+
35
+ # Build the directory structure
36
+ #
37
+ # @return [Hash] Directory structure with entries and metadata
38
+ def build
39
+ {
40
+ entries: @entries,
41
+ chunk_size: @chunk_size,
42
+ num_chunks: calculate_num_chunks,
43
+ }
44
+ end
45
+
46
+ # Build AOLL (Archive Object List List) chunk
47
+ #
48
+ # @return [String] Binary AOLL chunk data
49
+ def build_aoll_chunk
50
+ # Build all entry data first
51
+ entries_data = @entries.map { |entry| encode_entry(entry) }.join
52
+
53
+ # Calculate quickref offset (starts after entries data)
54
+ quickref_offset = entries_data.bytesize
55
+
56
+ # Build AOLL header
57
+ header = Binary::LITStructures::AOLLHeader.new
58
+ header.tag = Binary::LITStructures::Tags::AOLL
59
+ header.quickref_offset = quickref_offset
60
+ header.current_chunk_low = 0
61
+ header.current_chunk_high = 0
62
+ header.prev_chunk_low = 0xFFFFFFFF
63
+ header.prev_chunk_high = 0xFFFFFFFF
64
+ header.next_chunk_low = 0xFFFFFFFF
65
+ header.next_chunk_high = 0xFFFFFFFF
66
+ header.entries_so_far = @entries.size
67
+ header.reserved = 0
68
+ header.chunk_distance = 0
69
+ header.reserved2 = 0
70
+
71
+ header.to_binary_s + entries_data
72
+ end
73
+
74
+ # Calculate total size needed for directory
75
+ #
76
+ # @return [Integer] Size in bytes
77
+ def calculate_size
78
+ # IFCM header + AOLL chunk + padding
79
+ ifcm_size = Binary::LITStructures::IFCMHeader.new.to_binary_s.bytesize
80
+ aoll_size = build_aoll_chunk.bytesize
81
+ target_size = @chunk_size
82
+
83
+ [ifcm_size + aoll_size, target_size].max
84
+ end
85
+
86
+ private
87
+
88
+ # Calculate number of chunks needed
89
+ #
90
+ # @return [Integer] Number of chunks
91
+ def calculate_num_chunks
92
+ return 1 if @entries.empty?
93
+
94
+ total_size = @entries.sum { |e| estimate_entry_size(e) }
95
+ [1, (total_size / @chunk_size.to_f).ceil].max
96
+ end
97
+
98
+ # Estimate size of a directory entry
99
+ #
100
+ # @param entry [Hash] Directory entry
101
+ # @return [Integer] Estimated size
102
+ def estimate_entry_size(entry)
103
+ name_size = entry[:name].bytesize
104
+ # Name length (1-5 bytes) + name + section (1-5 bytes) + offset (1-5 bytes) + size (1-5 bytes)
105
+ 5 + name_size + 15
106
+ end
107
+
108
+ # Encode a directory entry with variable-length integers
109
+ #
110
+ # @param entry [Hash] Directory entry
111
+ # @return [String] Encoded entry data
112
+ def encode_entry(entry)
113
+ data = +""
114
+
115
+ # Encode name length and name
116
+ name = entry[:name].dup.force_encoding("UTF-8")
117
+ data += encode_vint(name.bytesize)
118
+ data += name
119
+
120
+ # Encode section, offset, size
121
+ data += encode_vint(entry[:section])
122
+ data += encode_vint(entry[:offset])
123
+ data += encode_vint(entry[:size])
124
+
125
+ data
126
+ end
127
+
128
+ # Write a variable-length integer (MSB = continuation bit)
129
+ #
130
+ # @param value [Integer] Value to encode
131
+ # @return [String] Encoded integer
132
+ def encode_vint(value)
133
+ return [0x00].pack("C") if value.zero?
134
+
135
+ bytes = []
136
+
137
+ # Extract 7-bit chunks from value
138
+ loop do
139
+ bytes.unshift(value & 0x7F)
140
+ value >>= 7
141
+ break if value.zero?
142
+ end
143
+
144
+ # Set MSB on all bytes except the last
145
+ (0...(bytes.size - 1)).each do |i|
146
+ bytes[i] |= 0x80
147
+ end
148
+
149
+ bytes.pack("C*")
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ module LIT
5
+ # Generates GUIDs for LIT files
6
+ class GuidGenerator
7
+ # Generate a random GUID
8
+ #
9
+ # @return [String] 16-byte random GUID
10
+ def self.generate
11
+ require "securerandom"
12
+ SecureRandom.random_bytes(16)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ module LIT
5
+ # Writes LIT headers to output
6
+ class HeaderWriter
7
+ # Initialize header writer
8
+ #
9
+ # @param io_system [System::IOSystem] I/O system for writing
10
+ def initialize(io_system)
11
+ @io_system = io_system
12
+ end
13
+
14
+ # Write primary header
15
+ #
16
+ # @param output_handle [System::FileHandle] Output file handle
17
+ # @param structure [Hash] LIT structure
18
+ # @return [Integer] Bytes written
19
+ def write_primary_header(output_handle, structure)
20
+ header = Binary::LITStructures::PrimaryHeader.new
21
+ header.signature = Binary::LITStructures::SIGNATURE
22
+ header.version = structure[:version]
23
+ header.header_length = 40
24
+ header.num_pieces = 5
25
+ header.secondary_header_length = structure[:secondary_header][:length]
26
+ header.header_guid = structure[:header_guid]
27
+
28
+ header_data = header.to_binary_s
29
+ @io_system.write(output_handle, header_data)
30
+ end
31
+
32
+ # Write piece structures
33
+ #
34
+ # @param output_handle [System::FileHandle] Output file handle
35
+ # @param pieces [Array<Hash>] Pieces array
36
+ # @return [Integer] Bytes written
37
+ def write_piece_structures(output_handle, pieces)
38
+ total_bytes = 0
39
+
40
+ pieces.each do |piece|
41
+ piece_struct = Binary::LITStructures::PieceStructure.new
42
+ piece_struct.offset_low = piece[:offset]
43
+ piece_struct.offset_high = 0
44
+ piece_struct.size_low = piece[:size]
45
+ piece_struct.size_high = 0
46
+
47
+ piece_data = piece_struct.to_binary_s
48
+ total_bytes += @io_system.write(output_handle, piece_data)
49
+ end
50
+
51
+ total_bytes
52
+ end
53
+
54
+ # Write secondary header block
55
+ #
56
+ # @param output_handle [System::FileHandle] Output file handle
57
+ # @param sec_hdr [Hash] Secondary header metadata
58
+ # @return [Integer] Bytes written
59
+ def write_secondary_header(output_handle, sec_hdr)
60
+ header = Binary::LITStructures::SecondaryHeader.new
61
+
62
+ # SECHDR block
63
+ header.sechdr_version = 2
64
+ header.sechdr_length = 152
65
+
66
+ # Entry directory info
67
+ header.entry_aoli_idx = 0
68
+ header.entry_aoli_idx_high = 0
69
+ header.entry_reserved1 = 0
70
+ header.entry_last_aoll = 0
71
+ header.entry_reserved2 = 0
72
+ header.entry_chunklen = sec_hdr[:entry_chunklen]
73
+ header.entry_two = 2
74
+ header.entry_reserved3 = 0
75
+ header.entry_depth = sec_hdr[:entry_depth]
76
+ header.entry_reserved4 = 0
77
+ header.entry_entries = sec_hdr[:entry_entries]
78
+ header.entry_reserved5 = 0
79
+
80
+ # Count directory info
81
+ header.count_aoli_idx = 0xFFFFFFFF
82
+ header.count_aoli_idx_high = 0xFFFFFFFF
83
+ header.count_reserved1 = 0
84
+ header.count_last_aoll = 0
85
+ header.count_reserved2 = 0
86
+ header.count_chunklen = sec_hdr[:count_chunklen]
87
+ header.count_two = 2
88
+ header.count_reserved3 = 0
89
+ header.count_depth = 1
90
+ header.count_reserved4 = 0
91
+ header.count_entries = sec_hdr[:count_entries]
92
+ header.count_reserved5 = 0
93
+
94
+ header.entry_unknown = sec_hdr[:entry_unknown]
95
+ header.count_unknown = sec_hdr[:count_unknown]
96
+
97
+ # CAOL block
98
+ header.caol_tag = Binary::LITStructures::Tags::CAOL
99
+ header.caol_version = 2
100
+ header.caol_length = 80 # 48 + 32
101
+ header.creator_id = sec_hdr[:creator_id]
102
+ header.caol_reserved1 = 0
103
+ header.caol_entry_chunklen = sec_hdr[:entry_chunklen]
104
+ header.caol_count_chunklen = sec_hdr[:count_chunklen]
105
+ header.caol_entry_unknown = sec_hdr[:entry_unknown]
106
+ header.caol_count_unknown = sec_hdr[:count_unknown]
107
+ header.caol_reserved2 = 0
108
+
109
+ # ITSF block
110
+ header.itsf_tag = Binary::LITStructures::Tags::ITSF
111
+ header.itsf_version = 4
112
+ header.itsf_length = 32
113
+ header.itsf_unknown = 1
114
+ header.content_offset_low = sec_hdr[:content_offset]
115
+ header.content_offset_high = 0
116
+ header.timestamp = sec_hdr[:timestamp]
117
+ header.language_id = sec_hdr[:language_id]
118
+
119
+ header_data = header.to_binary_s
120
+ @io_system.write(output_handle, header_data)
121
+ end
122
+ end
123
+ end
124
+ end