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__ |