✨ Add extract file feature
This commit is contained in:
parent
4aeba1709a
commit
78ac8ae304
4 changed files with 95 additions and 7 deletions
|
@ -28,6 +28,10 @@ interface ISsnFileEntry {
|
||||||
comprSize: number;
|
comprSize: number;
|
||||||
/** Decryption keys needed to decrypt the file */
|
/** Decryption keys needed to decrypt the file */
|
||||||
decryptionKeys: [number, number, number] | undefined;
|
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;
|
export default ISsnFileEntry;
|
||||||
|
|
17
src/ssn/decryption/decryptFile.ts
Normal file
17
src/ssn/decryption/decryptFile.ts
Normal 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
66
src/ssn/extractFile.ts
Normal 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;
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
|
||||||
|
|
||||||
//Go to start of central dir
|
//Go to start of central dir
|
||||||
pos -= 20 + centralDirSize;
|
pos -= 20 + centralDirSize;
|
||||||
|
const posCentralDirStart = pos;
|
||||||
|
|
||||||
for (let i = 0; i < numEntries; i += 1) {
|
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;
|
const extraFieldLength = dv.getUint16(pos, true); pos += 2;
|
||||||
/** file comment length */
|
/** file comment length */
|
||||||
const fileCommentLength = dv.getUint16(pos, true); pos += 2;
|
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 */
|
/** relative offset of local header */
|
||||||
const relOffset = dv.getUint32(pos, true); pos += 4;
|
const relOffset = dv.getUint32(pos, true); pos += 4;
|
||||||
/** file name (variable size) */
|
/** file name (variable size) */
|
||||||
|
@ -149,8 +152,12 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
|
||||||
diffDestLength,
|
diffDestLength,
|
||||||
diffSourceLength,
|
diffSourceLength,
|
||||||
diffType,
|
diffType,
|
||||||
|
diskNumberStart,
|
||||||
lastMod: new Date(1980 + (lastModDate >>> 9), (lastModDate & 0x1E0) >>> 5, lastModDate & 0x1F, lastModTime >>> 11, (lastModTime & 0x7E0) >>> 5, (lastModTime & 0x1F) * 2),
|
lastMod: new Date(1980 + (lastModDate >>> 9), (lastModDate & 0x1E0) >>> 5, lastModDate & 0x1F, lastModTime >>> 11, (lastModTime & 0x7E0) >>> 5, (lastModTime & 0x1F) * 2),
|
||||||
name: fileName,
|
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,
|
size: uncomprSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,11 +174,5 @@ export default function readSsnFile(buffer: Buffer): ISsnFileEntry[] {
|
||||||
fileEntries.push(fileEntry);
|
fileEntries.push(fileEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: read encrypted + compressed file
|
|
||||||
//pos = start of central dir
|
|
||||||
//
|
|
||||||
//fseek(fp, pos - centralDirOffset, SEEK_SET);
|
|
||||||
//...
|
|
||||||
|
|
||||||
return fileEntries;
|
return fileEntries;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue