|
| 1 | +/** |
| 2 | + * |
| 3 | + * LZNT1 Decompress. |
| 4 | + * |
| 5 | + * @author 0xThiebaut [thiebaut.dev] |
| 6 | + * @copyright Crown Copyright 2023 |
| 7 | + * @license Apache-2.0 |
| 8 | + * |
| 9 | + * https://github.com/Velocidex/go-ntfs/blob/master/parser%2Flznt1.go |
| 10 | + */ |
| 11 | + |
| 12 | +import Utils from "../Utils.mjs"; |
| 13 | +import OperationError from "../errors/OperationError.mjs"; |
| 14 | + |
| 15 | +const COMPRESSED_MASK = 1 << 15, |
| 16 | + SIZE_MASK = (1 << 12) - 1; |
| 17 | + |
| 18 | +/** |
| 19 | + * @param {number} offset |
| 20 | + * @returns {number} |
| 21 | + */ |
| 22 | +function getDisplacement(offset) { |
| 23 | + let result = 0; |
| 24 | + while (offset >= 0x10) { |
| 25 | + offset >>= 1; |
| 26 | + result += 1; |
| 27 | + } |
| 28 | + return result; |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * @param {byteArray} compressed |
| 33 | + * @returns {byteArray} |
| 34 | + */ |
| 35 | +export function decompress(compressed) { |
| 36 | + const decompressed = Array(); |
| 37 | + let coffset = 0; |
| 38 | + |
| 39 | + while (coffset + 2 <= compressed.length) { |
| 40 | + const doffset = decompressed.length; |
| 41 | + |
| 42 | + const block_header = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little"); |
| 43 | + coffset += 2; |
| 44 | + |
| 45 | + const size = block_header & SIZE_MASK; |
| 46 | + const block_end = coffset + size + 1; |
| 47 | + |
| 48 | + if (size === 0) { |
| 49 | + break; |
| 50 | + } else if (compressed.length < coffset + size) { |
| 51 | + throw new OperationError("Malformed LZNT1 stream: Block too small! Has the stream been truncated?"); |
| 52 | + } |
| 53 | + |
| 54 | + if ((block_header & COMPRESSED_MASK) !== 0) { |
| 55 | + while (coffset < block_end) { |
| 56 | + let header = compressed[coffset++]; |
| 57 | + |
| 58 | + for (let mask_idx = 0; mask_idx < 8 && coffset < block_end; mask_idx++) { |
| 59 | + if ((header & 1) === 0) { |
| 60 | + decompressed.push(compressed[coffset++]); |
| 61 | + } else { |
| 62 | + const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little"); |
| 63 | + coffset += 2; |
| 64 | + |
| 65 | + const displacement = getDisplacement(decompressed.length - doffset - 1); |
| 66 | + const symbol_offset = (pointer >> (12 - displacement)) + 1; |
| 67 | + const symbol_length = (pointer & (0xFFF >> displacement)) + 2; |
| 68 | + const shift_offset = decompressed.length - symbol_offset; |
| 69 | + |
| 70 | + for (let shift_delta = 0; shift_delta < symbol_length + 1; shift_delta++) { |
| 71 | + const shift = shift_offset + shift_delta; |
| 72 | + if (shift < 0 || decompressed.length <= shift) { |
| 73 | + throw new OperationError("Malformed LZNT1 stream: Invalid shift!"); |
| 74 | + } |
| 75 | + decompressed.push(decompressed[shift]); |
| 76 | + } |
| 77 | + } |
| 78 | + header >>= 1; |
| 79 | + } |
| 80 | + } |
| 81 | + } else { |
| 82 | + decompressed.push(...compressed.slice(coffset, coffset + size + 1)); |
| 83 | + coffset += size + 1; |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + return decompressed; |
| 88 | +} |
0 commit comments