|
@@ -0,0 +1,88 @@
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * LZNT1 Decompress.
|
|
|
+ *
|
|
|
+ * @author 0xThiebaut [thiebaut.dev]
|
|
|
+ * @copyright Crown Copyright 2023
|
|
|
+ * @license Apache-2.0
|
|
|
+ *
|
|
|
+ * https://github.com/Velocidex/go-ntfs/blob/master/parser%2Flznt1.go
|
|
|
+ */
|
|
|
+
|
|
|
+import Utils from "../Utils.mjs";
|
|
|
+import OperationError from "../errors/OperationError.mjs";
|
|
|
+
|
|
|
+const COMPRESSED_MASK = 1 << 15,
|
|
|
+ SIZE_MASK = (1 << 12) - 1;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param {number} offset
|
|
|
+ * @returns {number}
|
|
|
+ */
|
|
|
+function getDisplacement(offset) {
|
|
|
+ let result = 0;
|
|
|
+ while (offset >= 0x10) {
|
|
|
+ offset >>= 1;
|
|
|
+ result += 1;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param {byteArray} compressed
|
|
|
+ * @returns {byteArray}
|
|
|
+ */
|
|
|
+export function decompress(compressed) {
|
|
|
+ const decompressed = Array();
|
|
|
+ let coffset = 0;
|
|
|
+
|
|
|
+ while (coffset + 2 <= compressed.length) {
|
|
|
+ const doffset = decompressed.length;
|
|
|
+
|
|
|
+ const blockHeader = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
|
|
|
+ coffset += 2;
|
|
|
+
|
|
|
+ const size = blockHeader & SIZE_MASK;
|
|
|
+ const blockEnd = coffset + size + 1;
|
|
|
+
|
|
|
+ if (size === 0) {
|
|
|
+ break;
|
|
|
+ } else if (compressed.length < coffset + size) {
|
|
|
+ throw new OperationError("Malformed LZNT1 stream: Block too small! Has the stream been truncated?");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((blockHeader & COMPRESSED_MASK) !== 0) {
|
|
|
+ while (coffset < blockEnd) {
|
|
|
+ let header = compressed[coffset++];
|
|
|
+
|
|
|
+ for (let i = 0; i < 8 && coffset < blockEnd; i++) {
|
|
|
+ if ((header & 1) === 0) {
|
|
|
+ decompressed.push(compressed[coffset++]);
|
|
|
+ } else {
|
|
|
+ const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
|
|
|
+ coffset += 2;
|
|
|
+
|
|
|
+ const displacement = getDisplacement(decompressed.length - doffset - 1);
|
|
|
+ const symbolOffset = (pointer >> (12 - displacement)) + 1;
|
|
|
+ const symbolLength = (pointer & (0xFFF >> displacement)) + 2;
|
|
|
+ const shiftOffset = decompressed.length - symbolOffset;
|
|
|
+
|
|
|
+ for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {
|
|
|
+ const shift = shiftOffset + shiftDelta;
|
|
|
+ if (shift < 0 || decompressed.length <= shift) {
|
|
|
+ throw new OperationError("Malformed LZNT1 stream: Invalid shift!");
|
|
|
+ }
|
|
|
+ decompressed.push(decompressed[shift]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ header >>= 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ decompressed.push(...compressed.slice(coffset, coffset + size + 1));
|
|
|
+ coffset += size + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return decompressed;
|
|
|
+}
|