lzma1 0.1.2 → 0.3.0

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.
package/README.md CHANGED
@@ -1,9 +1,16 @@
1
- # lzma1
1
+ <div align="center">
2
2
 
3
- This is a [fork][fork-link] of [Nathan Rugg's][fork-author] LZMA-JS package.
3
+ <h1>lzma1</h1>
4
+
5
+ This is a [fork][fork-link] of [Nathan Rugg's][fork-author] LZMA-JS package and
6
+ inspiration of [Java][7zip-sdk] and Googles [Go][go-sdk] implementations.
7
+
8
+ </div>
4
9
 
5
10
  [fork-link]: https://github.com/LZMA-JS/LZMA-JS
6
11
  [fork-author]: https://github.com/nmrugg
12
+ [7zip-sdk]: https://www.7-zip.org/sdk.html
13
+ [go-sdk]: https://code.google.com/p/lzma
7
14
 
8
15
  ## Why
9
16
 
@@ -18,9 +25,9 @@ to read and maintain.
18
25
 
19
26
  - Encode and decode LZMA streams
20
27
  - Supports both string and Uint8Array data
21
- - Supports both browser and Node.js environments
22
- - Pure JavaScript implementation with TypeScript types
23
28
  - No dependencies on runtime-specific APIs
29
+ - Pure JavaScript implementation with TypeScript types
30
+ - Isomorphic browser- and runtime-independent implementation
24
31
 
25
32
  ## Installation
26
33
 
@@ -148,7 +155,14 @@ commonly used in the 7z archive format.
148
155
  The LZMA compressed data begins with a header that contains information needed
149
156
  for decompression:
150
157
 
151
- ![lzma](./docs/lzma.svg)
158
+ ```mermaid
159
+ packet-beta
160
+ 0-7: "lc (literal context)"
161
+ 8-11: "lp (literal pos)"
162
+ 12-15: "pb (position bits)"
163
+ 16-47: "Dictionary Size (32-bit, LE)"
164
+ 48-111: "Uncompressed Size (64-bit, LE)"
165
+ ```
152
166
 
153
167
  More [information][header_link] about the LZMA header structure.
154
168
 
@@ -156,7 +170,6 @@ More [information][header_link] about the LZMA header structure.
156
170
 
157
171
  ## Related
158
172
 
159
- - [7-Zip SDK](https://www.7-zip.org/sdk.html)
160
173
  - [lzma-purejs](https://github.com/cscott/lzma-purejs)
161
174
  - [lzmajs](https://github.com/glinscott/lzmajs)
162
175
  - [lzma-purejs fork](https://github.com/mauron85/lzma-purejs/tree/master)
package/lib/decoder.js ADDED
@@ -0,0 +1,468 @@
1
+ import { LzOutWindow } from "./lz-window.js";
2
+ import { RangeDecoder } from "./range-decoder.js";
3
+ import { _MAX_UINT32, CHOICE_ARRAY_SIZE, createBitTree, DEFAULT_WINDOW_SIZE, getLenToPosState, initArray, initBitModels, LITERAL_DECODER_SIZE, MATCH_DECODERS_SIZE, POS_DECODERS_SIZE, REP_DECODERS_SIZE, stateUpdateChar, } from "./utils.js";
4
+ export class Decoder {
5
+ rangeDecoder;
6
+ outWindow;
7
+ // Decoder state
8
+ state = 0;
9
+ rep0 = 0;
10
+ rep1 = 0;
11
+ rep2 = 0;
12
+ rep3 = 0;
13
+ prevByte = 0;
14
+ nowPos64 = 0n;
15
+ outSize = 0n;
16
+ // Decoder configuration
17
+ posStateMask = 0;
18
+ dictSizeCheck = 0;
19
+ // Probability models for different symbols
20
+ matchDecoders = [];
21
+ rep0LongDecoders = [];
22
+ repDecoders = [];
23
+ repG0Decoders = [];
24
+ repG1Decoders = [];
25
+ repG2Decoders = [];
26
+ posDecoders = [];
27
+ // Complex decoders
28
+ literalDecoder;
29
+ posSlotDecoders = [];
30
+ lenDecoder;
31
+ repLenDecoder;
32
+ posAlignDecoder;
33
+ // Alias for compatibility with LZMA class
34
+ get literalCoder() {
35
+ return this.literalDecoder;
36
+ }
37
+ constructor() {
38
+ // Initialize range decoder
39
+ this.rangeDecoder = new RangeDecoder();
40
+ // Initialize output window using proper LzOutWindow
41
+ this.outWindow = new LzOutWindow(null, DEFAULT_WINDOW_SIZE);
42
+ // Initialize probability models
43
+ this.matchDecoders = initArray(MATCH_DECODERS_SIZE);
44
+ this.rep0LongDecoders = initArray(MATCH_DECODERS_SIZE);
45
+ this.repDecoders = initArray(REP_DECODERS_SIZE);
46
+ this.repG0Decoders = initArray(REP_DECODERS_SIZE);
47
+ this.repG1Decoders = initArray(REP_DECODERS_SIZE);
48
+ this.repG2Decoders = initArray(REP_DECODERS_SIZE);
49
+ this.posDecoders = initArray(POS_DECODERS_SIZE);
50
+ // Initialize literal decoder
51
+ this.literalDecoder = {
52
+ coders: [],
53
+ numPrevBits: 0,
54
+ numPosBits: 0,
55
+ posMask: 0,
56
+ init: () => this.initLiteralDecoder(),
57
+ };
58
+ // Initialize position slot decoders (4 different length-to-position states)
59
+ for (let i = 0; i < 4; i++) {
60
+ this.posSlotDecoders[i] = createBitTree(6);
61
+ }
62
+ // Initialize length decoders
63
+ this.lenDecoder = this.createLenDecoder();
64
+ this.repLenDecoder = this.createLenDecoder();
65
+ // Initialize position alignment decoder
66
+ this.posAlignDecoder = createBitTree(4);
67
+ }
68
+ /**
69
+ * Read LZMA header, configure decoder, and prepare for decompression.
70
+ */
71
+ initDecompression(input, output) {
72
+ // Read 5-byte header properties
73
+ const properties = [];
74
+ for (let i = 0; i < 5; ++i) {
75
+ const r = input.readByte();
76
+ if (r === -1) {
77
+ throw new Error("truncated input");
78
+ }
79
+ properties[i] = r << 24 >> 24;
80
+ }
81
+ if (!this.setDecoderProperties(properties)) {
82
+ throw new Error("corrupted input");
83
+ }
84
+ // Read 8-byte uncompressed length from header
85
+ let outSize;
86
+ {
87
+ let value = 0n;
88
+ for (let i = 0; i < 8; i++) {
89
+ const r = input.readByte();
90
+ if (r === -1) {
91
+ throw new Error("truncated input");
92
+ }
93
+ value += BigInt(r & 0xFF) << BigInt(i * 8);
94
+ }
95
+ // Check for unknown size marker (all 0xFF bytes)
96
+ if (value === 0xffffffffffffffffn) {
97
+ outSize = -1n;
98
+ }
99
+ else if (value > BigInt(_MAX_UINT32)) {
100
+ outSize = -1n;
101
+ }
102
+ else {
103
+ outSize = value;
104
+ }
105
+ }
106
+ // Set up range decoder and output window
107
+ this.rangeDecoder.setStream(input);
108
+ this.flush();
109
+ this.outWindow.stream = null;
110
+ this.outWindow.stream = output;
111
+ // Initialize decoder state
112
+ this.init();
113
+ this.state = 0;
114
+ this.rep0 = 0;
115
+ this.rep1 = 0;
116
+ this.rep2 = 0;
117
+ this.rep3 = 0;
118
+ this.outSize = outSize;
119
+ this.nowPos64 = 0n;
120
+ this.prevByte = 0;
121
+ }
122
+ /**
123
+ * Full decompression: read header, decode all chunks, flush and cleanup.
124
+ */
125
+ decompress(input, output) {
126
+ this.initDecompression(input, output);
127
+ while (true) {
128
+ const result = this.codeOneChunk();
129
+ if (result === -1) {
130
+ throw new Error("corrupted input");
131
+ }
132
+ const isOutputComplete = (this.outSize >= 0n)
133
+ && (this.nowPos64 >= this.outSize);
134
+ if (result || isOutputComplete) {
135
+ this.flush();
136
+ this.cleanup();
137
+ return;
138
+ }
139
+ }
140
+ }
141
+ createLenDecoder() {
142
+ const decoder = {
143
+ choice: initArray(CHOICE_ARRAY_SIZE),
144
+ lowCoder: [],
145
+ midCoder: [],
146
+ highCoder: createBitTree(0x08),
147
+ numPosStates: 0,
148
+ };
149
+ return decoder;
150
+ }
151
+ setDecoderProperties(properties) {
152
+ if (properties.length < 5) {
153
+ return false;
154
+ }
155
+ const lc = properties[0] % 9;
156
+ const remainder = Math.floor(properties[0] / 9);
157
+ const lp = remainder % 5;
158
+ const pb = Math.floor(remainder / 5);
159
+ if (pb > 4) {
160
+ return false;
161
+ }
162
+ // Set literal decoder properties
163
+ this.literalDecoder.numPrevBits = lc;
164
+ this.literalDecoder.numPosBits = lp;
165
+ this.literalDecoder.posMask = (1 << lp) - 1;
166
+ // Set position state mask
167
+ this.posStateMask = (1 << pb) - 1;
168
+ // Calculate dictionary size from properties[1-4]
169
+ let dictSize = 0;
170
+ for (let i = 0; i < 4; i++) {
171
+ // Treat bytes as unsigned (0-255) instead of signed (-128 to 127)
172
+ const unsignedByte = properties[1 + i] & 0xFF;
173
+ dictSize += unsignedByte << (i * 8);
174
+ }
175
+ // Set dictionary size and check value
176
+ this.dictSizeCheck = Math.max(dictSize, 1);
177
+ // Initialize output window
178
+ if (dictSize > 0) {
179
+ this.outWindow.windowSize = Math.max(dictSize, 4096);
180
+ this.outWindow.buffer = new Uint8Array(this.outWindow.windowSize);
181
+ }
182
+ // Initialize literal decoder coders
183
+ const numStates = 1 << (this.literalDecoder.numPrevBits + this.literalDecoder.numPosBits);
184
+ this.literalDecoder.coders = [];
185
+ for (let i = 0; i < numStates; i++) {
186
+ this.literalDecoder.coders[i] = {
187
+ decoders: initArray(LITERAL_DECODER_SIZE), // 0x300
188
+ };
189
+ }
190
+ // Initialize length decoders
191
+ this.lenDecoder.numPosStates = 1 << pb;
192
+ this.repLenDecoder.numPosStates = 1 << pb;
193
+ // Initialize low and mid coders for length decoders
194
+ this.lenDecoder.lowCoder = [];
195
+ this.lenDecoder.midCoder = [];
196
+ this.repLenDecoder.lowCoder = [];
197
+ this.repLenDecoder.midCoder = [];
198
+ for (let posState = 0; posState < (1 << pb); posState++) {
199
+ this.lenDecoder.lowCoder[posState] = createBitTree(3);
200
+ this.lenDecoder.midCoder[posState] = createBitTree(3);
201
+ this.repLenDecoder.lowCoder[posState] = createBitTree(3);
202
+ this.repLenDecoder.midCoder[posState] = createBitTree(3);
203
+ }
204
+ return true;
205
+ }
206
+ // Methods that modify decoder state
207
+ copyBlock(len) {
208
+ const outputWindow = this.outWindow;
209
+ const distance = this.rep0;
210
+ let pos = outputWindow.pos - distance - 1;
211
+ if (pos < 0) {
212
+ pos += outputWindow.windowSize;
213
+ }
214
+ for (; len != 0; len -= 1) {
215
+ if (pos >= outputWindow.windowSize) {
216
+ pos = 0;
217
+ }
218
+ outputWindow.buffer[outputWindow.pos] = outputWindow.buffer[pos];
219
+ outputWindow.pos += 1;
220
+ pos += 1;
221
+ if (outputWindow.pos >= outputWindow.windowSize) {
222
+ this.flush();
223
+ }
224
+ }
225
+ }
226
+ putByte(b) {
227
+ this.outWindow.buffer[this.outWindow.pos] = b;
228
+ this.outWindow.pos += 1;
229
+ if (this.outWindow.pos >= this.outWindow.windowSize) {
230
+ this.flush();
231
+ }
232
+ }
233
+ getByte(distance) {
234
+ const outputWindow = this.outWindow;
235
+ let pos = outputWindow.pos - distance - 1;
236
+ if (pos < 0) {
237
+ pos += outputWindow.windowSize;
238
+ }
239
+ return outputWindow.buffer[pos];
240
+ }
241
+ getDecoder(pos, prevByte) {
242
+ // Calculate index based on position and previous byte
243
+ const positionMask = pos & this.literalDecoder.posMask;
244
+ const prevBitsMask = (prevByte & 0xFF) >>> (8 - this.literalDecoder.numPrevBits);
245
+ const index = (positionMask << this.literalDecoder.numPrevBits) + prevBitsMask;
246
+ // Return decoder at calculated index
247
+ return this.literalDecoder.coders[index];
248
+ }
249
+ initLiteralDecoder() {
250
+ let numStates = 1 << (this.literalDecoder.numPrevBits + this.literalDecoder.numPosBits);
251
+ for (let i = 0; i < numStates; ++i) {
252
+ // Initialize bit models for each coder
253
+ for (let j = 0; j < this.literalDecoder.coders[i].decoders.length; j++) {
254
+ this.literalDecoder.coders[i].decoders[j] = 1024;
255
+ }
256
+ }
257
+ }
258
+ init() {
259
+ this.outWindow.streamPos = 0;
260
+ this.outWindow.pos = 0;
261
+ initBitModels(this.matchDecoders);
262
+ initBitModels(this.rep0LongDecoders);
263
+ initBitModels(this.repDecoders);
264
+ initBitModels(this.repG0Decoders);
265
+ initBitModels(this.repG1Decoders);
266
+ initBitModels(this.repG2Decoders);
267
+ initBitModels(this.posDecoders);
268
+ this.initLiteralDecoder();
269
+ for (let i = 0; i < 4; ++i) {
270
+ initBitModels(this.posSlotDecoders[i].models);
271
+ }
272
+ this.initLenDecoder(this.lenDecoder);
273
+ this.initLenDecoder(this.repLenDecoder);
274
+ initBitModels(this.posAlignDecoder.models);
275
+ this.initRangeDecoder();
276
+ }
277
+ initLenDecoder(decoder) {
278
+ initBitModels(decoder.choice);
279
+ for (let posState = 0; posState < decoder.numPosStates; ++posState) {
280
+ initBitModels(decoder.lowCoder[posState].models);
281
+ initBitModels(decoder.midCoder[posState].models);
282
+ }
283
+ initBitModels(decoder.highCoder.models);
284
+ }
285
+ outWindowReleaseStream() {
286
+ this.flush();
287
+ this.outWindow.stream = null;
288
+ }
289
+ decodeBit(probs, index) {
290
+ return this.rangeDecoder.decodeBit(probs, index);
291
+ }
292
+ decodeDirectBits(numTotalBits) {
293
+ return this.rangeDecoder.decodeDirectBits(numTotalBits);
294
+ }
295
+ initRangeDecoder() {
296
+ this.rangeDecoder.init();
297
+ }
298
+ rangeBitTreeDecoder(bitTree) {
299
+ let bitIndex, m = 1;
300
+ for (bitIndex = bitTree.numBitLevels; bitIndex != 0; bitIndex -= 1) {
301
+ m = (m << 1) + this.decodeBit(bitTree.models, m);
302
+ }
303
+ return m - (1 << bitTree.numBitLevels);
304
+ }
305
+ reverseDecode(models, startIndex, numBitLevels) {
306
+ let symbol = 0;
307
+ for (let bitIndex = 0, m = 1, bit; bitIndex < numBitLevels; ++bitIndex) {
308
+ bit = this.decodeBit(models, startIndex + m);
309
+ m <<= 1;
310
+ m += bit;
311
+ symbol |= bit << bitIndex;
312
+ }
313
+ return symbol;
314
+ }
315
+ reverseDecodeAlignDecoder() {
316
+ let symbol = 0;
317
+ for (let m = 1, bitIndex = 0, bit; bitIndex < this.posAlignDecoder.numBitLevels; ++bitIndex) {
318
+ bit = this.decodeBit(this.posAlignDecoder.models, m);
319
+ m <<= 1;
320
+ m += bit;
321
+ symbol |= bit << bitIndex;
322
+ }
323
+ return symbol;
324
+ }
325
+ // Update the placeholder implementations with actual logic
326
+ decodeNormalWithRangeDecoder(decoder) {
327
+ let symbol = 1;
328
+ do {
329
+ symbol = symbol << 1 | this.decodeBit(decoder.decoders, symbol);
330
+ } while (symbol < 0x100);
331
+ return symbol << 24 >> 24;
332
+ }
333
+ decodeWithMatchByteWithRangeDecoder(encoder, matchByte) {
334
+ let bit, matchBit, symbol = 1;
335
+ do {
336
+ matchBit = (matchByte >> 7) & 1;
337
+ matchByte <<= 1;
338
+ bit = this.decodeBit(encoder.decoders, ((1 + matchBit) << 8) + symbol);
339
+ symbol = symbol << 1 | bit;
340
+ if (matchBit != bit) {
341
+ while (symbol < 0x100) {
342
+ symbol = symbol << 1 | this.decodeBit(encoder.decoders, symbol);
343
+ }
344
+ break;
345
+ }
346
+ } while (symbol < 0x100);
347
+ return symbol << 24 >> 24;
348
+ }
349
+ decodeLenWithRangeDecoder(decoder, posState) {
350
+ if (!this.decodeBit(decoder.choice, 0)) {
351
+ return this.rangeBitTreeDecoder(decoder.lowCoder[posState]);
352
+ }
353
+ let symbol = 0x08;
354
+ if (!this.decodeBit(decoder.choice, 1)) {
355
+ symbol += this.rangeBitTreeDecoder(decoder.midCoder[posState]);
356
+ }
357
+ else {
358
+ symbol += 0x08 + this.rangeBitTreeDecoder(decoder.highCoder);
359
+ }
360
+ return symbol;
361
+ }
362
+ codeOneChunk() {
363
+ let decoder2, distance, len, numDirectBits, positionSlot;
364
+ let posState = Number(this.nowPos64 & 0xffffffffn) & this.posStateMask;
365
+ if (!this.decodeBit(this.matchDecoders, (this.state << 4) + posState)) {
366
+ decoder2 = this.getDecoder(Number(this.nowPos64 & 0xffffffffn), this.prevByte);
367
+ if (this.state < 7) {
368
+ this.prevByte = this.decodeNormalWithRangeDecoder(decoder2);
369
+ }
370
+ else {
371
+ this.prevByte = this.decodeWithMatchByteWithRangeDecoder(decoder2, this.getByte(this.rep0));
372
+ }
373
+ this.putByte(this.prevByte);
374
+ this.state = stateUpdateChar(this.state);
375
+ this.nowPos64 += 1n;
376
+ }
377
+ else {
378
+ if (this.decodeBit(this.repDecoders, this.state)) {
379
+ len = 0;
380
+ if (!this.decodeBit(this.repG0Decoders, this.state)) {
381
+ if (!this.decodeBit(this.rep0LongDecoders, (this.state << 4) + posState)) {
382
+ this.state = this.state < 7 ? 9 : 11;
383
+ len = 1;
384
+ }
385
+ }
386
+ else {
387
+ if (!this.decodeBit(this.repG1Decoders, this.state)) {
388
+ distance = this.rep1;
389
+ }
390
+ else {
391
+ if (!this.decodeBit(this.repG2Decoders, this.state)) {
392
+ distance = this.rep2;
393
+ }
394
+ else {
395
+ distance = this.rep3;
396
+ this.rep3 = this.rep2;
397
+ }
398
+ this.rep2 = this.rep1;
399
+ }
400
+ this.rep1 = this.rep0;
401
+ this.rep0 = distance;
402
+ }
403
+ if (!len) {
404
+ len = this.decodeLenWithRangeDecoder(this.repLenDecoder, posState) + 2;
405
+ this.state = this.state < 7 ? 0x08 : 11;
406
+ }
407
+ }
408
+ else {
409
+ this.rep3 = this.rep2;
410
+ this.rep2 = this.rep1;
411
+ this.rep1 = this.rep0;
412
+ len = 2 + this.decodeLenWithRangeDecoder(this.lenDecoder, posState);
413
+ this.state = this.state < 7 ? 7 : 10;
414
+ positionSlot = this.rangeBitTreeDecoder(this.posSlotDecoders[getLenToPosState(len)]);
415
+ if (positionSlot >= 4) {
416
+ numDirectBits = (positionSlot >> 1) - 1;
417
+ this.rep0 = (2 | (positionSlot & 1)) << numDirectBits;
418
+ if (positionSlot < 14) {
419
+ this.rep0 += this.reverseDecode(this.posDecoders, this.rep0 - positionSlot - 1, numDirectBits);
420
+ }
421
+ else {
422
+ this.rep0 += this.decodeDirectBits(numDirectBits - 4) << 4;
423
+ this.rep0 += this.reverseDecodeAlignDecoder();
424
+ if (this.rep0 < 0) {
425
+ if (this.rep0 == -1) {
426
+ return 1;
427
+ }
428
+ return -1;
429
+ }
430
+ }
431
+ }
432
+ else {
433
+ this.rep0 = positionSlot;
434
+ }
435
+ }
436
+ if (BigInt(this.rep0) >= this.nowPos64 || this.rep0 >= this.dictSizeCheck) {
437
+ return -1;
438
+ }
439
+ this.copyBlock(len);
440
+ this.nowPos64 += BigInt(len);
441
+ this.prevByte = this.getByte(0);
442
+ }
443
+ return 0;
444
+ }
445
+ writeToOutput(buffer, data, offset, length) {
446
+ buffer.writeBytes(data, offset, length);
447
+ }
448
+ flush() {
449
+ const size = this.outWindow.pos - this.outWindow.streamPos;
450
+ if (!size) {
451
+ return;
452
+ }
453
+ if (this.outWindow.stream && this.outWindow.buffer) {
454
+ this.outWindow.stream.writeBytes(this.outWindow.buffer, this.outWindow.streamPos, size);
455
+ }
456
+ if (this.outWindow.pos >= this.outWindow.windowSize) {
457
+ this.outWindow.pos = 0;
458
+ }
459
+ this.outWindow.streamPos = this.outWindow.pos;
460
+ }
461
+ /**
462
+ * Cleanup decoder resources
463
+ */
464
+ cleanup() {
465
+ this.outWindow.stream = null;
466
+ this.rangeDecoder.stream = null;
467
+ }
468
+ }