line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
2
|
|
|
|
|
|
|
package AppleII::ProDOS; |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# Copyright 1996-2006 Christopher J. Madsen |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# Author: Christopher J. Madsen |
7
|
|
|
|
|
|
|
# Created: 26 Jul 1996 |
8
|
|
|
|
|
|
|
# |
9
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify |
10
|
|
|
|
|
|
|
# it under the same terms as Perl itself. |
11
|
|
|
|
|
|
|
# |
12
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, |
13
|
|
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the |
15
|
|
|
|
|
|
|
# GNU General Public License or the Artistic License for more details. |
16
|
|
|
|
|
|
|
# |
17
|
|
|
|
|
|
|
# ABSTRACT: Access files on Apple II ProDOS disk images |
18
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
19
|
|
|
|
|
|
|
|
20
|
2
|
|
|
2
|
|
60743
|
use 5.006; |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
80
|
|
21
|
2
|
|
|
2
|
|
1100
|
use AppleII::Disk 0.09; |
|
2
|
|
|
|
|
67
|
|
|
2
|
|
|
|
|
56
|
|
22
|
2
|
|
|
2
|
|
12
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
107
|
|
23
|
2
|
|
|
2
|
|
1813
|
use POSIX 'mktime'; |
|
2
|
|
|
|
|
17598
|
|
|
2
|
|
|
|
|
19
|
|
24
|
2
|
|
|
2
|
|
4260
|
use bytes; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
18
|
|
25
|
2
|
|
|
2
|
|
56
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
85
|
|
26
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
82
|
|
27
|
|
|
|
|
|
|
|
28
|
2
|
|
|
2
|
|
10
|
use Exporter 5.57 'import'; # exported import method |
|
2
|
|
|
|
|
65
|
|
|
2
|
|
|
|
|
4637
|
|
29
|
|
|
|
|
|
|
our @ISA = qw(AppleII::ProDOS::Members); |
30
|
|
|
|
|
|
|
our @EXPORT = qw(); |
31
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
32
|
|
|
|
|
|
|
pack_date pack_name parse_date parse_name parse_type shell_wc |
33
|
|
|
|
|
|
|
short_date unpack_date valid_date valid_name a2_croak |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
my %vol_fields = ( |
37
|
|
|
|
|
|
|
bitmap => undef, |
38
|
|
|
|
|
|
|
disk => undef, |
39
|
|
|
|
|
|
|
diskSize => undef, |
40
|
|
|
|
|
|
|
name => undef, |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Methods to be passed along to the current directory: |
44
|
|
|
|
|
|
|
my %dir_methods = ( |
45
|
|
|
|
|
|
|
catalog => undef, |
46
|
|
|
|
|
|
|
get_file => undef, |
47
|
|
|
|
|
|
|
new_dir => undef, |
48
|
|
|
|
|
|
|
put_file => undef, |
49
|
|
|
|
|
|
|
); |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
#===================================================================== |
52
|
|
|
|
|
|
|
# Package Global Variables: |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Filetype list from About Apple II File Type Notes -- June 1992 |
57
|
|
|
|
|
|
|
my @filetypes = qw( |
58
|
|
|
|
|
|
|
NON BAD PCD PTX TXT PDA BIN FNT FOT BA3 DA3 WPF SOS $0D $0E DIR |
59
|
|
|
|
|
|
|
RPD RPI AFD AFM AFR SCL PFS $17 $18 ADB AWP ASP $1C $1D $1E $1F |
60
|
|
|
|
|
|
|
TDM $21 $22 $23 $24 $25 $26 $27 $28 $29 8SC 8OB 8IC 8LD P8C $2F |
61
|
|
|
|
|
|
|
$30 $31 $32 $33 $34 $35 $36 $37 $38 $39 $3A $3B $3C $3D $3E $3F |
62
|
|
|
|
|
|
|
DIC $41 FTD $43 $44 $45 $46 $47 $48 $49 $4A $4B $4C $4D $4E $4F |
63
|
|
|
|
|
|
|
GWP GSS GDB DRW GDP HMD EDU STN HLP COM CFG ANM MUM ENT DVU FIN |
64
|
|
|
|
|
|
|
$60 $61 $62 $63 $64 $65 $66 $67 $68 $69 $6A BIO $6C TDR PRE HDV |
65
|
|
|
|
|
|
|
$70 $71 $72 $73 $74 $75 $76 $77 $78 $79 $7A $7B $7C $7D $7E $7F |
66
|
|
|
|
|
|
|
$80 $81 $82 $83 $84 $85 $86 $87 $88 $89 $8A $8B $8C $8D $8E $8F |
67
|
|
|
|
|
|
|
$90 $91 $92 $93 $94 $95 $96 $97 $98 $99 $9A $9B $9C $9D $9E $9F |
68
|
|
|
|
|
|
|
WP $A1 $A2 $A3 $A4 $A5 $A6 $A7 $A8 $A9 $AA GSB TDF BDF $AE $AF |
69
|
|
|
|
|
|
|
SRC OBJ LIB S16 RTL EXE PIF TIF NDA CDA TOL DVR LDF FST $BE DOC |
70
|
|
|
|
|
|
|
PNT PIC ANI PAL $C4 OOG SCR CDV FON FND ICN $CB $CC $CD $CE $CF |
71
|
|
|
|
|
|
|
$D0 $D1 $D2 $D3 $D4 MUS INS MDI SND $D9 $DA DBM $DC $DD $DE $DF |
72
|
|
|
|
|
|
|
LBR $E1 ATK $E3 $E4 $E5 $E6 $E7 $E8 $E9 $EA $EB $EC $ED R16 PAS |
73
|
|
|
|
|
|
|
CMD $F1 $F2 $F3 $F4 $F5 $F6 $F7 $F8 OS INT IVR BAS VAR REL SYS |
74
|
|
|
|
|
|
|
); # end filetypes |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
#===================================================================== |
77
|
|
|
|
|
|
|
# package AppleII::ProDOS: |
78
|
|
|
|
|
|
|
# |
79
|
|
|
|
|
|
|
# Member Variables: |
80
|
|
|
|
|
|
|
# bitmap: |
81
|
|
|
|
|
|
|
# An AppleII::ProDOS::Bitmap containing the volume bitmap |
82
|
|
|
|
|
|
|
# directories: |
83
|
|
|
|
|
|
|
# Array of AppleII::ProDOS::Directory starting with the volume dir |
84
|
|
|
|
|
|
|
# disk: |
85
|
|
|
|
|
|
|
# The AppleII::Disk we are accessing |
86
|
|
|
|
|
|
|
# diskSize: |
87
|
|
|
|
|
|
|
# The number of blocks on the disk |
88
|
|
|
|
|
|
|
# name: |
89
|
|
|
|
|
|
|
# The volume name of the disk |
90
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
91
|
|
|
|
|
|
|
# Constructor for creating a new disk: |
92
|
|
|
|
|
|
|
# |
93
|
|
|
|
|
|
|
# Input: |
94
|
|
|
|
|
|
|
# name: |
95
|
|
|
|
|
|
|
# The volume name for the new disk |
96
|
|
|
|
|
|
|
# diskSize: |
97
|
|
|
|
|
|
|
# The size of the disk in blocks |
98
|
|
|
|
|
|
|
# filename: |
99
|
|
|
|
|
|
|
# The pathname of the image file you want to open |
100
|
|
|
|
|
|
|
# mode: (optional) |
101
|
|
|
|
|
|
|
# A string indicating how the image should be opened |
102
|
|
|
|
|
|
|
# See AppleII::Disk::new for details. |
103
|
|
|
|
|
|
|
# 'rw' is always appended to the mode |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub new |
106
|
|
|
|
|
|
|
{ |
107
|
0
|
|
|
0
|
1
|
0
|
my ($type, $name, $diskSize, $filename, $mode) = @_; |
108
|
|
|
|
|
|
|
|
109
|
0
|
0
|
|
|
|
0
|
a2_croak("Invalid name `$name'") unless valid_name($name); |
110
|
0
|
|
|
|
|
0
|
$name = uc $name; |
111
|
|
|
|
|
|
|
|
112
|
0
|
|
0
|
|
|
0
|
my $disk = AppleII::Disk->new($filename, ($mode || '') . 'rw'); |
113
|
0
|
|
|
|
|
0
|
$disk->blocks($diskSize); |
114
|
|
|
|
|
|
|
|
115
|
0
|
|
|
|
|
0
|
my $bitmap = AppleII::ProDOS::Bitmap->new($disk,6,$diskSize); |
116
|
|
|
|
|
|
|
|
117
|
0
|
|
|
|
|
0
|
my $self = { |
118
|
|
|
|
|
|
|
bitmap => $bitmap, |
119
|
|
|
|
|
|
|
directories => [ AppleII::ProDOS::Directory->new( |
120
|
|
|
|
|
|
|
$name, $disk, [2 .. 5], $bitmap |
121
|
|
|
|
|
|
|
) ], |
122
|
|
|
|
|
|
|
disk => $disk, |
123
|
|
|
|
|
|
|
name => $name, |
124
|
|
|
|
|
|
|
_dir_methods => \%dir_methods, |
125
|
|
|
|
|
|
|
_permitted => \%vol_fields, |
126
|
|
|
|
|
|
|
}; |
127
|
|
|
|
|
|
|
|
128
|
0
|
|
|
|
|
0
|
$bitmap->write_disk; |
129
|
0
|
|
|
|
|
0
|
$self->{directories}[0]->write_disk; |
130
|
|
|
|
|
|
|
|
131
|
0
|
|
|
|
|
0
|
bless $self, $type; |
132
|
|
|
|
|
|
|
} # end AppleII::ProDOS::new |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
135
|
|
|
|
|
|
|
# Constructor for opening an existing disk: |
136
|
|
|
|
|
|
|
# |
137
|
|
|
|
|
|
|
# There are two forms: |
138
|
|
|
|
|
|
|
# open(disk); or |
139
|
|
|
|
|
|
|
# open(filename, mode); |
140
|
|
|
|
|
|
|
# |
141
|
|
|
|
|
|
|
# Input: |
142
|
|
|
|
|
|
|
# disk: |
143
|
|
|
|
|
|
|
# The AppleII::Disk to use |
144
|
|
|
|
|
|
|
# filename: |
145
|
|
|
|
|
|
|
# The pathname of the image file you want to open |
146
|
|
|
|
|
|
|
# mode: |
147
|
|
|
|
|
|
|
# A string indicating how the image should be opened |
148
|
|
|
|
|
|
|
# May contain any of the following characters (case sensitive): |
149
|
|
|
|
|
|
|
# r Allow reads (this is actually ignored; you can always read) |
150
|
|
|
|
|
|
|
# w Allow writes |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub open |
153
|
|
|
|
|
|
|
{ |
154
|
1
|
|
|
1
|
1
|
19847
|
my ($type, $disk, $mode) = @_; |
155
|
1
|
|
|
|
|
11
|
my $self = { |
156
|
|
|
|
|
|
|
_dir_methods => \%dir_methods, |
157
|
|
|
|
|
|
|
_permitted => \%vol_fields, |
158
|
|
|
|
|
|
|
}; |
159
|
1
|
50
|
|
|
|
23
|
$disk = AppleII::Disk->new($disk, $mode) unless ref $disk; |
160
|
1
|
|
|
|
|
4
|
$self->{disk} = $disk; |
161
|
|
|
|
|
|
|
|
162
|
1
|
|
|
|
|
5
|
my $volDir = $disk->read_block(2); |
163
|
|
|
|
|
|
|
|
164
|
1
|
|
|
|
|
3
|
my $storageType; |
165
|
1
|
|
|
|
|
7
|
($storageType, $self->{name}) = parse_name(substr($volDir,0x04,16)); |
166
|
1
|
50
|
|
|
|
6
|
croak('This is not a ProDOS disk') unless $storageType == 0xF; |
167
|
|
|
|
|
|
|
|
168
|
1
|
|
|
|
|
5
|
my ($startBlock, $diskSize) = unpack('x39v2',$volDir); |
169
|
1
|
|
|
|
|
10
|
$disk->blocks($diskSize); |
170
|
|
|
|
|
|
|
|
171
|
1
|
|
|
|
|
9
|
$self->{bitmap} = |
172
|
|
|
|
|
|
|
AppleII::ProDOS::Bitmap->open($disk,$startBlock,$diskSize); |
173
|
|
|
|
|
|
|
|
174
|
1
|
|
|
|
|
9
|
$self->{directories} = [ |
175
|
|
|
|
|
|
|
AppleII::ProDOS::Directory->open($disk, 2, $self->{bitmap}) |
176
|
|
|
|
|
|
|
]; |
177
|
1
|
|
|
|
|
3
|
$self->{diskSize} = $diskSize; |
178
|
|
|
|
|
|
|
|
179
|
1
|
|
|
|
|
6
|
bless $self, $type; |
180
|
|
|
|
|
|
|
} # end AppleII::ProDOS::open |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
183
|
|
|
|
|
|
|
# Return the current directory: |
184
|
|
|
|
|
|
|
# |
185
|
|
|
|
|
|
|
# Returns: |
186
|
|
|
|
|
|
|
# The current AppleII::ProDOS::Directory |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub dir { |
189
|
0
|
|
|
0
|
1
|
0
|
shift->{directories}[-1]; |
190
|
|
|
|
|
|
|
} # end AppleII::ProDOS::dir |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
193
|
|
|
|
|
|
|
# Return or change the current path: |
194
|
|
|
|
|
|
|
# |
195
|
|
|
|
|
|
|
# Input: |
196
|
|
|
|
|
|
|
# newpath: The path to change to |
197
|
|
|
|
|
|
|
# |
198
|
|
|
|
|
|
|
# Returns: |
199
|
|
|
|
|
|
|
# The current path (begins and ends with '/') |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub path |
202
|
|
|
|
|
|
|
{ |
203
|
3
|
|
|
3
|
1
|
10
|
my ($self, $newpath) = @_; |
204
|
|
|
|
|
|
|
|
205
|
3
|
50
|
|
|
|
13
|
if ($newpath) { |
206
|
|
|
|
|
|
|
# Change directory: |
207
|
3
|
|
|
|
|
6
|
my @directories = @{$self->{directories}}; |
|
3
|
|
|
|
|
12
|
|
208
|
3
|
100
|
|
|
|
48
|
$#directories = 0 if $newpath =~ s!^/\Q$self->{name}\E/?!!i; |
209
|
|
|
|
|
|
|
pop @directories |
210
|
3
|
|
33
|
|
|
16
|
while $#directories and $newpath =~ s'^\.\.(?:/|$)''; #' |
211
|
3
|
|
|
|
|
5
|
my $dir; |
212
|
3
|
|
|
|
|
11
|
foreach $dir (split(/\//, $newpath)) { |
213
|
2
|
|
|
|
|
4
|
eval { push @directories, $directories[-1]->open_dir($dir) }; |
|
2
|
|
|
|
|
10
|
|
214
|
2
|
50
|
|
|
|
10
|
a2_croak("No such directory `$_[1]'") |
215
|
|
|
|
|
|
|
if $@ =~ /^LibA2: No such directory/; |
216
|
2
|
50
|
|
|
|
9
|
die $@ if $@; |
217
|
|
|
|
|
|
|
} |
218
|
3
|
|
|
|
|
12
|
$self->{directories} = \@directories; |
219
|
|
|
|
|
|
|
} # end if changing path |
220
|
|
|
|
|
|
|
|
221
|
3
|
|
|
|
|
11
|
'/'.join('/',map { $_->{name} } @{$self->{directories}}).'/'; |
|
5
|
|
|
|
|
36
|
|
|
3
|
|
|
|
|
8
|
|
222
|
|
|
|
|
|
|
} # end AppleII::ProDOS::path |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
225
|
|
|
|
|
|
|
# Pass method calls along to the current directory: |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
sub AUTOLOAD |
228
|
|
|
|
|
|
|
{ |
229
|
19
|
|
|
19
|
|
10029
|
my $self = $_[0]; |
230
|
19
|
|
|
|
|
36
|
my $name = our $AUTOLOAD; |
231
|
19
|
|
|
|
|
198
|
$name =~ s/.*://; # strip fully-qualified portion |
232
|
19
|
100
|
66
|
|
|
165
|
unless (ref($self) and exists $self->{'_dir_methods'}{$name}) { |
233
|
|
|
|
|
|
|
# Try to access a field by that name: |
234
|
3
|
|
|
|
|
6
|
$AppleII::ProDOS::Members::AUTOLOAD = $AUTOLOAD; |
235
|
3
|
|
|
|
|
28
|
goto &AppleII::ProDOS::Members::AUTOLOAD; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
16
|
|
|
|
|
45
|
shift @_; # Remove self |
239
|
16
|
|
|
|
|
105
|
$self->{directories}[-1]->$name(@_); |
240
|
|
|
|
|
|
|
} # end AppleII::ProDOS::AUTOLOAD |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
243
|
|
|
|
|
|
|
# Like croak, but get out of all AppleII::ProDOS classes: |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub a2_croak |
246
|
|
|
|
|
|
|
{ |
247
|
0
|
|
|
0
|
0
|
0
|
local $Carp::CarpLevel = $Carp::CarpLevel; |
248
|
0
|
|
|
|
|
0
|
while ((caller $Carp::CarpLevel)[0] =~ /^AppleII::ProDOS/) { |
249
|
0
|
|
|
|
|
0
|
++$Carp::CarpLevel; |
250
|
|
|
|
|
|
|
} |
251
|
0
|
|
|
|
|
0
|
croak("LibA2: " . $_[0]); |
252
|
|
|
|
|
|
|
} # end AppleII::ProDOS::a2_croak |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
255
|
|
|
|
|
|
|
# Convert a time to ProDOS format: |
256
|
|
|
|
|
|
|
# |
257
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
258
|
|
|
|
|
|
|
# |
259
|
|
|
|
|
|
|
# Input: |
260
|
|
|
|
|
|
|
# time: The time to convert |
261
|
|
|
|
|
|
|
# |
262
|
|
|
|
|
|
|
# Returns: |
263
|
|
|
|
|
|
|
# Packed string |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub pack_date |
266
|
|
|
|
|
|
|
{ |
267
|
4
|
50
|
|
4
|
0
|
532
|
if (@_ == 1) { # Unix timestamp |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
268
|
0
|
|
|
|
|
0
|
@_ = (localtime($_[0]))[5,4,3,2,1]; |
269
|
0
|
|
|
|
|
0
|
++$_[1]; |
270
|
|
|
|
|
|
|
} elsif (@_ == 3) { # Year, Month, Day |
271
|
1
|
|
|
|
|
3
|
push @_, 0, 0; # Hour, Minute |
272
|
|
|
|
|
|
|
} elsif (@_ < 5) { |
273
|
0
|
|
|
|
|
0
|
croak "Usage: pack_date(TIMESTAMP | Y,M,D | Y,M,D,H,M)"; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
4
|
|
|
|
|
63
|
pack('vC2', (($_[0]%100)<<9) + ($_[1]<<5) + $_[2], @_[4,3]); |
277
|
|
|
|
|
|
|
} # end AppleII::ProDOS::pack_date |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
280
|
|
|
|
|
|
|
# Convert a filename to ProDOS format (length nibble): |
281
|
|
|
|
|
|
|
# |
282
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
283
|
|
|
|
|
|
|
# |
284
|
|
|
|
|
|
|
# Input: |
285
|
|
|
|
|
|
|
# type: The high nibble of the type/length byte |
286
|
|
|
|
|
|
|
# name: The name |
287
|
|
|
|
|
|
|
# |
288
|
|
|
|
|
|
|
# Returns: |
289
|
|
|
|
|
|
|
# Packed string |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub pack_name |
292
|
|
|
|
|
|
|
{ |
293
|
9
|
|
|
9
|
0
|
42
|
pack('Ca15',($_[0] << 4) + length($_[1]), uc $_[1]); |
294
|
|
|
|
|
|
|
} # end AppleII::ProDOS::pack_name |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
297
|
|
|
|
|
|
|
# Extract a date & time: |
298
|
|
|
|
|
|
|
# |
299
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
300
|
|
|
|
|
|
|
# |
301
|
|
|
|
|
|
|
# Input: |
302
|
|
|
|
|
|
|
# dateField: The date/time field |
303
|
|
|
|
|
|
|
# |
304
|
|
|
|
|
|
|
# Returns: |
305
|
|
|
|
|
|
|
# Standard time for use with gmtime (not localtime) |
306
|
|
|
|
|
|
|
# undef if no date |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub parse_date |
309
|
|
|
|
|
|
|
{ |
310
|
0
|
|
|
0
|
0
|
0
|
my ($date, $minute, $hour) = unpack('vC2', $_[0]); |
311
|
0
|
0
|
|
|
|
0
|
return undef unless $date; |
312
|
0
|
|
|
|
|
0
|
my ($year, $month, $day) = ($date>>9, (($date>>5) & 0x0F), $date & 0x1F); |
313
|
0
|
|
|
|
|
0
|
mktime(0, $minute, $hour, $day, $month-1, $year); |
314
|
|
|
|
|
|
|
} # end AppleII::ProDOS::parse_date |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
317
|
|
|
|
|
|
|
# Extract a filename: |
318
|
|
|
|
|
|
|
# |
319
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
320
|
|
|
|
|
|
|
# |
321
|
|
|
|
|
|
|
# Input: |
322
|
|
|
|
|
|
|
# nameField: The type/length byte followed by the name |
323
|
|
|
|
|
|
|
# |
324
|
|
|
|
|
|
|
# Returns: |
325
|
|
|
|
|
|
|
# (type, name) |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub parse_name |
328
|
|
|
|
|
|
|
{ |
329
|
94
|
|
|
94
|
0
|
122
|
my $typeLen = ord $_[0]; |
330
|
94
|
|
|
|
|
244
|
($typeLen >> 4, substr($_[0],1,$typeLen & 0x0F)); |
331
|
|
|
|
|
|
|
} # end AppleII::ProDOS::parse_name |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
334
|
|
|
|
|
|
|
# Convert a filetype to its abbreviation: |
335
|
|
|
|
|
|
|
# |
336
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
337
|
|
|
|
|
|
|
# |
338
|
|
|
|
|
|
|
# Input: |
339
|
|
|
|
|
|
|
# type: The filetype to convert (0-255) |
340
|
|
|
|
|
|
|
# |
341
|
|
|
|
|
|
|
# Returns: |
342
|
|
|
|
|
|
|
# The abbreviation for the filetype |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub parse_type |
345
|
|
|
|
|
|
|
{ |
346
|
12
|
|
|
12
|
0
|
71
|
$filetypes[$_[0]]; |
347
|
|
|
|
|
|
|
} # end AppleII::ProDOS::parse_type |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
350
|
|
|
|
|
|
|
# Convert shell-type wildcards to Perl regexps: |
351
|
|
|
|
|
|
|
# |
352
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
353
|
|
|
|
|
|
|
# |
354
|
|
|
|
|
|
|
# Input: |
355
|
|
|
|
|
|
|
# The filename with optional wildcards |
356
|
|
|
|
|
|
|
# |
357
|
|
|
|
|
|
|
# Returns: |
358
|
|
|
|
|
|
|
# A Perl regexp |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
sub shell_wc |
361
|
|
|
|
|
|
|
{ |
362
|
0
|
|
|
|
|
0
|
'^' . |
363
|
|
|
|
|
|
|
join('', |
364
|
0
|
0
|
|
0
|
0
|
0
|
map { if (/\?/) {'.'} elsif (/\*/) {'.*'} else {quotemeta $_}} |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
365
|
|
|
|
|
|
|
split(//,$_[0])); |
366
|
|
|
|
|
|
|
} # end AppleII::ProDOS::shell_wc |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
369
|
|
|
|
|
|
|
# Convert a date & time to a short string: |
370
|
|
|
|
|
|
|
# |
371
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
372
|
|
|
|
|
|
|
# |
373
|
|
|
|
|
|
|
# Input: |
374
|
|
|
|
|
|
|
# dateField: The date/time field |
375
|
|
|
|
|
|
|
# |
376
|
|
|
|
|
|
|
# Returns: |
377
|
|
|
|
|
|
|
# "dd-Mmm-yy hh:mm" or " " |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub short_date |
382
|
|
|
|
|
|
|
{ |
383
|
24
|
|
|
24
|
0
|
77
|
my ($date, $minute, $hour) = unpack('vC2', $_[0]); |
384
|
24
|
100
|
|
|
|
61
|
return " " unless $date; |
385
|
22
|
|
|
|
|
50
|
my ($year, $month, $day) = ($date>>9, (($date>>5) & 0x0F), $date & 0x1F); |
386
|
22
|
|
|
|
|
185
|
sprintf('%2d-%s-%02d %2d:%02d',$day,$months[$month-1],$year,$hour,$minute); |
387
|
|
|
|
|
|
|
} # end AppleII::ProDOS::short_date |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
390
|
|
|
|
|
|
|
# Convert a date & time to Date::Calc format: |
391
|
|
|
|
|
|
|
# |
392
|
|
|
|
|
|
|
# This is NOT a method; it's just a regular subroutine. |
393
|
|
|
|
|
|
|
# |
394
|
|
|
|
|
|
|
# Input: |
395
|
|
|
|
|
|
|
# dateField: The date/time field |
396
|
|
|
|
|
|
|
# |
397
|
|
|
|
|
|
|
# Returns: |
398
|
|
|
|
|
|
|
# (YEAR, MONTH, DAY, HOUR, MINUTE) |
399
|
|
|
|
|
|
|
# The empty list if the date is null |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub unpack_date |
402
|
|
|
|
|
|
|
{ |
403
|
2
|
|
|
2
|
0
|
1371
|
my ($date, $minute, $hour) = unpack('vC2', $_[0]); |
404
|
2
|
50
|
|
|
|
8
|
return unless $date; |
405
|
|
|
|
|
|
|
|
406
|
2
|
|
|
|
|
5
|
my $year = $date >> 9; |
407
|
|
|
|
|
|
|
|
408
|
2
|
50
|
|
|
|
23
|
return ((($year < 77) ? $year + 2000 : $year + 1900), |
409
|
|
|
|
|
|
|
(($date>>5) & 0x0F), $date & 0x1F, $hour, $minute); |
410
|
|
|
|
|
|
|
} # end AppleII::ProDOS::unpack_date |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
413
|
|
|
|
|
|
|
# Determine if a date is valid: |
414
|
|
|
|
|
|
|
# |
415
|
|
|
|
|
|
|
# May be called as a method or a normal subroutine. |
416
|
|
|
|
|
|
|
# |
417
|
|
|
|
|
|
|
# This is not a very strenuous check; it doesn't know that not all |
418
|
|
|
|
|
|
|
# months have 31 days. [FIXME] |
419
|
|
|
|
|
|
|
# |
420
|
|
|
|
|
|
|
# Input: |
421
|
|
|
|
|
|
|
# The date to check in ProDOS format (4 byte packed string) |
422
|
|
|
|
|
|
|
# |
423
|
|
|
|
|
|
|
# Returns: |
424
|
|
|
|
|
|
|
# 0 if the date is invalid |
425
|
|
|
|
|
|
|
# 1 if the date is zero (no date) |
426
|
|
|
|
|
|
|
# 2 if the date is valid |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
sub valid_date |
429
|
|
|
|
|
|
|
{ |
430
|
2
|
50
|
|
2
|
0
|
6
|
return 1 if $_[-1] eq "\0\0\0\0"; # No date |
431
|
2
|
|
|
|
|
8
|
my ($date, $minute, $hour) = unpack('vC2', $_[-1]); |
432
|
2
|
|
|
|
|
6
|
my ($year, $month, $day) = ($date>>9, (($date>>5) & 0x0F), $date & 0x1F); |
433
|
2
|
50
|
33
|
|
|
50
|
return 0 if $minute > 59 or $hour > 23 or $year > 99 |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
434
|
|
|
|
|
|
|
or $month > 12 or $month < 1 or $day > 31 or $day < 1; |
435
|
2
|
|
|
|
|
4
|
2; # Valid date |
436
|
|
|
|
|
|
|
} # end AppleII::ProDOS::valid_date |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
439
|
|
|
|
|
|
|
# Determine if a filename is valid: |
440
|
|
|
|
|
|
|
# |
441
|
|
|
|
|
|
|
# May be called as a method or a normal subroutine. |
442
|
|
|
|
|
|
|
# |
443
|
|
|
|
|
|
|
# Input: |
444
|
|
|
|
|
|
|
# The file to check |
445
|
|
|
|
|
|
|
# |
446
|
|
|
|
|
|
|
# Returns: |
447
|
|
|
|
|
|
|
# True if the filename is valid |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
sub valid_name |
450
|
|
|
|
|
|
|
{ |
451
|
2
|
|
|
2
|
0
|
21
|
$_[-1] =~ /\A[a-z][a-z0-9.]{0,14}\Z(?!\n)/i; |
452
|
|
|
|
|
|
|
} # end AppleII::ProDOS::valid_name |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
#===================================================================== |
455
|
|
|
|
|
|
|
package AppleII::ProDOS::Bitmap; |
456
|
|
|
|
|
|
|
# |
457
|
|
|
|
|
|
|
# Member Variables: |
458
|
|
|
|
|
|
|
# bitmap: The volume bitmap itself |
459
|
|
|
|
|
|
|
# blocks: An array of the block numbers where the bitmap is stored |
460
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
461
|
|
|
|
|
|
|
# diskSize: The number of blocks on the disk |
462
|
|
|
|
|
|
|
# free: The number of free blocks |
463
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
464
|
|
|
|
|
|
|
|
465
|
2
|
|
|
2
|
|
16
|
use Carp; |
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
147
|
|
466
|
2
|
|
|
2
|
|
14
|
use bytes; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
11
|
|
467
|
2
|
|
|
2
|
|
59
|
use strict; |
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
68
|
|
468
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
2047
|
|
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
our @ISA = 'AppleII::ProDOS::Members'; |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# Map ProDOS bit order to Perl's vec(): |
473
|
|
|
|
|
|
|
my @adjust = (7, 5, 3, 1, -1, -3, -5, -7); |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
my %bit_fields = ( |
476
|
|
|
|
|
|
|
diskSize => undef, |
477
|
|
|
|
|
|
|
free => undef, |
478
|
|
|
|
|
|
|
); |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
481
|
|
|
|
|
|
|
# Constructor for creating a new bitmap: |
482
|
|
|
|
|
|
|
# |
483
|
|
|
|
|
|
|
# All blocks are marked free, except for blocks 0 thru the end of the |
484
|
|
|
|
|
|
|
# bitmap, which are marked used. |
485
|
|
|
|
|
|
|
# |
486
|
|
|
|
|
|
|
# Input: |
487
|
|
|
|
|
|
|
# disk: The AppleII::Disk to use |
488
|
|
|
|
|
|
|
# startBlock: The block number where the volume bitmap begins |
489
|
|
|
|
|
|
|
# diskSize: The size of the disk in blocks |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
sub new |
492
|
|
|
|
|
|
|
{ |
493
|
0
|
|
|
0
|
|
0
|
my ($type, $disk, $startBlock, $diskSize) = @_; |
494
|
0
|
|
|
|
|
0
|
my $self = { |
495
|
|
|
|
|
|
|
bitmap => ("\xFF" x int($diskSize / 8)), |
496
|
|
|
|
|
|
|
disk => $disk, |
497
|
|
|
|
|
|
|
diskSize => $diskSize, |
498
|
|
|
|
|
|
|
free => $diskSize, |
499
|
|
|
|
|
|
|
_permitted => \%bit_fields, |
500
|
|
|
|
|
|
|
}; |
501
|
0
|
|
|
|
|
0
|
bless $self, $type; |
502
|
0
|
|
|
|
|
0
|
$self->mark([ $diskSize-8 .. $diskSize-1], 1); # Mark odd blocks at end |
503
|
|
|
|
|
|
|
|
504
|
0
|
|
|
|
|
0
|
my @blocks; |
505
|
0
|
|
|
|
|
0
|
do { |
506
|
0
|
|
|
|
|
0
|
push @blocks, $startBlock++; |
507
|
|
|
|
|
|
|
} while ($diskSize -= 0x1000) > 0; |
508
|
|
|
|
|
|
|
|
509
|
0
|
|
|
|
|
0
|
$self->mark([ 0 .. $blocks[-1] ], 0); # Mark initial blocks as used |
510
|
|
|
|
|
|
|
|
511
|
0
|
|
|
|
|
0
|
$self->{bitmap} = |
512
|
|
|
|
|
|
|
AppleII::Disk::pad_block($self->{bitmap},"\0",($#blocks+1) * 0x200); |
513
|
0
|
|
|
|
|
0
|
$self->{blocks} = \@blocks; |
514
|
0
|
|
|
|
|
0
|
$self->{free} = unpack('%32b*', $self->{bitmap}); |
515
|
|
|
|
|
|
|
|
516
|
0
|
|
|
|
|
0
|
$self; |
517
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::new |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
520
|
|
|
|
|
|
|
# Constructor for reading an existing bitmap: |
521
|
|
|
|
|
|
|
# |
522
|
|
|
|
|
|
|
# Input: |
523
|
|
|
|
|
|
|
# disk: The AppleII::Disk to use |
524
|
|
|
|
|
|
|
# startBlock: The block number where the volume bitmap begins |
525
|
|
|
|
|
|
|
# diskSize: The size of the disk in blocks |
526
|
|
|
|
|
|
|
# STARTBLOCK & BLOCKS are optional. If they are omitted, we get |
527
|
|
|
|
|
|
|
# the information from the volume directory. |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
sub open |
530
|
|
|
|
|
|
|
{ |
531
|
1
|
|
|
1
|
|
4
|
my ($type, $disk, $startBlock, $diskSize) = @_; |
532
|
1
|
|
|
|
|
2
|
my $self = {}; |
533
|
1
|
|
|
|
|
4
|
$self->{disk} = $disk; |
534
|
1
|
|
|
|
|
3
|
$self->{'_permitted'} = \%bit_fields; |
535
|
1
|
50
|
33
|
|
|
9
|
unless ($startBlock and $diskSize) { |
536
|
0
|
|
|
|
|
0
|
my $volDir = $disk->read_block(2); |
537
|
0
|
|
|
|
|
0
|
($startBlock, $diskSize) = unpack('v2',substr($volDir,0x27,4)); |
538
|
|
|
|
|
|
|
} |
539
|
1
|
|
|
|
|
3
|
$self->{diskSize} = $diskSize; |
540
|
1
|
|
|
|
|
2
|
do { |
541
|
1
|
|
|
|
|
2
|
push @{$self->{blocks}}, $startBlock++; |
|
1
|
|
|
|
|
9
|
|
542
|
|
|
|
|
|
|
} while ($diskSize -= 0x1000) > 0; |
543
|
|
|
|
|
|
|
|
544
|
1
|
|
|
|
|
4
|
bless $self, $type; |
545
|
1
|
|
|
|
|
52
|
$self->read_disk; |
546
|
1
|
|
|
|
|
3
|
$self; |
547
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::open |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
550
|
|
|
|
|
|
|
# Get some free blocks: |
551
|
|
|
|
|
|
|
# |
552
|
|
|
|
|
|
|
# Input: |
553
|
|
|
|
|
|
|
# count: The number of blocks requested |
554
|
|
|
|
|
|
|
# |
555
|
|
|
|
|
|
|
# Returns: |
556
|
|
|
|
|
|
|
# A list of block numbers (which have been marked as used) |
557
|
|
|
|
|
|
|
# The empty list if there aren't enough free blocks |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
sub get_blocks |
560
|
|
|
|
|
|
|
{ |
561
|
2
|
|
|
2
|
|
5
|
my ($self, $count) = @_; |
562
|
2
|
50
|
|
|
|
9
|
return () if $count > $self->{free}; |
563
|
2
|
|
|
|
|
3
|
my @blocks; |
564
|
2
|
|
|
|
|
7
|
my $bitmap = $self->{bitmap}; |
565
|
|
|
|
|
|
|
BLOCK: |
566
|
2
|
|
|
|
|
15
|
while ($bitmap =~ m/([^\0])/g) { |
567
|
4
|
|
|
|
|
23
|
my ($offset, $byte) = (8*pos($bitmap)-9, unpack('B8',$1)); |
568
|
4
|
|
|
|
|
17
|
while ($byte =~ m/1/g) { |
569
|
13
|
|
|
|
|
17
|
push @blocks, $offset + pos($byte); |
570
|
13
|
100
|
|
|
|
46
|
last BLOCK unless --$count; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
} # end while BLOCK |
573
|
2
|
50
|
|
|
|
5
|
return () if $count; # We couldn't find enough |
574
|
2
|
|
|
|
|
8
|
$self->mark(\@blocks,0); # Mark blocks as in use |
575
|
2
|
|
|
|
|
79
|
@blocks; |
576
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::get_blocks |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
579
|
|
|
|
|
|
|
# See if a block is free: |
580
|
|
|
|
|
|
|
# |
581
|
|
|
|
|
|
|
# This method is not currently used and may be removed. |
582
|
|
|
|
|
|
|
# |
583
|
|
|
|
|
|
|
# Input: |
584
|
|
|
|
|
|
|
# block: The block number to check |
585
|
|
|
|
|
|
|
# |
586
|
|
|
|
|
|
|
# Returns: |
587
|
|
|
|
|
|
|
# True if the block is free |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
sub is_free |
590
|
|
|
|
|
|
|
{ |
591
|
0
|
|
|
0
|
|
0
|
my ($self, $block) = @_; |
592
|
0
|
0
|
0
|
|
|
0
|
croak("No block $block") if $block < 0 or $block >= $self->{diskSize}; |
593
|
0
|
|
|
|
|
0
|
vec($self->{bitmap}, $block + $adjust[$block % 8],1); |
594
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::is_free |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
597
|
|
|
|
|
|
|
# Mark blocks as free or used: |
598
|
|
|
|
|
|
|
# |
599
|
|
|
|
|
|
|
# Input: |
600
|
|
|
|
|
|
|
# blocks: A block number or list of block numbers to mark |
601
|
|
|
|
|
|
|
# mark: 1 for Free, 0 for Used |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
sub mark |
604
|
|
|
|
|
|
|
{ |
605
|
2
|
|
|
2
|
|
6
|
my ($self, $blocks, $mark) = @_; |
606
|
2
|
|
|
|
|
5
|
my $diskSize = $self->{diskSize}; |
607
|
2
|
50
|
|
|
|
7
|
$blocks = [ $blocks ] unless ref $blocks; |
608
|
|
|
|
|
|
|
|
609
|
2
|
|
|
|
|
3
|
my $block; |
610
|
2
|
|
|
|
|
6
|
foreach $block (@$blocks) { |
611
|
13
|
50
|
33
|
|
|
52
|
croak("No block $block") if $block < 0 or $block >= $diskSize; |
612
|
13
|
|
|
|
|
41
|
vec($self->{bitmap}, $block + $adjust[$block % 8],1) = $mark; |
613
|
|
|
|
|
|
|
} |
614
|
2
|
50
|
|
|
|
12
|
$self->{free} += ($mark ? 1 : -1) * ($#$blocks + 1); |
615
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::mark |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
618
|
|
|
|
|
|
|
# Read bitmap from disk: |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
sub read_disk |
621
|
|
|
|
|
|
|
{ |
622
|
1
|
|
|
1
|
|
3
|
my $self = shift; |
623
|
1
|
|
|
|
|
16
|
$self->{bitmap} = $self->{disk}->read_blocks($self->{blocks}); |
624
|
1
|
|
|
|
|
9
|
$self->{free} = unpack('%32b*', $self->{bitmap}); |
625
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::read_disk |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
628
|
|
|
|
|
|
|
# Return the block number where the bitmap begins: |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
sub start_block |
631
|
|
|
|
|
|
|
{ |
632
|
0
|
|
|
0
|
|
0
|
shift->{blocks}[0]; |
633
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::start_block |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
636
|
|
|
|
|
|
|
# Write bitmap to disk: |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
sub write_disk |
639
|
|
|
|
|
|
|
{ |
640
|
2
|
|
|
2
|
|
4
|
my $self = shift; |
641
|
2
|
|
|
|
|
11
|
$self->{disk}->write_blocks($self->{blocks}, $self->{bitmap}); |
642
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Bitmap::write_disk |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
#===================================================================== |
645
|
|
|
|
|
|
|
package AppleII::ProDOS::Directory; |
646
|
|
|
|
|
|
|
# |
647
|
|
|
|
|
|
|
# Member Variables: |
648
|
|
|
|
|
|
|
# access: |
649
|
|
|
|
|
|
|
# The access attributes for this directory |
650
|
|
|
|
|
|
|
# bitmap: |
651
|
|
|
|
|
|
|
# The AppleII::ProDOS::Bitmap for the disk |
652
|
|
|
|
|
|
|
# blocks: |
653
|
|
|
|
|
|
|
# The list of blocks used by this directory |
654
|
|
|
|
|
|
|
# disk: |
655
|
|
|
|
|
|
|
# An AppleII::Disk |
656
|
|
|
|
|
|
|
# entries: |
657
|
|
|
|
|
|
|
# The list of directory entries |
658
|
|
|
|
|
|
|
# name: |
659
|
|
|
|
|
|
|
# The directory name |
660
|
|
|
|
|
|
|
# created: |
661
|
|
|
|
|
|
|
# The date/time the directory was created |
662
|
|
|
|
|
|
|
# reserved: |
663
|
|
|
|
|
|
|
# The contents of the reserved section (8 byte string) |
664
|
|
|
|
|
|
|
# type: |
665
|
|
|
|
|
|
|
# 0xF for a volume directory, 0xE for a subdirectory |
666
|
|
|
|
|
|
|
# version: |
667
|
|
|
|
|
|
|
# The contents of the VERSION & MIN_VERSION (2 byte string) |
668
|
|
|
|
|
|
|
# |
669
|
|
|
|
|
|
|
# For subdirectories: |
670
|
|
|
|
|
|
|
# parent: The block number in the parent directory where our entry is |
671
|
|
|
|
|
|
|
# parentNum: Our entry number within that block of the parent directory |
672
|
|
|
|
|
|
|
# fixParent: True means our parent entry needs to be updated |
673
|
|
|
|
|
|
|
# |
674
|
|
|
|
|
|
|
# We also use the os_openDirs field of the disk to keep track of open |
675
|
|
|
|
|
|
|
# directories. It contains a hash of Directory objects indexed by key |
676
|
|
|
|
|
|
|
# block. The constructors automatically add the new objects to the |
677
|
|
|
|
|
|
|
# hash, and the destructor removes them. |
678
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
AppleII::ProDOS->import(qw(a2_croak pack_date pack_name parse_name |
681
|
|
|
|
|
|
|
short_date valid_date valid_name)); |
682
|
2
|
|
|
2
|
|
57
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
119
|
|
683
|
2
|
|
|
2
|
|
8
|
use bytes; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
13
|
|
684
|
2
|
|
|
2
|
|
46
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
62
|
|
685
|
2
|
|
|
2
|
|
8
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5351
|
|
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
our @ISA = 'AppleII::ProDOS::Members'; |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
my %dir_fields = ( |
690
|
|
|
|
|
|
|
access => 0xFF, |
691
|
|
|
|
|
|
|
created => \&valid_date, |
692
|
|
|
|
|
|
|
name => \&valid_name, |
693
|
|
|
|
|
|
|
type => undef, |
694
|
|
|
|
|
|
|
version => undef, |
695
|
|
|
|
|
|
|
); |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
698
|
|
|
|
|
|
|
# Constructor for creating a new directory: |
699
|
|
|
|
|
|
|
# |
700
|
|
|
|
|
|
|
# You must supply parent & parentNum when creating a subdirectory. |
701
|
|
|
|
|
|
|
# |
702
|
|
|
|
|
|
|
# Input: |
703
|
|
|
|
|
|
|
# name: The name of the new directory |
704
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
705
|
|
|
|
|
|
|
# blocks: A block number or array of block numbers for the directory |
706
|
|
|
|
|
|
|
# bitmap: The AppleII::ProDOS::Bitmap for the disk |
707
|
|
|
|
|
|
|
# parent: The block number in the parent directory where our entry is |
708
|
|
|
|
|
|
|
# parentNum: Our entry number within that block of the parent directory |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
sub new |
711
|
|
|
|
|
|
|
{ |
712
|
0
|
|
|
0
|
|
0
|
my ($type, $name, $disk, $blocks, $bitmap, $parent, $parentNum) = @_; |
713
|
|
|
|
|
|
|
|
714
|
0
|
0
|
|
|
|
0
|
a2_croak("Invalid name `$name'") unless valid_name($name); |
715
|
|
|
|
|
|
|
|
716
|
0
|
|
|
|
|
0
|
my $self = { |
717
|
|
|
|
|
|
|
access => 0xE3, |
718
|
|
|
|
|
|
|
bitmap => $bitmap, |
719
|
|
|
|
|
|
|
blocks => $blocks, |
720
|
|
|
|
|
|
|
disk => $disk, |
721
|
|
|
|
|
|
|
entries => [], |
722
|
|
|
|
|
|
|
name => uc $name, |
723
|
|
|
|
|
|
|
version => "\0\0", |
724
|
|
|
|
|
|
|
created => pack_date(time), |
725
|
|
|
|
|
|
|
_permitted => \%dir_fields, |
726
|
|
|
|
|
|
|
}; |
727
|
|
|
|
|
|
|
|
728
|
0
|
0
|
|
|
|
0
|
if ($parent) { |
729
|
0
|
|
|
|
|
0
|
$self->{type} = 0xE; # Subdirectory |
730
|
0
|
|
|
|
|
0
|
$self->{parent} = $parent; |
731
|
0
|
|
|
|
|
0
|
$self->{parentNum} = $parentNum; |
732
|
0
|
|
|
|
|
0
|
$self->{reserved} = "\x75\x23\x00\xC3\x27\x0D\x00\x00"; |
733
|
|
|
|
|
|
|
} else { |
734
|
0
|
|
|
|
|
0
|
$self->{type} = 0xF; # Volume directory |
735
|
0
|
|
|
|
|
0
|
$self->{reserved} = "\0" x 8; # 8 bytes reserved |
736
|
|
|
|
|
|
|
} # end else volume directory |
737
|
|
|
|
|
|
|
|
738
|
0
|
|
|
|
|
0
|
bless $self, $type; |
739
|
0
|
|
|
|
|
0
|
$disk->{os_openDirs}{$blocks->[0]} = $self; |
740
|
0
|
|
|
|
|
0
|
$self; |
741
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::new |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
744
|
|
|
|
|
|
|
# Constructor for reading an existing directory: |
745
|
|
|
|
|
|
|
# |
746
|
|
|
|
|
|
|
# Input: |
747
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
748
|
|
|
|
|
|
|
# block: The block number where the directory begins |
749
|
|
|
|
|
|
|
# bitmap: The AppleII::ProDOS::Bitmap for the disk |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
sub open |
752
|
|
|
|
|
|
|
{ |
753
|
3
|
|
|
3
|
|
16
|
my ($type, $disk, $block, $bitmap) = @_; |
754
|
3
|
|
|
|
|
19
|
my $self = { |
755
|
|
|
|
|
|
|
bitmap => $bitmap, |
756
|
|
|
|
|
|
|
disk => $disk, |
757
|
|
|
|
|
|
|
_permitted => \%dir_fields, |
758
|
|
|
|
|
|
|
}; |
759
|
|
|
|
|
|
|
|
760
|
3
|
|
|
|
|
13
|
bless $self, $type; |
761
|
3
|
|
|
|
|
10
|
$disk->{os_openDirs}{$block} = $self; |
762
|
3
|
|
|
|
|
14
|
$self->read_disk($block); |
763
|
3
|
|
|
|
|
11
|
$self; |
764
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::open |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
767
|
|
|
|
|
|
|
# Destructor: |
768
|
|
|
|
|
|
|
# |
769
|
|
|
|
|
|
|
# Removes the directory from the hash of open directories. |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
sub DESTROY |
772
|
|
|
|
|
|
|
{ |
773
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
774
|
1
|
|
|
|
|
33
|
delete $self->{disk}{os_openDirs}{$self->{blocks}[0]}; |
775
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::DESTROY |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
778
|
|
|
|
|
|
|
# Add entry: |
779
|
|
|
|
|
|
|
# |
780
|
|
|
|
|
|
|
# Dies if the entry can't be added. |
781
|
|
|
|
|
|
|
# |
782
|
|
|
|
|
|
|
# Input: |
783
|
|
|
|
|
|
|
# entry: An AppleII::ProDOS::DirEntry |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
sub add_entry |
786
|
|
|
|
|
|
|
{ |
787
|
2
|
|
|
2
|
|
4
|
my ($self,$entry) = @_; |
788
|
|
|
|
|
|
|
|
789
|
2
|
50
|
|
|
|
22
|
a2_croak($entry->name . ' already exists') |
790
|
|
|
|
|
|
|
if $self->find_entry($entry->name); |
791
|
|
|
|
|
|
|
|
792
|
2
|
|
|
|
|
6
|
my $entries = $self->{entries}; |
793
|
|
|
|
|
|
|
|
794
|
2
|
|
|
|
|
3
|
my $i; |
795
|
2
|
|
|
|
|
7
|
for ($i=0; $i <= $#$entries; ++$i) { |
796
|
5
|
50
|
|
|
|
22
|
last if $entries->[$i]{num} > $i+1; |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
2
|
50
|
|
|
|
4
|
if ($i+1 >= 0xD * scalar @{$self->{blocks}}) { |
|
2
|
|
|
|
|
8
|
|
800
|
0
|
0
|
|
|
|
0
|
a2_croak('Volume full') unless $self->{type} == 0xE; # Subdirectory |
801
|
0
|
|
|
|
|
0
|
my @blocks = $self->{bitmap}->get_blocks(1); |
802
|
0
|
0
|
|
|
|
0
|
a2_croak('Volume full') unless @blocks; |
803
|
0
|
|
|
|
|
0
|
push @{$self->{blocks}}, @blocks; |
|
0
|
|
|
|
|
0
|
|
804
|
0
|
|
|
|
|
0
|
$self->{fixParent} = 1; |
805
|
|
|
|
|
|
|
} # end if directory full |
806
|
|
|
|
|
|
|
|
807
|
2
|
|
|
|
|
9
|
$entry->{num} = $i+1; |
808
|
2
|
|
|
|
|
6
|
splice @$entries, $i, 0, $entry; |
809
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::add_entry |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
812
|
|
|
|
|
|
|
# Return the directory listing and free space information: |
813
|
|
|
|
|
|
|
# |
814
|
|
|
|
|
|
|
# Returns: |
815
|
|
|
|
|
|
|
# A string containing the catalog in ProDOS format |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
sub catalog |
818
|
|
|
|
|
|
|
{ |
819
|
4
|
|
|
4
|
|
9
|
my $self = shift; |
820
|
4
|
|
|
|
|
7
|
my $result = |
821
|
|
|
|
|
|
|
sprintf("%-15s%s %s %-14s %-14s %8s %s\n", |
822
|
|
|
|
|
|
|
qw(Name Type Blocks Modified Created Size Subtype)); |
823
|
4
|
|
|
|
|
8
|
my $entry; |
824
|
4
|
|
|
|
|
8
|
foreach $entry (@{$self->{entries}}) { |
|
4
|
|
|
|
|
17
|
|
825
|
12
|
|
|
|
|
70
|
$result .= sprintf("%-15s %-3s %5d %s %s %8d \$%04X\n", |
826
|
|
|
|
|
|
|
$entry->name, $entry->short_type, $entry->blksUsed, |
827
|
|
|
|
|
|
|
short_date($entry->modified), |
828
|
|
|
|
|
|
|
short_date($entry->created), |
829
|
|
|
|
|
|
|
$entry->size, $entry->auxtype); |
830
|
|
|
|
|
|
|
} # end foreach entry |
831
|
|
|
|
|
|
|
|
832
|
4
|
|
|
|
|
12
|
my $bitmap = $self->{bitmap}; |
833
|
4
|
|
|
|
|
23
|
my ($free, $total, $used) = ($bitmap->free, $bitmap->diskSize); |
834
|
4
|
|
|
|
|
9
|
$used = $total - $free; |
835
|
|
|
|
|
|
|
|
836
|
4
|
|
|
|
|
63
|
$result . |
837
|
|
|
|
|
|
|
"Blocks free: $free Blocks used: $used Total blocks: $total\n"; |
838
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::catalog |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
841
|
|
|
|
|
|
|
# Return the list of entries: |
842
|
|
|
|
|
|
|
# |
843
|
|
|
|
|
|
|
# Returns: |
844
|
|
|
|
|
|
|
# A list of AppleII::ProDOS::DirEntry objects |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
sub entries |
847
|
|
|
|
|
|
|
{ |
848
|
0
|
|
|
0
|
|
0
|
@{shift->{entries}}; |
|
0
|
|
|
|
|
0
|
|
849
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::entries |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
852
|
|
|
|
|
|
|
# Find an entry: |
853
|
|
|
|
|
|
|
# |
854
|
|
|
|
|
|
|
# Input: |
855
|
|
|
|
|
|
|
# filename: The filename to match |
856
|
|
|
|
|
|
|
# |
857
|
|
|
|
|
|
|
# Returns: |
858
|
|
|
|
|
|
|
# The entry representing that filename |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
sub find_entry |
861
|
|
|
|
|
|
|
{ |
862
|
14
|
|
|
14
|
|
27
|
my ($self, $filename) = @_; |
863
|
14
|
|
|
|
|
29
|
$filename = uc $filename; |
864
|
14
|
|
|
|
|
22
|
(grep {uc($_->name) eq $filename} @{$self->{'entries'}})[0]; |
|
42
|
|
|
|
|
179
|
|
|
14
|
|
|
|
|
40
|
|
865
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::find_entry |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
868
|
|
|
|
|
|
|
# Read a file: |
869
|
|
|
|
|
|
|
# |
870
|
|
|
|
|
|
|
# Input: |
871
|
|
|
|
|
|
|
# file: |
872
|
|
|
|
|
|
|
# The name of the file to read, OR |
873
|
|
|
|
|
|
|
# an AppleII::ProDOS::DirEntry object representing a file |
874
|
|
|
|
|
|
|
# |
875
|
|
|
|
|
|
|
# Returns: |
876
|
|
|
|
|
|
|
# A new AppleII::ProDOS::File object for the file |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub get_file |
879
|
|
|
|
|
|
|
{ |
880
|
10
|
|
|
10
|
|
21
|
my ($self, $filename) = @_; |
881
|
|
|
|
|
|
|
|
882
|
10
|
50
|
33
|
|
|
52
|
my $entry = (ref($filename) |
883
|
|
|
|
|
|
|
? $filename |
884
|
|
|
|
|
|
|
: ($self->find_entry($filename) |
885
|
|
|
|
|
|
|
or a2_croak("No such file `$filename'"))); |
886
|
|
|
|
|
|
|
|
887
|
10
|
|
|
|
|
56
|
AppleII::ProDOS::File->open($self->{disk}, $entry); |
888
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::get_file |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
891
|
|
|
|
|
|
|
# List files matching a regexp: |
892
|
|
|
|
|
|
|
# |
893
|
|
|
|
|
|
|
# Input: |
894
|
|
|
|
|
|
|
# pattern: |
895
|
|
|
|
|
|
|
# The Perl regexp to match |
896
|
|
|
|
|
|
|
# (AppleII::ProDOS::shell_wc converts shell-type wildcards to regexps) |
897
|
|
|
|
|
|
|
# filter: (optional) |
898
|
|
|
|
|
|
|
# A subroutine to run against the entries |
899
|
|
|
|
|
|
|
# It must return a true value for the file to be accepted. |
900
|
|
|
|
|
|
|
# There are three special values: |
901
|
|
|
|
|
|
|
# undef Match anything |
902
|
|
|
|
|
|
|
# 'DIR' Match only directories |
903
|
|
|
|
|
|
|
# '!DIR' Match anything but directories |
904
|
|
|
|
|
|
|
# |
905
|
|
|
|
|
|
|
# Returns: |
906
|
|
|
|
|
|
|
# A list of filenames matching the pattern |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
sub list_matches |
909
|
|
|
|
|
|
|
{ |
910
|
0
|
|
|
0
|
|
0
|
my ($self, $pattern, $filter) = @_; |
911
|
0
|
0
|
|
|
|
0
|
$filter = \&is_dir if $filter eq 'DIR'; |
912
|
0
|
0
|
|
|
|
0
|
$filter = \&isnt_dir if $filter eq '!DIR'; |
913
|
0
|
0
|
|
|
|
0
|
$filter = \&true unless $filter; |
914
|
0
|
0
|
0
|
|
|
0
|
map { ($_->name =~ /$pattern/i and &$filter($_)) |
|
0
|
|
|
|
|
0
|
|
915
|
|
|
|
|
|
|
? $_->name |
916
|
|
|
|
|
|
|
: () } |
917
|
0
|
|
|
|
|
0
|
@{$self->{'entries'}}; |
918
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::list_matches |
919
|
|
|
|
|
|
|
|
920
|
0
|
|
|
0
|
|
0
|
sub is_dir { $_[0]->type == 0x0F } # True if entry is directory |
921
|
0
|
|
|
0
|
|
0
|
sub isnt_dir { $_[0]->type != 0x0F } # True if entry is not directory |
922
|
0
|
|
|
0
|
|
0
|
sub true { 1 } # Accept anything |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
925
|
|
|
|
|
|
|
# Create a subdirectory: |
926
|
|
|
|
|
|
|
# |
927
|
|
|
|
|
|
|
# Input: |
928
|
|
|
|
|
|
|
# dir: The name of the subdirectory to create |
929
|
|
|
|
|
|
|
# size: The number of entries the directory should hold |
930
|
|
|
|
|
|
|
# The default is to create a 1 block directory |
931
|
|
|
|
|
|
|
# |
932
|
|
|
|
|
|
|
# Returns: |
933
|
|
|
|
|
|
|
# The DirEntry object for the new directory |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
sub new_dir |
936
|
|
|
|
|
|
|
{ |
937
|
0
|
|
|
0
|
|
0
|
my ($self, $dir, $size) = @_; |
938
|
|
|
|
|
|
|
|
939
|
0
|
0
|
|
|
|
0
|
a2_croak("Invalid name `$dir'") unless valid_name($dir); |
940
|
0
|
|
|
|
|
0
|
$dir = uc $dir; |
941
|
|
|
|
|
|
|
|
942
|
0
|
0
|
|
|
|
0
|
$size = 1 unless $size; |
943
|
0
|
|
|
|
|
0
|
$size = int(($size + 0xD) / 0xD); # Compute # of blocks (+ dir header) |
944
|
|
|
|
|
|
|
|
945
|
0
|
0
|
|
|
|
0
|
my @blocks = $self->{bitmap}->get_blocks($size) |
946
|
|
|
|
|
|
|
or a2_croak("Not enough free space"); |
947
|
|
|
|
|
|
|
|
948
|
0
|
|
|
|
|
0
|
my $entry = AppleII::ProDOS::DirEntry->new; |
949
|
|
|
|
|
|
|
|
950
|
0
|
|
|
|
|
0
|
eval { |
951
|
0
|
|
|
|
|
0
|
$entry->storage(0xD); # Directory |
952
|
0
|
|
|
|
|
0
|
$entry->name($dir); |
953
|
0
|
|
|
|
|
0
|
$entry->type(0x0F); # Directory |
954
|
0
|
|
|
|
|
0
|
$entry->block($blocks[0]); |
955
|
0
|
|
|
|
|
0
|
$entry->blksUsed($#blocks + 1); |
956
|
0
|
|
|
|
|
0
|
$entry->size(0x200 * ($#blocks + 1)); |
957
|
|
|
|
|
|
|
|
958
|
0
|
|
|
|
|
0
|
$self->add_entry($entry); |
959
|
0
|
|
|
|
|
0
|
my $subdir = AppleII::ProDOS::Directory->new( |
960
|
|
|
|
|
|
|
$dir, $self->{disk}, \@blocks, $self->{bitmap}, |
961
|
|
|
|
|
|
|
$self->{blocks}[int($entry->num / 0xD)], int($entry->num % 0xD)+1 |
962
|
|
|
|
|
|
|
); |
963
|
|
|
|
|
|
|
|
964
|
0
|
|
|
|
|
0
|
$subdir->write_disk; |
965
|
0
|
|
|
|
|
0
|
$self->write_disk; |
966
|
0
|
|
|
|
|
0
|
$self->{bitmap}->write_disk; |
967
|
|
|
|
|
|
|
}; # end eval |
968
|
0
|
0
|
|
|
|
0
|
if ($@) { |
969
|
0
|
|
|
|
|
0
|
my $error = $@; # Clean up after error |
970
|
0
|
|
|
|
|
0
|
$self->read_disk; |
971
|
0
|
|
|
|
|
0
|
$self->{bitmap}->read_disk; |
972
|
0
|
|
|
|
|
0
|
die $error; |
973
|
|
|
|
|
|
|
} # end if error while creating directory |
974
|
|
|
|
|
|
|
|
975
|
0
|
|
|
|
|
0
|
$entry; |
976
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::new_dir |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
979
|
|
|
|
|
|
|
# Open a subdirectory: |
980
|
|
|
|
|
|
|
# |
981
|
|
|
|
|
|
|
# Input: |
982
|
|
|
|
|
|
|
# dir: The name of the subdirectory to open, OR |
983
|
|
|
|
|
|
|
# an AppleII::ProDOS::DirEntry object representing the directory |
984
|
|
|
|
|
|
|
# |
985
|
|
|
|
|
|
|
# Returns: |
986
|
|
|
|
|
|
|
# A new AppleII::ProDOS::Directory object for the subdirectory |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
sub open_dir |
989
|
|
|
|
|
|
|
{ |
990
|
2
|
|
|
2
|
|
4
|
my ($self, $dir) = @_; |
991
|
|
|
|
|
|
|
|
992
|
2
|
50
|
33
|
|
|
15
|
my $entry = (ref($dir) |
993
|
|
|
|
|
|
|
? $dir |
994
|
|
|
|
|
|
|
: ($self->find_entry($dir) |
995
|
|
|
|
|
|
|
or a2_croak("No such directory `$dir'"))); |
996
|
|
|
|
|
|
|
|
997
|
2
|
50
|
|
|
|
18
|
a2_croak('`' . $entry->name . "' is not a directory") |
998
|
|
|
|
|
|
|
unless $entry->type == 0x0F; |
999
|
|
|
|
|
|
|
|
1000
|
2
|
|
|
|
|
13
|
AppleII::ProDOS::Directory->open($self->{disk}, $entry->block, |
1001
|
|
|
|
|
|
|
$self->{bitmap}); |
1002
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::open_dir |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1005
|
|
|
|
|
|
|
# Add a new file to the directory: |
1006
|
|
|
|
|
|
|
# |
1007
|
|
|
|
|
|
|
# Input: |
1008
|
|
|
|
|
|
|
# file: The AppleII::ProDOS::File to add |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
sub put_file |
1011
|
|
|
|
|
|
|
{ |
1012
|
2
|
|
|
2
|
|
6
|
my ($self, $file) = @_; |
1013
|
|
|
|
|
|
|
|
1014
|
2
|
|
|
|
|
4
|
eval { |
1015
|
2
|
|
|
|
|
13
|
$file->allocate_space($self->{bitmap}); |
1016
|
2
|
|
|
|
|
15
|
$self->add_entry($file); |
1017
|
2
|
|
|
|
|
8
|
$file->write_disk($self->{disk}); |
1018
|
2
|
|
|
|
|
11
|
$self->write_disk; |
1019
|
2
|
|
|
|
|
9
|
$self->{bitmap}->write_disk; |
1020
|
|
|
|
|
|
|
}; |
1021
|
2
|
50
|
|
|
|
12
|
if ($@) { |
1022
|
0
|
|
|
|
|
0
|
my $error = $@; |
1023
|
|
|
|
|
|
|
# Clean up after failure: |
1024
|
0
|
|
|
|
|
0
|
$self->read_disk; |
1025
|
0
|
|
|
|
|
0
|
$self->{bitmap}->read_disk; |
1026
|
0
|
|
|
|
|
0
|
die $error; |
1027
|
|
|
|
|
|
|
} |
1028
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::put_file |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1031
|
|
|
|
|
|
|
# Read directory from disk: |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
sub read_disk |
1034
|
|
|
|
|
|
|
{ |
1035
|
3
|
|
|
3
|
|
7
|
my ($self, $block) = @_; |
1036
|
3
|
50
|
|
|
|
9
|
$block = $self->{blocks}[0] unless $block; |
1037
|
|
|
|
|
|
|
|
1038
|
3
|
|
|
|
|
6
|
my (@blocks,@entries); |
1039
|
3
|
|
|
|
|
12
|
my $disk = $self->{disk}; |
1040
|
3
|
|
|
|
|
5
|
my $entry = 0; |
1041
|
3
|
|
|
|
|
10
|
while ($block) { |
1042
|
6
|
|
|
|
|
23
|
push @blocks, $block; |
1043
|
6
|
|
|
|
|
25
|
my $data = $disk->read_block($block); |
1044
|
6
|
|
|
|
|
24
|
$block = unpack('v',substr($data,0x02,2)); # Pointer to next block |
1045
|
6
|
|
|
|
|
13
|
substr($data,0,4) = ''; # Remove block pointers |
1046
|
6
|
|
|
|
|
18
|
while ($data) { |
1047
|
84
|
|
|
|
|
141
|
my ($type, $name) = parse_name($data); |
1048
|
84
|
100
|
|
|
|
210
|
if (($type & 0xE) == 0xE) { |
|
|
100
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
# Directory header |
1050
|
3
|
|
|
|
|
8
|
$self->{name} = $name; |
1051
|
3
|
|
|
|
|
9
|
$self->{type} = $type; |
1052
|
3
|
|
|
|
|
10
|
$self->{reserved} = substr($data, 0x14-4,8); |
1053
|
3
|
|
|
|
|
8
|
$self->{created} = substr($data, 0x1C-4,4); |
1054
|
3
|
|
|
|
|
12
|
$self->{version} = substr($data, 0x20-4,2); |
1055
|
3
|
|
|
|
|
12
|
$self->{access} = ord substr($data, 0x22-4,1); |
1056
|
3
|
100
|
|
|
|
17
|
if ($type == 0xE) { |
1057
|
|
|
|
|
|
|
# For subdirectory, read parent pointers |
1058
|
2
|
|
|
|
|
8
|
@{$self}{qw(parent parentNum)} = |
|
2
|
|
|
|
|
7
|
|
1059
|
|
|
|
|
|
|
unpack('vC',substr($data,0x27-4,3)); |
1060
|
|
|
|
|
|
|
} # end if subdirectory |
1061
|
|
|
|
|
|
|
} elsif ($type) { |
1062
|
|
|
|
|
|
|
# File entry |
1063
|
9
|
|
|
|
|
30
|
push @entries, AppleII::ProDOS::DirEntry->new($entry, $data); |
1064
|
|
|
|
|
|
|
} |
1065
|
84
|
|
|
|
|
108
|
substr($data,0,0x27) = ''; # Remove record |
1066
|
84
|
|
|
|
|
166
|
++$entry; |
1067
|
|
|
|
|
|
|
} # end while more records |
1068
|
|
|
|
|
|
|
} # end if rebuilding block list |
1069
|
|
|
|
|
|
|
|
1070
|
3
|
|
|
|
|
8
|
@{$self}{qw(blocks entries)} = (\@blocks, \@entries); |
|
3
|
|
|
|
|
15
|
|
1071
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::read_disk |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1074
|
|
|
|
|
|
|
# Write directory to disk: |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
sub write_disk |
1077
|
|
|
|
|
|
|
{ |
1078
|
2
|
|
|
2
|
|
4
|
my ($self) = @_; |
1079
|
|
|
|
|
|
|
|
1080
|
2
|
|
|
|
|
6
|
my $disk = $self->{disk}; |
1081
|
2
|
|
|
|
|
4
|
my @blocks = @{$self->{blocks}}; |
|
2
|
|
|
|
|
9
|
|
1082
|
2
|
|
|
|
|
3
|
my @entries = @{$self->{'entries'}}; |
|
2
|
|
|
|
|
9
|
|
1083
|
2
|
|
|
|
|
5
|
my $keyBlock = $blocks[0]; |
1084
|
|
|
|
|
|
|
|
1085
|
2
|
50
|
|
|
|
9
|
if ($self->{fixParent}) { |
1086
|
0
|
|
|
|
|
0
|
delete $self->{fixParent}; |
1087
|
0
|
|
|
|
|
0
|
my $data = $disk->read_block($self->{parent}); |
1088
|
0
|
|
|
|
|
0
|
my $entry = 4 + 0x27*($self->{parentNum}-1); |
1089
|
0
|
|
|
|
|
0
|
substr($data, $entry + 0x11, 7) = |
1090
|
|
|
|
|
|
|
pack('v2VX', $keyBlock, scalar(@blocks), 0x200 * scalar(@blocks)); |
1091
|
|
|
|
|
|
|
# FIXME update modified date? |
1092
|
0
|
|
|
|
|
0
|
$disk->write_block($self->{parent}, $data); |
1093
|
0
|
|
|
|
|
0
|
my $parentBlock = unpack('v', substr($data,$entry + 0x25, 2)); |
1094
|
0
|
0
|
|
|
|
0
|
$disk->{os_openDirs}{$parentBlock}->read_disk |
1095
|
|
|
|
|
|
|
if $disk->{os_openDirs}{$parentBlock}; |
1096
|
|
|
|
|
|
|
} # end if parent entry needs updating |
1097
|
|
|
|
|
|
|
|
1098
|
2
|
|
|
|
|
4
|
push @blocks, 0; # Add marker at beginning and end |
1099
|
2
|
|
|
|
|
5
|
unshift @blocks, 0; |
1100
|
2
|
|
|
|
|
3
|
my ($i, $entry); |
1101
|
2
|
|
|
|
|
11
|
for ($i=1, $entry=0; $i < $#blocks; $i++) { |
1102
|
2
|
|
|
|
|
10
|
my $data = pack('v2',$blocks[$i-1],$blocks[$i+1]); # Block pointers |
1103
|
2
|
|
|
|
|
7
|
while (length($data) < 0x1FF) { |
1104
|
26
|
100
|
|
|
|
43
|
if ($entry) { |
1105
|
|
|
|
|
|
|
# Add a file entry: |
1106
|
24
|
100
|
66
|
|
|
69
|
if (@entries and $entries[0]{num} == $entry) { |
1107
|
7
|
|
|
|
|
28
|
$data .= $entries[0]->packed($keyBlock); shift @entries; |
|
7
|
|
|
|
|
9
|
|
1108
|
|
|
|
|
|
|
} else { |
1109
|
17
|
|
|
|
|
26
|
$data .= "\0" x 0x27; |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
} else { |
1112
|
|
|
|
|
|
|
# Add the directory header: |
1113
|
2
|
|
|
|
|
4
|
$data .= pack_name(@{$self}{'type','name'}); |
|
2
|
|
|
|
|
10
|
|
1114
|
2
|
|
|
|
|
8
|
$data .= $self->{reserved}; |
1115
|
2
|
|
|
|
|
4
|
$data .= $self->{created}; |
1116
|
2
|
|
|
|
|
5
|
$data .= $self->{version}; |
1117
|
2
|
|
|
|
|
6
|
$data .= chr $self->{access}; |
1118
|
2
|
|
|
|
|
5
|
$data .= "\x27\x0D"; # Entry length, entries per block |
1119
|
2
|
|
|
|
|
5
|
$data .= pack('v',$#entries+1); |
1120
|
2
|
50
|
|
|
|
8
|
if ($self->{type} == 0xF) { |
1121
|
0
|
|
|
|
|
0
|
my $bitmap = $self->{bitmap}; |
1122
|
0
|
|
|
|
|
0
|
$data .= pack('v2',$bitmap->start_block,$bitmap->diskSize); |
1123
|
|
|
|
|
|
|
} else { |
1124
|
2
|
|
|
|
|
3
|
$data .= pack('vCC',@{$self}{'parent','parentNum'}, |
|
2
|
|
|
|
|
8
|
|
1125
|
|
|
|
|
|
|
0x27); # Parent entry length |
1126
|
|
|
|
|
|
|
} # end else subdirectory |
1127
|
|
|
|
|
|
|
} # end else if directory header |
1128
|
26
|
|
|
|
|
76
|
++$entry; |
1129
|
|
|
|
|
|
|
} # end while more room in block |
1130
|
2
|
|
|
|
|
13
|
$disk->write_block($blocks[$i],$data."\0"); |
1131
|
|
|
|
|
|
|
} # end for each directory block |
1132
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Directory::write_disk |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
#===================================================================== |
1135
|
|
|
|
|
|
|
package AppleII::ProDOS::DirEntry; |
1136
|
|
|
|
|
|
|
# |
1137
|
|
|
|
|
|
|
# Member Variables: |
1138
|
|
|
|
|
|
|
# access: The access attributes |
1139
|
|
|
|
|
|
|
# auxtype: The auxiliary type |
1140
|
|
|
|
|
|
|
# block: The key block for this file |
1141
|
|
|
|
|
|
|
# blksUsed: The number of blocks used by this file |
1142
|
|
|
|
|
|
|
# created: The creation date/time |
1143
|
|
|
|
|
|
|
# modified: The date/time of last modification |
1144
|
|
|
|
|
|
|
# name: The filename |
1145
|
|
|
|
|
|
|
# num: The entry number of this entry |
1146
|
|
|
|
|
|
|
# size: The file size in bytes |
1147
|
|
|
|
|
|
|
# storage: The storage type |
1148
|
|
|
|
|
|
|
# type: The file type |
1149
|
|
|
|
|
|
|
# version: The contents of the VERSION & MIN_VERSION (2 byte string) |
1150
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1151
|
|
|
|
|
|
|
AppleII::ProDOS->import(qw(pack_date pack_name parse_name parse_type |
1152
|
|
|
|
|
|
|
valid_date valid_name)); |
1153
|
2
|
|
|
2
|
|
14
|
use integer; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
15
|
|
1154
|
2
|
|
|
2
|
|
155
|
use bytes; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
10
|
|
1155
|
2
|
|
|
2
|
|
38
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
55
|
|
1156
|
2
|
|
|
2
|
|
16
|
use warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
1297
|
|
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
our @ISA = 'AppleII::ProDOS::Members'; |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
my %de_fields = ( |
1161
|
|
|
|
|
|
|
access => 0xFF, |
1162
|
|
|
|
|
|
|
auxtype => 0xFFFF, |
1163
|
|
|
|
|
|
|
block => sub { not defined $_[0]{block} }, |
1164
|
|
|
|
|
|
|
blksUsed => sub { not defined $_[0]{blksUsed} }, |
1165
|
|
|
|
|
|
|
created => \&valid_date, |
1166
|
|
|
|
|
|
|
modified => \&valid_date, |
1167
|
|
|
|
|
|
|
name => \&valid_name, |
1168
|
|
|
|
|
|
|
num => sub { not defined $_[0]{num} }, |
1169
|
|
|
|
|
|
|
size => sub { not defined $_[0]{size} }, |
1170
|
|
|
|
|
|
|
storage => sub { not defined $_[0]{storage} }, |
1171
|
|
|
|
|
|
|
type => 0xFF, |
1172
|
|
|
|
|
|
|
); |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1175
|
|
|
|
|
|
|
# Constructor: |
1176
|
|
|
|
|
|
|
# |
1177
|
|
|
|
|
|
|
# Input: |
1178
|
|
|
|
|
|
|
# number: The entry number |
1179
|
|
|
|
|
|
|
# entry: The directory entry |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
sub new |
1182
|
|
|
|
|
|
|
{ |
1183
|
9
|
|
|
9
|
|
19
|
my ($type, $number, $entry) = @_; |
1184
|
9
|
|
|
|
|
12
|
my $self = {}; |
1185
|
|
|
|
|
|
|
|
1186
|
9
|
|
|
|
|
24
|
$self->{'_permitted'} = \%de_fields; |
1187
|
9
|
50
|
|
|
|
21
|
if ($entry) { |
1188
|
9
|
|
|
|
|
18
|
$self->{num} = $number; |
1189
|
9
|
|
|
|
|
17
|
@{$self}{'storage', 'name'} = parse_name($entry); |
|
9
|
|
|
|
|
24
|
|
1190
|
9
|
|
|
|
|
29
|
@{$self}{qw(type block blksUsed size)} = unpack('x16Cv2V',$entry); |
|
9
|
|
|
|
|
36
|
|
1191
|
9
|
|
|
|
|
16
|
$self->{size} &= 0xFFFFFF; # Size is only 3 bytes long |
1192
|
9
|
|
|
|
|
18
|
@{$self}{qw(access auxtype)} = unpack('x30Cv',$entry); |
|
9
|
|
|
|
|
20
|
|
1193
|
|
|
|
|
|
|
|
1194
|
9
|
|
|
|
|
21
|
$self->{created} = substr($entry,0x18,4); |
1195
|
9
|
|
|
|
|
19
|
$self->{modified} = substr($entry,0x21,4); |
1196
|
9
|
|
|
|
|
19
|
$self->{version} = substr($entry,0x1C,2); |
1197
|
|
|
|
|
|
|
} else { |
1198
|
|
|
|
|
|
|
# Blank entry: |
1199
|
0
|
|
|
|
|
0
|
$self->{created} = $self->{modified} = pack_date(time); |
1200
|
0
|
|
|
|
|
0
|
@{$self}{qw(access auxtype type version)} = |
|
0
|
|
|
|
|
0
|
|
1201
|
|
|
|
|
|
|
(0xE3, 0x0000, 0x00, "\0\0"); |
1202
|
|
|
|
|
|
|
} |
1203
|
9
|
|
|
|
|
32
|
bless $self, $type; |
1204
|
|
|
|
|
|
|
} # end AppleII::ProDOS::DirEntry::new |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1207
|
|
|
|
|
|
|
# Return the entry as a packed string: |
1208
|
|
|
|
|
|
|
# |
1209
|
|
|
|
|
|
|
# Input: |
1210
|
|
|
|
|
|
|
# keyBlock: The block number of the beginning of the directory |
1211
|
|
|
|
|
|
|
# |
1212
|
|
|
|
|
|
|
# Returns: |
1213
|
|
|
|
|
|
|
# A directory entry ready to put in a ProDOS directory |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
sub packed |
1216
|
|
|
|
|
|
|
{ |
1217
|
7
|
|
|
7
|
|
11
|
my ($self, $keyBlock) = @_; |
1218
|
7
|
|
|
|
|
9
|
my $data = pack_name(@{$self}{'storage', 'name'}); |
|
7
|
|
|
|
|
102
|
|
1219
|
7
|
|
|
|
|
12
|
$data .= pack('Cv2VX',@{$self}{qw(type block blksUsed size)}); |
|
7
|
|
|
|
|
26
|
|
1220
|
7
|
|
|
|
|
21
|
$data .= $self->{created} . $self->{version}; |
1221
|
7
|
|
|
|
|
9
|
$data .= pack('Cv',@{$self}{qw(access auxtype)}); |
|
7
|
|
|
|
|
18
|
|
1222
|
7
|
|
|
|
|
12
|
$data .= $self->{modified}; |
1223
|
7
|
|
|
|
|
21
|
$data .= pack('v',$keyBlock); |
1224
|
|
|
|
|
|
|
} # end AppleII::ProDOS::DirEntry::packed |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1227
|
|
|
|
|
|
|
# Return the filetype as a string: |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
sub short_type |
1230
|
|
|
|
|
|
|
{ |
1231
|
12
|
|
|
12
|
|
40
|
parse_type(shift->{type}); |
1232
|
|
|
|
|
|
|
} # end AppleII::ProDOS::DirEntry::short_type |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
#===================================================================== |
1235
|
|
|
|
|
|
|
package AppleII::ProDOS::File; |
1236
|
|
|
|
|
|
|
# |
1237
|
|
|
|
|
|
|
# Member Variables: |
1238
|
|
|
|
|
|
|
# data: The contents of the file |
1239
|
|
|
|
|
|
|
# indexBlocks: For tree files, the number of subindex blocks needed |
1240
|
|
|
|
|
|
|
# |
1241
|
|
|
|
|
|
|
# Private Members (for communication between allocate_space & write_disk): |
1242
|
|
|
|
|
|
|
# blocks: The list of data blocks allocated for this file |
1243
|
|
|
|
|
|
|
# indexBlocks: For tree files, the list of subindex blocks |
1244
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
AppleII::ProDOS->import(qw(a2_croak valid_date valid_name)); |
1247
|
2
|
|
|
2
|
|
11
|
use Carp; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
115
|
|
1248
|
2
|
|
|
2
|
|
9
|
use bytes; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
8
|
|
1249
|
2
|
|
|
2
|
|
126
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
67
|
|
1250
|
2
|
|
|
2
|
|
178
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
2806
|
|
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
our @ISA = 'AppleII::ProDOS::DirEntry'; |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
my %fil_fields = ( |
1255
|
|
|
|
|
|
|
access => 0xFF, |
1256
|
|
|
|
|
|
|
auxtype => 0xFFFF, |
1257
|
|
|
|
|
|
|
blksUsed => undef, |
1258
|
|
|
|
|
|
|
created => \&valid_date, |
1259
|
|
|
|
|
|
|
data => undef, |
1260
|
|
|
|
|
|
|
modified => \&valid_date, |
1261
|
|
|
|
|
|
|
name => \&valid_name, |
1262
|
|
|
|
|
|
|
size => undef, |
1263
|
|
|
|
|
|
|
type => 0xFF, |
1264
|
|
|
|
|
|
|
); |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1267
|
|
|
|
|
|
|
# Constructor for creating a new file: |
1268
|
|
|
|
|
|
|
# |
1269
|
|
|
|
|
|
|
# Input: |
1270
|
|
|
|
|
|
|
# name: The filename |
1271
|
|
|
|
|
|
|
# data: The contents of the file |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
sub new |
1274
|
|
|
|
|
|
|
{ |
1275
|
2
|
|
|
2
|
|
7
|
my ($type, $name, $data) = @_; |
1276
|
2
|
50
|
|
|
|
8
|
a2_croak("Invalid name `$name'") unless valid_name($name); |
1277
|
|
|
|
|
|
|
|
1278
|
2
|
|
|
|
|
28
|
my $self = { |
1279
|
|
|
|
|
|
|
access => 0xE3, |
1280
|
|
|
|
|
|
|
auxtype => 0, |
1281
|
|
|
|
|
|
|
created => "\0\0\0\0", |
1282
|
|
|
|
|
|
|
data => $data, |
1283
|
|
|
|
|
|
|
modified => "\0\0\0\0", |
1284
|
|
|
|
|
|
|
name => uc $name, |
1285
|
|
|
|
|
|
|
size => length($data), |
1286
|
|
|
|
|
|
|
type => 0, |
1287
|
|
|
|
|
|
|
version => "\0\0", |
1288
|
|
|
|
|
|
|
_permitted => \%fil_fields |
1289
|
|
|
|
|
|
|
}; |
1290
|
|
|
|
|
|
|
|
1291
|
2
|
|
|
|
|
13
|
bless $self, $type; |
1292
|
|
|
|
|
|
|
} # end AppleII::ProDOS::File::new |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1295
|
|
|
|
|
|
|
# Open a file: |
1296
|
|
|
|
|
|
|
# |
1297
|
|
|
|
|
|
|
# Input: |
1298
|
|
|
|
|
|
|
# disk: The disk to read |
1299
|
|
|
|
|
|
|
# entry: The AppleII::ProDOS::DirEntry that describes the file |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
sub open |
1302
|
|
|
|
|
|
|
{ |
1303
|
10
|
|
|
10
|
|
23
|
my ($type, $disk, $entry) = @_; |
1304
|
10
|
|
|
|
|
35
|
my $self = { _permitted => \%fil_fields }; |
1305
|
10
|
|
|
|
|
42
|
my @fields = qw(access auxtype blksUsed created modified name size |
1306
|
|
|
|
|
|
|
storage type version); |
1307
|
10
|
|
|
|
|
20
|
@{$self}{@fields} = @{$entry}{@fields}; |
|
10
|
|
|
|
|
89
|
|
|
10
|
|
|
|
|
48
|
|
1308
|
|
|
|
|
|
|
|
1309
|
10
|
|
|
|
|
29
|
my ($storage, $keyBlock, $size) = |
1310
|
10
|
|
|
|
|
24
|
@{$entry}{qw(storage block size)}; |
1311
|
|
|
|
|
|
|
|
1312
|
10
|
|
|
|
|
15
|
my $data; |
1313
|
10
|
100
|
|
|
|
35
|
if ($storage == 1) { |
1314
|
2
|
|
|
|
|
12
|
$data = $disk->read_block($keyBlock); |
1315
|
|
|
|
|
|
|
} else { |
1316
|
|
|
|
|
|
|
# Calculate the number of data blocks: |
1317
|
|
|
|
|
|
|
# (In a sparse file, not all these blocks |
1318
|
|
|
|
|
|
|
# are actually allocated.) |
1319
|
8
|
|
|
|
|
20
|
my $blksUsed = int(($size + 0x1FF) / 0x200); |
1320
|
|
|
|
|
|
|
|
1321
|
8
|
100
|
|
|
|
28
|
if ($storage == 2) { |
|
|
50
|
|
|
|
|
|
1322
|
5
|
|
|
|
|
31
|
my $index = AppleII::ProDOS::Index->open($disk,$keyBlock,$blksUsed); |
1323
|
5
|
|
|
|
|
38
|
$data = $disk->read_blocks($index->blocks); |
1324
|
|
|
|
|
|
|
} elsif ($storage == 3) { |
1325
|
3
|
|
|
|
|
9
|
my $indexBlocks = int(($blksUsed + 0xFF) / 0x100); |
1326
|
3
|
|
|
|
|
24
|
my $index = AppleII::ProDOS::Index->open($disk,$keyBlock,$indexBlocks); |
1327
|
3
|
|
|
|
|
6
|
my (@blocks,$block); |
1328
|
3
|
|
|
|
|
9
|
foreach $block (@{$index->blocks}) { |
|
3
|
|
|
|
|
31
|
|
1329
|
6
|
50
|
|
|
|
22
|
if ($block) { |
1330
|
6
|
|
|
|
|
21
|
my $subindex = AppleII::ProDOS::Index->open($disk,$block); |
1331
|
6
|
|
|
|
|
10
|
push @blocks,@{$subindex->blocks}; |
|
6
|
|
|
|
|
33
|
|
1332
|
|
|
|
|
|
|
} else { |
1333
|
0
|
|
|
|
|
0
|
push @blocks, (0) x 0x100; # Sparse index block |
1334
|
|
|
|
|
|
|
} |
1335
|
|
|
|
|
|
|
} # end foreach subindex block |
1336
|
3
|
|
|
|
|
31
|
$#blocks = $blksUsed-1; # Use only the first $blksUsed blocks |
1337
|
3
|
|
|
|
|
19
|
$data = $disk->read_blocks(\@blocks); |
1338
|
3
|
|
|
|
|
49
|
$self->{indexBlocks} = $indexBlocks; |
1339
|
|
|
|
|
|
|
} else { |
1340
|
0
|
|
|
|
|
0
|
croak("Unsupported storage type $storage"); |
1341
|
|
|
|
|
|
|
} |
1342
|
|
|
|
|
|
|
} # end else not a seedling file |
1343
|
|
|
|
|
|
|
|
1344
|
10
|
100
|
|
|
|
60
|
substr($data, $size) = '' if length($data) > $size; |
1345
|
10
|
|
|
|
|
695
|
$self->{'data'} = $data; |
1346
|
|
|
|
|
|
|
|
1347
|
10
|
|
|
|
|
87
|
bless $self, $type; |
1348
|
|
|
|
|
|
|
} # end AppleII::ProDOS::File::open |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1351
|
|
|
|
|
|
|
# Allocate space for the file: |
1352
|
|
|
|
|
|
|
# |
1353
|
|
|
|
|
|
|
# Input: |
1354
|
|
|
|
|
|
|
# bitmap: The AppleII::ProDOS::Bitmap we should use |
1355
|
|
|
|
|
|
|
# |
1356
|
|
|
|
|
|
|
# Input Variables: |
1357
|
|
|
|
|
|
|
# data: The data we're trying to store |
1358
|
|
|
|
|
|
|
# |
1359
|
|
|
|
|
|
|
# Output Variables: |
1360
|
|
|
|
|
|
|
# blksUsed: The number of blocks used by the file (including indexes) |
1361
|
|
|
|
|
|
|
# blocks: The list of data blocks allocated |
1362
|
|
|
|
|
|
|
# indexBlocks: The list of subindex blocks allocated |
1363
|
|
|
|
|
|
|
# storage: The storage type of the file |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
sub allocate_space |
1366
|
|
|
|
|
|
|
{ |
1367
|
2
|
|
|
2
|
|
5
|
my ($self, $bitmap) = @_; |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
# Decide which storage type this file requires: |
1370
|
2
|
|
|
|
|
7
|
my $dataRef = \$self->{data}; |
1371
|
|
|
|
|
|
|
|
1372
|
2
|
|
|
|
|
34
|
my @dataBlks = (1) x int((length($$dataRef) + 0x1FF) / 0x200); |
1373
|
2
|
|
|
|
|
4
|
my @subindexBlks; |
1374
|
|
|
|
|
|
|
my $storage; |
1375
|
|
|
|
|
|
|
|
1376
|
2
|
100
|
|
|
|
11
|
if (@dataBlks > 0x100) { |
|
|
50
|
|
|
|
|
|
1377
|
1
|
|
|
|
|
4
|
$storage = 3; # > 128KB = Tree |
1378
|
1
|
|
|
|
|
5
|
@subindexBlks = (1) x int((@dataBlks + 0xFF) / 0x100); |
1379
|
|
|
|
|
|
|
} elsif (@dataBlks > 1) { |
1380
|
1
|
|
|
|
|
3
|
$storage = 2; # 513 bytes - 128KB = Sapling |
1381
|
|
|
|
|
|
|
} else { |
1382
|
0
|
|
|
|
|
0
|
$storage = 1; # 0 - 512 bytes = Seedling |
1383
|
0
|
|
|
|
|
0
|
@dataBlks = (1); # Even empty files need one block |
1384
|
|
|
|
|
|
|
} |
1385
|
|
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
# Calculate how many blocks the file will occupy: |
1387
|
2
|
|
|
|
|
5
|
my $blksUsed = scalar @dataBlks; |
1388
|
|
|
|
|
|
|
|
1389
|
2
|
50
|
|
|
|
8
|
if ($storage > 1) { |
1390
|
2
|
|
|
|
|
74
|
$blksUsed += 1 + @subindexBlks; # Add in the index blocks |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
# Check to see if this file is sparse: |
1393
|
2
|
|
|
|
|
4
|
my $index = 0; |
1394
|
2
|
|
|
|
|
4
|
foreach (@dataBlks) { |
1395
|
266
|
100
|
|
|
|
5130
|
unless (substr($$dataRef, $index, 0x200) =~ /[^\0]/) { |
1396
|
257
|
|
|
|
|
275
|
$_ = 0; # This data block doesn't need to be allocated |
1397
|
257
|
|
|
|
|
330
|
--$blksUsed; |
1398
|
|
|
|
|
|
|
} # end unless this block contains data |
1399
|
266
|
|
|
|
|
885
|
$index += 0x200; # 512 bytes per data block |
1400
|
|
|
|
|
|
|
} # end foreach data block |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
# For tree files, figure out which subindex blocks are needed: |
1403
|
2
|
100
|
|
|
|
19
|
if (@subindexBlks) { |
1404
|
1
|
|
|
|
|
110
|
my @blocks = @dataBlks; |
1405
|
1
|
|
|
|
|
4
|
foreach my $ib (@subindexBlks) { |
1406
|
2
|
50
|
|
|
|
11
|
unless (grep { $_ } splice @blocks, 0, 0x100) { |
|
259
|
|
|
|
|
282
|
|
1407
|
0
|
|
|
|
|
0
|
$ib = 0; # This subindex block doesn't need to be allocated |
1408
|
0
|
|
|
|
|
0
|
--$blksUsed; |
1409
|
|
|
|
|
|
|
} # end unless this subindex block is required |
1410
|
|
|
|
|
|
|
} # end foreach subindex block |
1411
|
|
|
|
|
|
|
} # end if tree file |
1412
|
|
|
|
|
|
|
} # end if not seedling |
1413
|
|
|
|
|
|
|
|
1414
|
2
|
|
|
|
|
7
|
$self->{storage} = $storage; |
1415
|
2
|
|
|
|
|
6
|
$self->{blksUsed} = $blksUsed; |
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
# Now allocate the blocks and record them: |
1418
|
2
|
50
|
|
|
|
13
|
my @blocks = $bitmap->get_blocks($blksUsed) |
1419
|
|
|
|
|
|
|
or a2_croak("Not enough free space"); |
1420
|
|
|
|
|
|
|
|
1421
|
2
|
|
|
|
|
5
|
$self->{block} = $blocks[0]; |
1422
|
|
|
|
|
|
|
|
1423
|
2
|
50
|
|
|
|
7
|
shift @blocks if $storage > 1; # Remove index block from list |
1424
|
|
|
|
|
|
|
|
1425
|
2
|
|
|
|
|
6
|
foreach (@subindexBlks, @dataBlks) { |
1426
|
|
|
|
|
|
|
# If this block needs to be allocated, assign it one of our blocks: |
1427
|
268
|
100
|
|
|
|
460
|
$_ = shift @blocks if $_; |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
|
1430
|
2
|
100
|
|
|
|
8
|
if ($storage == 3) { |
1431
|
1
|
|
|
|
|
5
|
$self->{indexBlocks} = \@subindexBlks; |
1432
|
|
|
|
|
|
|
} else { |
1433
|
1
|
|
|
|
|
2
|
delete $self->{indexBlocks}; # Just in case |
1434
|
|
|
|
|
|
|
} |
1435
|
|
|
|
|
|
|
|
1436
|
2
|
|
|
|
|
88
|
$self->{blocks} = \@dataBlks; |
1437
|
|
|
|
|
|
|
} # end AppleII::ProDOS::File::allocate_space |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1440
|
|
|
|
|
|
|
# Return the file's contents as text: |
1441
|
|
|
|
|
|
|
# |
1442
|
|
|
|
|
|
|
# Returns: |
1443
|
|
|
|
|
|
|
# The file's contents with hi bits stripped and CRs converted to \n |
1444
|
|
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
sub as_text |
1446
|
|
|
|
|
|
|
{ |
1447
|
9
|
|
|
9
|
|
9511
|
my $self = shift; |
1448
|
9
|
|
|
|
|
84
|
my $data = $self->{data}; |
1449
|
9
|
|
|
|
|
8496
|
$data =~ tr/\x0D\x8D\x80-\xFF/\n\n\x00-\x7F/; |
1450
|
9
|
|
|
|
|
1373
|
$data; |
1451
|
|
|
|
|
|
|
} # end AppleII::ProDOS::File::as_text |
1452
|
|
|
|
|
|
|
|
1453
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1454
|
|
|
|
|
|
|
# Write the file to disk: |
1455
|
|
|
|
|
|
|
# |
1456
|
|
|
|
|
|
|
# You must have already called allocate_space. |
1457
|
|
|
|
|
|
|
# |
1458
|
|
|
|
|
|
|
# Input: |
1459
|
|
|
|
|
|
|
# disk: The disk to write to |
1460
|
|
|
|
|
|
|
# |
1461
|
|
|
|
|
|
|
# Input Variables: |
1462
|
|
|
|
|
|
|
# blocks: The list of data blocks allocated |
1463
|
|
|
|
|
|
|
# indexBlocks: The list of subindex blocks allocated |
1464
|
|
|
|
|
|
|
# |
1465
|
|
|
|
|
|
|
# Output Variables: |
1466
|
|
|
|
|
|
|
# indexBlocks: The number of subindex blocks needed |
1467
|
|
|
|
|
|
|
|
1468
|
|
|
|
|
|
|
sub write_disk |
1469
|
|
|
|
|
|
|
{ |
1470
|
2
|
|
|
2
|
|
4
|
my ($self, $disk) = @_; |
1471
|
|
|
|
|
|
|
|
1472
|
2
|
|
|
|
|
21
|
$disk->write_blocks($self->{blocks}, $self->{'data'}, "\0"); |
1473
|
|
|
|
|
|
|
|
1474
|
2
|
|
|
|
|
7
|
my $storage = $self->{storage}; |
1475
|
2
|
100
|
|
|
|
13
|
if ($storage == 2) { |
|
|
50
|
|
|
|
|
|
1476
|
1
|
|
|
|
|
6
|
my $index = AppleII::ProDOS::Index->new($disk, |
1477
|
1
|
|
|
|
|
1
|
@{$self}{qw(block blocks)}); |
1478
|
1
|
|
|
|
|
4
|
$index->write_disk; |
1479
|
|
|
|
|
|
|
} elsif ($storage == 3) { |
1480
|
1
|
|
|
|
|
12
|
my $index = |
1481
|
1
|
|
|
|
|
4
|
AppleII::ProDOS::Index->new($disk, @{$self}{qw(block indexBlocks)}); |
1482
|
1
|
|
|
|
|
6
|
$index->write_disk; |
1483
|
1
|
|
|
|
|
3
|
my @blocks = @{$self->{blocks}}; |
|
1
|
|
|
|
|
23
|
|
1484
|
1
|
|
|
|
|
2
|
my $block; |
1485
|
1
|
|
|
|
|
4
|
foreach $block (@{$self->{indexBlocks}}) { |
|
1
|
|
|
|
|
4
|
|
1486
|
2
|
50
|
|
|
|
9
|
if ($block) { |
1487
|
2
|
|
|
|
|
26
|
$index = AppleII::ProDOS::Index->new($disk, $block, |
1488
|
|
|
|
|
|
|
[splice(@blocks,0,0x100)]); |
1489
|
2
|
|
|
|
|
18
|
$index->write_disk; |
1490
|
|
|
|
|
|
|
} else { |
1491
|
0
|
|
|
|
|
0
|
splice(@blocks,0,0x100); |
1492
|
|
|
|
|
|
|
} # end else sparse index block is not actually allocated |
1493
|
|
|
|
|
|
|
} # end for each subindex block |
1494
|
1
|
|
|
|
|
3
|
$self->{indexBlocks} = scalar @{$self->{indexBlocks}}; |
|
1
|
|
|
|
|
6
|
|
1495
|
|
|
|
|
|
|
} # end elsif tree file |
1496
|
|
|
|
|
|
|
|
1497
|
2
|
|
|
|
|
16
|
delete $self->{blocks}; |
1498
|
|
|
|
|
|
|
} # end AppleII::ProDOS::File::write_disk |
1499
|
|
|
|
|
|
|
|
1500
|
|
|
|
|
|
|
#===================================================================== |
1501
|
|
|
|
|
|
|
package AppleII::ProDOS::Index; |
1502
|
|
|
|
|
|
|
# |
1503
|
|
|
|
|
|
|
# Member Variables: |
1504
|
|
|
|
|
|
|
# block: The block number of the index block |
1505
|
|
|
|
|
|
|
# blocks: The list of blocks pointed to by this index block |
1506
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
1507
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1508
|
|
|
|
|
|
|
|
1509
|
2
|
|
|
2
|
|
15
|
use integer; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
16
|
|
1510
|
2
|
|
|
2
|
|
49
|
use bytes; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
10
|
|
1511
|
2
|
|
|
2
|
|
44
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
56
|
|
1512
|
2
|
|
|
2
|
|
15
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1034
|
|
1513
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
our @ISA = 'AppleII::ProDOS::Members'; |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
my %in_fields = ( |
1517
|
|
|
|
|
|
|
blocks => undef, |
1518
|
|
|
|
|
|
|
); |
1519
|
|
|
|
|
|
|
|
1520
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1521
|
|
|
|
|
|
|
# Constructor for creating a new index block: |
1522
|
|
|
|
|
|
|
# |
1523
|
|
|
|
|
|
|
# Input: |
1524
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
1525
|
|
|
|
|
|
|
# block: The block number of the index block |
1526
|
|
|
|
|
|
|
# blocks: The list of blocks that are pointed to by this block |
1527
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
sub new |
1529
|
|
|
|
|
|
|
{ |
1530
|
4
|
|
|
4
|
|
8
|
my ($type, $disk, $block, $blocks) = @_; |
1531
|
4
|
|
|
|
|
22
|
my $self = { |
1532
|
|
|
|
|
|
|
disk => $disk, |
1533
|
|
|
|
|
|
|
block => $block, |
1534
|
|
|
|
|
|
|
blocks => $blocks, |
1535
|
|
|
|
|
|
|
_permitted => \%in_fields, |
1536
|
|
|
|
|
|
|
}; |
1537
|
|
|
|
|
|
|
|
1538
|
4
|
|
|
|
|
19
|
bless $self, $type; |
1539
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Index::new |
1540
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1542
|
|
|
|
|
|
|
# Constructor for reading an existing index block: |
1543
|
|
|
|
|
|
|
# |
1544
|
|
|
|
|
|
|
# Input: |
1545
|
|
|
|
|
|
|
# disk: An AppleII::Disk |
1546
|
|
|
|
|
|
|
# block: The block number to read |
1547
|
|
|
|
|
|
|
# count: The number of blocks that are pointed to by this block |
1548
|
|
|
|
|
|
|
# (optional; default is 256) |
1549
|
|
|
|
|
|
|
|
1550
|
|
|
|
|
|
|
sub open |
1551
|
|
|
|
|
|
|
{ |
1552
|
14
|
|
|
14
|
|
32
|
my ($type, $disk, $block, $count) = @_; |
1553
|
14
|
|
|
|
|
26
|
my $self = {}; |
1554
|
14
|
|
|
|
|
36
|
$self->{disk} = $disk; |
1555
|
14
|
|
|
|
|
26
|
$self->{block} = $block; |
1556
|
14
|
|
|
|
|
30
|
$self->{'_permitted'} = \%in_fields; |
1557
|
|
|
|
|
|
|
|
1558
|
14
|
|
|
|
|
44
|
bless $self, $type; |
1559
|
14
|
|
|
|
|
38
|
$self->read_disk($count); |
1560
|
14
|
|
|
|
|
39
|
$self; |
1561
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Index::open |
1562
|
|
|
|
|
|
|
|
1563
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1564
|
|
|
|
|
|
|
# Read contents of index block from disk: |
1565
|
|
|
|
|
|
|
# |
1566
|
|
|
|
|
|
|
# Input: |
1567
|
|
|
|
|
|
|
# count: |
1568
|
|
|
|
|
|
|
# The number of blocks that are pointed to by this block |
1569
|
|
|
|
|
|
|
# (optional; default is 256) |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
sub read_disk |
1572
|
|
|
|
|
|
|
{ |
1573
|
14
|
|
|
14
|
|
25
|
my ($self, $count) = @_; |
1574
|
14
|
100
|
|
|
|
37
|
$count = 0x100 unless $count; |
1575
|
14
|
|
|
|
|
82
|
my @dataLo = unpack('C*',$self->{disk}->read_block($self->{block})); |
1576
|
14
|
|
|
|
|
667
|
my @dataHi = splice @dataLo, 0x100; |
1577
|
14
|
|
|
|
|
117
|
my @blocks; |
1578
|
|
|
|
|
|
|
|
1579
|
14
|
|
|
|
|
41
|
while (--$count >= 0) { |
1580
|
1809
|
|
|
|
|
9606
|
push @blocks, shift(@dataLo) + 0x100 * shift(@dataHi); |
1581
|
|
|
|
|
|
|
} |
1582
|
|
|
|
|
|
|
|
1583
|
14
|
|
|
|
|
133
|
$self->{blocks} = \@blocks; |
1584
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Index::read_disk |
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1587
|
|
|
|
|
|
|
# Write index block to disk: |
1588
|
|
|
|
|
|
|
|
1589
|
|
|
|
|
|
|
sub write_disk |
1590
|
|
|
|
|
|
|
{ |
1591
|
4
|
|
|
4
|
|
9
|
my $self = shift; |
1592
|
4
|
|
|
|
|
9
|
my $disk = $self->{disk}; |
1593
|
|
|
|
|
|
|
|
1594
|
4
|
|
|
|
|
4
|
my ($dataLo, $dataHi); |
1595
|
4
|
|
|
|
|
6
|
$dataLo = $dataHi = pack('v*',@{$self->{blocks}}); |
|
4
|
|
|
|
|
24
|
|
1596
|
4
|
|
|
|
|
164
|
$dataLo =~ s/(.)./$1/gs; # Keep just the low byte |
1597
|
4
|
|
|
|
|
143
|
$dataHi =~ s/.(.)/$1/gs; # Keep just the high byte |
1598
|
|
|
|
|
|
|
|
1599
|
4
|
|
|
|
|
15
|
$disk->write_block($self->{block}, |
1600
|
|
|
|
|
|
|
AppleII::Disk::pad_block($dataLo,"\0",0x100) . $dataHi, |
1601
|
|
|
|
|
|
|
"\0"); |
1602
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Index::write_disk |
1603
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
#===================================================================== |
1605
|
|
|
|
|
|
|
package AppleII::ProDOS::Members; |
1606
|
|
|
|
|
|
|
# |
1607
|
|
|
|
|
|
|
# Provides access functions for member variables. This class is based |
1608
|
|
|
|
|
|
|
# on code from Tom Christiansen's FMTEYEWTK on OO Perl vs. C++. |
1609
|
|
|
|
|
|
|
# |
1610
|
|
|
|
|
|
|
# Only those member variables whose names are listed in the _permitted |
1611
|
|
|
|
|
|
|
# hash may be accessed. |
1612
|
|
|
|
|
|
|
# |
1613
|
|
|
|
|
|
|
# The value in the _permitted hash is used for validating the new |
1614
|
|
|
|
|
|
|
# value of a field. The possible values are: |
1615
|
|
|
|
|
|
|
# undef No changes allowed (read-only) |
1616
|
|
|
|
|
|
|
# CODE ref Call CODE with our @_. It returns true if OK. |
1617
|
|
|
|
|
|
|
# scalar New value must be an integer between 0 and _permitted |
1618
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
1619
|
|
|
|
|
|
|
|
1620
|
2
|
|
|
2
|
|
12
|
use Carp; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
971
|
|
1621
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
sub AUTOLOAD |
1623
|
|
|
|
|
|
|
{ |
1624
|
150
|
|
|
150
|
|
2114
|
my $self = $_[0]; |
1625
|
150
|
50
|
|
|
|
367
|
my $type = ref($self) or croak("$self is not an object"); |
1626
|
150
|
|
|
|
|
276
|
my $name = our $AUTOLOAD; |
1627
|
150
|
|
|
|
|
761
|
$name =~ s/.*://; # strip fully-qualified portion |
1628
|
150
|
|
|
|
|
405
|
my $field = $name; |
1629
|
150
|
|
|
|
|
324
|
$field =~ s/_([a-z])/\u$1/g; # squash underlines into mixed case |
1630
|
150
|
50
|
|
|
|
475
|
unless (exists $self->{'_permitted'}{$field}) { |
1631
|
|
|
|
|
|
|
# Ignore special methods like DESTROY: |
1632
|
0
|
0
|
|
|
|
0
|
return undef if $name =~ /^[A-Z]+$/; |
1633
|
0
|
|
|
|
|
0
|
croak("Can't access `$name' field in object of class $type"); |
1634
|
|
|
|
|
|
|
} |
1635
|
150
|
100
|
|
|
|
322
|
if ($#_) { |
1636
|
2
|
|
|
|
|
5
|
my $check = $self->{'_permitted'}{$field}; |
1637
|
2
|
|
|
|
|
4
|
my $ok; |
1638
|
2
|
50
|
|
|
|
7
|
if (ref($check) eq 'CODE') { |
|
|
0
|
|
|
|
|
|
1639
|
2
|
|
|
|
|
4
|
$ok = &$check; # Pass our @_ to validator |
1640
|
|
|
|
|
|
|
} elsif ($check) { |
1641
|
0
|
|
0
|
|
|
0
|
$ok = ($_[1] =~ /^[0-9]+$/ and $_[1] >= 0 and $_[1] <= $check); |
1642
|
|
|
|
|
|
|
} else { |
1643
|
0
|
|
|
|
|
0
|
croak("Field `$name' of class $type is read-only"); |
1644
|
|
|
|
|
|
|
} |
1645
|
2
|
50
|
|
|
|
9
|
return $self->{$field} = $_[1] if $ok; |
1646
|
0
|
|
|
|
|
0
|
croak("Invalid value `$_[1]' for field `$name' of class $type"); |
1647
|
|
|
|
|
|
|
} |
1648
|
148
|
|
|
|
|
1051
|
return $self->{$field}; |
1649
|
|
|
|
|
|
|
} # end AppleII::ProDOS::Members::AUTOLOAD |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
#===================================================================== |
1652
|
|
|
|
|
|
|
# Package Return Value: |
1653
|
|
|
|
|
|
|
|
1654
|
|
|
|
|
|
|
1; |
1655
|
|
|
|
|
|
|
|
1656
|
|
|
|
|
|
|
__END__ |