File size: 3,214 Bytes
b5430db
73f1392
075be5d
73f1392
 
 
 
 
 
 
b5430db
73f1392
b5430db
73f1392
 
b5430db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73f1392
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { checkFilename } from './check-filename';
import { WebBlob } from './WebBlob';

export async function* checkDduf(url: string): AsyncGenerator<string> {
	const blob = await WebBlob.create(new URL(url));

	yield 'File size: ' + blob.size;

	// DDUF is a zip file, uncompressed.

	const last100kB = await blob.slice(blob.size - 100000, blob.size).arrayBuffer();

	const view = new DataView(last100kB);

	let index = view.byteLength - 22;
	let found = false;

	while (index >= 0) {
		if (view.getUint32(index, true) === 0x06054b50) {
			found = true;
			break;
		}

		index--;
	}

	if (!found) {
		throw new Error('DDUF footer not found in last 100kB of file');
	}

	yield 'DDUF footer found at offset ' + (blob.size - last100kB.byteLength + index);

	const diskNumber = view.getUint16(index + 4, true);

	if (diskNumber === 0xffff) {
		throw new Error('Spanned archives (ZIP64) not yet supported');
	}

	if (diskNumber !== 0) {
		throw new Error('Multi-disk archives not supported');
	}

	const fileCount = view.getUint16(index + 10, true);
	const centralDirSize = view.getUint32(index + 12, true);
	const centralDirOffset = view.getUint32(index + 16, true);

	yield 'File count: ' + fileCount;
	yield 'Central directory size: ' + centralDirSize;
	yield 'Central directory offset: ' + centralDirOffset;

	const centralDir =
		centralDirOffset > blob.size - last100kB.byteLength
			? last100kB.slice(
					centralDirOffset - (blob.size - last100kB.byteLength),
					centralDirOffset - (blob.size - last100kB.byteLength) + centralDirSize
				)
			: await blob.slice(centralDirOffset, centralDirOffset + centralDirSize).arrayBuffer();

	const centralDirView = new DataView(centralDir);
	let offset = 0;

	for (let i = 0; i < fileCount; i++) {
		if (centralDirView.getUint32(offset + 0, true) !== 0x02014b50) {
			throw new Error('Invalid central directory file header');
		}

		if (offset + 46 > centralDir.byteLength) {
			throw new Error('Unexpected end of central directory');
		}

		const compressionMethod = centralDirView.getUint16(offset + 10, true);

		if (compressionMethod !== 0) {
			throw new Error('Unsupported compression method: ' + compressionMethod);
		}

		const size = centralDirView.getUint32(offset + 24, true);
		const compressedSize = centralDirView.getUint32(offset + 20, true);

		if (size !== compressedSize) {
			throw new Error('Compressed size and size differ');
		}

		const filenameLength = centralDirView.getUint16(offset + 28, true);
		const fileName = new TextDecoder().decode(
			new Uint8Array(centralDir, offset + 46, filenameLength)
		);

		yield 'File ' + i;
		yield 'File name: ' + fileName;
		yield 'File size: ' + size;

		checkFilename(fileName);

		const fileDiskNumber = centralDirView.getUint16(34, true);

		if (fileDiskNumber !== 0) {
			throw new Error('Multi-disk archives not supported');
		}

		const extraFieldLength = centralDirView.getUint16(offset + 30, true);
		const commentLength = centralDirView.getUint16(offset + 32, true);

		const filePosition = centralDirView.getUint32(offset + 42, true);

		yield 'File position in archive: ' + filePosition;

		offset += 46 + filenameLength + extraFieldLength + commentLength;
	}

	yield 'All files checked';
}