Add extract file feature

This commit is contained in:
C-3PO 2018-06-22 15:53:37 +02:00
parent 4aeba1709a
commit 78ac8ae304
Signed by: c3po
GPG key ID: 62993C4BB4D86F24
4 changed files with 95 additions and 7 deletions

View file

@ -28,6 +28,10 @@ interface ISsnFileEntry {
comprSize: number;
/** Decryption keys needed to decrypt the file */
decryptionKeys: [number, number, number] | undefined;
/** Number of the disk where the file is stored (0=.z01, 1=.z02 etc.) */
diskNumberStart: number;
/** Offset */
offset: number;
}
export default ISsnFileEntry;

View file

@ -0,0 +1,17 @@
import getCrc from './getCrc';
import int32Mul from './int32Mul';
export default function decryptFile(dv: DataView, length: number, [key0, key1, key2]: [number, number, number]) {
for (let j = 0; j < length; j += 1) {
let testChar = dv.getUint8(j);
const keyPart = (key2 | 2) & 0xFFFF;
const decryptedByte = (keyPart * (keyPart ^ 1)) >>> 8;
testChar ^= decryptedByte & 0xFF;
key0 = getCrc(key0, testChar);
key1 = ((int32Mul(((key1 + (key0 & 0xFF)) >>> 0), 134775813) >>> 0) + 1) >>> 0;
key2 = getCrc(key2, key1 >>> 24);
dv.setUint8(j, testChar);
}
//if it was decrypted, we skip the first 12 bytes (random encryption header)
return new DataView(dv.buffer, 12);
}

66
src/ssn/extractFile.ts Normal file
View file

@ -0,0 +1,66 @@
import * as zlib from 'zlib';
import ISsnFileEntry from '../interfaces/ISsnFileEntry';
import decryptFile from './decryption/decryptFile';
class ByteReader {
private dvArray: DataView[];
private dvIndex = 0;
private pos = 0;
constructor(dvArray: DataView[], startDvIndex: number, offset: number) {
this.dvArray = dvArray;
this.dvIndex = startDvIndex;
this.pos = offset;
}
public readByte() {
const curByte = this.dvArray[this.dvIndex].getUint8(this.pos);
this.pos += 1;
if (this.pos >= this.dvArray[this.dvIndex].byteLength) {
this.pos = 0;
this.dvIndex += 1;
if (this.dvIndex >= this.dvArray.length) { throw new Error('Tried to read beyond DataView boundary in extractFile'); }
}
return curByte;
}
public seek(num: number) {
this.pos += num;
if (this.pos >= this.dvArray[this.dvIndex].byteLength) {
this.pos -= this.dvArray[this.dvIndex].byteLength;
this.dvIndex += 1;
if (this.dvIndex >= this.dvArray.length) { throw new Error('Tried to read beyond DataView boundary in extractFile'); }
}
}
public extractDv(length: number) {
const dv = new DataView(new ArrayBuffer(length));
for (let i = 0; i < length; i += 1) {
dv.setUint8(i, this.readByte()); //TODO: refactor this so it is more optimized
}
return dv;
}
}
/** Extracts the given file from the given DataView array and returns it as an ArrayBuffer.
* Will throw an error when end of final DataView is reached.
*/
export default function extractFile(file: ISsnFileEntry, dvArray: DataView[]): ArrayBuffer {
//use ByteReader for reading a uint8 and seeking forward across DataView boundaries
const byteReader = new ByteReader(dvArray, file.diskNumberStart, file.offset);
if (byteReader.readByte() !== 0x50 || byteReader.readByte() !== 0x4B || byteReader.readByte() !== 0x03 || byteReader.readByte() !== 0x04) {
throw new Error('Local file header had wrong magic');
}
byteReader.seek(22);
const localFilenameSize = byteReader.readByte() + (byteReader.readByte() << 8);
const localExtraSize = byteReader.readByte() + (byteReader.readByte() << 8);
byteReader.seek(localFilenameSize + localExtraSize);
let dvFinal = byteReader.extractDv(file.comprSize);
//decrypt file if necessary
if (file.decryptionKeys !== undefined) {
dvFinal = decryptFile(dvFinal, file.comprSize, file.decryptionKeys);
}
//uncompress file
const uncompressedBuffer = zlib.inflateRawSync(dvFinal);
return uncompressedBuffer.buffer as ArrayBuffer;
}

View file

@ -60,6 +60,7 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
//Go to start of central dir
pos -= 20 + centralDirSize;
const posCentralDirStart = pos;
for (let i = 0; i < numEntries; i += 1) {
{
@ -99,7 +100,9 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
const extraFieldLength = dv.getUint16(pos, true); pos += 2;
/** file comment length */
const fileCommentLength = dv.getUint16(pos, true); pos += 2;
pos += 8; //skip disk number start, internal file attributes and external file attributes
/** disk number start */
const diskNumberStart = dv.getUint16(pos, true); pos += 2; //0=.z01, 1=.z02 etc.
pos += 6; //skip internal file attributes and external file attributes
/** relative offset of local header */
const relOffset = dv.getUint32(pos, true); pos += 4;
/** file name (variable size) */
@ -149,8 +152,12 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
diffDestLength,
diffSourceLength,
diffType,
diskNumberStart,
lastMod: new Date(1980 + (lastModDate >>> 9), (lastModDate & 0x1E0) >>> 5, lastModDate & 0x1F, lastModTime >>> 11, (lastModTime & 0x7E0) >>> 5, (lastModTime & 0x1F) * 2),
name: fileName,
offset: (centralDirOffset > 0) ? //If files are included in this archive, the centralDirOffset will not start from the beginning
posCentralDirStart - centralDirOffset + relOffset : //if file is in this archive
relOffset, //if we need to look in a disk (e.g. .z01 for this file)
size: uncomprSize,
};
@ -167,11 +174,5 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
fileEntries.push(fileEntry);
}
//TODO: read encrypted + compressed file
//pos = start of central dir
//
//fseek(fp, pos - centralDirOffset, SEEK_SET);
//...
return fileEntries;
}