Created
July 29, 2019 13:16
-
-
Save krtx/cf219ccdbc3bd6423145f4ae6e0578c5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:io'; | |
import 'dart:convert'; | |
import 'package:args/args.dart'; | |
// read bytes into integer in little endian | |
getBytes(List<int> bytes, int offset, int length) { | |
int res = 0; | |
for (var i = 0; i < length; i++) { | |
res += bytes[i + offset] << (i * 8); | |
} | |
return res; | |
} | |
class CentralDirectoryHeader { | |
var versionMadeBy; | |
var versionExtract; | |
var bitFlag; | |
var compressionMethod; | |
DateTime lastModified; | |
var crc32; | |
var compressedSize; | |
var uncompressedSize; | |
var diskNumber; | |
var internalFileAtrributes; | |
var externalFileAtrributes; | |
var localHeaderOffset; | |
var fileName; | |
var extraField; | |
var comment; | |
decode(List<int> bytes, int offset) { | |
assert(bytes[offset] == 0x50 && | |
bytes[offset + 1] == 0x4b && | |
bytes[offset + 2] == 0x01 && | |
bytes[offset + 3] == 0x02); | |
this.versionMadeBy = getBytes(bytes, offset + 4, 2); | |
this.versionExtract = getBytes(bytes, offset + 6, 2); | |
this.bitFlag = getBytes(bytes, offset + 8, 2); | |
this.compressionMethod = getBytes(bytes, offset + 10, 2); | |
var b12 = getBytes(bytes, offset + 12, 2); | |
var hour = b12 >> 11; | |
var minute = (b12 >> 5) & 63; | |
var second = (b12 & 31) * 2; | |
var b14 = getBytes(bytes, offset + 14, 2); | |
var year = (b14 >> 9) + 1980; | |
var month = (b14 >> 5) & 15; | |
var day = b14 & 31; | |
this.lastModified = DateTime(year, month, day, hour, minute, second); | |
this.crc32 = getBytes(bytes, offset + 16, 4); | |
this.compressedSize = getBytes(bytes, offset + 20, 4); | |
this.uncompressedSize = getBytes(bytes, offset + 24, 4); | |
var fileNameLength = getBytes(bytes, offset + 28, 2); | |
var extraFieldLength = getBytes(bytes, offset + 30, 2); | |
var fileCommentLength = getBytes(bytes, offset + 32, 2); | |
this.diskNumber = getBytes(bytes, offset + 34, 2); | |
this.internalFileAtrributes = getBytes(bytes, offset + 36, 2); | |
this.externalFileAtrributes = getBytes(bytes, offset + 38, 4); | |
this.localHeaderOffset = getBytes(bytes, offset + 42, 4); | |
this.fileName = Utf8Decoder().convert(bytes | |
.getRange( | |
offset + 46, | |
offset + 46 + fileNameLength, | |
) | |
.toList(growable: false)); | |
this.extraField = bytes | |
.getRange( | |
offset + 46 + fileNameLength, | |
offset + 46 + fileNameLength + extraFieldLength, | |
) | |
.toList(growable: false); | |
this.comment = Utf8Decoder().convert(bytes | |
.getRange( | |
offset + 46 + fileNameLength + extraFieldLength, | |
offset + 46 + fileNameLength + extraFieldLength + fileCommentLength, | |
) | |
.toList(growable: false)); | |
return offset + 46 + fileNameLength + extraFieldLength + fileCommentLength; | |
} | |
String toString() { | |
return ''' | |
versionMadeBy = ${this.versionMadeBy} | |
versionExtract = ${this.versionExtract} | |
bitFlag = ${this.bitFlag} | |
compressionMethod = ${this.compressionMethod} | |
lastModified = ${this.lastModified} | |
crc32 = ${this.crc32} | |
compressedSize = ${this.compressedSize} | |
uncompressedSize = ${this.uncompressedSize} | |
diskNumber = ${this.diskNumber} | |
internalFileAttributes = ${this.internalFileAtrributes} | |
externalFileAttributes = ${this.externalFileAtrributes} | |
localHeaderOffset = ${this.localHeaderOffset} | |
fileName = "${this.fileName}" | |
comment = "${this.comment}"'''; | |
} | |
} | |
class EndOfCentralDirectoryRecord { | |
var diskNumber; | |
var firstDiskNumber; | |
var filesInDisk; | |
var files; | |
var totalHeaderSize; | |
var headerOffset; | |
var comment; | |
// シグニチャを探してそこからdecodeする | |
decode(List<int> bytes) { | |
// 後ろから探す | |
for (var i = bytes.length - 4; i >= 0; i--) { | |
if (bytes[i] == 0x50 && | |
bytes[i + 1] == 0x4b && | |
bytes[i + 2] == 0x05 && | |
bytes[i + 3] == 0x06) { | |
this.diskNumber = getBytes(bytes, i + 4, 2); | |
this.firstDiskNumber = getBytes(bytes, i + 6, 2); | |
this.filesInDisk = getBytes(bytes, i + 8, 2); | |
this.files = getBytes(bytes, i + 10, 2); | |
this.totalHeaderSize = getBytes(bytes, i + 12, 4); | |
this.headerOffset = getBytes(bytes, i + 16, 4); | |
var commentLength = getBytes(bytes, i + 20, 2); | |
this.comment = Utf8Decoder() | |
.convert(bytes.getRange(i + 22, i + 22 + commentLength).toList()); | |
return; | |
} | |
} | |
throw FormatException('end of central directory record is not found'); | |
} | |
String toString() { | |
return ''' | |
diskNumber = ${diskNumber} | |
firstDiskNumber = ${firstDiskNumber} | |
filesInDisk = ${filesInDisk} | |
files = ${files} | |
totalHeaderSize = ${totalHeaderSize} | |
headerOffset = ${headerOffset} | |
comment = "${comment}"'''; | |
} | |
} | |
main(List<String> arguments) async { | |
var parser = ArgParser(); | |
parser.addOption('dir', abbr: 'd', defaultsTo: ''); | |
var results = parser.parse(arguments); | |
var targetDir = results['dir']; | |
if (results.rest.isEmpty) { | |
print('filename is required'); | |
exit(1); | |
} | |
var filename = results.rest[0]; | |
var bytes = await File(filename).readAsBytes(); | |
var eocd = EndOfCentralDirectoryRecord(); | |
eocd.decode(bytes); | |
List<CentralDirectoryHeader> headers = []; | |
var offset = eocd.headerOffset; | |
for (var i = 0; i < eocd.files; i++) { | |
var header = CentralDirectoryHeader(); | |
offset = header.decode(bytes, offset); | |
headers.add(header); | |
} | |
for (var header in headers) { | |
var fileNameLength = getBytes(bytes, header.localHeaderOffset + 26, 2); | |
var extraFieldLength = getBytes(bytes, header.localHeaderOffset + 28, 2); | |
var rawBytes = bytes | |
.getRange( | |
header.localHeaderOffset + 30 + fileNameLength + extraFieldLength, | |
header.localHeaderOffset + | |
30 + | |
fileNameLength + | |
extraFieldLength + | |
header.compressedSize) | |
.toList(); | |
var data = rawBytes; | |
if (header.compressionMethod == 8) { | |
var inflator = RawZLibFilter.inflateFilter(raw: true); | |
inflator.process(data, 0, data.length); | |
data = inflator.processed(end: true); | |
} | |
if (targetDir != '') { | |
Directory(targetDir).createSync(recursive: true); | |
} | |
if (header.fileName.endsWith('/')) { | |
Directory('${targetDir}/${header.fileName}').createSync(recursive: true); | |
} else { | |
File('${targetDir}/${header.fileName}').writeAsBytesSync(data); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment