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