UNPKG

8.29 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7var _assert = _interopRequireDefault(require("assert"));
8var _zlib = _interopRequireDefault(require("zlib"));
9var _definitions = require("./definitions");
10var _virtualBuffer = require("./virtual-buffer");
11function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12const SECTOR_SIZE = 512;
13const HEADER_SIZE = 512;
14const VERSION_OFFSET = 4;
15function parseDescriptor(descriptorSlice) {
16 const descriptorText = descriptorSlice.toString('ascii').replace(/\x00+$/, '');
17 const descriptorDict = {};
18 const extentList = [];
19 const lines = descriptorText.split(/\r?\n/).filter(line => {
20 return line.trim().length > 0 && line[0] !== '#';
21 });
22 for (const line of lines) {
23 const defLine = line.split('=');
24 if (defLine.length === 2 && defLine[0].indexOf('"') === -1) {
25 descriptorDict[defLine[0]] = defLine[1].replace(/['"]+/g, '');
26 } else {
27 const items = line.split(' ');
28 extentList.push({
29 access: items[0],
30 sizeSectors: items[1],
31 size: items[1] * 512,
32 type: items[2],
33 name: items[3],
34 offset: items.length > 4 ? items[4] : 0
35 });
36 }
37 }
38 return {
39 descriptor: descriptorDict,
40 extents: extentList
41 };
42}
43function readGrain(offsetSectors, buffer, compressed) {
44 const offset = offsetSectors * SECTOR_SIZE;
45 const size = buffer.readUInt32LE(offset + 8);
46 const grainBuffer = buffer.slice(offset + 12, offset + 12 + size);
47 const grainContent = compressed ? _zlib.default.inflateSync(grainBuffer) : grainBuffer;
48 const lba = (0, _definitions.parseU64b)(buffer, offset, 'l2Lba');
49 return {
50 offsetSectors,
51 offset,
52 lba,
53 lbaBytes: lba * SECTOR_SIZE,
54 size,
55 buffer: grainBuffer,
56 grain: grainContent,
57 grainSize: grainContent.byteLength
58 };
59}
60function parseMarker(buffer) {
61 const value = buffer.readUInt32LE(0);
62 const size = buffer.readUInt32LE(8);
63 const type = buffer.readUInt32LE(12);
64 return {
65 value,
66 size,
67 type
68 };
69}
70function alignSectors(number) {
71 return Math.ceil(number / SECTOR_SIZE) * SECTOR_SIZE;
72}
73class VMDKDirectParser {
74 constructor(readStream, grainLogicalAddressList, grainFileOffsetList, gzipped = false, length) {
75 if (gzipped) {
76 const unzipStream = _zlib.default.createGunzip();
77 readStream.pipe(unzipStream);
78 readStream = unzipStream;
79 }
80 this.grainLogicalAddressList = grainLogicalAddressList;
81 this.grainFileOffsetList = grainFileOffsetList;
82 this.virtualBuffer = new _virtualBuffer.VirtualBuffer(readStream);
83 this.header = null;
84 this._length = length;
85 }
86 async _readL1() {
87 const position = this.virtualBuffer.position;
88 const l1entries = Math.floor((this.header.capacitySectors + this.header.l1EntrySectors - 1) / this.header.l1EntrySectors);
89 const sectorAlignedL1Bytes = alignSectors(l1entries * 4);
90 const l1Buffer = await this.virtualBuffer.readChunk(sectorAlignedL1Bytes, 'L1 table ' + position);
91 let l2Start = 0;
92 let l2IsContiguous = true;
93 for (let i = 0; i < l1entries; i++) {
94 const l1Entry = l1Buffer.readUInt32LE(i * 4);
95 if (i > 0) {
96 const previousL1Entry = l1Buffer.readUInt32LE((i - 1) * 4);
97 l2IsContiguous = l2IsContiguous && l1Entry - previousL1Entry === 4;
98 } else {
99 l2IsContiguous = l1Entry * SECTOR_SIZE === this.virtualBuffer.position || l1Entry * SECTOR_SIZE === this.virtualBuffer.position + SECTOR_SIZE;
100 l2Start = l1Entry * SECTOR_SIZE;
101 }
102 }
103 if (!l2IsContiguous) {
104 return null;
105 }
106 const l1L2FreeSpace = l2Start - this.virtualBuffer.position;
107 if (l1L2FreeSpace > 0) {
108 await this.virtualBuffer.readChunk(l1L2FreeSpace, 'freeSpace between L1 and L2');
109 }
110 const l2entries = Math.ceil(this.header.capacitySectors / this.header.grainSizeSectors);
111 const l2ByteSize = alignSectors(l1entries * this.header.numGTEsPerGT * 4);
112 const l2Buffer = await this.virtualBuffer.readChunk(l2ByteSize, 'L2 table ' + position);
113 let firstGrain = null;
114 for (let i = 0; i < l2entries; i++) {
115 const l2Entry = l2Buffer.readUInt32LE(i * 4);
116 if (firstGrain === null) {
117 firstGrain = l2Entry;
118 }
119 }
120 const freeSpace = firstGrain * SECTOR_SIZE - this.virtualBuffer.position;
121 if (freeSpace > 0) {
122 await this.virtualBuffer.readChunk(freeSpace, 'freeSpace after L2');
123 }
124 }
125 async readHeader() {
126 const headerBuffer = await this.virtualBuffer.readChunk(HEADER_SIZE, 'readHeader');
127 const magicString = headerBuffer.slice(0, 4).toString('ascii');
128 if (magicString !== 'KDMV') {
129 throw new Error('not a VMDK file');
130 }
131 const version = headerBuffer.readUInt32LE(VERSION_OFFSET);
132 if (version !== 1 && version !== 3) {
133 throw new Error('unsupported VMDK version ' + version + ', only version 1 and 3 are supported');
134 }
135 this.header = (0, _definitions.unpackHeader)(headerBuffer);
136 const descriptorLength = this.header.descriptorSizeSectors * SECTOR_SIZE;
137 const descriptorBuffer = await this.virtualBuffer.readChunk(descriptorLength, 'descriptor');
138 this.descriptor = parseDescriptor(descriptorBuffer);
139 let l1PositionBytes = null;
140 if (this.header.grainDirectoryOffsetSectors !== -1 && this.header.grainDirectoryOffsetSectors !== 0) {
141 l1PositionBytes = this.header.grainDirectoryOffsetSectors * SECTOR_SIZE;
142 }
143 const endOfDescriptor = this.virtualBuffer.position;
144 if (l1PositionBytes !== null && (l1PositionBytes === endOfDescriptor || l1PositionBytes === endOfDescriptor + SECTOR_SIZE)) {
145 if (l1PositionBytes === endOfDescriptor + SECTOR_SIZE) {
146 await this.virtualBuffer.readChunk(SECTOR_SIZE, 'skipping L1 marker');
147 }
148 await this._readL1();
149 }
150 return this.header;
151 }
152 async parseMarkedGrain(expectedLogicalAddress) {
153 const position = this.virtualBuffer.position;
154 const sector = await this.virtualBuffer.readChunk(SECTOR_SIZE, `marker starting at ${position}`);
155 const marker = parseMarker(sector);
156 if (marker.size === 0) {
157 throw new Error(`expected grain marker, received ${marker}`);
158 } else if (marker.size > 10) {
159 const grainDiskSize = marker.size + 12;
160 const alignedGrainDiskSize = alignSectors(grainDiskSize);
161 const remainOfBufferSize = alignedGrainDiskSize - SECTOR_SIZE;
162 const remainderOfGrainBuffer = await this.virtualBuffer.readChunk(remainOfBufferSize, `grain remainder ${this.virtualBuffer.position} -> ${this.virtualBuffer.position + remainOfBufferSize}`);
163 const grainBuffer = Buffer.concat([sector, remainderOfGrainBuffer]);
164 const grainObject = readGrain(0, grainBuffer, this.header.compressionMethod === _definitions.compressionDeflate && this.header.flags.compressedGrains);
165 _assert.default.strictEqual(grainObject.lba * SECTOR_SIZE, expectedLogicalAddress);
166 return grainObject.grain;
167 }
168 }
169 async *blockIterator() {
170 for (let tableIndex = 0; tableIndex < this.grainFileOffsetList.length; tableIndex++) {
171 const position = this.virtualBuffer.position;
172 const grainPosition = this.grainFileOffsetList[tableIndex] * SECTOR_SIZE;
173 const grainSizeBytes = this.header.grainSizeSectors * SECTOR_SIZE;
174 const lba = this.grainLogicalAddressList[tableIndex] * grainSizeBytes;
175 _assert.default.strictEqual(grainPosition >= position, true, `Grain position ${grainPosition} must be after current position ${position}`);
176 await this.virtualBuffer.readChunk(grainPosition - position, `blank from ${position} to ${grainPosition}`);
177 let grain;
178 if (this.header.flags.hasMarkers) {
179 grain = await this.parseMarkedGrain(lba);
180 } else {
181 grain = await this.virtualBuffer.readChunk(grainSizeBytes, 'grain ' + this.virtualBuffer.position);
182 }
183 yield {
184 logicalAddressBytes: lba,
185 data: grain
186 };
187 }
188 if (this._length !== undefined) {
189 while (this.virtualBuffer.position < this._length) {
190 await this.virtualBuffer.readChunk(Math.min(this._length - this.virtualBuffer.position, 1024 * 1024), 'draining');
191 }
192 }
193 }
194}
195exports.default = VMDKDirectParser;
196//# sourceMappingURL=vmdk-read.js.map
\No newline at end of file