/** * An LZMA decoder (raw stream, as a 7z coder stores it: the properties apart, the unpacked size known), after the * LZMA SDK's reference decoder (LzmaSpec.cpp). For 7z headers: those are small, so the whole output is one buffer. */ const MODEL_BITS = 11 const BIT_MODEL_TOTAL = 1 << MODEL_BITS const MOVE_BITS = 5 const TOP = 1 << 24 const POS_BITS_MAX = 4 const STATES = 12 const LEN_TO_POS_STATES = 4 const ALIGN_BITS = 4 const END_POS_MODEL_INDEX = 14 const FULL_DISTANCES = 1 << (END_POS_MODEL_INDEX >> 1) const MATCH_MIN_LEN = 2 export class LzmaError extends Error {} const probs = (n: number) => new Uint16Array(n).fill(BIT_MODEL_TOTAL >>> 1) class RangeDecoder { private range = 0xffffffff private code = 0 constructor( private input: Uint8Array, private pos: number ) { if (this.byte() !== 0) throw new LzmaError('LZMA: bad stream start') for (let i = 0; i < 4; i++) this.code = ((this.code << 8) | this.byte()) >>> 0 if (this.code === this.range) throw new LzmaError('LZMA: corrupt stream') } private byte(): number { if (this.pos >= this.input.length) throw new LzmaError('LZMA: input ended') return this.input[this.pos++] } private normalize() { if (this.range < TOP) { this.range = (this.range << 8) >>> 0 this.code = ((this.code << 8) | this.byte()) >>> 0 } } direct(bits: number): number { let result = 0 do { this.range >>>= 1 this.code = (this.code - this.range) >>> 0 const t = this.code >>> 31 ? 0xffffffff : 0 this.code = (this.code + (this.range & t)) >>> 0 this.normalize() result = ((result << 1) | (t ? 0 : 1)) >>> 0 } while (--bits) return result } bit(p: Uint16Array, i: number): number { const v = p[i] const bound = ((this.range >>> MODEL_BITS) * v) >>> 0 let symbol: number if (this.code < bound) { p[i] = v + ((BIT_MODEL_TOTAL - v) >>> MOVE_BITS) this.range = bound symbol = 0 } else { p[i] = v - (v >>> MOVE_BITS) this.code = (this.code - bound) >>> 0 this.range = (this.range - bound) >>> 0 symbol = 1 } this.normalize() return symbol } tree(p: Uint16Array, offset: number, bits: number): number { let m = 1 for (let i = 0; i < bits; i++) m = (m << 1) + this.bit(p, offset + m) return m - (1 << bits) } reverseTree(p: Uint16Array, offset: number, bits: number): number { let m = 1 let symbol = 0 for (let i = 0; i < bits; i++) { const bit = this.bit(p, offset + m) m = (m << 1) + bit symbol |= bit << i } return symbol } } class LenDecoder { private choice = probs(2) private low = probs((1 << POS_BITS_MAX) << 3) private mid = probs((1 << POS_BITS_MAX) << 3) private high = probs(1 << 8) decode(rc: RangeDecoder, posState: number): number { if (rc.bit(this.choice, 0) === 0) return rc.tree(this.low, posState << 3, 3) if (rc.bit(this.choice, 1) === 0) return 8 + rc.tree(this.mid, posState << 3, 3) return 16 + rc.tree(this.high, 0, 8) } } /** Decodes `size` bytes of a raw LZMA stream with its 5 property bytes (lc/lp/pb, dictionary size). */ export function lzmaDecode(properties: Uint8Array, input: Uint8Array, size: number): Buffer { if (properties.length < 5) throw new LzmaError('LZMA: short properties') let d = properties[0] if (d >= 9 * 5 * 5) throw new LzmaError('LZMA: bad properties') const lc = d % 9 d = Math.floor(d / 9) const lp = d % 5 const pb = Math.floor(d / 5) const dictSize = Math.max(properties[1] | (properties[2] << 8) | (properties[3] << 16) | (properties[4] << 24), 1 << 12) >>> 0 const out = Buffer.alloc(size) let pos = 0 const rc = new RangeDecoder(input, 0) const literals = probs(0x300 << (lc + lp)) const posSlot = probs(LEN_TO_POS_STATES << 6) const align = probs(1 << ALIGN_BITS) const posDecoders = probs(1 + FULL_DISTANCES - END_POS_MODEL_INDEX) const isMatch = probs(STATES << POS_BITS_MAX) const isRep = probs(STATES) const isRepG0 = probs(STATES) const isRepG1 = probs(STATES) const isRepG2 = probs(STATES) const isRep0Long = probs(STATES << POS_BITS_MAX) const lenDecoder = new LenDecoder() const repLenDecoder = new LenDecoder() const distance = (len: number): number => { const lenState = Math.min(len, LEN_TO_POS_STATES - 1) const slot = rc.tree(posSlot, lenState << 6, 6) if (slot < 4) return slot const directBits = (slot >>> 1) - 1 let dist = ((2 | (slot & 1)) * 2 ** directBits) >>> 0 if (slot < END_POS_MODEL_INDEX) { dist = (dist + rc.reverseTree(posDecoders, dist - slot, directBits)) >>> 0 } else { dist = (dist + rc.direct(directBits - ALIGN_BITS) * 2 ** ALIGN_BITS) >>> 0 dist = (dist + rc.reverseTree(align, 0, ALIGN_BITS)) >>> 0 } return dist } let state = 0 let rep0 = 0 let rep1 = 0 let rep2 = 0 let rep3 = 0 while (pos < size) { const posState = pos & ((1 << pb) - 1) if (rc.bit(isMatch, (state << POS_BITS_MAX) + posState) === 0) { const prev = pos > 0 ? out[pos - 1] : 0 const base = 0x300 * (((pos & ((1 << lp) - 1)) << lc) + (prev >>> (8 - lc))) let symbol = 1 if (state >= 7) { let match = out[pos - rep0 - 1] do { const matchBit = (match >>> 7) & 1 match <<= 1 const bit = rc.bit(literals, base + ((1 + matchBit) << 8) + symbol) symbol = (symbol << 1) | bit if (matchBit !== bit) break } while (symbol < 0x100) } while (symbol < 0x100) symbol = (symbol << 1) | rc.bit(literals, base + symbol) out[pos++] = symbol - 0x100 state = state < 4 ? 0 : state < 10 ? state - 3 : state - 6 continue } let len: number if (rc.bit(isRep, state) !== 0) { if (pos === 0) throw new LzmaError('LZMA: repeat before any output') if (rc.bit(isRepG0, state) === 0) { if (rc.bit(isRep0Long, (state << POS_BITS_MAX) + posState) === 0) { state = state < 7 ? 9 : 11 out[pos] = out[pos - rep0 - 1] pos++ continue } } else { let dist: number if (rc.bit(isRepG1, state) === 0) { dist = rep1 } else { if (rc.bit(isRepG2, state) === 0) { dist = rep2 } else { dist = rep3 rep3 = rep2 } rep2 = rep1 } rep1 = rep0 rep0 = dist } len = repLenDecoder.decode(rc, posState) state = state < 7 ? 8 : 11 } else { rep3 = rep2 rep2 = rep1 rep1 = rep0 len = lenDecoder.decode(rc, posState) state = state < 7 ? 7 : 10 rep0 = distance(len) // The end marker: allowed only where the output is complete, which the loop already ended at. if (rep0 === 0xffffffff) throw new LzmaError('LZMA: end marker before the end') if (rep0 >= dictSize || rep0 >= pos) throw new LzmaError('LZMA: distance out of range') } len += MATCH_MIN_LEN if (len > size - pos) throw new LzmaError('LZMA: match past the end') for (let i = 0; i < len; i++, pos++) out[pos] = out[pos - rep0 - 1] } return out }