Further work
parent
902870ffac
commit
909f20a097
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const isoParser = require("../lib/iso9660");
|
||||
const yargs = require("yargs");
|
||||
|
||||
const fsSeekable = require("../lib/seekable/fs");
|
||||
const httpSeekable = require("../lib/seekable/http");
|
||||
const renderVolumeTable = require("../lib/render/volume-table");
|
||||
const renderFilesystemTree = require("../lib/render/filesystem-tree");
|
||||
|
||||
let argv = yargs.argv;
|
||||
let command, target, subCommands;
|
||||
|
||||
if (argv._.length === 1) {
|
||||
command = "volumes";
|
||||
target = argv._[0];
|
||||
} else if (argv._.length > 1) {
|
||||
[command, target, ...subCommands] = argv._;
|
||||
} else {
|
||||
throw new Error("You must specify at least a target file or URL");
|
||||
}
|
||||
|
||||
Promise.try(() => {
|
||||
if (target.match(/^https?:\/\//)) {
|
||||
return httpSeekable(target);
|
||||
} else {
|
||||
return fsSeekable(target);
|
||||
}
|
||||
}).then((seekable) => {
|
||||
let parser = isoParser(seekable);
|
||||
|
||||
if (command === "volumes") {
|
||||
return Promise.try(() => {
|
||||
return parser.getUniqueVolumeDescriptors();
|
||||
}).then((volumeDescriptors) => {
|
||||
console.log(renderVolumeTable(volumeDescriptors));
|
||||
});
|
||||
} else if (command === "tree") {
|
||||
return Promise.try(() => {
|
||||
if (argv.volume != null) {
|
||||
let volumeId = parseInt(argv.volume);
|
||||
|
||||
if (isNaN(volumeId)) {
|
||||
throw new Error("--volume argument must be numeric");
|
||||
}
|
||||
|
||||
return parser.getRootDirectory(volumeId);
|
||||
} else {
|
||||
return parser.getRootDirectory();
|
||||
}
|
||||
}).then((rootDirectory) => {
|
||||
return rootDirectory.getTree();
|
||||
}).then((tree) => {
|
||||
console.log(renderFilesystemTree(tree));
|
||||
})
|
||||
}
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(rules) {
|
||||
let fields = rules.map((field, i) => {
|
||||
if (field != null) {
|
||||
return {
|
||||
field: field,
|
||||
value: Math.pow(2, i)
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter((item) => item != null);
|
||||
|
||||
return function parseBits(value) {
|
||||
return fields.reduce((flags, item) => {
|
||||
flags[item.field] = !!(value & item.value);
|
||||
return flags;
|
||||
}, {});
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function createBufferReaderFactory(rules) {
|
||||
return function createBufferReader(buffer) {
|
||||
let defaultMethods = {
|
||||
slice: function readSlice(start, length) {
|
||||
return buffer.slice(start, start + length);
|
||||
}
|
||||
}
|
||||
|
||||
let customMethods = Object.keys(rules).map((ruleName) => {
|
||||
let rule = rules[ruleName];
|
||||
let readerFunction;
|
||||
|
||||
return {
|
||||
key: ruleName,
|
||||
value: function(start, length) {
|
||||
let actualLength;
|
||||
|
||||
if (rule.length != null) {
|
||||
actualLength = rule.length;
|
||||
} else {
|
||||
actualLength = length;
|
||||
}
|
||||
|
||||
return rule.decode(buffer.slice(start, start + actualLength));
|
||||
}
|
||||
}
|
||||
}).reduce((methods, item) => {
|
||||
methods[item.key] = item.value;
|
||||
return methods;
|
||||
}, {});
|
||||
|
||||
return Object.assign(defaultMethods, customMethods);
|
||||
}
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const deepEql = require("deep-eql");
|
||||
|
||||
module.exports = function deeplyUnique(items) {
|
||||
return items.reduce((uniques, item) => {
|
||||
let index = uniques.findIndex((uniqueItem) => {
|
||||
return (uniqueItem.type === item.type && deepEql(uniqueItem.data, item.data));
|
||||
});
|
||||
|
||||
if (index === -1) {
|
||||
return uniques.concat([item]);
|
||||
} else {
|
||||
return uniques;
|
||||
}
|
||||
}, []);
|
||||
}
|
@ -1,43 +1,182 @@
|
||||
'use strict';
|
||||
|
||||
const types = require("./types");
|
||||
|
||||
module.exports = function createBufferReader(buffer) {
|
||||
let typeSpecs = {
|
||||
strA: {},
|
||||
strD: {},
|
||||
strFilename: {},
|
||||
int8: { length: 1 },
|
||||
sint8: { length: 1 },
|
||||
int16_LSB: { length: 2 },
|
||||
int16_MSB: { length: 2 },
|
||||
int16_LSB_MSB: { length: 4 },
|
||||
sint16_LSB: { length: 2 },
|
||||
sint16_MSB: { length: 2 },
|
||||
sint16_LSB_MSB: { length: 4 },
|
||||
int32_LSB: { length: 4 },
|
||||
int32_MSB: { length: 4 },
|
||||
int32_LSB_MSB: { length: 8 },
|
||||
sint32_LSB: { length: 4 },
|
||||
sint32_MSB: { length: 4 },
|
||||
sint32_LSB_MSB: { length: 8 },
|
||||
decDatetime: { length: 17 },
|
||||
directoryDatetime: { length: 7 }
|
||||
}
|
||||
const moment = require("moment");
|
||||
|
||||
const bufferReaderFactory = require("../buffer-reader");
|
||||
const decode = require("./decode");
|
||||
const removeRightPadding = require("../remove-right-padding");
|
||||
|
||||
function offsetInMinutes(value) {
|
||||
/* "Time zone offset from GMT in 15 minute intervals, starting at interval -48 (west) and running up to interval 52 (east).
|
||||
* So value 0 indicates interval -48 which equals GMT-12 hours, and value 100 indicates interval 52 which equals GMT+13 hours."
|
||||
*
|
||||
* ref: http://wiki.osdev.org/ISO_9660#Date.2Ftime_format
|
||||
*/
|
||||
|
||||
return (value - 48) * 15;
|
||||
}
|
||||
|
||||
module.exports = function createReader(buffer, {encoding} = {encoding: "ascii"}) {
|
||||
let bufferReaderRules = {
|
||||
/* String types */
|
||||
strA: {
|
||||
decode: function parseStrA(buffer) {
|
||||
return removeRightPadding(decode(buffer, "ascii"));
|
||||
}
|
||||
},
|
||||
strD: {
|
||||
decode: function parseStrD(buffer) {
|
||||
return bufferReaderRules.strA.decode(buffer);
|
||||
}
|
||||
},
|
||||
strA1: {
|
||||
decode: function parseStrA(buffer) {
|
||||
return removeRightPadding(decode(buffer, encoding));
|
||||
}
|
||||
},
|
||||
strD1: {
|
||||
decode: function parseStrD(buffer) {
|
||||
return bufferReaderRules.strA1.decode(buffer);
|
||||
}
|
||||
},
|
||||
strFilename: {
|
||||
decode: function parseStrFilename(buffer) {
|
||||
let decodedString = decode(buffer, encoding);
|
||||
let [filename, version] = decodedString.split(";");
|
||||
|
||||
return {filename, version};
|
||||
}
|
||||
},
|
||||
|
||||
return Object.keys(typeSpecs).reduce((methods, type) => {
|
||||
let options = typeSpecs[type];
|
||||
/* 8-bit integers */
|
||||
int8: {
|
||||
length: 1,
|
||||
decode: function parseInt8(buffer) {
|
||||
return buffer.readUInt8(0);
|
||||
}
|
||||
},
|
||||
sint8: {
|
||||
length: 1,
|
||||
decode: function parseSInt8(buffer) {
|
||||
return buffer.readInt8(0);
|
||||
}
|
||||
},
|
||||
|
||||
if (options.length == null) {
|
||||
methods[type] = function(offset, length) {
|
||||
return types[type](buffer.slice(offset, offset + length));
|
||||
/* 16-bit integers */
|
||||
int16_LSB: {
|
||||
length: 2,
|
||||
decode: function parseInt16_LSB(buffer) {
|
||||
return buffer.readUInt16LE(0);
|
||||
}
|
||||
},
|
||||
int16_MSB: {
|
||||
length: 2,
|
||||
decode: function parseInt16_LSB(buffer) {
|
||||
return buffer.readUInt16BE(0);
|
||||
}
|
||||
},
|
||||
int16_LSB_MSB: {
|
||||
length: 4,
|
||||
decode: function parseInt16_LSB(buffer) {
|
||||
return bufferReaderRules.int16_LSB.decode(buffer.slice(0, 2));
|
||||
}
|
||||
},
|
||||
sint16_LSB: {
|
||||
length: 2,
|
||||
decode: function parseSInt16_LSB(buffer) {
|
||||
return buffer.readInt16LE(0);
|
||||
}
|
||||
} else {
|
||||
methods[type] = function(offset) {
|
||||
return types[type](buffer.slice(offset, offset + options.length));
|
||||
},
|
||||
sint16_MSB: {
|
||||
length: 2,
|
||||
decode: function parseSInt16_LSB(buffer) {
|
||||
return buffer.readInt16BE(0);
|
||||
}
|
||||
},
|
||||
sint16_LSB_MSB: {
|
||||
length: 4,
|
||||
decode: function parseSInt16_LSB(buffer) {
|
||||
return bufferReaderRules.sint16_LSB.decode(buffer.slice(0, 2));
|
||||
}
|
||||
},
|
||||
|
||||
/* 32-bit integers */
|
||||
int32_LSB: {
|
||||
length: 4,
|
||||
decode: function parseInt32_LSB(buffer) {
|
||||
return buffer.readUInt32LE(0);
|
||||
}
|
||||
},
|
||||
int32_MSB: {
|
||||
length: 4,
|
||||
decode: function parseInt32_LSB(buffer) {
|
||||
return buffer.readUInt32BE(0);
|
||||
}
|
||||
},
|
||||
int32_LSB_MSB: {
|
||||
length: 8,
|
||||
decode: function parseInt32_LSB(buffer) {
|
||||
return bufferReaderRules.int32_LSB.decode(buffer.slice(0, 4));
|
||||
}
|
||||
},
|
||||
sint32_LSB: {
|
||||
length: 4,
|
||||
decode: function parseSInt32_LSB(buffer) {
|
||||
return buffer.readInt32LE(0);
|
||||
}
|
||||
},
|
||||
sint32_MSB: {
|
||||
length: 4,
|
||||
decode: function parseSInt32_LSB(buffer) {
|
||||
return buffer.readInt32BE(0);
|
||||
}
|
||||
},
|
||||
sint32_LSB_MSB: {
|
||||
length: 8,
|
||||
decode: function parseSInt32_LSB(buffer) {
|
||||
return bufferReaderRules.sint32_LSB.decode(buffer.slice(0, 4));
|
||||
}
|
||||
},
|
||||
|
||||
/* Date/time */
|
||||
decDatetime: {
|
||||
length: 17,
|
||||
decode: function parseDecDatetime(buffer) {
|
||||
let year = parseInt(bufferReaderRules.strD.decode(buffer.slice(0, 4)));
|
||||
let month = parseInt(bufferReaderRules.strD.decode(buffer.slice(4, 6))) - 1; // "Note that like moment(Array) and new Date(year, month, date), months are 0 indexed."
|
||||
let day = parseInt(bufferReaderRules.strD.decode(buffer.slice(6, 8)));
|
||||
|
||||
let hour = parseInt(bufferReaderRules.strD.decode(buffer.slice(8, 10)));
|
||||
let minute = parseInt(bufferReaderRules.strD.decode(buffer.slice(10, 12)));
|
||||
let second = parseInt(bufferReaderRules.strD.decode(buffer.slice(12, 14)));
|
||||
let centisecond = parseInt(bufferReaderRules.strD.decode(buffer.slice(14, 16)));
|
||||
|
||||
let timezoneOffset = bufferReaderRules.int8.decode(buffer.slice(16, 17));
|
||||
|
||||
if (year === 0 && month === 0 && day === 0 && hour === 0 && minute === 0 && second === 0 && centisecond === 0 && timezoneOffset === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return moment({
|
||||
year, month, day, hour, minute, second,
|
||||
millisecond: centisecond * 10,
|
||||
}).utcOffset(offsetInMinutes(timezoneOffset));
|
||||
}
|
||||
}
|
||||
},
|
||||
directoryDatetime: {
|
||||
length: 7,
|
||||
decode: function parseDirectoryDatetime(buffer) {
|
||||
return moment({
|
||||
year: 1900 + bufferReaderRules.int8.decode(buffer.slice(0, 0 + 1)),
|
||||
month: bufferReaderRules.int8.decode(buffer.slice(1, 1 + 1)) - 1,
|
||||
day: bufferReaderRules.int8.decode(buffer.slice(2, 2 + 1)),
|
||||
hour: bufferReaderRules.int8.decode(buffer.slice(3, 3 + 1)),
|
||||
minute: bufferReaderRules.int8.decode(buffer.slice(4, 4 + 1)),
|
||||
second: bufferReaderRules.int8.decode(buffer.slice(5, 5 + 1)),
|
||||
}).utcOffset(offsetInMinutes(bufferReaderRules.int8.decode(buffer.slice(6, 6 + 1))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}, {});
|
||||
return bufferReaderFactory(bufferReaderRules)(buffer);
|
||||
};
|
||||
|
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const iconvLite = require("iconv-lite");
|
||||
|
||||
let encodingMap = {
|
||||
"ascii": "ascii",
|
||||
/* The following are strictly not the same thing, but there's backwards compatibility. */
|
||||
"ucs2-level1": "utf16-be",
|
||||
"ucs2-level2": "utf16-be",
|
||||
"ucs2-level3": "utf16-be"
|
||||
}
|
||||
|
||||
module.exports = function decode(buffer, encoding) {
|
||||
if (encodingMap[encoding] == null) {
|
||||
throw new Error(`No such encoding: ${encoding}`);
|
||||
}
|
||||
|
||||
return iconvLite.decode(buffer, encodingMap[encoding]);
|
||||
};
|
@ -1,5 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const createBufferReader = require("../../buffer-reader");
|
||||
const createBitParser = require("../../../bit-parser");
|
||||
const parsePrimaryVolumeDescriptor = require("./primary-volume");
|
||||
const parseEscapeSequences = require("../../parse/escape-sequences");
|
||||
|
||||
/* Supplementary volume flags: (ref http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf)
|
||||
* Bit Description
|
||||
* 0 If set, the Escape Sequences field specifies at least one escape sequence not registered according to ISO 2375.
|
||||
* 1 - 7 Reserved
|
||||
*/
|
||||
|
||||
let parseVolumeFlags = createBitParser([
|
||||
"hasNonISO2375EscapeSequences",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]);
|
||||
|
||||
module.exports = function parseSupplementaryVolumeDescriptor(data) {
|
||||
throw new Error("Not implemented yet");
|
||||
let bufferReader = createBufferReader(data);
|
||||
|
||||
let escapeSequences = data.slice(81, 113);
|
||||
let encodings = parseEscapeSequences(escapeSequences);
|
||||
|
||||
/* The format of a primary and supplementary volume descriptor are mostly the same - the only real difference is that a supplementary volume descriptor will also have a volumeFlags and escapeSequences field. We can therefore just parse this volume descriptor like a primary volume descriptor, and then merge in the additional fields. */
|
||||
|
||||
return Object.assign(parsePrimaryVolumeDescriptor(data, encodings[0]), {
|
||||
volumeFlags: parseVolumeFlags(bufferReader.int8(0)),
|
||||
escapeSequences: escapeSequences,
|
||||
encodings: encodings
|
||||
});
|
||||
};
|
||||
|
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const renderBytes = require("../../render/bytes");
|
||||
|
||||
let sequenceMap = {
|
||||
"ucs2-level1": Buffer.from([0x25, 0x2F, 0x40]),
|
||||
"ucs2-level2": Buffer.from([0x25, 0x2F, 0x43]),
|
||||
"ucs2-level3": Buffer.from([0x25, 0x2F, 0x45])
|
||||
}
|
||||
|
||||
module.exports = function parseEscapeSequences(escapeSequences) {
|
||||
let pos = 0;
|
||||
let finished = false;
|
||||
let encodings = [];
|
||||
|
||||
while (finished === false) {
|
||||
if (escapeSequences.readUInt8(pos) === 0) {
|
||||
/* We've run out of escape sequences to parse */
|
||||
break;
|
||||
}
|
||||
|
||||
/* We only check for 3-byte-long escape sequences, for now... */
|
||||
let slice3 = escapeSequences.slice(pos, pos + 3);
|
||||
|
||||
let encoding = Object.keys(sequenceMap).find((encoding) => sequenceMap[encoding].equals(slice3));
|
||||
|
||||
if (encoding == null) {
|
||||
throw new Error(`Encountered unrecognized escape sequence; remaining bytes to parse: ${renderBytes(escapeSequences.slice(pos))}`);
|
||||
} else {
|
||||
pos += sequenceMap[encoding].length;
|
||||
encodings.push(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (encodings.length === 0) {
|
||||
return ["ascii"];
|
||||
} else {
|
||||
return encodings;
|
||||
}
|
||||
};
|
@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
const createBufferReader = require("../buffer-reader");
|
||||
const createBitParser = require("../../bit-parser");
|
||||
const decode = require("../decode");
|
||||
const parseEscapeSequences = require("./escape-sequences");
|
||||
|
||||
let parsePermissions = createBitParser([
|
||||
"groupOwnerReadable",
|
||||
null,
|
||||
"groupOwnerExecutable",
|
||||
null,
|
||||
"ownerReadable",
|
||||
null,
|
||||
"ownerExecutable",
|
||||
null,
|
||||
"groupReadable",
|
||||
null,
|
||||
"groupExecutable",
|
||||
null,
|
||||
"worldReadable",
|
||||
null,
|
||||
"worldExecutable",
|
||||
null
|
||||
], {inverse: [0, 2, 4, 6, 8, 10, 12, 14]})
|
||||
|
||||
function parseRecordFormat(value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return "unspecified";
|
||||
case 1:
|
||||
return "fixedLength";
|
||||
case 2:
|
||||
return "variableLengthLSB";
|
||||
case 3:
|
||||
return "variableLengthMSB";
|
||||
default:
|
||||
throw new Error(`Unknown record format encountered: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseRecordAttributes(value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return "lineFeedCarriageReturn";
|
||||
case 1:
|
||||
return "iso1539";
|
||||
case 2:
|
||||
return "containedInRecord";
|
||||
default:
|
||||
throw new Error(`Unknown record attributes encountered: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function parseExtendedAttributeRecord(data, {encoding} = {encoding: "ascii"}) {
|
||||
let bufferReader = createBufferReader(data, {encoding: encoding});
|
||||
|
||||
let applicationUseLength = bufferReader.int16_LSB_MSB(246);
|
||||
let escapeSequenceLength = bufferReader.int8(181);
|
||||
|
||||
return {
|
||||
ownerIdentification: bufferReader.int16_LSB_MSB(0),
|
||||
groupIdentification: bufferReader.int16_LSB_MSB(4),
|
||||
permissions: parsePermissions(bufferReader.int16_MSB(8)),
|
||||
creationDate: bufferReader.decDatetime(10),
|
||||
modificationDate: bufferReader.decDatetime(27),
|
||||
expirationDate: bufferReader.decDatetime(44),
|
||||
effectiveDate: bufferReader.decDatetime(61),
|
||||
recordFormat: parseRecordFormat(bufferReader.int8(78)),
|
||||
recordAttributes: parseRecordAttributes(bufferReader.int8(79)),
|
||||
recordLength: bufferReader.int16_LSB_MSB(80),
|
||||
systemIdentifier: bufferReader.strA1(84, 32),
|
||||
systemUse: bufferReader.slice(116, 64),
|
||||
earVersion: bufferReader.int8(180),
|
||||
escapeSequenceLength: escapeSequenceLength,
|
||||
applicationUseLength: applicationUseLength,
|
||||
applicationUse: bufferReader.slice(250, applicationUseLength),
|
||||
escapeSequences: parseEscapeSequences(bufferReader.slice(250 + applicationUseLength, escapeSequenceLength))
|
||||
}
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const Promise = require("bluebird");
|
||||
|
||||
const parseDirectoryRecord = require("../parse/directory-record");
|
||||
|
||||
module.exports = function readDirectoryExtent(image, sector, length, encoding) {
|
||||
return Promise.try(() => {
|
||||
return image.getSectorSize();
|
||||
}).then((sectorSize) => {
|
||||
const roundToNextSector = require("../round-to-next-sector")(sectorSize);
|
||||
|
||||
function splitRecordBuffers(buffer) {
|
||||
let pos = 0;
|
||||
let recordBuffers = [];
|
||||
|
||||
while (pos < buffer.length) {
|
||||
let recordLength = buffer.readUInt8(pos);
|
||||
|
||||
if (recordLength === 0) {
|
||||
/* We ran out of records for this sector, skip to the next. */
|
||||
pos = roundToNextSector(pos);
|
||||
} else {
|
||||
let directoryRecord = buffer.slice(pos, pos + recordLength);
|
||||
recordBuffers.push(directoryRecord);
|
||||
pos += recordLength;
|
||||
}
|
||||
}
|
||||
|
||||
return recordBuffers;
|
||||
}
|
||||
|
||||
return Promise.try(() => {
|
||||
let sectorOffset = sector * sectorSize;
|
||||
return image.readRange(sectorOffset, sectorOffset + length - 1);
|
||||
}).then((buffer) => {
|
||||
let recordBuffers = splitRecordBuffers(buffer);
|
||||
})
|
||||
})
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const Promise = require("bluebird");
|
||||
|
||||
const parseExtendedAttributeRecord = require("../parse/extended-attribute-record");
|
||||
|
||||
module.exports = function readExtendedAttributeRecord(image, sector, sectorCount) {
|
||||
return Promise.try(() => {
|
||||
return image.readSectors(sector, sectorCount);
|
||||
}).then((buffer) => {
|
||||
return parseExtendedAttributeRecord(buffer);
|
||||
});
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const Promise = require("bluebird");
|
||||
|
||||
const parsePathTable = require("../parse/path-table");
|
||||
|
||||
module.exports = function readPathTable(image) {
|
||||
return Promise.try(() => {
|
||||
return image.getPrimaryVolumeDescriptor();
|
||||
}).then((primaryVolumeDescriptor) => {
|
||||
return Promise.try(() => {
|
||||
let start = primaryVolumeDescriptor.data.pathTableLocationL * primaryVolumeDescriptor.data.sectorSize;
|
||||
let end = start + primaryVolumeDescriptor.data.pathTableSize - 1;
|
||||
|
||||
return image.readRange(start, end);
|
||||
}).then((buffer) => {
|
||||
return parsePathTable(buffer, primaryVolumeDescriptor.data.sectorSize);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const promiseWhile = require("promise-while-loop");
|
||||
|
||||
const parseVolumeDescriptor = require("../parse/volume-descriptor");
|
||||
|
||||
module.exports = function readVolumeDescriptors(image) {
|
||||
return Promise.try(() => {
|
||||
let encounteredTerminator = false;
|
||||
let currentSector = 16;
|
||||
|
||||
return promiseWhile(() => encounteredTerminator === false, (lastResult) => {
|
||||
return Promise.try(() => {
|
||||
let offset = currentSector * 2048; // Always assume a sector size of 2048 for volume descriptors
|
||||
|
||||
return image.readRange(offset, offset + 2047);
|
||||
}).then((buffer) => {
|
||||
let descriptor = parseVolumeDescriptor(buffer);
|
||||
|
||||
if (descriptor.type === "terminator") {
|
||||
encounteredTerminator = true;
|
||||
}
|
||||
|
||||
currentSector += 1;
|
||||
return descriptor;
|
||||
});
|
||||
});
|
||||
}).map((volumeDescriptor, i) => {
|
||||
return Object.assign({
|
||||
volumeDescriptorIndex: i
|
||||
}, volumeDescriptor);
|
||||
});
|
||||
};
|
@ -1,130 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const moment = require("moment");
|
||||
const removeRightPadding = require("../remove-right-padding");
|
||||
|
||||
/* Basic ISO 9660 types: (ref http://wiki.osdev.org/ISO_9660#Numerical_formats)
|
||||
* int8 Unsigned 8-bit integer.
|
||||
* sint8 Signed 8-bit integer.
|
||||
* int16_LSB Little-endian encoded unsigned 16-bit integer.
|
||||
* int16_MSB Big-endian encoded unsigned 16-bit integer.
|
||||
* int16_LSB-MSB Little-endian followed by big-endian encoded unsigned 16-bit integer.
|
||||
* sint16_LSB Little-endian encoded signed 16-bit integer.
|
||||
* sint16_MSB Big-endian encoded signed 16-bit integer.
|
||||
* sint16_LSB-MSB Little-endian followed by big-endian encoded signed 16-bit integer.
|
||||
* int32_LSB Little-endian encoded unsigned 32-bit integer.
|
||||
* int32_MSB Big-endian encoded unsigned 32-bit integer.
|
||||
* int32_LSB-MSB Little-endian followed by big-endian encoded unsigned 32-bit integer.
|
||||
* sint32_LSB Little-endian encoded signed 32-bit integer.
|
||||
* sint32_MSB Big-endian encoded signed 32-bit integer.
|
||||
* sint32_LSB-MSB Little-endian followed by big-endian encoded signed 32-bit integer.
|
||||
*
|
||||
* "Where a both-endian format is present, the x86 architecture makes use of the first little-endian sequence and ignores the big-endian sequence."
|
||||
*/
|
||||
|
||||
function offsetInMinutes(value) {
|
||||
/* "Time zone offset from GMT in 15 minute intervals, starting at interval -48 (west) and running up to interval 52 (east).
|
||||
* So value 0 indicates interval -48 which equals GMT-12 hours, and value 100 indicates interval 52 which equals GMT+13 hours."
|
||||
*
|
||||
* ref: http://wiki.osdev.org/ISO_9660#Date.2Ftime_format
|
||||
*/
|
||||
|
||||
return (value - 48) * 15;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/* String types */
|
||||
strA: function parseStrA(buffer) {
|
||||
return removeRightPadding(buffer.toString("ascii"));
|
||||
},
|
||||
strD: function parseStrD(buffer) {
|
||||
return this.strA(buffer);
|
||||
},
|
||||
strFilename: function parseStrFilename(buffer) {
|
||||
let str = this.strA(buffer);
|
||||
let [filename, version] = str.split(";");
|
||||
|
||||
return {filename, version};
|
||||
},
|
||||
|
||||
/* 8-bit integers */
|
||||
int8: function parseInt8(buffer) {
|
||||
return buffer.readUInt8(0);
|
||||
},
|
||||
sint8: function parseSInt8(buffer) {
|
||||
return buffer.readInt8(0);
|
||||
},
|
||||
|
||||
/* 16-bit integers */
|
||||
int16_LSB: function parseInt16_LSB(buffer) {
|
||||
return buffer.readUInt16LE(0);
|
||||
},
|
||||
int16_MSB: function parseInt16_LSB(buffer) {
|
||||
return buffer.readUInt16BE(0);
|
||||
},
|
||||
int16_LSB_MSB: function parseInt16_LSB(buffer) {
|
||||
return this.int16_LSB(buffer.slice(0, 2));
|
||||
},
|
||||
sint16_LSB: function parseSInt16_LSB(buffer) {
|
||||
return buffer.readInt16LE(0);
|
||||
},
|
||||
sint16_MSB: function parseSInt16_LSB(buffer) {
|
||||
return buffer.readInt16BE(0);
|
||||
},
|
||||
sint16_LSB_MSB: function parseSInt16_LSB(buffer) {
|
||||
return this.sint16_LSB(buffer.slice(0, 2));
|
||||
},
|
||||
|
||||
/* 32-bit integers */
|
||||
int32_LSB: function parseInt32_LSB(buffer) {
|
||||
return buffer.readUInt32LE(0);
|
||||
},
|
||||
int32_MSB: function parseInt32_LSB(buffer) {
|
||||
return buffer.readUInt32BE(0);
|
||||
},
|
||||
int32_LSB_MSB: function parseInt32_LSB(buffer) {
|
||||
return this.int32_LSB(buffer.slice(0, 4));
|
||||
},
|
||||
sint32_LSB: function parseSInt32_LSB(buffer) {
|
||||
return buffer.readInt32LE(0);
|
||||
},
|
||||
sint32_MSB: function parseSInt32_LSB(buffer) {
|
||||
return buffer.readInt32BE(0);
|
||||
},
|
||||
sint32_LSB_MSB: function parseSInt32_LSB(buffer) {
|
||||
return this.sint32_LSB(buffer.slice(0, 4));
|
||||
},
|
||||
|
||||
/* Date/time */
|
||||
decDatetime: function parseDecDatetime(buffer) {
|
||||
let year = parseInt(this.strD(buffer.slice(0, 4)));
|
||||
let month = parseInt(this.strD(buffer.slice(4, 6))) - 1; // "Note that like moment(Array) and new Date(year, month, date), months are 0 indexed."
|
||||
let day = parseInt(this.strD(buffer.slice(6, 8)));
|
||||
|
||||
let hour = parseInt(this.strD(buffer.slice(8, 10)));
|
||||
let minute = parseInt(this.strD(buffer.slice(10, 12)));
|
||||
let second = parseInt(this.strD(buffer.slice(12, 14)));
|
||||
let centisecond = parseInt(this.strD(buffer.slice(14, 16)));
|
||||
|
||||
let timezoneOffset = this.int8(buffer.slice(16, 17));
|
||||
|
||||
if (year === 0 && month === 0 && day === 0 && hour === 0 && minute === 0 && second === 0 && centisecond === 0 && timezoneOffset === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return moment({
|
||||
year, month, day, hour, minute, second,
|
||||
millisecond: centisecond * 10,
|
||||
}).utcOffset(offsetInMinutes(timezoneOffset));
|
||||
}
|
||||
},
|
||||
directoryDatetime: function parseDirectoryDatetime(buffer) {
|
||||
return moment({
|
||||
year: 1900 + this.int8(buffer.slice(0, 0 + 1)),
|
||||
month: this.int8(buffer.slice(1, 1 + 1)) - 1,
|
||||
day: this.int8(buffer.slice(2, 2 + 1)),
|
||||
hour: this.int8(buffer.slice(3, 3 + 1)),
|
||||
minute: this.int8(buffer.slice(4, 4 + 1)),
|
||||
second: this.int8(buffer.slice(5, 5 + 1)),
|
||||
}).utcOffset(offsetInMinutes(this.int8(buffer.slice(6, 6 + 1))));
|
||||
}
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function removeRightPadding(string) {
|
||||
return string.replace(/[ ]+$/, "");
|
||||
};
|
||||
return string.replace(/[ \u0000]+$/, "");
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function renderBytes(buffer) {
|
||||
return Array.from(buffer.values()).map((byte) => byte.toString(16)).join(" ");
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const archy = require("archy");
|
||||
const util = require("util");
|
||||
|
||||
function makeArchyNode(node) {
|
||||
if (node.type === "directory") {
|
||||
return {
|
||||
label: node.filename,
|
||||
nodes: node.children.map(makeArchyNode)
|
||||
}
|
||||
} else {
|
||||
return node.filename
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function renderFilesystemTree(rootDirectory) {
|
||||
let archyTree = {
|
||||
label: "(root)",
|
||||
nodes: rootDirectory.map(makeArchyNode)
|
||||
}
|
||||
|
||||
return archy(archyTree);
|
||||
};
|
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const dotty = require("dotty");
|
||||
const asciiDataTable = require("ascii-data-table");
|
||||
const defaultValue = require("default-value");
|
||||
const buffertrim = require("buffertrim");
|
||||
|
||||
const renderBytes = require("./bytes");
|
||||
|
||||
function formatCreationDate(creationDate) {
|
||||
if (creationDate != null) {
|
||||
return creationDate.format("llll");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function formatVolumePosition(sequenceNumber, setSize) {
|
||||
if (sequenceNumber != null) {
|
||||
return `${sequenceNumber}/${setSize}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function formatEncodings(encodings) {
|
||||
if (encodings != null) {
|
||||
return encodings.join(", ");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
let headers = [
|
||||
"ID",
|
||||
"Type",
|
||||
"System",
|
||||
"Volume",
|
||||
"Volume identifier",
|
||||
"Encoding",
|
||||
"Extent",
|
||||
"Creation date"
|
||||
];
|
||||
|
||||
module.exports = function renderVolumeTable(volumeDescriptors) {
|
||||
let tableData = volumeDescriptors.map((descriptor, i) => {
|
||||
|
||||
let volumeIndex = descriptor.volumeDescriptorIndex;
|
||||
let volumeType = descriptor.type;
|
||||
let systemIdentifier = defaultValue(descriptor.data.systemIdentifier, "");
|
||||
let volumeIdentifier = defaultValue(descriptor.data.volumeIdentifier, "");
|
||||
let extentSector = defaultValue(dotty.get(descriptor, "data.rootDirectory.extentLocation"), "");
|
||||
let creationDate = formatCreationDate(descriptor.data.creationDate);
|
||||
let volumePosition = formatVolumePosition(descriptor.data.sequenceNumber, descriptor.data.setSize);
|
||||
let encodings = formatEncodings(descriptor.data.encodings);
|
||||
|
||||
return [
|
||||
volumeIndex,
|
||||
volumeType,
|
||||
systemIdentifier,
|
||||
volumePosition,
|
||||
volumeIdentifier,
|
||||
encodings,
|
||||
extentSector,
|
||||
creationDate
|
||||
];
|
||||
});
|
||||
|
||||
let tableDataWithHeaders = [headers].concat(tableData);
|
||||
|
||||
return asciiDataTable.default.run(tableDataWithHeaders);
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const table = require("table");
|
||||
|
||||
table.default([ [ 0, 'PC', 'SC2KSEPC', '1/1', 24, 'Mon, Apr 13, 1998 8:28 PM' ],
|
||||
[ 1, 'PC', 'SC2KSEPC', '1/1', 24, 'Mon, Apr 13, 1998 8:28 PM' ],
|
||||
[ 2,
|
||||
'\u0000P\u0000C\u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000',
|
||||
'\u0000S\u0000C\u00002\u0000K\u0000S\u0000E\u0000P\u0000C\u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000 \u0000',
|
||||
'1/1',
|
||||
25,
|
||||
'Mon, Apr 13, 1998 8:28 PM' ],
|
||||
[ 3, '', '', 'undefined/undefined', '', '' ] ]);
|
Loading…
Reference in New Issue