1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.default = void 0;
|
7 | var _assert = _interopRequireDefault(require("assert"));
|
8 | var _zlib = _interopRequireDefault(require("zlib"));
|
9 | var _definitions = require("./definitions");
|
10 | var _virtualBuffer = require("./virtual-buffer");
|
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
12 | const SECTOR_SIZE = 512;
|
13 | const HEADER_SIZE = 512;
|
14 | const VERSION_OFFSET = 4;
|
15 | function 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 | }
|
43 | function 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 | }
|
60 | function 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 | }
|
70 | function alignSectors(number) {
|
71 | return Math.ceil(number / SECTOR_SIZE) * SECTOR_SIZE;
|
72 | }
|
73 | class 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 | }
|
195 | exports.default = VMDKDirectParser;
|
196 |
|
\ | No newline at end of file |