| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package FileSystem::LL::FAT; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
#use 5.008008; |
|
4
|
|
|
|
|
|
|
#use warnings; |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
require Exporter; |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# Items to export into callers namespace by default. Note: do not export |
|
11
|
|
|
|
|
|
|
# names by default without a very good reason. Use EXPORT_OK instead. |
|
12
|
|
|
|
|
|
|
# Do not simply export all your public functions/methods/constants. |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# This allows declaration use FileSystem::LL::FAT ':all'; |
|
15
|
|
|
|
|
|
|
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK |
|
16
|
|
|
|
|
|
|
# will save memory. |
|
17
|
|
|
|
|
|
|
%EXPORT_TAGS = ( 'all' => [ qw( |
|
18
|
|
|
|
|
|
|
MBR_2_partitions debug_partitions emit_fat32 interpret_directory |
|
19
|
|
|
|
|
|
|
check_bootsector interpret_bootsector |
|
20
|
|
|
|
|
|
|
check_FAT_array FAT_2array cluster_chain read_FAT_data |
|
21
|
|
|
|
|
|
|
write_file write_dir list_dir compress_FAT uncompress_FAT |
|
22
|
|
|
|
|
|
|
) ] ); |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
@EXPORT = qw( |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
); |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
$VERSION = '0.05'; |
|
31
|
1
|
|
|
1
|
|
27878
|
use strict; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
12191
|
|
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $lim_read = $ENV{FAT_READ_NEEDS_1SECTOR}; |
|
34
|
|
|
|
|
|
|
$lim_read = ($^O eq 'os2') unless defined $lim_read; #Bug in OS/2 FAT32 driver? |
|
35
|
|
|
|
|
|
|
$lim_read = $lim_read ? 512 : (1<<24); |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Preloaded methods go here. |
|
38
|
|
|
|
|
|
|
sub decode_fields ($$) { |
|
39
|
0
|
|
|
0
|
0
|
|
my ($fields2, $in) = (shift,shift); |
|
40
|
0
|
|
|
|
|
|
my $lastfield = @$fields2/2 - 1; |
|
41
|
0
|
|
|
|
|
|
my $extract = join ' ', @$fields2[map 2*$_ + 1, 0 .. $lastfield]; |
|
42
|
0
|
|
|
|
|
|
my @values = unpack $extract, $in; |
|
43
|
0
|
|
|
|
|
|
map( ($$fields2[2*$_] => $values[$_]), 0 .. $lastfield); |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub MBR_2_partitions ($) { |
|
49
|
0
|
|
|
0
|
1
|
|
my $bootsect = shift; |
|
50
|
|
|
|
|
|
|
return # die "Expect to have \\x55\\xAA in MBR" |
|
51
|
0
|
0
|
0
|
|
|
|
unless length($bootsect) == 512 and "\x55\xAA" eq substr $bootsect, -2; |
|
52
|
|
|
|
|
|
|
# Up to offset 1BEh the MBR consists purely of machine code and data (strings |
|
53
|
|
|
|
|
|
|
# etc.). At offset 1BEh the first primary partition is defined, this takes 16 |
|
54
|
|
|
|
|
|
|
# bytes, after which the second primary partition is defined, followed by |
|
55
|
|
|
|
|
|
|
# the third and fourth, the data structures are the same. |
|
56
|
|
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
|
my ($code, @parts) = unpack 'a446 a16 a16 a16 a16 v', $bootsect; |
|
58
|
0
|
|
|
|
|
|
my $check = pop @parts; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# 00h 1 Set to 80h if this partition is active. |
|
61
|
|
|
|
|
|
|
# 01h 1 Partition's starting head. |
|
62
|
|
|
|
|
|
|
# 02h 2 Partition's starting [48]sector and track. |
|
63
|
|
|
|
|
|
|
# 04h 1 Partition's [49]ID number. |
|
64
|
|
|
|
|
|
|
# 05h 1 Partition's ending head. |
|
65
|
|
|
|
|
|
|
# 06h 2 Partition's ending [50]sector and track. |
|
66
|
|
|
|
|
|
|
# 08h 4 Starting LBA. |
|
67
|
|
|
|
|
|
|
# 0Ch 4 Partition's length in sectors. |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# Format of sector and track information.Bits 15-6 Bits 5-0 |
|
70
|
|
|
|
|
|
|
# Track Sector |
|
71
|
|
|
|
|
|
|
# ID numbers: |
|
72
|
|
|
|
|
|
|
# 0Bh Win95 OSR2+ FAT32 (512MB-2TB) (primary?) |
|
73
|
|
|
|
|
|
|
# 0Ch Win95 OSR2+ FAT32 (512MB-2TB LBA) (extended?) |
|
74
|
0
|
|
|
|
|
|
my @part = ( # code => 'A446', |
|
75
|
|
|
|
|
|
|
is_active => 'C', |
|
76
|
|
|
|
|
|
|
start_head => 'C', |
|
77
|
|
|
|
|
|
|
start_sec_track => 'v', |
|
78
|
|
|
|
|
|
|
type => 'C', |
|
79
|
|
|
|
|
|
|
end_head => 'C', |
|
80
|
|
|
|
|
|
|
end_sec_track => 'v', |
|
81
|
|
|
|
|
|
|
start_lba => 'V', |
|
82
|
|
|
|
|
|
|
sectors => 'V', |
|
83
|
|
|
|
|
|
|
); |
|
84
|
|
|
|
|
|
|
|
|
85
|
0
|
|
|
|
|
|
my @part_value = map { {raw => $_, decode_fields \@part, $_} } @parts; |
|
|
0
|
|
|
|
|
|
|
|
86
|
0
|
|
|
|
|
|
for my $p (@part_value) { |
|
87
|
0
|
|
|
|
|
|
$p->{start_sec} = $p->{start_sec_track} & 0x3f; |
|
88
|
0
|
|
|
|
|
|
$p->{start_track} = $p->{start_sec_track} >> 6; |
|
89
|
0
|
|
|
|
|
|
$p->{end_sec} = $p->{end_sec_track} & 0x3f; |
|
90
|
0
|
|
|
|
|
|
$p->{end_track} = $p->{end_sec_track} >> 6; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
0
|
|
|
|
|
|
({bootcode => $code, signature => $check}, @part_value); |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub debug_partitions ($@) { |
|
96
|
0
|
|
|
0
|
0
|
|
my($fh, @partitions) = @_; |
|
97
|
0
|
|
|
|
|
|
my $n; |
|
98
|
0
|
|
|
|
|
|
for my $p (@partitions) { |
|
99
|
0
|
|
|
|
|
|
$n++; |
|
100
|
0
|
|
|
|
|
|
print $fh " Part $n\n"; |
|
101
|
0
|
|
|
|
|
|
print $fh "$_\t=> $p->{$_}\n" for sort keys %$p; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Experimental convertor from empty fat to empty fat32... |
|
106
|
|
|
|
|
|
|
sub emit_fat32 ($$$$) { # Also, essentially, seeks to bootsector |
|
107
|
0
|
|
|
0
|
0
|
|
my($p, $b, $emit_prefat32, $reader) = (shift, shift, shift, shift); |
|
108
|
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
|
my $offset_in_sectors = $p->{start_lba}; |
|
110
|
0
|
0
|
|
|
|
|
die "The partition type is not defined" unless $p->{type}; |
|
111
|
0
|
0
|
|
|
|
|
die "start_lba value is 0" unless $offset_in_sectors; |
|
112
|
|
|
|
|
|
|
|
|
113
|
0
|
|
|
|
|
|
substr($b, 446 + 4, 1) = chr 0x0B; # Win95 OSR2+ FAT32 (512MB-2TB) (primary?) |
|
114
|
0
|
0
|
|
|
|
|
die "Need emit_prefat32 defined too in presence of partition table" |
|
115
|
|
|
|
|
|
|
unless defined $emit_prefat32; |
|
116
|
0
|
0
|
|
|
|
|
open F32, "> $emit_prefat32" or die "Error opening `$emit_prefat32' for write: $!"; |
|
117
|
0
|
|
|
|
|
|
binmode F32; |
|
118
|
0
|
|
|
|
|
|
syswrite F32, $b; |
|
119
|
0
|
0
|
|
|
|
|
if ($offset_in_sectors > 1) { |
|
120
|
0
|
|
|
|
|
|
my $in = $reader->(512*($offset_in_sectors - 1)); |
|
121
|
0
|
|
|
|
|
|
syswrite F32, $in; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
0
|
0
|
|
|
|
|
close F32 or die "Error closing `$emit_prefat32' for write: $!"; |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Directory Entry Layout. |
|
127
|
|
|
|
|
|
|
# |
|
128
|
|
|
|
|
|
|
# The old style directory entry had 10 reserved bytes starting at 0Ch, |
|
129
|
|
|
|
|
|
|
# these are now used. |
|
130
|
|
|
|
|
|
|
# 00h 8 Filename padded with spaces if required (see above). |
|
131
|
|
|
|
|
|
|
# 08h 3 Filename extension padded with spaces if required. |
|
132
|
|
|
|
|
|
|
# 0Bh 1 File Attribute Byte. |
|
133
|
|
|
|
|
|
|
# 0Ch 10 Reserved or extra data. |
|
134
|
|
|
|
|
|
|
# 16h 2 Time of last write to file (last modified or when created). |
|
135
|
|
|
|
|
|
|
# 18h 2 Date of last write to file (last modified or when created). |
|
136
|
|
|
|
|
|
|
# 1Ah 2 Starting cluster. |
|
137
|
|
|
|
|
|
|
# 1Ch 4 File size (set to zero if a directory). |
|
138
|
|
|
|
|
|
|
# |
|
139
|
|
|
|
|
|
|
# |
|
140
|
|
|
|
|
|
|
# Extra data Layout (previously reserved area). |
|
141
|
|
|
|
|
|
|
# |
|
142
|
|
|
|
|
|
|
# The old style directory entry had 10 reserved bytes starting at 0Ch, |
|
143
|
|
|
|
|
|
|
# these are now used as follows. Presumably these fields are used if |
|
144
|
|
|
|
|
|
|
# non-zero. |
|
145
|
|
|
|
|
|
|
# Offset Length Field |
|
146
|
|
|
|
|
|
|
# 0Ch 1 Reserved for use by Windows NT. |
|
147
|
|
|
|
|
|
|
# 0Dh 1 Tenths of a second at time of file creation, 0-199 is valid. |
|
148
|
|
|
|
|
|
|
# 0Eh 2 Time when file was created. |
|
149
|
|
|
|
|
|
|
# 10h 2 Date when file was created. |
|
150
|
|
|
|
|
|
|
# 12h 2 Date when file was last accessed. |
|
151
|
|
|
|
|
|
|
# 14h 2 High word of cluster number (always 0 for FAT12 and FAT16). |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
my @file_f = ( basename => 'A8', |
|
154
|
|
|
|
|
|
|
ext => 'A3', |
|
155
|
|
|
|
|
|
|
attrib => 'C', |
|
156
|
|
|
|
|
|
|
name_ext_case => 'C', |
|
157
|
|
|
|
|
|
|
creation_01sec => 'C', |
|
158
|
|
|
|
|
|
|
time_creation => 'v', |
|
159
|
|
|
|
|
|
|
date_create => 'v', |
|
160
|
|
|
|
|
|
|
date_access => 'v', |
|
161
|
|
|
|
|
|
|
cluster_high => 'v', |
|
162
|
|
|
|
|
|
|
time_write => 'v', |
|
163
|
|
|
|
|
|
|
date_write => 'v', |
|
164
|
|
|
|
|
|
|
cluster_low => 'v', |
|
165
|
|
|
|
|
|
|
size => 'V', |
|
166
|
|
|
|
|
|
|
); |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
my @lfn_f = ( seq_number => 'C', |
|
169
|
|
|
|
|
|
|
name_chars_1 => 'a10', |
|
170
|
|
|
|
|
|
|
attrib => 'C', |
|
171
|
|
|
|
|
|
|
nt_reserved => 'C', |
|
172
|
|
|
|
|
|
|
checksum_dosname => 'C', |
|
173
|
|
|
|
|
|
|
name_chars_2 => 'a12', |
|
174
|
|
|
|
|
|
|
cluster_low => 'v', |
|
175
|
|
|
|
|
|
|
name_chars_3 => 'a4', |
|
176
|
|
|
|
|
|
|
); |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
my $nn = 0; |
|
179
|
|
|
|
|
|
|
my %file_attrib = map +($_ => 1<<($nn++)), |
|
180
|
|
|
|
|
|
|
qw(is_readonly is_hidden is_system is_volume_label |
|
181
|
|
|
|
|
|
|
is_subdir is_archive is_device); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub dos_chksum ($$) { |
|
184
|
0
|
|
|
0
|
0
|
|
my ($n,$ext,$sum) = (shift, shift, 0); |
|
185
|
|
|
|
|
|
|
$sum = ((($sum & 1)<<7) + ($sum >> 1) + ord $_) & 0xFF |
|
186
|
0
|
|
|
|
|
|
for split //, sprintf "%-8s%-3s", $n, $ext; |
|
187
|
0
|
|
|
|
|
|
$sum |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub interpret_directory ($$;$$$) { |
|
191
|
0
|
|
|
0
|
1
|
|
my ($dir, $is_fat32, $keep_del, $keep_dots, $keep_labels) = |
|
192
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift); |
|
193
|
0
|
|
|
|
|
|
my ($res, @files, @lfn, $lfn_checksum, $lfn_seq, $lfn_tot, $lfn_del); |
|
194
|
0
|
|
|
|
|
|
while (length $dir) { |
|
195
|
0
|
0
|
|
|
|
|
$dir =~ s/^((.).{31})//s or die "short directory!"; |
|
196
|
0
|
0
|
|
|
|
|
$res = 'end', last if $2 eq "\0"; # No entries after this point |
|
197
|
0
|
0
|
0
|
|
|
|
next if not $keep_del and 0xE5 == ord $2; # deleted or not filled |
|
198
|
0
|
|
|
|
|
|
my %f = decode_fields \@file_f, $1; |
|
199
|
0
|
|
|
|
|
|
$f{deleted} = 0; |
|
200
|
0
|
0
|
|
|
|
|
$f{deleted} = 1 if 0xE5 == ord $2; # deleted or not filled |
|
201
|
0
|
0
|
|
|
|
|
if ($f{attrib} == 0x0F) { # LFN |
|
202
|
|
|
|
|
|
|
# next; |
|
203
|
0
|
|
|
|
|
|
%f = decode_fields \@lfn_f, $1; |
|
204
|
0
|
0
|
|
|
|
|
if (not $keep_del) { # XXX How to process? Ignore seq numbers??? |
|
205
|
0
|
0
|
|
|
|
|
@lfn = (), next if $f{seq_number} & 0x80; # Deleted entry |
|
206
|
|
|
|
|
|
|
} else { # Deleted entry for non-deleted file? |
|
207
|
0
|
0
|
0
|
|
|
|
@lfn = (), next if $f{seq_number} & 0x80 and $f{seq_number} != 0xE5; |
|
208
|
0
|
0
|
|
|
|
|
if ($f{seq_number} == 0xE5) { |
|
209
|
0
|
0
|
0
|
|
|
|
die "Deleted LFN subrecord in middle of LFN" if @lfn and !$lfn_del; |
|
210
|
0
|
|
|
|
|
|
$lfn_del = 1; |
|
211
|
0
|
|
|
|
|
|
$f{deleted} = 1; |
|
212
|
|
|
|
|
|
|
} else { # Ignore deleted LFN preceeding a valid LFN |
|
213
|
0
|
0
|
0
|
|
|
|
@lfn = () if @lfn and $lfn_del; |
|
214
|
0
|
|
|
|
|
|
$lfn_del = 0; |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
} |
|
217
|
0
|
|
|
|
|
|
$f{raw} = $1; |
|
218
|
0
|
0
|
|
|
|
|
unless ($f{deleted}) { |
|
219
|
0
|
0
|
0
|
|
|
|
die "LFN start unexpected" if @lfn and $f{seq_number} & 0x40; |
|
220
|
0
|
0
|
0
|
|
|
|
die "LFN continuation unexpected" unless @lfn or $f{seq_number} & 0x40; |
|
221
|
|
|
|
|
|
|
|
|
222
|
0
|
0
|
0
|
|
|
|
die "LFN continuation out-of-order: $f{seq_number} after $lfn_seq" |
|
223
|
|
|
|
|
|
|
if @lfn and $f{seq_number} != ($lfn_seq & ~0x40) - 1; |
|
224
|
0
|
|
|
|
|
|
$lfn_seq = $f{seq_number}; |
|
225
|
|
|
|
|
|
|
|
|
226
|
0
|
0
|
0
|
|
|
|
die "Mismatch in checksums" |
|
227
|
|
|
|
|
|
|
if @lfn and $lfn_checksum != $f{checksum_dosname}; |
|
228
|
|
|
|
|
|
|
} |
|
229
|
0
|
|
|
|
|
|
$lfn_checksum = $f{checksum_dosname}; |
|
230
|
0
|
|
|
|
|
|
$f{lfn_chars} = "$f{name_chars_1}$f{name_chars_2}$f{name_chars_3}"; |
|
231
|
0
|
0
|
|
|
|
|
$f{lfn_chars} =~ s/\0\0(\xFF\xFF){0,11}$// # may be non-terminated... |
|
232
|
|
|
|
|
|
|
# or die "LFN `$f{lfn_chars}' not terminated by 0x0000" |
|
233
|
|
|
|
|
|
|
unless @lfn; |
|
234
|
0
|
0
|
|
|
|
|
if (@lfn) { |
|
235
|
0
|
|
|
|
|
|
$lfn_tot = "$f{lfn_chars}$lfn_tot" |
|
236
|
|
|
|
|
|
|
} else { |
|
237
|
0
|
|
|
|
|
|
$lfn_tot = $f{lfn_chars} |
|
238
|
|
|
|
|
|
|
} |
|
239
|
0
|
|
|
|
|
|
push @lfn, \%f; |
|
240
|
0
|
|
|
|
|
|
next; |
|
241
|
|
|
|
|
|
|
} |
|
242
|
0
|
|
|
|
|
|
$f{raw} = $1; |
|
243
|
0
|
|
|
|
|
|
$f{basename} =~ s/^\x05/\xE5/; |
|
244
|
0
|
0
|
0
|
|
|
|
@lfn = (), next |
|
|
|
|
0
|
|
|
|
|
|
245
|
|
|
|
|
|
|
if not $keep_dots and $f{basename} =~ /^\.\.?$/ and $f{ext} eq ''; # . .. |
|
246
|
|
|
|
|
|
|
# DOSname is mangled for deleted files, so there is no point in checksum... |
|
247
|
0
|
0
|
0
|
|
|
|
@lfn = (), warn("Mis-attached LFN (chksum mismatch: $lfn_checksum vs `$f{basename}.$f{ext}')") |
|
|
|
|
0
|
|
|
|
|
|
248
|
|
|
|
|
|
|
if @lfn and not $lfn_del and $lfn_checksum != dos_chksum($f{basename}, $f{ext}); |
|
249
|
0
|
0
|
0
|
|
|
|
next if ($f{attrib} & 0x08) and not $keep_labels; |
|
250
|
0
|
0
|
|
|
|
|
if ($is_fat32) { |
|
251
|
0
|
|
|
|
|
|
$f{cluster} = $f{cluster_low} + ($f{cluster_high} << 16); |
|
252
|
|
|
|
|
|
|
} else { |
|
253
|
0
|
|
|
|
|
|
$f{cluster} = $f{cluster_low}; # cluster_high has EA info? |
|
254
|
|
|
|
|
|
|
} |
|
255
|
0
|
0
|
|
|
|
|
$f{basename} = lc $f{basename} if $f{name_ext_case} & (1<<3); |
|
256
|
0
|
0
|
|
|
|
|
$f{ext} = lc $f{ext} if $f{name_ext_case} & (1<<4); |
|
257
|
0
|
0
|
|
|
|
|
my $ext = length $f{ext} ? ".$f{ext}" : ''; |
|
258
|
0
|
|
|
|
|
|
$f{dos_name} = $f{name} = "$f{basename}$ext"; |
|
259
|
0
|
|
|
|
|
|
$f{time_create} = $f{time_creation} + $f{creation_01sec}/100; |
|
260
|
0
|
|
|
|
|
|
$f{$_} = $f{attrib} & $file_attrib{$_} for keys %file_attrib; |
|
261
|
0
|
0
|
|
|
|
|
if (@lfn) { |
|
262
|
0
|
|
|
|
|
|
$f{lfn_raw} = [@lfn]; |
|
263
|
0
|
|
|
|
|
|
$f{name} = join '', map chr, unpack 'v*', $lfn_tot; |
|
264
|
0
|
|
|
|
|
|
$f{lfn_name_UTF16} = $lfn_tot; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
0
|
|
|
|
|
|
push @files, \%f; |
|
267
|
0
|
|
|
|
|
|
@lfn = (); |
|
268
|
|
|
|
|
|
|
} |
|
269
|
0
|
0
|
0
|
|
|
|
$res ||= 'mid' if @lfn; |
|
270
|
0
|
|
|
|
|
|
($res, \@files); |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# FAT12/FAT16 Boot Sector/Boot Record Layout. |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# The data contained in the boot sector after the OEM name string is |
|
276
|
|
|
|
|
|
|
# referred to as the BIOS parameter block or BPB. |
|
277
|
|
|
|
|
|
|
# Offset Length Field |
|
278
|
|
|
|
|
|
|
# 00h 3 Machine code for jump over the data. |
|
279
|
|
|
|
|
|
|
# 03h 8 OEM name string (of OS which formatted the disk). |
|
280
|
|
|
|
|
|
|
# 0Bh 2 Bytes per sector, nearly always 512 but can be 1024,2048 or |
|
281
|
|
|
|
|
|
|
# 4096. |
|
282
|
|
|
|
|
|
|
# 0Dh 1 Sectors per cluster, valid number are: 1,2,4,8,16,32,64 and 128, |
|
283
|
|
|
|
|
|
|
# but a cluster size larger than 32K should not occur. |
|
284
|
|
|
|
|
|
|
# 0Eh 2 Reserved sectors (number of sectors before the first FAT |
|
285
|
|
|
|
|
|
|
# including the boot sector), usually 1. |
|
286
|
|
|
|
|
|
|
# 10h 1 Number of FAT's (nearly always 2). |
|
287
|
|
|
|
|
|
|
# 11h 2 Maximum number of root directory entries. |
|
288
|
|
|
|
|
|
|
# 13h 2 Total number of sectors (for small disks only, if the disk is |
|
289
|
|
|
|
|
|
|
# too big this is set to 0 and offset 20h is used instead). |
|
290
|
|
|
|
|
|
|
# 15h 1 Media descriptor byte, pretty meaningless now (see below). |
|
291
|
|
|
|
|
|
|
# 16h 2 Sectors per FAT. |
|
292
|
|
|
|
|
|
|
# 18h 2 Sectors per track. |
|
293
|
|
|
|
|
|
|
# 1Ah 2 Total number of heads/sides. |
|
294
|
|
|
|
|
|
|
# 1Ch 4 Number of hidden sectors (those preceding the boot sector). |
|
295
|
|
|
|
|
|
|
# 20h 4 Total number of sectors for large disks. |
|
296
|
|
|
|
|
|
|
# Starts FAT12/16-specific |
|
297
|
|
|
|
|
|
|
# 24h 26 Either extended BPB (see below) or machine code. |
|
298
|
|
|
|
|
|
|
# 3Eh 448 Machine code. |
|
299
|
|
|
|
|
|
|
# 1FEh 2 Boot Signature AA55h. |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# Starts FAT32-specific |
|
302
|
|
|
|
|
|
|
# 0x24 4 Sectors per file allocation table |
|
303
|
|
|
|
|
|
|
# 0x28 2 FAT Flags |
|
304
|
|
|
|
|
|
|
# 0x2a 2 Version |
|
305
|
|
|
|
|
|
|
# 0x2c 4 Cluster number of root directory start |
|
306
|
|
|
|
|
|
|
# 0x30 2 Sector number of FS Information Sector |
|
307
|
|
|
|
|
|
|
# 0x32 2 Sector number of a copy of this boot sector |
|
308
|
|
|
|
|
|
|
# 0x34 12 Reserved |
|
309
|
|
|
|
|
|
|
# 0x40 1 Physical Drive Number |
|
310
|
|
|
|
|
|
|
# 0x41 1 Reserved |
|
311
|
|
|
|
|
|
|
# 0x42 1 Extended boot signature. |
|
312
|
|
|
|
|
|
|
# 0x43 4 ID (serial number) |
|
313
|
|
|
|
|
|
|
# 0x47 11 Volume Label |
|
314
|
|
|
|
|
|
|
# 0x52 8 FAT file system type: "FAT32 " |
|
315
|
|
|
|
|
|
|
# 0x5a 420 Operating system boot code |
|
316
|
|
|
|
|
|
|
# 0x1FE 2 Boot sector signature (0x55 0xAA) |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
my @boot_c = ( jump => 'A3', |
|
320
|
|
|
|
|
|
|
oem => 'A8', |
|
321
|
|
|
|
|
|
|
sector_size => 'v', |
|
322
|
|
|
|
|
|
|
sectors_in_cluster => 'C', |
|
323
|
|
|
|
|
|
|
FAT_table_off => 'v', |
|
324
|
|
|
|
|
|
|
num_FAT_tables => 'C', |
|
325
|
|
|
|
|
|
|
root_dir_entries => 'v', |
|
326
|
|
|
|
|
|
|
total_sectors1 => 'v', |
|
327
|
|
|
|
|
|
|
media_type => 'C', |
|
328
|
|
|
|
|
|
|
sectors_per_FAT16 => 'v', |
|
329
|
|
|
|
|
|
|
sectors_per_track => 'v', |
|
330
|
|
|
|
|
|
|
heads => 'v', |
|
331
|
|
|
|
|
|
|
hidden_sectors => 'V', |
|
332
|
|
|
|
|
|
|
total_sectors2 => 'V', |
|
333
|
|
|
|
|
|
|
); |
|
334
|
|
|
|
|
|
|
my @boot_16 = ( extended_bpb => 'a26', |
|
335
|
|
|
|
|
|
|
machine_code => 'a448', |
|
336
|
|
|
|
|
|
|
boot_signature => 'v', |
|
337
|
|
|
|
|
|
|
); |
|
338
|
|
|
|
|
|
|
my @boot_32 = ( sectors_per_FAT32 => 'V', |
|
339
|
|
|
|
|
|
|
FAT_flags => 'v', |
|
340
|
|
|
|
|
|
|
version => 'v', |
|
341
|
|
|
|
|
|
|
rootdir_start_cluster => 'V', |
|
342
|
|
|
|
|
|
|
fsi_sector_sector => 'v', |
|
343
|
|
|
|
|
|
|
bootcopy_sector_sector => 'v', |
|
344
|
|
|
|
|
|
|
reserved1 => 'a12', |
|
345
|
|
|
|
|
|
|
physical_drive => 'C', |
|
346
|
|
|
|
|
|
|
reserved2 => 'C', |
|
347
|
|
|
|
|
|
|
ext_boot_signature => 'C', |
|
348
|
|
|
|
|
|
|
serial_number => 'V', |
|
349
|
|
|
|
|
|
|
volume_label => 'A11', |
|
350
|
|
|
|
|
|
|
FS_type => 'A8', |
|
351
|
|
|
|
|
|
|
machine_code => 'a420', |
|
352
|
|
|
|
|
|
|
boot_signature => 'v', |
|
353
|
|
|
|
|
|
|
); |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# FAT12/Fat16 Extended BPB. |
|
357
|
|
|
|
|
|
|
# |
|
358
|
|
|
|
|
|
|
# The Extended BIOS parameter block is not present prior to DOS 4.0 |
|
359
|
|
|
|
|
|
|
# formatted disks. |
|
360
|
|
|
|
|
|
|
# Offset |
|
361
|
|
|
|
|
|
|
# Length (in bytes) |
|
362
|
|
|
|
|
|
|
# Field |
|
363
|
|
|
|
|
|
|
# 24h 1 Physical drive number (BIOS system ie 80h is first HDD, 00h is first FDD). |
|
364
|
|
|
|
|
|
|
# 25h 1 Current head (not used for this; WinNT bit 0 is a dirty flag to request chkdsk at boot time. bit 1 requests surface scan too). |
|
365
|
|
|
|
|
|
|
# 26h 1 Signature (must be 28h or 29h to be recognised by NT). |
|
366
|
|
|
|
|
|
|
# 27h 4 The serial number, the serial number is stored in reverse order |
|
367
|
|
|
|
|
|
|
# and is the hex representation of the bytes stored here. |
|
368
|
|
|
|
|
|
|
# 2Bh 11 Volume label. |
|
369
|
|
|
|
|
|
|
# 36h 8 File system ID. "FAT12", "FAT16" or "FAT ". |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# Further structure used by FAT32: |
|
372
|
|
|
|
|
|
|
# Byte Offset Length (bytes) Description |
|
373
|
|
|
|
|
|
|
# 0x24 4 Sectors per file allocation table |
|
374
|
|
|
|
|
|
|
# 0x28 2 FAT Flags |
|
375
|
|
|
|
|
|
|
# 0x2a 2 Version |
|
376
|
|
|
|
|
|
|
# 0x2c 4 Cluster number of root directory start |
|
377
|
|
|
|
|
|
|
# 0x30 2 Sector number of FS Information Sector |
|
378
|
|
|
|
|
|
|
# 0x32 2 Sector number of a copy of this boot sector |
|
379
|
|
|
|
|
|
|
# 0x34 12 Reserved |
|
380
|
|
|
|
|
|
|
# 0x40 1 Physical Drive Number |
|
381
|
|
|
|
|
|
|
# 0x41 1 Reserved |
|
382
|
|
|
|
|
|
|
# 0x42 1 Extended boot signature (0x28 0x29). |
|
383
|
|
|
|
|
|
|
# 0x43 4 ID (serial number) |
|
384
|
|
|
|
|
|
|
# 0x47 11 Volume Label |
|
385
|
|
|
|
|
|
|
# 0x52 8 FAT file system type: "FAT32 " |
|
386
|
|
|
|
|
|
|
# 0x5a 420 Operating system boot code |
|
387
|
|
|
|
|
|
|
# 0x1FE 2 Boot sector signature (0x55 0xAA) |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
my @e_boot = ( |
|
390
|
|
|
|
|
|
|
physical_drive => 'C', |
|
391
|
|
|
|
|
|
|
head___dirty_flags => 'C', |
|
392
|
|
|
|
|
|
|
ext_boot_signature => 'C', |
|
393
|
|
|
|
|
|
|
serial_number => 'V', |
|
394
|
|
|
|
|
|
|
volume_label => 'A11', |
|
395
|
|
|
|
|
|
|
FS_type => 'A8', |
|
396
|
|
|
|
|
|
|
); |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# FS Information Sector |
|
399
|
|
|
|
|
|
|
# Byte Offset Length (bytes) Description |
|
400
|
|
|
|
|
|
|
# 0x00 4 FS information sector signature (0x52 0x52 0x61 0x41 / "RRaA") |
|
401
|
|
|
|
|
|
|
# 0x04 480 Reserved (byte values are 0x00) |
|
402
|
|
|
|
|
|
|
# 0x1e4 4 FS information sector signature (0x72 0x72 0x41 0x61 / "rrAa") |
|
403
|
|
|
|
|
|
|
# 0x1e8 4 Number of free clusters on the drive, or -1 if unknown |
|
404
|
|
|
|
|
|
|
# 0x1ec 4 Number of the most recently allocated cluster |
|
405
|
|
|
|
|
|
|
# 0x1f0 14 Reserved (byte values are 0x00) |
|
406
|
|
|
|
|
|
|
# 0x1fe 2 FS information sector signature (0x55 0xAA) |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
sub preprocess_bootsect ($) { |
|
409
|
0
|
|
|
0
|
0
|
|
my $s = shift; |
|
410
|
0
|
|
0
|
|
|
|
$s->{total_sectors} = $s->{total_sectors1} || $s->{total_sectors2}; |
|
411
|
0
|
|
0
|
|
|
|
$s->{sectors_per_FAT} = $s->{sectors_per_FAT32} || $s->{sectors_per_FAT16}; |
|
412
|
0
|
|
|
|
|
|
$s->{pre_sectors} = $s->{FAT_table_off} |
|
413
|
|
|
|
|
|
|
+ $s->{num_FAT_tables} * $s->{sectors_per_FAT} |
|
414
|
|
|
|
|
|
|
+ $s->{root_dir_entries} * 0x20 / $s->{sector_size}; |
|
415
|
0
|
|
|
|
|
|
$s->{sector_of_cluster0} = $s->{pre_sectors} - 2*$s->{sectors_in_cluster}; |
|
416
|
0
|
|
|
|
|
|
$s->{last_cluster} = int(($s->{total_sectors} - $s->{pre_sectors} |
|
417
|
|
|
|
|
|
|
+ $s->{sectors_in_cluster} - 1)/$s->{sectors_in_cluster}) + 2; |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
sub guess_width ($$) { |
|
421
|
0
|
|
|
0
|
0
|
|
my ($s, $raw) = (shift, shift); |
|
422
|
0
|
|
|
|
|
|
my $w = 12; |
|
423
|
0
|
|
|
|
|
|
my %bpb = decode_fields \@e_boot, $s->{extended_bpb}; |
|
424
|
0
|
0
|
0
|
|
|
|
if ($s->{last_cluster} >= 0x10000 or $s->{root_dir_entries} == 0 |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
425
|
|
|
|
|
|
|
or $bpb{ext_boot_signature} != 0x28 and $bpb{ext_boot_signature} != 0x29) { |
|
426
|
0
|
|
|
|
|
|
$w = 32; |
|
427
|
0
|
|
|
|
|
|
%$s = decode_fields [@boot_c, @boot_32], $raw; |
|
428
|
0
|
|
|
|
|
|
preprocess_bootsect $s; |
|
429
|
|
|
|
|
|
|
} elsif ($s->{last_cluster} >= 0x1000) { |
|
430
|
0
|
|
|
|
|
|
$w = 16 |
|
431
|
|
|
|
|
|
|
} else { # Any other way to determine width??? |
|
432
|
|
|
|
|
|
|
} |
|
433
|
0
|
|
|
|
|
|
$s->{bpb_ext_boot_signature} = $bpb{ext_boot_signature}; |
|
434
|
0
|
0
|
|
|
|
|
@$s{keys %bpb} = values %bpb unless $w == 32; |
|
435
|
0
|
|
|
|
|
|
$s->{guessed_FAT_flavor} = $w; |
|
436
|
0
|
|
|
|
|
|
$s->{raw} = $raw; |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub interpret_bootsector ($) { |
|
440
|
0
|
|
|
0
|
1
|
|
my $bootsect = shift; |
|
441
|
0
|
|
|
|
|
|
my $s = {decode_fields [@boot_c, @boot_16], $bootsect}; |
|
442
|
0
|
|
|
|
|
|
preprocess_bootsect $s; |
|
443
|
0
|
|
|
|
|
|
guess_width $s, $bootsect; |
|
444
|
0
|
|
|
|
|
|
$s |
|
445
|
|
|
|
|
|
|
} |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub check_bootsector ($;$) { |
|
448
|
0
|
|
|
0
|
1
|
|
my $s = shift; |
|
449
|
|
|
|
|
|
|
# Expected size of FAT with 12bit per entry |
|
450
|
0
|
|
|
|
|
|
my $exp = $s->{last_cluster} * $s->{guessed_FAT_flavor}/8/$s->{sector_size}; |
|
451
|
0
|
0
|
|
|
|
|
die "FAT has $s->{sectors_per_FAT} sectors: expecting $exp" |
|
452
|
|
|
|
|
|
|
unless $s->{sectors_per_FAT} >= $exp; |
|
453
|
0
|
0
|
|
|
|
|
warn "FAT has $s->{sectors_per_FAT} sectors: expecting $exp" |
|
454
|
|
|
|
|
|
|
unless $s->{sectors_per_FAT} <= $exp + 10; |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# How to distinguish bootsector from MBR? Jump on FAT12 is "EB 3C 90"; |
|
457
|
|
|
|
|
|
|
# 0x90 is NOP, 0xEB is jump(displacement8). In FAT32, there are extra |
|
458
|
|
|
|
|
|
|
# 28 bytes, so displacement should be 0x58. To be extra safe (e.g., allow |
|
459
|
|
|
|
|
|
|
# chymera bootsector-and-MBR), one should tolerate other jumps... |
|
460
|
|
|
|
|
|
|
|
|
461
|
0
|
0
|
0
|
|
|
|
die sprintf "Unexpected bootsector: first byte %#02x\n", |
|
462
|
|
|
|
|
|
|
ord substr $s->{raw},0,1 |
|
463
|
|
|
|
|
|
|
if shift and not $s->{raw} =~ /^\xEB/; # Check JMP instruction |
|
464
|
0
|
0
|
0
|
|
|
|
return 1 if |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
465
|
|
|
|
|
|
|
($s->{ext_boot_signature} == 0x28 or $s->{ext_boot_signature} == 0x29) |
|
466
|
|
|
|
|
|
|
and $s->{boot_signature} == 0xAA55 |
|
467
|
|
|
|
|
|
|
and $s->{FS_type} =~ /^fat(\d{2})?/i |
|
468
|
|
|
|
|
|
|
and (not $1 or $1 eq $s->{guessed_FAT_flavor}); |
|
469
|
0
|
|
|
|
|
|
die <
|
|
470
|
|
|
|
|
|
|
Unexpected bootsector: guessed_width=$s->{guessed_FAT_flavor}, last_cluster=$s->{last_cluster}, root_dir_entries=$s->{root_dir_entries}, |
|
471
|
|
|
|
|
|
|
boot_signature=$s->{boot_signature}, ext_boot_signature16=$s->{bpb_ext_boot_signature}, ext_boot_signature=$s->{ext_boot_signature}, FS_type=$s->{FS_type} |
|
472
|
|
|
|
|
|
|
EOD |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub string_to_n ($$$$) { |
|
476
|
0
|
|
|
0
|
0
|
|
my($s, $offset, $n, $w) = (shift, shift, shift, shift); |
|
477
|
0
|
|
|
|
|
|
my($n2, $w2) = ($n, ($w>>3)); |
|
478
|
0
|
0
|
|
|
|
|
$n2 >>= 1, $w2 = 3 if $w == 12; |
|
479
|
0
|
|
|
|
|
|
$offset += $w2 * $n2; |
|
480
|
0
|
|
|
|
|
|
my $out = unpack 'V', substr($$s, $offset, $w2) . "\0\0"; |
|
481
|
0
|
0
|
|
|
|
|
if ($w == 12) { |
|
482
|
0
|
0
|
|
|
|
|
if ($n % 2) { |
|
483
|
0
|
|
|
|
|
|
$out >>= 12 |
|
484
|
|
|
|
|
|
|
} else { |
|
485
|
0
|
|
|
|
|
|
$out &= 0xFFF |
|
486
|
|
|
|
|
|
|
} |
|
487
|
|
|
|
|
|
|
} |
|
488
|
|
|
|
|
|
|
$out |
|
489
|
0
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
sub FAT_2array ($$$;$$) { |
|
492
|
0
|
|
0
|
0
|
1
|
|
my($fat, $s, $w, $offset, $lim) = (shift, shift, shift, shift || 0, shift); |
|
493
|
0
|
0
|
|
|
|
|
$lim = length($$s) - $offset unless defined $lim; |
|
494
|
0
|
0
|
|
|
|
|
die "Too large offset=$offset, lim=$lim" if $lim + $offset > length $$s; |
|
495
|
0
|
0
|
|
|
|
|
if ($w == 12) { |
|
496
|
0
|
|
|
|
|
|
$lim += $offset; |
|
497
|
0
|
|
|
|
|
|
while ($offset < $lim) { |
|
498
|
0
|
|
|
|
|
|
my $ss = substr $$s, $offset, 3; |
|
499
|
0
|
|
|
|
|
|
my $n32 = unpack 'V', "$ss\0"; |
|
500
|
|
|
|
|
|
|
# warn sprintf "got %#04x\n", $n32; |
|
501
|
0
|
|
|
|
|
|
push @$fat, ($n32 & 0xFFF), ($n32 >> 12); |
|
502
|
0
|
|
|
|
|
|
$offset += 3; |
|
503
|
|
|
|
|
|
|
} |
|
504
|
|
|
|
|
|
|
} else { |
|
505
|
0
|
0
|
|
|
|
|
my $f = ($w == 32) ? 'V' : 'v'; |
|
506
|
0
|
|
|
|
|
|
$w >>= 3; |
|
507
|
0
|
|
|
|
|
|
$lim = int($lim/$w); |
|
508
|
|
|
|
|
|
|
# Do not extend stack too much: |
|
509
|
0
|
|
|
|
|
|
while ($lim >= 1) { |
|
510
|
0
|
0
|
|
|
|
|
my $l = ($lim > 1000) ? 1000 : $lim; |
|
511
|
0
|
|
|
|
|
|
push @$fat, unpack "x$offset $f$l", $$s; |
|
512
|
0
|
|
|
|
|
|
$lim -= $l; |
|
513
|
0
|
|
|
|
|
|
$offset += $l * $w; |
|
514
|
|
|
|
|
|
|
} |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
# warn "FAT = @$fat\n" |
|
517
|
|
|
|
|
|
|
} |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
sub check_FAT_array ($$;$) { |
|
520
|
0
|
|
0
|
0
|
1
|
|
my ($fat, $b, $offset, @fat) = (shift, shift, shift || 0); |
|
521
|
0
|
0
|
|
|
|
|
FAT_2array(\@fat, $fat, $b->{guessed_FAT_flavor}, $offset, 2*4), |
|
522
|
|
|
|
|
|
|
$fat = \@fat unless 'ARRAY' eq ref $fat; # Make into array |
|
523
|
|
|
|
|
|
|
|
|
524
|
0
|
|
|
|
|
|
my $max_cluster = (1<<$b->{guessed_FAT_flavor}) - 1; |
|
525
|
0
|
0
|
|
|
|
|
die sprintf "Wrong signature %d=%#x, media=%#x in cluster(0)", |
|
526
|
|
|
|
|
|
|
$fat->[0], $fat->[0], $b->{media_type} |
|
527
|
|
|
|
|
|
|
unless $fat->[0] == (($b->{media_type} | 0xffffff00) & $max_cluster); |
|
528
|
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
|
my $eof = $fat->[1]; # Leading 0 in FAT32: |
|
530
|
0
|
0
|
|
|
|
|
die sprintf "Wrong signature %d=%#x in cluster(1)", $eof, $eof |
|
531
|
|
|
|
|
|
|
unless ($eof >> 3) == ($max_cluster >> (3 + 4*(32==$b->{guessed_FAT_flavor}))); |
|
532
|
0
|
|
|
|
|
|
return 1; |
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub cluster_chain ($$$$;$$) { |
|
536
|
0
|
|
0
|
0
|
1
|
|
my ($cluster, $maxc, $fat, $b, $compress, $offset) = (shift, shift, shift, shift, shift, shift||0); |
|
537
|
0
|
|
|
|
|
|
my $last_cluster = $b->{last_cluster}; |
|
538
|
0
|
0
|
0
|
|
|
|
die "problem with cluster=$cluster as a cluster leader" |
|
539
|
|
|
|
|
|
|
unless $cluster >= 2 and $cluster <= $last_cluster; |
|
540
|
0
|
|
|
|
|
|
my ($c, @clusters) = (1, $cluster); |
|
541
|
0
|
|
|
|
|
|
my $w = $b->{guessed_FAT_flavor}; |
|
542
|
0
|
|
|
|
|
|
my $stop_3 = (1<<($w - 3 - 4*($w==32))) - 1; # Leading 0 in FAT32 |
|
543
|
0
|
|
|
|
|
|
my $total = 1; |
|
544
|
0
|
|
0
|
|
|
|
my $subr = ($compress and ref $compress eq 'CODE' and $compress); |
|
545
|
0
|
|
|
|
|
|
while (--$maxc) { |
|
546
|
|
|
|
|
|
|
# warn "processing $cluster, rem=$maxc, stop_3=$stop_3, w=$w"; |
|
547
|
0
|
|
|
|
|
|
my $next; |
|
548
|
0
|
0
|
|
|
|
|
if (ref $fat eq 'ARRAY') { |
|
549
|
0
|
|
|
|
|
|
$next = $fat->[$cluster]; |
|
550
|
|
|
|
|
|
|
} else { # A reference to 'V*'-string |
|
551
|
0
|
|
|
|
|
|
$next = string_to_n($fat, $offset, $cluster, $w); |
|
552
|
|
|
|
|
|
|
} |
|
553
|
0
|
0
|
|
|
|
|
if ($compress) { |
|
554
|
0
|
0
|
|
|
|
|
$c++, next if $next == ++$cluster; |
|
555
|
0
|
0
|
|
|
|
|
if ($subr) { |
|
556
|
0
|
|
|
|
|
|
$subr->($clusters[-1], $c); |
|
557
|
0
|
|
|
|
|
|
pop @clusters; |
|
558
|
|
|
|
|
|
|
} else { |
|
559
|
0
|
|
|
|
|
|
push @clusters, $c; |
|
560
|
|
|
|
|
|
|
} # New cluster would be inserted later |
|
561
|
0
|
|
|
|
|
|
$total += $c - 1; |
|
562
|
0
|
|
|
|
|
|
$c = 1; |
|
563
|
|
|
|
|
|
|
} |
|
564
|
0
|
0
|
|
|
|
|
return $total, \@clusters if ($next >> 3) == $stop_3; |
|
565
|
0
|
0
|
|
|
|
|
$next = 'undef' unless defined $next; # XXX ??? |
|
566
|
0
|
0
|
0
|
|
|
|
die "problem with cluster(+1)=$cluster => $next in a cluster chain" |
|
567
|
|
|
|
|
|
|
unless $next >= 2 and $next <= $last_cluster; |
|
568
|
0
|
|
|
|
|
|
$total++, push @clusters, $cluster = $next; |
|
569
|
|
|
|
|
|
|
} |
|
570
|
0
|
|
|
|
|
|
return 0, \@clusters |
|
571
|
|
|
|
|
|
|
} |
|
572
|
|
|
|
|
|
|
|
|
573
|
0
|
0
|
|
0
|
0
|
|
sub min($$){my($a,$b)=@_;$a>$b? $b:$a} |
|
|
0
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub seek_and_read ($$$$;$) { |
|
576
|
0
|
|
|
0
|
0
|
|
my ($fh, $seek, $read) = (shift,shift,shift); |
|
577
|
0
|
0
|
0
|
|
|
|
sysseek $fh, $seek, 0 or die "sysseek $seek: $!" if defined $seek; |
|
578
|
0
|
0
|
|
|
|
|
$_[0]=' ', $_[0] x= $read, $_[0] = '' unless defined $_[0]; |
|
579
|
0
|
0
|
0
|
|
|
|
die "seek_and_read outside of string" if ($_[1] || 0) > length $_[0]; |
|
580
|
0
|
|
0
|
|
|
|
substr($_[0], $_[1] || 0) = ''; |
|
581
|
0
|
|
|
|
|
|
my($r,$t,$c) = ($read, 0); |
|
582
|
0
|
|
0
|
|
|
|
$r -= $c, $t += $c |
|
583
|
|
|
|
|
|
|
while $r and $c = sysread $fh, $_[0], min($r, $lim_read), length $_[0]; |
|
584
|
0
|
0
|
|
|
|
|
die "Short read ($t instead of $read)" unless $t == $read; |
|
585
|
0
|
|
|
|
|
|
1; |
|
586
|
|
|
|
|
|
|
} |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
sub read_FAT_data ($$;$$$) { |
|
589
|
0
|
|
0
|
0
|
1
|
|
my ($fh, $how, $offset, $b, $FAT) = (shift, shift, shift||0, shift, shift); |
|
590
|
0
|
|
|
|
|
|
my ($close, $inif, $out, $mbr, $b_read); |
|
591
|
0
|
0
|
|
|
|
|
unless (ref $fh) { |
|
592
|
0
|
0
|
|
|
|
|
open IN, '<', $inif = $fh or die "open `$fh' for read: $!"; |
|
593
|
0
|
|
|
|
|
|
$fh = \*IN; |
|
594
|
0
|
|
|
|
|
|
$close = 1; |
|
595
|
|
|
|
|
|
|
} |
|
596
|
0
|
|
|
|
|
|
binmode $fh; |
|
597
|
0
|
0
|
|
|
|
|
if (defined $how->{do_MBR}) { |
|
598
|
0
|
|
|
|
|
|
seek_and_read $fh, $offset, 512, $mbr; |
|
599
|
0
|
0
|
0
|
|
|
|
if ($how->{do_MBR} eq 'maybe' and defined $how->{do_bootsector}) { |
|
600
|
0
|
0
|
|
|
|
|
eval { my $b1 = interpret_bootsector $mbr; |
|
|
0
|
|
|
|
|
|
|
|
601
|
0
|
|
|
|
|
|
check_bootsector $b1; |
|
602
|
0
|
|
|
|
|
|
$out->{bootsect_off} = $offset; |
|
603
|
0
|
|
|
|
|
|
$b = $out->{bootsector} = $b1; |
|
604
|
0
|
|
|
|
|
|
$b_read = 1 } and undef $mbr; |
|
605
|
|
|
|
|
|
|
} |
|
606
|
0
|
0
|
0
|
|
|
|
if ($mbr and (defined $how->{parse_MBR} or defined $how->{do_bootsector} |
|
|
|
|
0
|
|
|
|
|
|
607
|
|
|
|
|
|
|
or defined $how->{do_rootdir} or defined $how->{do_FAT})) { |
|
608
|
0
|
0
|
|
|
|
|
my($fields, @p) = MBR_2_partitions $mbr or die "Wrong signature in MBR"; |
|
609
|
0
|
0
|
|
|
|
|
my @valid = defined $how->{partition} ? $how->{partition} : (0..3); |
|
610
|
|
|
|
|
|
|
# Type = 0 is Empty; FreeSpace is not marked as a partition??? |
|
611
|
0
|
|
0
|
|
|
|
@valid = grep $p[$_]{start_lba} && $p[$_]{sectors} && $p[$_]{type}, @valid; |
|
612
|
0
|
0
|
|
|
|
|
unless (@valid) { |
|
613
|
0
|
0
|
|
|
|
|
die "Partition $how->{partition} invalid" if $how->{partition}; |
|
614
|
0
|
|
|
|
|
|
die "No valid partition found"; |
|
615
|
|
|
|
|
|
|
} |
|
616
|
0
|
0
|
|
|
|
|
die "Too many valid partitions: @valid" if @valid > 1; |
|
617
|
0
|
|
|
|
|
|
$offset += $p[$valid[0]]{start_lba} * 512; |
|
618
|
0
|
|
|
|
|
|
$out->{mbr} = {%$fields, partitions => \@p}; |
|
619
|
|
|
|
|
|
|
} |
|
620
|
|
|
|
|
|
|
} |
|
621
|
0
|
0
|
0
|
|
|
|
if (defined $how->{do_bootsector} and not $b_read) { |
|
622
|
0
|
0
|
|
|
|
|
die "Bootsector given as argument and needs to be read too?" if $b; |
|
623
|
0
|
|
|
|
|
|
seek_and_read $fh, $offset, 512, my $bs; |
|
624
|
0
|
0
|
0
|
|
|
|
if (defined $how->{parse_bootsector} or defined $how->{do_rootdir} |
|
|
|
|
0
|
|
|
|
|
|
625
|
|
|
|
|
|
|
or defined $how->{do_FAT}) { |
|
626
|
0
|
|
|
|
|
|
$b = interpret_bootsector $bs; |
|
627
|
0
|
|
|
|
|
|
check_bootsector $b; |
|
628
|
|
|
|
|
|
|
} else { |
|
629
|
0
|
|
|
|
|
|
$b = {raw => $bs}; |
|
630
|
|
|
|
|
|
|
} |
|
631
|
0
|
|
|
|
|
|
$out->{bootsector_offset} = $offset; |
|
632
|
0
|
|
|
|
|
|
$out->{bootsector} = $b; |
|
633
|
|
|
|
|
|
|
} |
|
634
|
0
|
0
|
|
|
|
|
if (defined $how->{do_FAT}) { |
|
635
|
0
|
0
|
|
|
|
|
die "need bootsector" unless $b; |
|
636
|
0
|
0
|
|
|
|
|
die "FAT given as argument and needs to be read too?" if $FAT; |
|
637
|
0
|
|
|
|
|
|
my $o = $offset; |
|
638
|
0
|
0
|
|
|
|
|
$o += ($b->{FAT_table_off} + $how->{do_FAT} * $b->{sectors_per_FAT}) |
|
639
|
|
|
|
|
|
|
* $b->{sector_size} unless $how->{FAT_separate}; |
|
640
|
0
|
0
|
|
|
|
|
die "FAT[$how->{do_FAT}] not present: only $b->{num_FAT_tables} FAT table" |
|
641
|
|
|
|
|
|
|
if $b->{num_FAT_tables} <= $how->{do_FAT}; |
|
642
|
0
|
|
|
|
|
|
seek_and_read $fh, $o, $b->{sector_size} * $b->{sectors_per_FAT}, my $F; |
|
643
|
0
|
0
|
0
|
|
|
|
if (defined $how->{parse_FAT} |
|
|
|
|
0
|
|
|
|
|
|
644
|
|
|
|
|
|
|
and $b->{last_cluster} < ($how->{parse_FAT} || 3e6)) { |
|
645
|
0
|
|
|
|
|
|
my @f; |
|
646
|
0
|
|
|
|
|
|
$#f = $b->{last_cluster}; |
|
647
|
0
|
|
|
|
|
|
@f = (); |
|
648
|
0
|
|
|
|
|
|
FAT_2array(\@f, \$F, $b->{guessed_FAT_flavor}); |
|
649
|
0
|
|
|
|
|
|
$FAT = \@f; |
|
650
|
|
|
|
|
|
|
} else { |
|
651
|
0
|
|
|
|
|
|
$FAT = \$F; |
|
652
|
|
|
|
|
|
|
} |
|
653
|
0
|
|
|
|
|
|
$out->{FAT} = $FAT; |
|
654
|
0
|
0
|
0
|
|
|
|
$out->{FAT_raw} = \$F if $how->{raw_FAT} or not defined $how->{parse_FAT}; |
|
655
|
|
|
|
|
|
|
} |
|
656
|
0
|
0
|
|
|
|
|
if (defined $how->{do_rootdir}) { |
|
657
|
0
|
0
|
|
|
|
|
die "need bootsector" unless $b; |
|
658
|
0
|
|
|
|
|
|
my($s, $l, $o) = ''; |
|
659
|
0
|
0
|
|
|
|
|
if ($how->{rootdir_is_standalone}) { |
|
660
|
0
|
|
|
|
|
|
local $/; |
|
661
|
0
|
|
|
|
|
|
$s = <$fh>; |
|
662
|
|
|
|
|
|
|
} else { |
|
663
|
0
|
|
|
|
|
|
my($L, $S) = ($b->{sector_size} * $b->{sectors_in_cluster}, |
|
664
|
|
|
|
|
|
|
$offset + $b->{sector_of_cluster0}*$b->{sector_size}); |
|
665
|
0
|
0
|
|
|
|
|
if ($b->{guessed_FAT_flavor} == 32) { |
|
666
|
|
|
|
|
|
|
my $appender = sub ($$) { |
|
667
|
0
|
|
|
0
|
|
|
my($start, $len) = (shift, shift); |
|
668
|
0
|
|
|
|
|
|
seek_and_read $fh, $S + $L * $start, $len * $L, $s, length $s; |
|
669
|
|
|
|
|
|
|
} |
|
670
|
0
|
|
|
|
|
|
; |
|
671
|
0
|
0
|
|
|
|
|
if ($FAT) { |
|
672
|
0
|
|
|
|
|
|
cluster_chain($b->{rootdir_start_cluster}, 0, $FAT, $b, $appender); |
|
673
|
|
|
|
|
|
|
} else { |
|
674
|
0
|
|
|
|
|
|
$appender->($b->{rootdir_start_cluster}, 1); # XXX Assume 1 cluster |
|
675
|
|
|
|
|
|
|
} |
|
676
|
|
|
|
|
|
|
} else { |
|
677
|
0
|
|
|
|
|
|
my $off = ($offset + $b->{sector_size} * |
|
678
|
|
|
|
|
|
|
($b->{FAT_table_off} + $b->{num_FAT_tables} * $b->{sectors_per_FAT})); |
|
679
|
0
|
|
|
|
|
|
seek_and_read $fh, $off, $b->{root_dir_entries} * 0x20, $s; |
|
680
|
|
|
|
|
|
|
} |
|
681
|
|
|
|
|
|
|
} |
|
682
|
0
|
0
|
|
|
|
|
if (defined $how->{parse_rootdir}) { |
|
683
|
0
|
|
|
|
|
|
my($res, $f) = interpret_directory $s, $b->{guessed_FAT_flavor} == 32, |
|
684
|
|
|
|
|
|
|
$how->{keep_del}, $how->{keep_dots}, $how->{keep_labels}; |
|
685
|
0
|
0
|
0
|
|
|
|
die "Directory ended in the middle of LFN" if ($res || 0) eq 'mid'; |
|
686
|
0
|
|
|
|
|
|
$out->{rootdir_files} = $f; |
|
687
|
0
|
|
|
|
|
|
$out->{rootdir_ended} = $res; |
|
688
|
|
|
|
|
|
|
} |
|
689
|
0
|
|
|
|
|
|
$out->{rootdir_raw} = $s; |
|
690
|
|
|
|
|
|
|
} |
|
691
|
0
|
0
|
0
|
|
|
|
close $fh or die "close `$inif' for read: $!" if $close; |
|
692
|
0
|
|
|
|
|
|
return $out; |
|
693
|
|
|
|
|
|
|
} |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
sub output_cluster_chain ($$$$$$;$) { |
|
696
|
0
|
|
0
|
0
|
0
|
|
my($ifh, $ofh, $start, $size, $b, $FAT, $offset) = |
|
697
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift, shift, shift||0); |
|
698
|
0
|
0
|
|
|
|
|
return unless $size; |
|
699
|
0
|
|
|
|
|
|
my($L, $S) = ($b->{sector_size} * $b->{sectors_in_cluster}, |
|
700
|
|
|
|
|
|
|
$offset + $b->{sector_of_cluster0}*$b->{sector_size}); |
|
701
|
|
|
|
|
|
|
my $piper = sub ($$) { |
|
702
|
0
|
|
|
0
|
|
|
my($start1, $len) = ($L * shift, $L * shift); |
|
703
|
|
|
|
|
|
|
# warn "Piper: start=$start1, len=$len\n"; |
|
704
|
0
|
0
|
|
|
|
|
if ($len > $size) { |
|
705
|
0
|
0
|
|
|
|
|
die "Cluster chain too long, len=$len, cl=$L, sz=$size" if $len - $L >= $size; |
|
706
|
0
|
|
|
|
|
|
$len = $size; |
|
707
|
|
|
|
|
|
|
} |
|
708
|
0
|
|
|
|
|
|
while ($len) { |
|
709
|
0
|
0
|
|
|
|
|
my $l = ($len > (1<<24)) ? (1<<24) : $len; # 16M chunks |
|
710
|
0
|
|
|
|
|
|
my $s; |
|
711
|
0
|
|
|
|
|
|
seek_and_read $ifh, $S + $start1, $l, $s; |
|
712
|
0
|
|
|
|
|
|
syswrite $ofh, $s, length $s; |
|
713
|
0
|
|
|
|
|
|
$len -= $l, $size -= $l, $start1 += $l; |
|
714
|
|
|
|
|
|
|
} |
|
715
|
0
|
|
|
|
|
|
}; |
|
716
|
0
|
|
|
|
|
|
my $sz = int(($size + $L - 1)/$L); |
|
717
|
0
|
0
|
|
|
|
|
$piper->($start, $sz), return 1 if not defined $FAT; |
|
718
|
|
|
|
|
|
|
# Inspect the last cluster for end of chain too |
|
719
|
0
|
|
|
|
|
|
my ($total) = cluster_chain $start, $sz+1, $FAT, $b, $piper; |
|
720
|
0
|
0
|
|
|
|
|
die "No end of cluster chain" unless $total; |
|
721
|
0
|
|
|
|
|
|
1; |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
sub read_cluster_chain ($$$$;$$) { # No size, as in dir... |
|
725
|
0
|
|
0
|
0
|
0
|
|
my($ifh, $start, $b, $FAT, $offset, $exp_len) = |
|
726
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift||0, shift); |
|
727
|
0
|
|
|
|
|
|
my($L, $S, $s) = ($b->{sector_size} * $b->{sectors_in_cluster}, |
|
728
|
|
|
|
|
|
|
$offset + $b->{sector_of_cluster0}*$b->{sector_size}, ''); |
|
729
|
0
|
0
|
0
|
|
|
|
(seek_and_read $ifh, $S + $L * $start, $exp_len, $s), |
|
730
|
|
|
|
|
|
|
return \$s if not defined $FAT and defined $exp_len; |
|
731
|
|
|
|
|
|
|
my $piper = sub ($$) { |
|
732
|
0
|
|
|
0
|
|
|
my($start1, $l) = (shift, shift); |
|
733
|
0
|
|
|
|
|
|
seek_and_read $ifh, $S + $L * $start1, $L * $l, $s, length $s; |
|
734
|
0
|
|
|
|
|
|
}; |
|
735
|
0
|
|
|
|
|
|
my ($total) = cluster_chain $start, 0, $FAT, $b, $piper; |
|
736
|
0
|
0
|
|
|
|
|
die "No end of cluster chain" unless $total; |
|
737
|
0
|
|
|
|
|
|
\$s; |
|
738
|
|
|
|
|
|
|
} |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
sub write_file ($$$$$;$) { |
|
741
|
0
|
|
0
|
0
|
1
|
|
my ($fh, $dir, $f, $b, $FAT, $offset) = |
|
742
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift, shift||0); |
|
743
|
0
|
0
|
0
|
|
|
|
return if $f->{is_volume_label} |
|
|
|
|
0
|
|
|
|
|
|
744
|
|
|
|
|
|
|
or $f->{name} eq 'EA DATA. SF' or $f->{name} eq 'WP ROOT. SF'; |
|
745
|
0
|
0
|
|
|
|
|
die "directory `$f->{name}' as file!" if $f->{is_subdir}; |
|
746
|
0
|
|
|
|
|
|
my $name = "$dir/$f->{name}"; |
|
747
|
0
|
0
|
|
|
|
|
open O, '>', $name or die "error opening $name for write: $!"; |
|
748
|
0
|
|
|
|
|
|
binmode O; |
|
749
|
0
|
|
|
|
|
|
output_cluster_chain($fh, \*O, $f->{cluster}, $f->{size}, $b, $FAT, $offset); |
|
750
|
0
|
0
|
|
|
|
|
close O or die "error closing $name for write: $!"; |
|
751
|
0
|
0
|
|
|
|
|
chmod 0555, $name if $f->{attrib} & 0x1; # read only |
|
752
|
|
|
|
|
|
|
# unset archive mode? |
|
753
|
|
|
|
|
|
|
} |
|
754
|
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
sub recurse_dir ($$$$$$$;$); |
|
756
|
|
|
|
|
|
|
sub recurse_dir ($$$$$$$;$) { |
|
757
|
0
|
|
0
|
0
|
0
|
|
my ($callbk, $path, $fh, $how, $f, $b, $FAT, $offset) = |
|
758
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift, shift, shift, shift||0); |
|
759
|
0
|
|
|
|
|
|
my $files = |
|
760
|
|
|
|
|
|
|
interpret_directory( $$f, $b->{guessed_FAT_flavor} == 32, $how->{keep_del}, |
|
761
|
|
|
|
|
|
|
$how->{keep_dots}, $how->{keep_labels} ); |
|
762
|
0
|
|
|
|
|
|
for my $file (@$files) { |
|
763
|
|
|
|
|
|
|
# next if $file->{is_volume_label}; |
|
764
|
0
|
|
|
|
|
|
my $res = $callbk->($path, $file); |
|
765
|
0
|
0
|
0
|
|
|
|
if ($res and $file->{is_subdir} and not $file->{deleted} |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
766
|
|
|
|
|
|
|
and $file->{name} !~ /^\.(\.)?$/) { |
|
767
|
0
|
|
|
|
|
|
push @$path, $file->{name}; |
|
768
|
0
|
|
|
|
|
|
my $exp_len; |
|
769
|
0
|
0
|
|
|
|
|
$exp_len = $b->{sector_size} * $b->{sectors_in_cluster} |
|
770
|
|
|
|
|
|
|
unless defined $FAT; # XXXX Expect dir size of one cluster??? |
|
771
|
0
|
|
|
|
|
|
recurse_dir($callbk, $path, $fh, $how, |
|
772
|
|
|
|
|
|
|
read_cluster_chain($fh, $file->{cluster}, $b, $FAT, $offset, |
|
773
|
|
|
|
|
|
|
$exp_len), |
|
774
|
|
|
|
|
|
|
$b, $FAT, $offset); |
|
775
|
0
|
|
|
|
|
|
pop @$path; |
|
776
|
|
|
|
|
|
|
} |
|
777
|
|
|
|
|
|
|
} |
|
778
|
|
|
|
|
|
|
} |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
sub write_dir ($$$$$;$$$$) { |
|
781
|
0
|
|
0
|
0
|
1
|
|
my ($fh, $o_root, $ff, $b, $FAT, $how, $depth, $offset, $exists) = |
|
782
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift, shift, shift||0, shift); |
|
783
|
0
|
0
|
|
|
|
|
$depth = 1e100 unless defined $depth; |
|
784
|
|
|
|
|
|
|
my $callbk = sub ($$) { |
|
785
|
0
|
|
|
0
|
|
|
my($path,$f) = (shift, shift); |
|
786
|
0
|
0
|
0
|
|
|
|
next if $f->{is_volume_label} or $f->{name} =~ /^\.(\.)?$/; |
|
787
|
0
|
|
|
|
|
|
my $p = join '/', $o_root, @$path; |
|
788
|
0
|
0
|
|
|
|
|
return write_file $fh, $p, $f, $b, $FAT, $offset unless $f->{is_subdir}; |
|
789
|
0
|
0
|
|
|
|
|
return 0 if @$path >= $depth; |
|
790
|
0
|
0
|
0
|
|
|
|
mkdir "$p/$f->{name}", 0777 or die "mkdir `$p/$f->{name}': $!" |
|
|
|
|
0
|
|
|
|
|
|
791
|
|
|
|
|
|
|
unless $exists and not @$path; |
|
792
|
0
|
|
|
|
|
|
return 1; |
|
793
|
0
|
|
|
|
|
|
}; |
|
794
|
0
|
|
0
|
|
|
|
recurse_dir($callbk, [], $fh, $how||{}, $ff, $b, $FAT, $offset); |
|
795
|
|
|
|
|
|
|
} |
|
796
|
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
sub list_dir ($$$$;$$$) { |
|
798
|
0
|
|
0
|
0
|
0
|
|
my ($fh, $ff, $b, $FAT, $how, $depth, $offset) = |
|
799
|
|
|
|
|
|
|
(shift, shift, shift, shift, shift, shift, shift||0, shift); |
|
800
|
0
|
0
|
|
|
|
|
$depth = 1e100 unless defined $depth; |
|
801
|
|
|
|
|
|
|
my $callbk = sub ($$) { |
|
802
|
0
|
|
|
0
|
|
|
my($path,$f,$pre) = (shift, shift, ''); |
|
803
|
0
|
0
|
|
|
|
|
print("# label=$f->{name}\n"), return if $f->{is_volume_label}; |
|
804
|
0
|
|
|
|
|
|
my $p = join '/', @$path, $f->{name}; |
|
805
|
0
|
0
|
|
|
|
|
$p .= '/' if $f->{is_subdir}; |
|
806
|
0
|
0
|
|
|
|
|
$pre = '#del ' if $f->{deleted}; |
|
807
|
0
|
0
|
|
|
|
|
$pre = '# ' if $f->{name} =~ /^\.(\.)?$/; |
|
808
|
0
|
|
|
|
|
|
print "$pre$f->{attrib}\t$f->{size}\t$f->{date_write}/$f->{time_write}\t$f->{cluster}\t$p\n"; |
|
809
|
0
|
|
|
|
|
|
return @$path < $depth; |
|
810
|
0
|
|
|
|
|
|
}; |
|
811
|
0
|
|
0
|
|
|
|
recurse_dir($callbk, [], $fh, $how||{}, $ff, $b, $FAT, $offset); |
|
812
|
|
|
|
|
|
|
} |
|
813
|
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
# First FAT entry contains 0xFF*, the rest 0x0F*; so 0x2*, 0xA* do not conflict |
|
815
|
|
|
|
|
|
|
sub compress_FAT ($$$) { # Down to 2-4 bytes/file after gzip... |
|
816
|
0
|
|
|
0
|
0
|
|
my($FAT, $w, $fh) = (shift, shift, shift); |
|
817
|
0
|
|
|
|
|
|
my ($c, $cc, $c0, $off, $ee, $remain, @out, $F) = (0, 0, 0, 0); |
|
818
|
0
|
|
|
|
|
|
local $\ = ''; |
|
819
|
0
|
|
|
|
|
|
while (1) { |
|
820
|
0
|
0
|
|
|
|
|
if (ref $FAT eq 'ARRAY') { |
|
821
|
0
|
|
|
|
|
|
$F = $FAT, $remain = 0; |
|
822
|
|
|
|
|
|
|
} else { |
|
823
|
0
|
0
|
|
|
|
|
$remain = length $$FAT unless defined $remain; |
|
824
|
0
|
|
|
|
|
|
my $l = $remain; |
|
825
|
0
|
0
|
|
|
|
|
$l = 750000 if $l > 750000; # Should be divisible by 12... |
|
826
|
0
|
|
|
|
|
|
FAT_2array($F = [], $FAT, $w, $off, $l); |
|
827
|
0
|
|
|
|
|
|
$remain -= $l, $off += $l; |
|
828
|
|
|
|
|
|
|
} |
|
829
|
0
|
|
|
|
|
|
for my $e (@$F) { |
|
830
|
0
|
|
|
|
|
|
$c++; # Next cluster |
|
831
|
0
|
0
|
|
|
|
|
if ($e) { |
|
832
|
0
|
0
|
|
|
|
|
(push @out, 0xA0000000 + $c0), $c0 = 0 if $c0; |
|
833
|
0
|
0
|
|
|
|
|
$cc++, next if $e == $c; |
|
834
|
0
|
0
|
|
|
|
|
(push @out, 0x20000000 + $cc), $cc = 0 if $cc; |
|
835
|
0
|
|
|
|
|
|
push @out, $e; |
|
836
|
|
|
|
|
|
|
} else { |
|
837
|
0
|
0
|
|
|
|
|
(push @out, 0x20000000 + $cc), $cc = 0 if $cc; |
|
838
|
0
|
|
|
|
|
|
$c0++, next; |
|
839
|
|
|
|
|
|
|
} |
|
840
|
0
|
0
|
|
|
|
|
(print $fh pack 'V*', @out), @out = () if @out > 1000; |
|
841
|
|
|
|
|
|
|
} |
|
842
|
0
|
0
|
|
|
|
|
last unless $remain; |
|
843
|
|
|
|
|
|
|
} |
|
844
|
0
|
0
|
|
|
|
|
push @out, 0xA0000000 + $c0 if $c0; |
|
845
|
0
|
0
|
|
|
|
|
push @out, 0x20000000 + $cc if $cc; |
|
846
|
0
|
|
|
|
|
|
print $fh pack 'V*', @out; |
|
847
|
|
|
|
|
|
|
} |
|
848
|
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
sub _FAT_2string ($$$$) { |
|
850
|
0
|
|
|
0
|
|
|
my($FAT, $w, $start, $c) = @_; |
|
851
|
0
|
0
|
|
|
|
|
if ($w eq 12) { |
|
852
|
0
|
|
|
|
|
|
my($out, $e) = ('', $start + $c); |
|
853
|
0
|
|
|
|
|
|
while ($start < $e) { # Assume even |
|
854
|
0
|
|
|
|
|
|
my $x = pack 'V', $FAT->[$start] + ($FAT->[$start+1]<<12); |
|
855
|
0
|
|
|
|
|
|
$out .= substr $x, 0, 3; |
|
856
|
0
|
|
|
|
|
|
$start += 2; |
|
857
|
|
|
|
|
|
|
} |
|
858
|
0
|
|
|
|
|
|
$out; |
|
859
|
|
|
|
|
|
|
} else { # $w is 'V' or 'v' |
|
860
|
0
|
|
|
|
|
|
pack "$w*", @$FAT[$start .. $start + $c - 1]; |
|
861
|
|
|
|
|
|
|
} |
|
862
|
|
|
|
|
|
|
} |
|
863
|
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
sub output_FAT ($$$) { |
|
865
|
0
|
|
|
0
|
0
|
|
my($FAT, $w, $fh, $s) = (shift, shift, shift, 0); |
|
866
|
0
|
|
|
|
|
|
local $\ = ''; |
|
867
|
0
|
0
|
|
|
|
|
(print $fh $$FAT), return unless ref $FAT eq 'ARRAY'; |
|
868
|
0
|
|
|
|
|
|
my $c = @$FAT; |
|
869
|
0
|
0
|
|
|
|
|
$w = (32 == $w) ? 'V' : 'v' if $w != 12; |
|
|
|
0
|
|
|
|
|
|
|
870
|
0
|
|
|
|
|
|
while ($c) { |
|
871
|
0
|
0
|
|
|
|
|
my $cc = ($c > 750000) ? 750000 : $c; |
|
872
|
0
|
|
|
|
|
|
print $fh _FAT_2string($FAT, $w, $s, $cc); |
|
873
|
0
|
|
|
|
|
|
$c -= $cc, $s += $cc; |
|
874
|
|
|
|
|
|
|
} |
|
875
|
|
|
|
|
|
|
} |
|
876
|
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
sub __emit ($$$) { |
|
878
|
0
|
|
|
0
|
|
|
my($ofh, $out, $w) = (shift, shift, shift); |
|
879
|
0
|
|
|
|
|
|
my $outc = @$out; |
|
880
|
0
|
|
|
|
|
|
my $cut; |
|
881
|
0
|
0
|
0
|
|
|
|
$cut = 1, $outc-- if $w eq 12 and $outc % 2; |
|
882
|
0
|
|
|
|
|
|
print $ofh _FAT_2string($out, $w, 0, $outc); |
|
883
|
0
|
0
|
|
|
|
|
@$out = $cut ? $$out[-1] : (); |
|
884
|
|
|
|
|
|
|
} |
|
885
|
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
sub uncompress_FAT ($$$) { |
|
887
|
0
|
|
|
0
|
0
|
|
my($ifh, $w, $ofh) = (shift, shift, shift); |
|
888
|
0
|
|
|
|
|
|
my ($c, @f, @out, $F) = (0); |
|
889
|
0
|
|
|
|
|
|
@f[0x2, 0xA] = (0x2, 0xA); |
|
890
|
0
|
|
|
|
|
|
local $\ = ''; |
|
891
|
0
|
0
|
|
|
|
|
$w = (32 == $w) ? 'V' : 'v' if $w != 12; |
|
|
|
0
|
|
|
|
|
|
|
892
|
0
|
|
|
|
|
|
while (1) { |
|
893
|
0
|
0
|
|
|
|
|
last unless sysread $ifh, $F, 4*1e4; |
|
894
|
0
|
|
|
|
|
|
for my $n (unpack 'V*', $F) { |
|
895
|
0
|
|
|
|
|
|
my $n1 = $f[$n >> 28]; |
|
896
|
0
|
0
|
|
|
|
|
if ($n1) { # Special |
|
897
|
0
|
|
|
|
|
|
my $cc = $n & 0xFFFFFFF; |
|
898
|
0
|
|
|
|
|
|
while ($cc) { |
|
899
|
0
|
|
|
|
|
|
my ($ccc, @rest) = $cc; |
|
900
|
0
|
0
|
|
|
|
|
$ccc = 1e4 if $ccc > 1e4; |
|
901
|
0
|
0
|
|
|
|
|
if ($n1 == 0x2) { # A run |
|
902
|
0
|
|
|
|
|
|
push @out, $c + 1 .. $c + $ccc; |
|
903
|
|
|
|
|
|
|
} else { # 0s |
|
904
|
0
|
|
|
|
|
|
push @out, (0) x $ccc; |
|
905
|
|
|
|
|
|
|
} |
|
906
|
0
|
|
|
|
|
|
$cc -= $ccc, $c += $ccc; |
|
907
|
0
|
0
|
|
|
|
|
__emit($ofh, \@out, $w) if @out >= 1e4; |
|
908
|
|
|
|
|
|
|
} |
|
909
|
|
|
|
|
|
|
} else { |
|
910
|
0
|
|
|
|
|
|
$c++; |
|
911
|
0
|
|
|
|
|
|
push @out, $n; |
|
912
|
|
|
|
|
|
|
} |
|
913
|
|
|
|
|
|
|
} |
|
914
|
0
|
0
|
|
|
|
|
__emit($ofh, \@out, $w) if @out >= 1e4; |
|
915
|
|
|
|
|
|
|
} |
|
916
|
0
|
|
|
|
|
|
__emit($ofh, \@out, $w); |
|
917
|
0
|
0
|
|
|
|
|
die "Odd number of uncompressed items, w=$w" if @out; |
|
918
|
|
|
|
|
|
|
} |
|
919
|
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
1; |
|
921
|
|
|
|
|
|
|
__END__ |