#!/usr/bin/env perl # fix-onedrive-zip # # Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid # 'Total Number of Disks' field in the 'ZIP64 End Central Directory # Locator'. The value in this field should be 1, but OneDrive/Windows sets # it to 0. This makes it difficult to work with these files using standard # unzip utilities. # # This program changes the 'Total Number of Disks' field value to 1. # # Copyright (c) 2020-2023 Paul Marquess. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl itself. use strict; use warnings; use Fcntl qw(SEEK_SET SEEK_END); use Getopt::Long; # Signatures for the headers we need to check use constant ZIP_LOCAL_HDR_SIG => 0x04034b50; use constant ZIP_END_CENTRAL_HDR_SIG => 0x06054b50; use constant ZIP64_END_CENTRAL_LOC_HDR_SIG => 0x07064b50; sub Seek; sub Read; my $VERSION = '1.02' ; my $dry_run ; BEGIN { # Check for a 32-bit Perl if (!eval { pack "Q", 1 }) { warn "The perl executable you are running is 32-bit.\n" . "You need to install a 64-bit perl executable to continue\n"; exit(1); } } GetOptions ("dry-run" => \$dry_run, "help" => \&Usage) or Usage("Error in command line arguments\n"); Usage() unless @ARGV >= 1; for my $filename (@ARGV) { open my $fh, "+<$filename" or die "Error: Cannot open '$filename': $!\n"; } for my $filename (@ARGV) { print "\nChecking '$filename'\n"; open my $fh, "+<$filename" or die "Cannot open '$filename': $!\n"; my $data = Read $filename, $fh, 4; my $sig = unpack("V", $data) ; die "Error: No Zip signature at start of '$filename'\n" unless $sig == ZIP_LOCAL_HDR_SIG ; # Assume no comment or other trailing data # The last two things in the file are the Z64 & EOCD records Seek $filename, $fh, -42 ; $data = Read $filename, $fh, 42; my $eocd = substr($data, 20); my $eocd_sig = unpack("V", substr($eocd, 0, 4)) ; die "Error: Cannot find Zip signature at end of '$filename'\n" unless $eocd_sig == ZIP_END_CENTRAL_HDR_SIG ; my $offset = unpack("VV", substr($eocd, 16, 4)) ; die sprintf "Error: bad offset 0x%X at end of '$filename'\n", $offset unless $offset == 0xFFFFFFFF ; my $z64_sig = unpack("V", substr($data, 0, 4)) ; die "Error: Cannot find Zip64 signature at end of '$filename'\n" unless $z64_sig == ZIP64_END_CENTRAL_LOC_HDR_SIG ; my $total_disks = unpack("V", substr($data, 16, 4)) ; if ($total_disks == 1) { print "Nothing to do: 'Total Number of Disks' field is already 1\n"; next } if ($total_disks != 0) { die "Error: 'Total Number of Disks' field is $total_disks\n"; } Seek $filename, $fh, -42 + 16 ; if ($dry_run) { print "Dry-Run: No change made to '$filename'\n"; } else { print $fh pack "V", 1 ; print "Updated '$filename'\n"; } } sub Seek { my $filename = shift; my $fh = shift ; my $offset = shift ; seek $fh, $offset, SEEK_END or die "Cannot seek '$filename': $!\n" ; } sub Read { my $filename = shift ; my $fh = shift; my $size = shift ; my $data ; read($fh, $data, $size) == $size or die "Cannot read from '$filename': $!\n" ; return $data; } sub Usage { print <<'EOM'; Usage: fix-onedrive-zip [--dry-run] file1.zip [file2.zip...] Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid 'Total Number of Disks' field in the 'ZIP64 End Central Directory Locator'. The value in this field should be 1, but OneDrive/Windows sets it to 0. This makes it difficult to work with these files using standard unzip utilities. This program changes the 'Total Number of Disks' field value to 1. See https://github.com/pmqs/Fix-OneDrive-Zip for support. Copyright (c) 2020-2023 Paul Marquess (pmqs@cpan.org). All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. EOM exit; }