line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mac::Finder::DSStore; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Mac::Finder::DSStore - Read and write Macintosh Finder DS_Store files |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 DESCRIPTION |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
C provides a handful of functions for reading and |
10
|
|
|
|
|
|
|
writing the desktop database files created by the Macintosh Finder. |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 FUNCTIONS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Many functions take a C<$store> argument which is the opened file as |
15
|
|
|
|
|
|
|
an instance of L, or a C<$block> |
16
|
|
|
|
|
|
|
argument which is a specific block of the file as an instance of |
17
|
|
|
|
|
|
|
L. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=cut |
20
|
|
|
|
|
|
|
|
21
|
3
|
|
|
3
|
|
134631
|
use strict; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
1565
|
|
22
|
3
|
|
|
3
|
|
24
|
use warnings; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
119
|
|
23
|
3
|
|
|
3
|
|
3158
|
use POSIX qw(ceil); |
|
3
|
|
|
|
|
35283
|
|
|
3
|
|
|
|
|
24
|
|
24
|
3
|
|
|
3
|
|
3695
|
use Carp qw(croak); |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
113
|
|
25
|
3
|
|
|
3
|
|
23
|
use Fcntl; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
7237
|
|
26
|
|
|
|
|
|
|
require Exporter; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our($VERSION) = '1.00'; |
29
|
|
|
|
|
|
|
our(@ISA) = qw(Exporter); |
30
|
|
|
|
|
|
|
our(@EXPORT_OK) = qw( getDSDBEntries putDSDBEntries writeDSDBEntries makeEntries ); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
our($testpoint); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head2 @records = &Mac::Finder::DSStore::getDSDBEntries($store[, $callback]) |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Retrieves the "superblock" pointed to by the C entry in the store's table |
37
|
|
|
|
|
|
|
of contents, and traverses the B-tree it points to, returning a list of |
38
|
|
|
|
|
|
|
the records in the tree. Alternately, you can supply a callback which will |
39
|
|
|
|
|
|
|
be invoked for each record, and C will return an empty list. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=cut |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub getBTreeRootblock { |
44
|
9
|
|
|
9
|
0
|
19
|
my($store) = @_; |
45
|
9
|
|
|
|
|
150
|
return $store->blockByNumber($store->{toc}->{DSDB})->read(20, 'N*'); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub getDSDBEntries { |
49
|
6
|
|
|
6
|
1
|
4946
|
my($file, $callback) = @_; |
50
|
|
|
|
|
|
|
|
51
|
6
|
|
|
|
|
13
|
my(@retval); |
52
|
|
|
|
|
|
|
|
53
|
6
|
50
|
|
633
|
|
45
|
$callback = sub { push(@retval, $_[0]); } unless defined $callback; |
|
633
|
|
|
|
|
1222
|
|
54
|
|
|
|
|
|
|
|
55
|
6
|
|
|
|
|
25
|
my($rootnode, $height, $nrec, $nnodes, $blksize) = &getBTreeRootblock($file); |
56
|
|
|
|
|
|
|
|
57
|
6
|
|
|
|
|
35
|
my($n) = &traverse_btree($file, $rootnode, $callback); |
58
|
|
|
|
|
|
|
|
59
|
6
|
50
|
|
|
|
20
|
warn "Header node count ($nrec) not equal to actual node count ($n)" |
60
|
|
|
|
|
|
|
if $n != $nrec; |
61
|
|
|
|
|
|
|
|
62
|
6
|
|
|
|
|
182
|
@retval; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head2 &Mac::Finder::DSStore::putDSDBEntries($store, $arrayref) |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
C<$arrayref> must contain a correctly ordered list of |
68
|
|
|
|
|
|
|
C objects. They will be evenly |
69
|
|
|
|
|
|
|
organized into a B-tree structure and written to the C<$store>. If there is |
70
|
|
|
|
|
|
|
an existing tree of records in the file already, it will be deallocated. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
This function does not flush the allocator's information back to the file. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=cut |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub putDSDBEntries { |
77
|
5
|
|
|
5
|
1
|
31
|
my($file, $recs) = @_; |
78
|
|
|
|
|
|
|
|
79
|
5
|
|
|
|
|
9
|
my($tocblock, $pagesize); |
80
|
0
|
|
|
|
|
0
|
my($pagecount, $reccount, $height); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Delete the old btree (but keep its superblock), or allocate a superblock. |
83
|
5
|
100
|
|
|
|
29
|
if(defined($file->{toc}->{DSDB})) { |
84
|
3
|
|
|
|
|
13
|
$tocblock = $file->{toc}->{DSDB}; |
85
|
3
|
|
|
|
|
6
|
my($old_rootblock); |
86
|
3
|
|
|
|
|
17
|
($old_rootblock, $pagesize) = (&getBTreeRootblock($file))[0, 4]; |
87
|
3
|
|
|
|
|
19
|
&freeBTreeNode($file, $old_rootblock); |
88
|
|
|
|
|
|
|
} else { |
89
|
2
|
|
|
|
|
12
|
$tocblock = $file->allocate( 20 ); |
90
|
2
|
|
|
|
|
8
|
$file->{toc}->{DSDB} = $tocblock; |
91
|
2
|
|
|
|
|
3
|
$pagesize = 0x1000; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
5
|
|
|
|
|
15
|
$reccount = @$recs; |
95
|
5
|
|
|
|
|
9
|
$pagecount = 0; |
96
|
5
|
|
|
|
|
8
|
$height = 0; |
97
|
|
|
|
|
|
|
|
98
|
5
|
|
|
|
|
11
|
my(@children); |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# Partition the records into btree nodes, from the bottom of |
101
|
|
|
|
|
|
|
# the tree working towards the root. |
102
|
5
|
|
|
|
|
13
|
do { |
103
|
8
|
|
|
|
|
12
|
my(@sizes); |
104
|
|
|
|
|
|
|
|
105
|
8
|
100
|
|
|
|
25
|
if (@children) { |
106
|
|
|
|
|
|
|
# Interior node: child pointers interleaved with records |
107
|
3
|
|
|
|
|
10
|
@sizes = map { 4 + $_->byteSize } @$recs; |
|
52
|
|
|
|
|
87
|
|
108
|
|
|
|
|
|
|
} else { |
109
|
|
|
|
|
|
|
# Leaf node: just a bunch of records |
110
|
5
|
|
|
|
|
22
|
@sizes = map { $_->byteSize } @$recs; |
|
624
|
|
|
|
|
1324
|
|
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# In addition to @sizes, each page contains a record |
114
|
|
|
|
|
|
|
# count and a flag/childnode field (4 bytes each) |
115
|
8
|
|
|
|
|
132
|
my(@interleaf) = &partition_sizes($pagesize - 8, @sizes); |
116
|
8
|
|
|
|
|
16
|
my(@nchildren); |
117
|
|
|
|
|
|
|
|
118
|
8
|
|
|
|
|
15
|
my($next) = 0; |
119
|
8
|
|
|
|
|
27
|
foreach my $non (@interleaf, 1+$#$recs) { |
120
|
60
|
|
|
|
|
379
|
my($blknr) = $file->allocate($pagesize); |
121
|
60
|
|
|
|
|
141
|
push(@nchildren, $blknr); |
122
|
60
|
|
|
|
|
403
|
my($blk) = $file->blockByNumber($blknr, 1); |
123
|
60
|
100
|
|
|
|
171
|
if (@children) { |
124
|
6
|
|
|
|
|
256
|
&writeBTreeNode($blk, |
125
|
|
|
|
|
|
|
[ @$recs[ $next .. $non-1 ] ], |
126
|
|
|
|
|
|
|
[ @children[ $next .. $non ] ] ); |
127
|
|
|
|
|
|
|
} else { |
128
|
54
|
|
|
|
|
426
|
&writeBTreeNode($blk, |
129
|
|
|
|
|
|
|
[ @$recs[ $next .. $non-1 ] ]); |
130
|
|
|
|
|
|
|
} |
131
|
60
|
|
|
|
|
263839
|
$blk->close(1); |
132
|
60
|
|
|
|
|
101
|
$next = $non + 1; |
133
|
60
|
|
|
|
|
366
|
$pagecount ++; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
8
|
|
|
|
|
17
|
$height ++; |
137
|
8
|
|
|
|
|
21
|
$recs = [ map { $recs->[$_] } @interleaf ]; |
|
52
|
|
|
|
|
86
|
|
138
|
8
|
|
|
|
|
46
|
@children = @nchildren; |
139
|
8
|
50
|
|
|
|
83
|
die unless @children == 1+@$recs; |
140
|
|
|
|
|
|
|
} while(@children > 1); |
141
|
5
|
50
|
|
|
|
16
|
die unless 0 == @$recs; |
142
|
|
|
|
|
|
|
|
143
|
5
|
|
|
|
|
24
|
my($masterblock) = $file->blockByNumber($tocblock, 1); |
144
|
5
|
|
|
|
|
24
|
$masterblock->write('NNNNN', |
145
|
|
|
|
|
|
|
$children[0], |
146
|
|
|
|
|
|
|
$height - 1, |
147
|
|
|
|
|
|
|
$reccount, |
148
|
|
|
|
|
|
|
$pagecount, |
149
|
|
|
|
|
|
|
$pagesize); |
150
|
5
|
|
|
|
|
18
|
$masterblock->close; |
151
|
|
|
|
|
|
|
|
152
|
5
|
|
|
|
|
17
|
1; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Given a list of sizes, break them into groups so that |
156
|
|
|
|
|
|
|
# each group sums to no more than $max, not including the items |
157
|
|
|
|
|
|
|
# that separate them (returned in @ejecta). |
158
|
|
|
|
|
|
|
sub partition_sizes { |
159
|
8
|
|
|
8
|
0
|
74
|
my($max, @sizes) = @_; |
160
|
8
|
|
|
|
|
15
|
my($sum) = 0; |
161
|
8
|
|
|
|
|
214
|
$sum += $_ foreach @sizes; |
162
|
|
|
|
|
|
|
|
163
|
8
|
100
|
|
|
|
41
|
return () if $sum <= $max; |
164
|
|
|
|
|
|
|
|
165
|
3
|
|
|
|
|
7
|
my(@ejecta); |
166
|
3
|
|
|
|
|
32
|
my($bcount) = ceil($sum / $max); |
167
|
3
|
|
|
|
|
7
|
my($target) = $sum / $bcount; |
168
|
|
|
|
|
|
|
|
169
|
3
|
|
|
|
|
7
|
my($n) = 0; |
170
|
3
|
|
|
|
|
5
|
for(;;) { |
171
|
55
|
|
|
|
|
63
|
my($bsum) = 0; |
172
|
55
|
|
66
|
|
|
332
|
while( $n < @sizes && $bsum < $target && ($bsum + $sizes[$n]) < $max ) { |
|
|
|
100
|
|
|
|
|
173
|
589
|
|
|
|
|
656
|
$bsum += $sizes[$n]; |
174
|
589
|
|
|
|
|
3342
|
$n ++; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
55
|
100
|
|
|
|
113
|
last if $n >= @sizes; |
178
|
|
|
|
|
|
|
|
179
|
52
|
|
|
|
|
76
|
push(@ejecta, $n); |
180
|
52
|
|
|
|
|
88
|
$n++; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
3
|
|
|
|
|
74
|
@ejecta; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub traverse_btree { |
187
|
61
|
|
|
61
|
0
|
117
|
my($store, $nodenr, $callback) = @_; |
188
|
61
|
|
|
|
|
134
|
my($count); |
189
|
61
|
|
|
|
|
219
|
my($values, $pointers) = &readBTreeNode( $store->blockByNumber( $nodenr ) ); |
190
|
|
|
|
|
|
|
|
191
|
61
|
50
|
|
|
|
258
|
if ($testpoint) { |
192
|
61
|
|
|
|
|
734
|
my($o) = Mac::Finder::DSStore::BuddyAllocator::StringBlock->new(); |
193
|
|
|
|
|
|
|
{ |
194
|
|
|
|
|
|
|
# Temporarily disable the test point so writeBTreeNode doesn't |
195
|
|
|
|
|
|
|
# recursively invoke it |
196
|
61
|
|
|
|
|
91
|
local($testpoint) = undef; |
|
61
|
|
|
|
|
94
|
|
197
|
61
|
|
|
|
|
162
|
&writeBTreeNode($o, $values, $pointers); |
198
|
|
|
|
|
|
|
} |
199
|
61
|
|
|
|
|
294
|
my($actual) = $store->blockByNumber( $nodenr )->copyback; |
200
|
61
|
|
|
|
|
267
|
my($roundtrip) = $o->copyback; |
201
|
61
|
|
|
|
|
258
|
$actual = substr($actual, 0, length($roundtrip)); |
202
|
61
|
|
|
|
|
227
|
$testpoint->( $actual, $roundtrip ); |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
61
|
|
|
|
|
45805
|
$count = @$values; |
206
|
|
|
|
|
|
|
|
207
|
61
|
100
|
|
|
|
165
|
if (defined $pointers) { |
208
|
6
|
50
|
|
|
|
28
|
die "Value count should be one less than pointer count" |
209
|
|
|
|
|
|
|
unless ( @$values + 1 ) == ( @$pointers ) ; |
210
|
6
|
|
|
|
|
34
|
$count += &traverse_btree($store, shift(@$pointers), $callback); |
211
|
6
|
|
|
|
|
25
|
while(@$values) { |
212
|
49
|
|
|
|
|
88
|
&{$callback}(shift @$values); |
|
49
|
|
|
|
|
100
|
|
213
|
49
|
|
|
|
|
146
|
$count += &traverse_btree($store, shift(@$pointers), $callback); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} else { |
216
|
55
|
|
|
|
|
131
|
&{$callback}($_) foreach @$values; |
|
584
|
|
|
|
|
976
|
|
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
61
|
|
|
|
|
299
|
$count; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
sub freeBTreeNode { |
223
|
58
|
|
|
58
|
0
|
89
|
my($allocator, $nodeid) = @_; |
224
|
58
|
|
|
|
|
179
|
my($block) = $allocator->blockByNumber( $nodeid ); |
225
|
|
|
|
|
|
|
|
226
|
58
|
100
|
|
|
|
180
|
if($block->read(4, 'N') != 0) { |
227
|
6
|
|
|
|
|
25
|
$block->seek(0); |
228
|
6
|
|
|
|
|
16
|
my(undef, $pointers) = &readBTreeNode($block); |
229
|
6
|
|
|
|
|
75
|
&freeBTreeNode($allocator, $_) foreach @$pointers; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
58
|
|
|
|
|
180
|
$allocator->free($nodeid); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub readBTreeNode { |
236
|
127
|
|
|
127
|
0
|
226
|
my($node) = @_; |
237
|
|
|
|
|
|
|
|
238
|
127
|
|
|
|
|
495
|
my($pointer) = $node->read(4, 'N'); |
239
|
|
|
|
|
|
|
|
240
|
127
|
|
|
|
|
427
|
my($count) = $node->read(4, 'N'); |
241
|
127
|
100
|
|
|
|
302
|
if ($pointer > 0) { |
242
|
18
|
|
|
|
|
27
|
my(@pointers, @values); |
243
|
18
|
|
|
|
|
54
|
while($count) { |
244
|
147
|
|
|
|
|
475
|
push(@pointers, $node->read(4, 'N')); |
245
|
147
|
|
|
|
|
411
|
push(@values, Mac::Finder::DSStore::Entry->readEntry($node)); |
246
|
147
|
|
|
|
|
376
|
$count --; |
247
|
|
|
|
|
|
|
} |
248
|
18
|
|
|
|
|
37
|
push(@pointers, $pointer); |
249
|
18
|
|
|
|
|
86
|
return \@values, \@pointers; |
250
|
|
|
|
|
|
|
} else { |
251
|
109
|
|
|
|
|
131
|
my(@values); |
252
|
109
|
|
|
|
|
264
|
while($count) { |
253
|
1159
|
|
|
|
|
3118
|
push(@values, Mac::Finder::DSStore::Entry->readEntry($node)); |
254
|
1159
|
|
|
|
|
3124
|
$count --; |
255
|
|
|
|
|
|
|
} |
256
|
109
|
|
|
|
|
656
|
return \@values, undef; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub writeBTreeNode { |
261
|
121
|
|
|
121
|
0
|
187
|
my($into, $values, $pointers) = @_; |
262
|
|
|
|
|
|
|
|
263
|
121
|
100
|
|
|
|
318
|
if (!$pointers) { |
264
|
|
|
|
|
|
|
# A leaf node: no pointers, just database entries. |
265
|
109
|
|
|
|
|
421
|
$into->write('NN', 0, scalar(@$values)); |
266
|
109
|
|
|
|
|
639
|
$_->write($into) foreach @$values; |
267
|
|
|
|
|
|
|
} else { |
268
|
|
|
|
|
|
|
# An internal node: interleaved pointers and values, |
269
|
|
|
|
|
|
|
# with the final pointer moved to the front. |
270
|
12
|
|
|
|
|
45
|
my(@vals) = @$values; |
271
|
12
|
|
|
|
|
44
|
my(@ps) = @$pointers; |
272
|
12
|
50
|
|
|
|
45
|
die "number of pointers must be one more than number of entries" |
273
|
|
|
|
|
|
|
unless 1+@vals == @ps; |
274
|
12
|
|
|
|
|
53
|
$into->write('NN', pop(@ps), scalar(@vals)); |
275
|
12
|
|
|
|
|
41
|
while(@vals) { |
276
|
98
|
|
|
|
|
301
|
$into->write('N', shift(@ps)); |
277
|
98
|
|
|
|
|
261
|
( shift(@vals) )->write($into); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
121
|
100
|
|
|
|
731
|
if($testpoint) { |
282
|
60
|
|
|
|
|
291
|
my($x) = [ &readBTreeNode($into->copyback) ]; |
283
|
60
|
|
|
|
|
491
|
$testpoint->( [ $values, $pointers], $x ); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 &Mac::Finder::DSStore::writeDSDBEntries($file, @entries) |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
A convenience function which sorts a list of entries and writes them |
290
|
|
|
|
|
|
|
to the specified file using C, then flushes the allocator's |
291
|
|
|
|
|
|
|
data structures to disk. |
292
|
|
|
|
|
|
|
C<$file> may be a filename or an open file handle. |
293
|
|
|
|
|
|
|
The store object is returned, but you don't need to do anything else with it. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=cut |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub writeDSDBEntries { |
298
|
1
|
|
|
1
|
1
|
1226
|
my($store, $recs); |
299
|
|
|
|
|
|
|
{ |
300
|
1
|
|
|
|
|
3
|
my($file, @entries) = @_; |
|
1
|
|
|
|
|
5
|
|
301
|
|
|
|
|
|
|
|
302
|
1
|
|
|
|
|
13
|
require IO::File; |
303
|
1
|
|
|
|
|
7
|
require Mac::Finder::DSStore::BuddyAllocator; |
304
|
|
|
|
|
|
|
|
305
|
1
|
50
|
|
|
|
7
|
unless(ref $file) { |
306
|
1
|
|
|
|
|
2
|
my($filename) = $file; |
307
|
1
|
|
|
|
|
11
|
$file = IO::File->new( $filename, Fcntl::O_RDWR | Fcntl::O_CREAT ); |
308
|
1
|
50
|
|
|
|
104
|
croak "$filename: $!, died" unless $file; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
1
|
50
|
|
|
|
17
|
if((stat($file))[7] > 32) { |
312
|
0
|
|
|
|
|
0
|
$store = Mac::Finder::DSStore::BuddyAllocator->open($file); |
313
|
|
|
|
|
|
|
} else { |
314
|
1
|
|
|
|
|
14
|
$store = Mac::Finder::DSStore::BuddyAllocator->new($file); |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
1
|
|
|
|
|
8
|
$recs = [ sort { $a->cmp($b) } @entries ]; |
|
18
|
|
|
|
|
33
|
|
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
1
|
|
|
|
|
15
|
putDSDBEntries($store, $recs); |
321
|
1
|
|
|
|
|
6
|
$store->writeMetaData; |
322
|
|
|
|
|
|
|
|
323
|
1
|
|
|
|
|
7
|
$store; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=head2 &Mac::Finder::DSStore::makeEntries($filename, [ what => value ... ]) |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
C encapsulates some information about the format of individual |
329
|
|
|
|
|
|
|
records in the DS_Store file. It returns a list of records constructed with the |
330
|
|
|
|
|
|
|
given filename and with the information specified in the rest of its args. |
331
|
|
|
|
|
|
|
Most args come in pairs, a name and a value, so C kind of looks |
332
|
|
|
|
|
|
|
like it takes a hash. Some names take no value and some could take several. |
333
|
|
|
|
|
|
|
Some produce more than one record as a result. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
See the output of the F script for an example of how |
336
|
|
|
|
|
|
|
to use this, and check the source code for a list of the formats it accepts. |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
This function might change in the future. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub makeEntries { |
343
|
11
|
|
|
11
|
1
|
6283
|
my($filename, @info) = @_; |
344
|
11
|
|
|
|
|
12
|
my(@results); |
345
|
|
|
|
|
|
|
|
346
|
11
|
|
|
|
|
23
|
while(@info) { |
347
|
16
|
|
|
|
|
21
|
my($recordType) = shift @info; |
348
|
|
|
|
|
|
|
|
349
|
16
|
100
|
|
|
|
54
|
if ($recordType =~ /^....$/) { |
|
|
100
|
|
|
|
|
|
350
|
5
|
|
|
|
|
13
|
my($record) = Mac::Finder::DSStore::Entry->new($filename, $recordType); |
351
|
5
|
|
|
|
|
34
|
$record->value( shift @info ); |
352
|
4
|
|
|
|
|
11
|
push(@results, $record); |
353
|
|
|
|
|
|
|
} elsif ($recordType =~ /^(....)_hex$/) { |
354
|
1
|
|
|
|
|
5
|
my($record) = Mac::Finder::DSStore::Entry->new($filename, $1); |
355
|
1
|
|
|
|
|
7
|
$record->value( pack('H*', shift @info) ); |
356
|
1
|
|
|
|
|
4
|
push(@results, $record); |
357
|
|
|
|
|
|
|
} else { |
358
|
10
|
|
|
|
|
30
|
my($mkr) = $Mac::Finder::DSStore::Entry::{'make_'.$recordType}; |
359
|
10
|
100
|
|
|
|
118
|
croak "Don't know how to handle '$recordType'" unless $mkr; |
360
|
9
|
|
|
|
|
12
|
push(@results, &{$mkr}($filename, $recordType, \@info)); |
|
9
|
|
|
|
|
24
|
|
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
6
|
|
|
|
|
27
|
@results; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
package Mac::Finder::DSStore::Entry; |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=head1 Mac::Finder::DSStore::Entry |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
This class holds the individual records from the database. Each record |
372
|
|
|
|
|
|
|
contains a filename (in some cases, "." to refer to the containing |
373
|
|
|
|
|
|
|
directory), a 4-character record type, and a value. The value is |
374
|
|
|
|
|
|
|
one of a few concrete types, according to the record type. |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=cut |
377
|
|
|
|
|
|
|
|
378
|
3
|
|
|
3
|
|
37
|
use strict; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
134
|
|
379
|
3
|
|
|
3
|
|
22
|
use warnings; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
108
|
|
380
|
3
|
|
|
3
|
|
3438
|
use Encode (); |
|
3
|
|
|
|
|
42900
|
|
|
3
|
|
|
|
|
86
|
|
381
|
3
|
|
|
3
|
|
28
|
use Carp qw(croak); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
7234
|
|
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
# |
384
|
|
|
|
|
|
|
# Concrete types of known ids |
385
|
|
|
|
|
|
|
# |
386
|
|
|
|
|
|
|
our(%types) = ( |
387
|
|
|
|
|
|
|
'BKGD' => 'blob', |
388
|
|
|
|
|
|
|
'bwsp' => 'blob', |
389
|
|
|
|
|
|
|
'cmmt' => 'ustr', |
390
|
|
|
|
|
|
|
'dilc' => 'blob', |
391
|
|
|
|
|
|
|
'dscl' => 'bool', |
392
|
|
|
|
|
|
|
'extn' => 'ustr', |
393
|
|
|
|
|
|
|
'fwi0' => 'blob', |
394
|
|
|
|
|
|
|
'fwsw' => 'long', |
395
|
|
|
|
|
|
|
'fwvh' => 'shor', |
396
|
|
|
|
|
|
|
'GRP0' => 'ustr', |
397
|
|
|
|
|
|
|
'icgo' => 'blob', |
398
|
|
|
|
|
|
|
'icsp' => 'blob', |
399
|
|
|
|
|
|
|
'icvo' => 'blob', |
400
|
|
|
|
|
|
|
'ICVO' => 'bool', |
401
|
|
|
|
|
|
|
'icvp' => 'blob', |
402
|
|
|
|
|
|
|
'icvt' => 'shor', |
403
|
|
|
|
|
|
|
'Iloc' => 'blob', |
404
|
|
|
|
|
|
|
'info' => 'blob', |
405
|
|
|
|
|
|
|
'lg1S' => 'comp', |
406
|
|
|
|
|
|
|
'logS' => 'comp', |
407
|
|
|
|
|
|
|
'lssp' => 'blob', |
408
|
|
|
|
|
|
|
'lsvo' => 'blob', |
409
|
|
|
|
|
|
|
'LSVO' => 'bool', |
410
|
|
|
|
|
|
|
'lsvP' => 'blob', |
411
|
|
|
|
|
|
|
'lsvp' => 'blob', |
412
|
|
|
|
|
|
|
'lsvt' => 'shor', |
413
|
|
|
|
|
|
|
'moDD' => 'dutc', |
414
|
|
|
|
|
|
|
'modD' => 'dutc', |
415
|
|
|
|
|
|
|
'ph1S' => 'comp', |
416
|
|
|
|
|
|
|
'phyS' => 'comp', |
417
|
|
|
|
|
|
|
'pict' => 'blob', |
418
|
|
|
|
|
|
|
'vSrn' => 'long', |
419
|
|
|
|
|
|
|
'vstl' => 'type', |
420
|
|
|
|
|
|
|
); |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=head2 $entry = ...::Entry->new($filename, $typecode) |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
Creates a new entry with no value. The concrete type is inferred from the |
425
|
|
|
|
|
|
|
record type code. |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=head2 $entry->filename |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
Gets the filename of an entry. |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=head2 $entry->strucId |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
Gets the record type of this entry, as a four-character string, indicating |
434
|
|
|
|
|
|
|
what aspect of the file the entry describes. |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=head2 $entry->value([$value]) |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Gets or sets the value of an entry. |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
If the concrete type is C or C, the value is interpreted as a byte string; |
441
|
|
|
|
|
|
|
if it is C, as a character string. |
442
|
|
|
|
|
|
|
If the concrete type is C, C, C, C, or C, |
443
|
|
|
|
|
|
|
then the value should be an integer. |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=cut |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub new { |
448
|
627
|
|
|
627
|
|
157669
|
my($class, $filename, $strucId, @opts) = @_; |
449
|
|
|
|
|
|
|
|
450
|
627
|
50
|
|
|
|
1603
|
croak "no opts supported yet, died" if @opts; |
451
|
|
|
|
|
|
|
|
452
|
627
|
|
33
|
|
|
5206
|
bless([ $filename, $strucId, $types{$strucId}, undef ], |
453
|
|
|
|
|
|
|
ref $class || $class); |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
sub filename { |
457
|
0
|
|
|
0
|
|
0
|
$_[0]->[0]; |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub strucId { |
461
|
0
|
|
|
0
|
|
0
|
$_[0]->[1]; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub value { |
465
|
627
|
|
|
627
|
|
2777
|
my($self, $value) = @_; |
466
|
|
|
|
|
|
|
|
467
|
627
|
50
|
|
|
|
1353
|
return $self->[3] unless defined $value; |
468
|
|
|
|
|
|
|
|
469
|
627
|
100
|
|
|
|
1482
|
croak "Can't set a value on an entry with no concrete type" |
470
|
|
|
|
|
|
|
unless defined($self->[2]); |
471
|
|
|
|
|
|
|
|
472
|
626
|
|
|
|
|
849
|
my($t) = $self->[2]; |
473
|
626
|
100
|
100
|
|
|
2617
|
if($t eq 'blob' or $t eq 'ustr') { |
|
|
50
|
100
|
|
|
|
|
|
|
0
|
66
|
|
|
|
|
474
|
623
|
|
|
|
|
1710
|
$self->[3] = '' . $value; |
475
|
|
|
|
|
|
|
} elsif ($t eq 'bool' or $t eq 'shor' or $t eq 'long') { |
476
|
3
|
|
|
|
|
5
|
$self->[3] = 0 + $value; |
477
|
|
|
|
|
|
|
} elsif ($t eq 'type') { |
478
|
0
|
|
|
|
|
0
|
$value = '' . $value; |
479
|
0
|
0
|
|
|
|
0
|
croak "'type' values must be exactly four bytes long" |
480
|
|
|
|
|
|
|
unless length($value) == 4; |
481
|
0
|
|
|
|
|
0
|
$self->[3] = $value; |
482
|
|
|
|
|
|
|
} else { |
483
|
0
|
|
|
|
|
0
|
die "Unknown concrete type $t, died"; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
626
|
|
|
|
|
1438
|
$self->[3]; |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
sub readEntry { |
490
|
1306
|
|
|
1306
|
|
1962
|
my($class, $block) = @_; |
491
|
|
|
|
|
|
|
|
492
|
1306
|
|
|
|
|
1894
|
my($filename, $strucId, $strucType, $value); |
493
|
|
|
|
|
|
|
|
494
|
1306
|
|
|
|
|
2667
|
$filename = &readFilename($block); |
495
|
1306
|
|
|
|
|
36810
|
$strucId = $block->read(4); |
496
|
1306
|
|
|
|
|
3763
|
$strucType = $block->read(4); |
497
|
|
|
|
|
|
|
|
498
|
1306
|
100
|
66
|
|
|
10140
|
if ($strucType eq 'bool') { |
|
|
100
|
0
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
499
|
3
|
|
|
|
|
10
|
$value = $block->read(1, 'C'); |
500
|
|
|
|
|
|
|
} elsif ($strucType eq 'long' or $strucType eq 'shor') { |
501
|
3
|
|
|
|
|
11
|
$value = $block->read(4, 'N'); |
502
|
|
|
|
|
|
|
} elsif ($strucType eq 'blob') { |
503
|
18
|
|
|
|
|
56
|
my($bloblen) = $block->read(4, 'N'); |
504
|
18
|
|
|
|
|
48
|
$value = $block->read($bloblen); |
505
|
|
|
|
|
|
|
} elsif ($strucType eq 'ustr') { |
506
|
1282
|
|
|
|
|
3854
|
my($strlen) = $block->read(4, 'N'); |
507
|
1282
|
|
|
|
|
4147
|
$value = Encode::decode('UTF-16BE', $block->read(2 * $strlen)); |
508
|
|
|
|
|
|
|
} elsif ($strucType eq 'type') { |
509
|
0
|
|
|
|
|
0
|
$value = $block->read(4); |
510
|
|
|
|
|
|
|
} elsif ($strucType eq 'comp' || $strucType eq 'dutc') { |
511
|
0
|
|
|
|
|
0
|
$value = $block->read(8, 'Q>'); |
512
|
|
|
|
|
|
|
} else { |
513
|
0
|
|
|
|
|
0
|
die "Unknown struc type '$strucType', died"; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
1306
|
|
33
|
|
|
43634
|
return bless([ $filename, $strucId, $strucType, $value ], |
517
|
|
|
|
|
|
|
ref($class) || $class); |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
sub readFilename { |
521
|
1306
|
|
|
1306
|
|
1683
|
my($block) = @_; |
522
|
|
|
|
|
|
|
|
523
|
1306
|
|
|
|
|
3767
|
my($flen) = $block->read(4, 'N'); |
524
|
1306
|
|
|
|
|
4255
|
my($utf16be) = $block->read(2 * $flen); |
525
|
|
|
|
|
|
|
|
526
|
1306
|
|
|
|
|
5480
|
return Encode::decode('UTF-16BE', $utf16be, Encode::FB_CROAK); |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
sub byteSize { |
530
|
676
|
|
|
676
|
|
703
|
my($filename, $strucId, $strucType, $value) = @{$_[0]}; |
|
676
|
|
|
|
|
1504
|
|
531
|
676
|
|
|
|
|
797
|
my($size); |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# TODO: We're assuming that the filename is completely normal |
534
|
|
|
|
|
|
|
# basic-multilingual-plane characters, and doesn't need to be de/re- |
535
|
|
|
|
|
|
|
# composed or anything. |
536
|
676
|
|
|
|
|
877
|
$size = length($filename) * 2 + 12; |
537
|
|
|
|
|
|
|
# 12 bytes: 4 each for filename length, struct id, and struct type |
538
|
|
|
|
|
|
|
|
539
|
676
|
100
|
66
|
|
|
5040
|
if ($strucType eq 'long' or $strucType eq 'shor' or $strucType eq 'type') { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
540
|
1
|
|
|
|
|
2
|
$size += 4; |
541
|
|
|
|
|
|
|
} elsif ($strucType eq 'bool') { |
542
|
1
|
|
|
|
|
3
|
$size += 1; |
543
|
|
|
|
|
|
|
} elsif ($strucType eq 'blob') { |
544
|
6
|
|
|
|
|
12
|
$size += 4 + length($value); |
545
|
|
|
|
|
|
|
} elsif ($strucType eq 'ustr') { |
546
|
668
|
|
|
|
|
1276
|
$size += 4 + 2 * length($value); |
547
|
|
|
|
|
|
|
} elsif ($strucType eq 'comp' or $strucType eq 'dutc') { |
548
|
0
|
|
|
|
|
0
|
$size += 8; |
549
|
|
|
|
|
|
|
} else { |
550
|
0
|
|
|
|
|
0
|
die "Unknown struc type '$strucType', died"; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
676
|
|
|
|
|
1706
|
$size; |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
sub write { |
557
|
1257
|
|
|
1257
|
|
1795
|
my($self, $into) = @_; |
558
|
|
|
|
|
|
|
|
559
|
1257
|
|
|
|
|
3451
|
my($fname) = Encode::encode('UTF-16BE', $self->[0]); |
560
|
|
|
|
|
|
|
|
561
|
1257
|
|
|
|
|
33597
|
my($strucType) = $self->[2]; |
562
|
|
|
|
|
|
|
|
563
|
1257
|
|
|
|
|
5338
|
$into->write('N a* a4 a4', length($fname)/2, $fname, |
564
|
|
|
|
|
|
|
$self->[1], $strucType); |
565
|
|
|
|
|
|
|
|
566
|
1257
|
100
|
66
|
|
|
8713
|
if ($strucType eq 'long' or $strucType eq 'shor') { |
|
|
100
|
0
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
567
|
3
|
|
|
|
|
9
|
$into->write('N', $self->[3]); |
568
|
|
|
|
|
|
|
} elsif ($strucType eq 'bool') { |
569
|
3
|
|
|
|
|
11
|
$into->write('C', $self->[3]); |
570
|
|
|
|
|
|
|
} elsif ($strucType eq 'blob') { |
571
|
18
|
|
|
|
|
57
|
$into->write('N', length($self->[3])); |
572
|
18
|
|
|
|
|
91
|
$into->write($self->[3]); |
573
|
|
|
|
|
|
|
} elsif ($strucType eq 'ustr') { |
574
|
1233
|
|
|
|
|
5840
|
$into->write('N', length($self->[3])); |
575
|
1233
|
|
|
|
|
3907
|
$into->write(Encode::encode('UTF-16BE', $self->[3])); |
576
|
|
|
|
|
|
|
} elsif ($strucType eq 'type') { |
577
|
0
|
|
|
|
|
0
|
$into->write('a4', $self->[3]); |
578
|
|
|
|
|
|
|
} elsif ($strucType eq 'comp' or $strucType eq 'dutc') { |
579
|
0
|
|
|
|
|
0
|
$into->write('Q>', $self->[3]); |
580
|
|
|
|
|
|
|
} else { |
581
|
0
|
|
|
|
|
0
|
die "Unknown struc type '$strucType', died"; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=head2 $entry->cmp($other) |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
Returns -1, 0, or 1 depending on the relative ordering of the two entries, |
588
|
|
|
|
|
|
|
according to (a guess at) the record ordering used by the store's B-tree. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=cut |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub cmp { |
593
|
1380
|
|
|
1380
|
|
9673
|
my($self, $other) = @_; |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
# |
596
|
|
|
|
|
|
|
# There's probably some wacky Mac-specific Unicode collation |
597
|
|
|
|
|
|
|
# rule for these, but case-insensitive comparison is a good |
598
|
|
|
|
|
|
|
# approximation |
599
|
|
|
|
|
|
|
# |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Ordering in the btree is Finder-filename-ordering on the files, |
602
|
|
|
|
|
|
|
# and simple bytewise ordering on the structure IDs. |
603
|
|
|
|
|
|
|
|
604
|
1380
|
100
|
|
|
|
5025
|
( lc($self->[0]) cmp lc($other->[0]) ) |
605
|
|
|
|
|
|
|
|| |
606
|
|
|
|
|
|
|
( $self->[1] cmp $other->[1] ); |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
# |
610
|
|
|
|
|
|
|
# The make_foo subs are used by Mac::Finder::DSStore::makeEntries. |
611
|
|
|
|
|
|
|
# |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub make_BKGD_default { |
614
|
1
|
|
|
1
|
|
2
|
my($filename, undef, undef) = @_; |
615
|
|
|
|
|
|
|
|
616
|
1
|
|
|
|
|
9
|
my($rec) = Mac::Finder::DSStore::Entry->new($filename, 'BKGD'); |
617
|
1
|
|
|
|
|
4
|
$rec->value( pack('A4 x8', 'DefB') ); |
618
|
1
|
|
|
|
|
5
|
$rec; |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
sub make_BKGD_color { |
622
|
4
|
|
|
4
|
|
5
|
my($filename, $strucId, $argv) = @_; |
623
|
4
|
|
|
|
|
6
|
my($color) = shift @$argv; |
624
|
4
|
|
|
|
|
4
|
my($rgb); |
625
|
|
|
|
|
|
|
|
626
|
4
|
100
|
|
|
|
14
|
if ($color =~ /^\#([0-9a-f]+)$/i) { |
627
|
3
|
100
|
|
|
|
15
|
if(length($1) == 3) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
628
|
1
|
|
|
|
|
8
|
( $rgb = $1 ) =~ s/(.)(.)(.)/$1$1$1$1$2$2$2$2$3$3$3$3/; |
629
|
|
|
|
|
|
|
} elsif (length($1) == 6) { |
630
|
1
|
|
|
|
|
12
|
( $rgb = $1 ) =~ s/(..)(..)(..)/$1$1$2$2$3$3/; |
631
|
|
|
|
|
|
|
} elsif (length($1) == 12) { |
632
|
1
|
|
|
|
|
2
|
$rgb = $1; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
} |
635
|
|
|
|
|
|
|
|
636
|
4
|
100
|
|
|
|
119
|
croak "Can't parse color string '$color'" |
637
|
|
|
|
|
|
|
unless $rgb; |
638
|
|
|
|
|
|
|
|
639
|
3
|
|
|
|
|
8
|
my($rec) = Mac::Finder::DSStore::Entry->new($filename, 'BKGD'); |
640
|
3
|
|
|
|
|
15
|
$rec->value( pack('A4 H12 x2', 'ClrB', $rgb) ); |
641
|
|
|
|
|
|
|
|
642
|
3
|
|
|
|
|
12
|
$rec; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
sub make_BKGD_alias { |
646
|
0
|
|
|
0
|
|
0
|
my($filename, $strucId, $argv) = @_; |
647
|
|
|
|
|
|
|
|
648
|
0
|
|
|
|
|
0
|
my($image) = shift @$argv; |
649
|
|
|
|
|
|
|
|
650
|
0
|
0
|
|
|
|
0
|
if(!ref $image) { |
651
|
0
|
|
|
|
|
0
|
require Mac::Memory; |
652
|
0
|
|
|
|
|
0
|
require Mac::Files; |
653
|
0
|
|
|
|
|
0
|
$image = Mac::Files::NewAlias($image); |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
0
|
|
|
|
|
0
|
my($isize) = $image->size; |
657
|
0
|
|
|
|
|
0
|
my($bkgd, $pict); |
658
|
|
|
|
|
|
|
|
659
|
0
|
|
|
|
|
0
|
$bkgd = Mac::Finder::DSStore::Entry->new($filename, 'BKGD'); |
660
|
0
|
|
|
|
|
0
|
$bkgd->value( pack('A4 N nn', 'PctB', $isize, 0, 0) ); |
661
|
|
|
|
|
|
|
|
662
|
0
|
|
|
|
|
0
|
$pict = Mac::Finder::DSStore::Entry->new($filename, 'pict'); |
663
|
0
|
|
|
|
|
0
|
$pict->value( $image->get ); |
664
|
|
|
|
|
|
|
|
665
|
0
|
|
|
|
|
0
|
( $bkgd, $pict ); |
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
sub _make_packed { |
669
|
2
|
|
|
2
|
|
5
|
my($filename, $strucId, $fmt, @values) = @_; |
670
|
2
|
|
|
|
|
5
|
my($record) = Mac::Finder::DSStore::Entry->new($filename, $strucId); |
671
|
2
|
|
|
|
|
7
|
$record->value( pack($fmt, @values) ); |
672
|
2
|
|
|
|
|
11
|
$record; |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
sub _make_packed_arrayref { |
676
|
4
|
|
|
4
|
|
7
|
my($filename, $strucId, $argv, $format, $reqcount, $dflt) = @_; |
677
|
4
|
|
|
|
|
6
|
my($values) = shift @$argv; |
678
|
|
|
|
|
|
|
|
679
|
4
|
50
|
|
|
|
9
|
croak "$strucId argument must be an array ref" |
680
|
|
|
|
|
|
|
unless ref $values; |
681
|
|
|
|
|
|
|
|
682
|
4
|
100
|
|
|
|
119
|
croak "$strucId argument must have at least $reqcount items" |
683
|
|
|
|
|
|
|
unless $reqcount <= @$values; |
684
|
|
|
|
|
|
|
|
685
|
3
|
|
|
|
|
4
|
my($max) = $reqcount + @$dflt; |
686
|
|
|
|
|
|
|
|
687
|
3
|
100
|
|
|
|
110
|
croak "$strucId argument can't have more than $max items" |
688
|
|
|
|
|
|
|
if $max < @$values; |
689
|
|
|
|
|
|
|
|
690
|
2
|
|
|
|
|
3
|
my(@fields) = @$values; |
691
|
2
|
50
|
|
|
|
4
|
if ($max > @fields) { |
692
|
2
|
|
|
|
|
5
|
push(@fields, @{$dflt}[ ( @fields - $max ) .. -1 ]); |
|
2
|
|
|
|
|
4
|
|
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
|
695
|
2
|
|
|
|
|
8
|
return &_make_packed($filename, substr($strucId, 0, 4), |
696
|
|
|
|
|
|
|
$format, @fields); |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
sub make_Iloc_xy { |
700
|
4
|
|
|
4
|
|
6
|
my($filename, $strucId, $argv) = @_; |
701
|
4
|
|
|
|
|
13
|
return &_make_packed_arrayref($filename, $strucId, $argv, |
702
|
|
|
|
|
|
|
'NN nnnn', 2, [65535, 65535, 65535, 0]); |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
sub make_fwi0_flds { |
706
|
0
|
|
|
0
|
|
|
my($filename, $strucId, $argv) = @_; |
707
|
0
|
|
|
|
|
|
my($flds) = shift @$argv; |
708
|
|
|
|
|
|
|
|
709
|
0
|
0
|
|
|
|
|
croak "$strucId argument must have 7 values" |
710
|
|
|
|
|
|
|
unless 7 == @$flds; |
711
|
|
|
|
|
|
|
|
712
|
0
|
|
|
|
|
|
return &_make_packed($filename, 'fwi0', 'n4 A4 n*', @$flds); |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=head1 SEE ALSO |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
See L for more detailed information on |
719
|
|
|
|
|
|
|
the record types found in a DS_Store file. |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
See L for the low-level organization |
722
|
|
|
|
|
|
|
of the DS_Store file. |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
=head1 AUTHOR |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
Copyright 2008 by Wim Lewis Ewiml@hhhh.orgE. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
Some information is from Mark Mentovai via the Mozilla project. |
729
|
|
|
|
|
|
|
Thanks also to Martin Baker for bug reports. |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=cut |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
1; |