DOS33-perl/a2tools.pl

1074 lines
26 KiB
Perl

#!/usr/bin/perl -w
#
# a2tools - utilities for transferring data between Unix and Apple II
# DOS 3.3 disk images.
#
# Copyright (C) 1998, 2001 Terry Kyriacopoulos
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author's e-mail address: terryk@echo-on.net
#
# -------------------------------------------------------------------
#
# Modified to be more portable: Unix specifics are marked as such.
# ANSI-C is assumed, code is now acceptable to C++ as well,
# type definitions are straighetend up, unused variables are removed,
# casts are added when required by C++.
#
# Paul Schlyter, 2001-03-20, pausch@saaf.se
#
# -------------------------------------------------------------------
#
# Improvements to accomodate MS-DOS have been made:
#
# - code fixed to work properly on a 16-bit platform
# - conditional compilation used to select OS-specific code
# automatically
# - user interface is now more OS-specific:
# - argv[0] command selection for UNIX, argv[1] for DOS
# - stdin/stdout forbidden on binary data in DOS
# - optional source/destination pathnames for in/out commands
# - improved documentation
# Terry Kyriacopoulos, April 8, 2001 terryk@echo-on.net
#
# Ported to Perl 20190226 Leeland Heins
#
use strict;
use File::Basename;
my $SEEK_SET = 0;
my $FILENAME_LENGTH = 30;
my $NUM_TRACKS = 35;
my $NUM_SECTORS = 16;
my $BYTES_PER_SECTOR = 256;
my $EOF = undef;
my $HelpText = "a2tools - utility for transferring files from/to Apple II .dsk images
Copyright (C) 1998, 2001 Terry Kyriacopoulos
Perl port 20190226 Leeland Heins
Usage:
a2 dir <dsk_image>
a2 out [-r] <dsk_image> <a2_name> [<dest_file>]
a2 in [-r] <type>[.<hex_addr>] <dsk_image> <a2_name> [<source>]
a2 del <dsk_image> <a2_name>
-r (raw mode): Suppress all filetype-dependent processing
and copy everything as-is.
<type>: one of t,i,a,b,s,r,x,y (do not use -)
<hex_addr>: base address in hex, for type B (binary)
\n
Quotes may be used around names with spaces, use \\\"
to include a quote in the name.\n";
# Apple Integer and AppleSoft BASIC tokens.
my @Integer_tokens = (
" HIMEM:", "", " _ ", ":",
" LOAD ", " SAVE ", " CON ", " RUN ",
" RUN ", " DEL ", ",", " NEW ",
" CLR ", " AUTO ", ",", " MAN ",
" HIMEM:", " LOMEM:", "+", "-",
"*", "/", "=", "#",
">=", ">", "<=", "<>",
"<", " AND ", " OR ", " MOD ",
" ^ ", "+", "(", ",",
" THEN ", " THEN ", ",", ",",
"\"", "\"", "(", "!",
"!", "(", " PEEK ", " RND ",
" SGN ", " ABS ", " PDL ", " RNDX ",
"(", "+", "-", " NOT ",
"(", "=", "#", " LEN(",
" ASC(", " SCRN(", ",", "(",
"\$", "\$", "(", ",",
",", ";", ";", ";",
",", ",", ",", " TEXT ",
" GR ", " CALL ", " DIM ", " DIM ",
" TAB ", " END ", " INPUT ", " INPUT ",
" INPUT ", " FOR ", "=", " TO ",
" STEP ", " NEXT ", ",", " RETURN ",
" GOSUB ", " REM ", " LET ", " GOTO ",
" IF ", " PRINT ", " PRINT ", " PRINT ",
" POKE ", ",", " COLOR=", " PLOT ",
",", " HLIN ", ",", " AT ",
" VLIN ", ",", " AT ", " VTAB ",
"=", "=", ")", ")",
" LIST ", ",", " LIST ", " POP ",
" NODSP ", " NODSP ", " NOTRACE ", " DSP ",
" DSP ", " TRACE ", " PR#", " IN#"
);
my @Applesoft_tokens = (
" END ", " FOR ", " NEXT ", " DATA ",
" INPUT ", " DEL ", " DIM ", " READ ",
" GR ", " TEXT ", " PR#", " IN#",
" CALL ", " PLOT ", " HLIN ", " VLIN ",
" HGR2 ", " HGR ", " HCOLOR=", " HPLOT ",
" DRAW ", " XDRAW ", " HTAB ", " HOME ",
" ROT=", " SCALE=", " SHLOAD ", " TRACE ",
" NOTRACE ", " NORMAL ", " INVERSE ", " FLASH ",
" COLOR=", " POP ", " VTAB ", " HIMEM:",
" LOMEM:", " ONERR ", " RESUME ", " RECALL ",
" STORE ", " SPEED=", " LET ", " GOTO ",
" RUN ", " IF ", " RESTORE ", " & ",
" GOSUB ", " RETURN ", " REM ", " STOP ",
" ON ", " WAIT ", " LOAD ", " SAVE ",
" DEF ", " POKE ", " PRINT ", " CONT ",
" LIST ", " CLEAR ", " GET ", " NEW ",
" TAB(", " TO ", " FN ", " SPC(",
" THEN ", " AT ", " NOT ", " STEP ",
" + ", " - ", " * ", " / ",
" ^ ", " AND ", " OR ", " > ",
" = ", " < ", " SGN ", " INT ",
" ABS ", " USR ", " FRE ", " SCRN(",
" PDL ", " POS ", " SQR ", " RND ",
" LOG ", " EXP ", " COS ", " SIN ",
" TAN ", " ATN ", " PEEK ", " LEN ",
" STR\$ ", " VAL ", " ASC ", " CHR\$ ",
" LEFT\$ ", " RIGHT\$ ", " MID\$ ", " ",
" SYNTAX ", " RETURN WITHOUT GOSUB ",
" OUT OF DATA ", " ILLEGAL QUANTITY ",
" OVERFLOW ", " OUT OF MEMORY ",
" UNDEF'D STATEMENT ", " BAD SUBSCRIPT ",
" REDIM'D ARRAY ", " DIVISION BY ZERO ",
" ILLEGAL DIRECT ", " TYPE MISMATCH ",
" STRING TOO LONG ", " FORMULA TOO COMPLEX ",
" CAN'T CONTINUE ", " UNDEF'D FUNCTION ",
" ERROR \a", "", "", ""
);
my $FILETYPE_T = 0x00;
my $FILETYPE_I = 0x01;
my $FILETYPE_A = 0x02;
my $FILETYPE_B = 0x04;
my $FILETYPE_S = 0x08;
my $FILETYPE_R = 0x10;
my $FILETYPE_X = 0x20;
my $FILETYPE_Y = 0x40;
# X - "new A", Y - "new B"
my $MAX_HOPS = 560;
my $VTOC_CHK_NO = 6;
my @vtoc_chk_offset = (0x03, 0x27, 0x34, 0x35, 0x36, 0x37);
my @vtoc_chk_value = (0x03, 0x7a, 0x23, 0x10, 0x00, 0x01);
my $from_file;
my $to_file;
my $image_fp;
my $extfilename;
my $extfilemode;
my @padded_name;
my @dir_entry_data;
my $vtocbuffer = '';
my $begun;
my $baseaddress;
my $rawmode;
my $filetype;
my $new_sectors;
my $dir_entry_pos;
sub quit {
my ($exitcode, $exitmsg) = @_;
print STDERR sprintf("%s", $exitmsg);
if ($image_fp) {
close($image_fp);
}
if ($from_file) {
close($from_file);
}
if ($to_file) {
close($to_file);
}
exit $exitcode;
}
sub seek_sect {
my ($track, $sector) = @_;
if ($track >= $NUM_TRACKS || $sector >= $NUM_SECTORS) {
quit(1, "seek on .dsk out of range trk=$track sec=$sector.\n");
}
return seek($image_fp, ($track * $NUM_SECTORS + $sector) * $BYTES_PER_SECTOR, $SEEK_SET);
}
sub read_sect {
my ($track, $sector, $buffer) = @_;
seek_sect($track, $sector);
my $rv = read($image_fp, $buffer, $BYTES_PER_SECTOR);
$_[2] = $buffer;
return $rv;
}
sub write_sect {
my ($track, $sector, $buffer) = @_;
seek_sect($track, $sector);
print $image_fp $buffer;
}
sub dump_sect {
my ($buf) = @_;
print "BUFFER=\n";
my @bytes = unpack "C*", $buf;
my $i = 0;
foreach my $byte (@bytes) {
printf("%02x ", $byte);
$i++;
print "\n" if !($i % 16);
}
print "\n";
}
sub dir_do {
my ($what_to_do) = @_;
my $buffer;
my $cur_trk;
my $cur_sec;
my $i;
my $found;
my $hop;
$hop = 0;
$found = 0;
my @vtoc_bytes = unpack "C*", $vtocbuffer;
$cur_trk = $vtoc_bytes[1];
$cur_sec = $vtoc_bytes[2];
while (++$hop < $MAX_HOPS && !$found && ($cur_trk || $cur_sec)) {
read_sect($cur_trk, $cur_sec, $buffer);
my @bytes = unpack "C*", $buffer;
my $nxt_trk = $bytes[1];
my $nxt_sec = $bytes[2];
$i = 0x0b;
while ($i <= 0xdd && !($found = $what_to_do->(substr($buffer, $i, 35)))) {
$i += 35;
}
if ($found) {
$dir_entry_pos = ($cur_trk * $NUM_SECTORS + $cur_sec) * $BYTES_PER_SECTOR + $i;
}
$cur_trk = $nxt_trk;
$cur_sec = $nxt_sec;
}
if ($hop >= $MAX_HOPS) {
quit(2, "\n***Corrupted directory\n\n");
}
return $found;
}
sub dir_find_name {
my ($buffer) = @_;
my @bytes = unpack "C*", $buffer;
if ($bytes[0] == 0xff || $bytes[3] == 0) {
return 0;
}
for (my $j = 0; $j < $FILENAME_LENGTH; $j++) {
if ($padded_name[$j] != (($bytes[$j + 3]) & 0x7f)) {
return 0;
}
}
my $y = 0;
for (my $x = 0; $x < 35; $x++) {
$dir_entry_data[$y++] = $bytes[$x];
}
return 1;
}
sub dir_find_space {
my ($buffer) = @_;
my @bytes = unpack "C*", $buffer;
return ($bytes[0] == 0xff || $bytes[3] == 0);
}
sub dir_print_entry {
my ($buffer) = @_;
my $j;
my @bytes = unpack "C*", $buffer;
if ($bytes[0] != 0xff && $bytes[3] != 0) {
# entry is present
print " ";
if ($bytes[2] & 0x80) {
print "*";
} else {
print " ";
}
my $filet = ($bytes[2] & 0x7f);
if ($filet == $FILETYPE_T) {
print "T";
} elsif ($filet == $FILETYPE_I) {
print "I";
} elsif ($filet == $FILETYPE_A) {
print "A";
} elsif ($filet == $FILETYPE_B) {
print "B";
} elsif ($filet == $FILETYPE_S) {
print "S";
} elsif ($filet == $FILETYPE_R) {
print "R";
} elsif ($filet == $FILETYPE_X) {
print "X";
} elsif ($filet == $FILETYPE_Y) {
print "Y";
} else {
print "?";
}
print sprintf(" %03u ", $bytes[33] + $bytes[34] * $BYTES_PER_SECTOR);
for ($j = 3; $j < 33; $j++) {
print sprintf("%c", ($bytes[$j] & 0x7f));
}
print "\n";
}
return 0;
}
sub preproc {
my ($procmode) = @_;
# procmode: 0 - raw, 1 - text, 2 - binary
my $bytepos;
my $lengthspec_pos;
my $c;
my $sect_pos;
$sect_pos = 0;
if (!$begun) {
$begun = 1;
$bytepos = 0;
$c = getc($from_file);
if ($procmode == 2) {
print $image_fp ($baseaddress & 0xff);
print $image_fp ($baseaddress >> 8);
# we don't know the length now, so save the spot in the image
$lengthspec_pos = ftell($image_fp);
print $image_fp 0xff;
print $image_fp 0xff;
$sect_pos = 4;
}
}
while ($c != $EOF && $sect_pos < $BYTES_PER_SECTOR) {
if ($procmode == 1) {
if (($c & 0x7f) == '\n') {
$c = '\r';
}
$c |= 0x80;
}
print $image_fp $c;
$c = getc($from_file);
$sect_pos++;
$bytepos++;
}
while ($sect_pos++ < $BYTES_PER_SECTOR) {
print $image_fp 0x00;
}
if ($c == $EOF && $procmode == 2) {
# now we know the length
seek($image_fp, $lengthspec_pos, $SEEK_SET);
print $image_fp, ($bytepos & 0xff);
print $image_fp ($bytepos >> 8);
}
return ($c == $EOF);
}
sub new_sector {
my ($track, $sector) = @_;
# find a free sector, quit if no more
my $byteoffset;
my $bitmask;
my $lasttrack;
my $cur_track;
my $cur_sector;
my $direction;
my @vtoc_bytes = unpack "C*", $vtocbuffer;
# force sane values, in case vtoc contains garbage
if ($vtoc_bytes[0x31] == 1) {
$direction = 1;
} else {
$direction = -1;
}
$lasttrack = $vtoc_bytes[0x30] % 35;
$cur_track = $lasttrack;
$cur_sector = 15;
for (;;) {
$byteoffset = 0x39 + ($cur_track << 2) - ($cur_sector >> 3 & 1);
$bitmask = (1 << ($cur_sector & 0x07));
if ($vtoc_bytes[$byteoffset] & $bitmask) {
$vtoc_bytes[$byteoffset] &= 0xff ^ $bitmask;
last;
} elsif (!$cur_sector--) {
$cur_sector = 15;
$cur_track += $direction;
if ($cur_track >= $NUM_TRACKS) {
$cur_track = 17;
$direction = -1;
} elsif ($cur_track < 0) {
$cur_track = 18;
$direction = 1;
}
if ($cur_track == $lasttrack) {
quit(3, "Disk Full.\n");
}
}
}
$track = $cur_track;
$vtoc_bytes[0x30] = $cur_track;
$sector = $cur_sector;
$vtoc_bytes[0x31] = $direction % $BYTES_PER_SECTOR;
$new_sectors++;
$vtocbuffer = pack "C*", @vtoc_bytes;
}
sub free_sector {
my ($track, $sector) = @_;
my @vtoc_bytes = unpack "C*", $vtocbuffer;
$vtoc_bytes[0x39 + ($track << 2) - ($sector >> 3&1)] |= 1 << ($sector & 0x07);
$vtocbuffer = pack "C*", @vtoc_bytes;
}
sub postproc_B {
my $filelength = 0;
my $bytepos = 0;
my $sect_pos = 0;
if (!$begun) {
$begun = 1;
$bytepos = 0;
getc($image_fp); # Ignore 2 byte base address
getc($image_fp);
my $len_lo = ord(getc($image_fp));
my $len_hi = ord(getc($image_fp));
$filelength = ($len_hi << 8) + $len_lo;
$sect_pos = 4;
}
while ($bytepos < $filelength && $sect_pos < $BYTES_PER_SECTOR) {
print $to_file getc($image_fp);
$sect_pos++;
$bytepos++;
}
}
sub postproc_A {
my $bufstat;
my $tokens_left;
my $lastspot;
my @lineheader;
my $sect_pos = 0;
my $c;
if (!$begun) { # first sector, initialize
$begun = 1;
getc($image_fp); # ignore the length data, we use
getc($image_fp); # null line pointer as EOF
$sect_pos = 2;
$lastspot = 0x0801; # normal absolute beginning address
$tokens_left = 0;
$bufstat = 0;
}
while ($lastspot && $sect_pos < $BYTES_PER_SECTOR) {
if (!$tokens_left && !$bufstat) {
$bufstat = 4;
}
while ($bufstat > 0 && $sect_pos < $BYTES_PER_SECTOR) {
$lineheader[4 - $bufstat] = getc($image_fp);
$sect_pos++;
$bufstat--;
}
if (!$tokens_left && !$bufstat && ($lastspot = ord($lineheader[0]) + ord($lineheader[1]) * 0x100)) {
$tokens_left = 1;
printf $to_file "\n";
print $to_file sprintf(" %u ", ord($lineheader[2]) + ord($lineheader[3]) * 0x100);
}
while ($tokens_left && $lastspot && $sect_pos < $BYTES_PER_SECTOR) {
if (($tokens_left = $c = ord(getc($image_fp))) & 0x80) {
print $to_file sprintf("%s", $Applesoft_tokens[($c & 0x7f)]);
} elsif ($c) {
print $to_file sprintf("%c", $c);
}
$sect_pos++;
}
}
if (!$lastspot) {
print $to_file "\n\n";
}
}
sub postproc_I {
my $filelength;
my $bytepos;
my $bufstat;
my $inputmode;
my $quotemode;
my $varmode;
my @numbuf;
my $sect_pos;
my $c;
$sect_pos = 0;
if (!$begun) { # first sector, initialize
$begun = 1;
$filelength = getc($image_fp) + (getc($image_fp) * $BYTES_PER_SECTOR);
$sect_pos = 2;
$bytepos = $inputmode = $bufstat = $quotemode = $varmode = 0;
}
# inputmode: 0 - header, 1 - integer, 2 - tokens
# varmode: 1 means we are in the middle of an identifier
while ($bytepos < $filelength && $sect_pos < $BYTES_PER_SECTOR) {
if ($inputmode < 2 && !$bufstat) {
$bufstat = 3 - $inputmode;
}
while ($bufstat > 0 && $bytepos < $filelength && $sect_pos < $BYTES_PER_SECTOR) {
$numbuf[3 - $bufstat] = getc($image_fp);
$sect_pos++;
$bytepos++;
$bufstat--;
}
if (!$bufstat && $inputmode == 0) {
print $to_file "\n";
print $to_file sprintf("%5u ", $numbuf[1] + ($numbuf[2] * $BYTES_PER_SECTOR));
$inputmode = 2;
}
if (!$bufstat && $inputmode == 1) {
printf $to_file sprintf("%u", $numbuf[1] + ($numbuf[2] * $BYTES_PER_SECTOR));
$inputmode = 2;
}
while ($inputmode == 2 && $bytepos < $filelength && $sect_pos < $BYTES_PER_SECTOR) {
$c = getc($image_fp);
$sect_pos++;
$bytepos++;
# 0x28: open quote, 0x29: close quote, 0x5d: REM token
if ($c == 0x28 || $c == 0x5d) {
$quotemode = 1;
}
if ($c == 0x29) {
$quotemode = 0;
}
# Look for integer, unless in comment, string, or identifier
if (!$quotemode && !$varmode && $c >= 0xb0 && $c <= 0xb9) {
$inputmode = 1;
} else {
# Identifiers begin with letter, may contain digit
$varmode = ($c >= 0xc1 && $c <= 0xda) || (($c >= 0xb0 && $c <= 0xb9) && $varmode);
if ($c == 0x01) {
$inputmode = $quotemode = 0;
} else {
if ($c & 0x80) {
print $to_file sprintf("%c", ($c & 0x7f));
} else {
print $to_file sprintf("%s", $Integer_tokens[$c]);
}
}
}
}
}
if ($bytepos >= $filelength) {
print $to_file "\n\n";
}
}
sub postproc_T {
my $not_eof;
my $sect_pos;
my $c;
$sect_pos = 0;
if (!$begun) {
$begun = $not_eof = 1;
}
while ($not_eof && $sect_pos < $BYTES_PER_SECTOR && ($not_eof = $c = getc($image_fp))) {
$c &= 0x7f;
if ($c == '\r') {
$c = '\n';
}
print $to_file, $c;
$sect_pos++;
}
}
sub postproc_raw {
my $sect_pos;
for ($sect_pos = 0; $sect_pos < $BYTES_PER_SECTOR; $sect_pos++) {
print $to_file getc($image_fp);
}
}
sub a2ls {
my $trkmap;
my $i;
my $j;
my $free_sect = 0;
my @vtoc_bytes = unpack "C*", $vtocbuffer;
# count the free sectors
for ($i = 0x38; $i <= 0xc0; $i += 4) {
$trkmap = $vtoc_bytes[$i] * 256 + $vtoc_bytes[$i + 1];
for ($j = 0; $j < $NUM_SECTORS; $j++) {
$free_sect += (($trkmap & (1 << $j)) != 0);
}
}
print sprintf("\nDisk Volume %u, Free Blocks: %u\n\n", $vtoc_bytes[0x06], $free_sect);
dir_do(\&dir_print_entry);
print "\n";
}
sub a2rm {
my $listbuffer;
my $hop;
my $next_trk;
my $next_sec;
my $i;
if (!dir_do(\&dir_find_name)) {
quit(4, "File not found.\n");
}
$hop = 0;
$begun = 0;
$next_trk = $dir_entry_data[0];
$next_sec = $dir_entry_data[1];
seek($image_fp, $dir_entry_pos, $SEEK_SET);
print $image_fp 0xff; # mark as deleted
while (++$hop < $MAX_HOPS && ($next_trk || $next_sec)) {
read_sect($next_trk, $next_sec, $listbuffer);
my @list_bytes = unpack "C*", $listbuffer;
free_sector($next_trk, $next_sec);
$next_trk = $list_bytes[1];
$next_sec = $list_bytes[2];
for ($i = 0x0c; $i <= 0xfe; $i += 2) {
if ($list_bytes[$i] || $list_bytes[$i + 1]) {
free_sector($list_bytes[$i], $list_bytes[$i + 1]);
}
}
}
if ($hop >= $MAX_HOPS) {
quit(5, "Corrupted sector list\n\n");
}
write_sect(0x11, 0, $vtocbuffer);
}
sub a2out {
my $listbuffer;
my $hop;
my $next_trk;
my $next_sec;
my $i;
my $j;
my $postproc_function = '';
if (!dir_do(\&dir_find_name)) {
quit(6, "File not found.\n");
}
$hop = 0;
$begun = 0;
$next_trk = $dir_entry_data[0];
$next_sec = $dir_entry_data[1];
$filetype = $dir_entry_data[2] & 0x7f;
if ($filetype == $FILETYPE_T) {
$postproc_function = \&postproc_T;
} elsif ($filetype == $FILETYPE_B) {
$postproc_function = \&postproc_B;
} elsif ($filetype == $FILETYPE_A) {
$postproc_function = \&postproc_A;
} elsif ($filetype == $FILETYPE_I) {
$postproc_function = \&postproc_I;
} elsif (!$rawmode) {
quit(7, "File type supported in raw mode only.\n");
}
if ($rawmode) {
$postproc_function = \&postproc_raw;
}
$extfilemode = "w";
if ((! defined $to_file || ! $to_file) && !(open($to_file, ">$extfilename"))) {
print "Error writing $extfilename\n";
quit(9, "");
}
while (++$hop < $MAX_HOPS && ($next_trk || $next_sec)) {
read_sect($next_trk, $next_sec, $listbuffer);
my @list_bytes = unpack "C*", $listbuffer;
$next_trk = $list_bytes[1];
$next_sec = $list_bytes[2];
for ($i = 0x0c; $i <= 0xfe; $i += 2) {
if (!$list_bytes[$i] && !$list_bytes[$i + 1]) {
if ($filetype != $FILETYPE_T || !$rawmode) {
$next_trk = 0;
$next_sec = 0;
last;
} else {
for ($j = 0; $j < $BYTES_PER_SECTOR; $j++) {
print $to_file 0x00;
}
}
} else {
++$hop;
seek_sect($list_bytes[$i], $list_bytes[$i + 1]);
$postproc_function->();
}
}
}
if ($hop >= $MAX_HOPS) {
quit(10, "Corrupted sector list\n\n");
}
close($to_file);
}
sub a2in {
my $listbuffer;
my @databuffer;
my $i;
my $curlist_trk;
my $curlist_sec;
my $listentry_pos;
my $list_no;
my $curdata_trk;
my $curdata_sec;
my $procmode;
my $newlist_trk;
my $newlist_sec;
my $c;
$new_sectors = 0;
$list_no = 0;
$procmode = 0;
if (!$rawmode) {
if ($filetype == $FILETYPE_T) {
$procmode = 1;
} elsif ($filetype == $FILETYPE_B) {
$procmode = 2;
} else {
quit(11, "This type is supported only in raw mode.\n");
}
}
$extfilemode = "r";
if (!$from_file && !($from_file = open($extfilename, $extfilemode))) {
perror($extfilename);
quit(13, "");
}
if (dir_do(\&dir_find_name)) {
quit(14, "File exists.\n");
}
if (!dir_do(\&dir_find_space)) {
quit(15, "No space in directory.\n");
}
if ($padded_name[0] < 'A') {
quit(16, "Bad first filename character, must be >= 'A'.\n");
}
for ($i = 0; $i < $FILENAME_LENGTH; $i++) {
if ($padded_name[$i] == ',') {
quit(17, "Filename must not contain a comma.\n");
}
}
for ($i = 0; $i < $FILENAME_LENGTH; $i++) {
$dir_entry_data[$i + 3] = $padded_name[$i] | 0x80;
}
$dir_entry_data[2] = $filetype;
new_sector($curlist_trk, $curlist_sec);
$dir_entry_data[0] = $curlist_trk;
$dir_entry_data[1] = $curlist_sec;
my @list_bytes = ();
for ($i = 0; $i < $BYTES_PER_SECTOR; $i++) {
$list_bytes[$i] = 0;
}
$listentry_pos = 0;
for (;;) {
if (!$rawmode || $filetype != $FILETYPE_T) {
new_sector($curdata_trk, $curdata_sec);
$list_bytes[0x0c + ($listentry_pos << 1)] = $curdata_trk;
$list_bytes[0x0d + ($listentry_pos << 1)] = $curdata_sec;
seek_sect($curdata_trk, $curdata_sec);
if (preproc($procmode)) {
last;
}
} else {
# Check for all-zero sectors for sparse T file
for ($i = 0; $i < $BYTES_PER_SECTOR; $i++) {
$databuffer[$i] = 0;
}
$i = 0;
while (($c = getc($from_file)) != $EOF && $i < $BYTES_PER_SECTOR) {
$databuffer[$i++] = $c;
}
while ($i && !$databuffer[$i - 1]) {
$i--;
}
if (!$i) {
$list_bytes[0x0c + ($listentry_pos << 1)] = 0;
$list_bytes[0x0d + ($listentry_pos << 1)] = 0;
} else {
new_sector($curdata_trk, $curdata_sec);
$list_bytes[0x0c + ($listentry_pos << 1)] = $curdata_trk;
$list_bytes[0x0d + ($listentry_pos << 1)] = $curdata_sec;
write_sect($curdata_trk, $curdata_sec, \@databuffer);
}
if ($c == $EOF) {
last;
}
ungetc($c, $from_file);
}
if (++$listentry_pos >= 0x7a) {
new_sector($newlist_trk, $newlist_sec);
$list_bytes[1] = $newlist_trk;
$list_bytes[2] = $newlist_sec;
$listbuffer = pack "C*", @list_bytes;
write_sect($curlist_trk, $curlist_sec, $listbuffer);
$curlist_trk = $newlist_trk;
$curlist_sec = $newlist_sec;
for ($i = 0; $i < $BYTES_PER_SECTOR; $i++) {
$list_bytes[$i] = 0;
}
$listentry_pos = 0;
$list_bytes[5] = (++$list_no * 0x7a) & 0xff;
$list_bytes[6] = ($list_no * 0x7a) >> 8;
}
}
$list_bytes[1] = $list_bytes[2] = 0;
$listbuffer = pack "C*", @list_bytes;
write_sect($curlist_trk, $curlist_sec, $listbuffer);
write_sect(0x11, 0, $vtocbuffer);
$dir_entry_data[33] = $new_sectors & 0xff;
$dir_entry_data[34] = $new_sectors >> 8;
seek($image_fp, $dir_entry_pos, $SEEK_SET);
# writing ff first ensures directory is always in a safe state
print $image_fp 0xff;
for ($i = 1; $i < 35; $i++) {
print $image_fp $dir_entry_data[$i];
}
seek($image_fp, $dir_entry_pos, $SEEK_SET);
print $image_fp $dir_entry_data[$0];
close($from_file);
}
## MAIN
my $image_name;
my $a2_name;
my $basename;
my $typestr;
my $i;
my $bad_vtoc;
my $in_cmd;
my $rm_cmd;
my $ls_hlp;
my $in_hlp;
my $out_hlp;
my $rm_hlp;
my $command = '';
$baseaddress = 0x2000; # default, hi-res page 1
$rawmode = 0;
$begun = 0;
$extfilename = "";
$a2_name = "";
$image_name = "";
$basename = basename($0);
$basename =~ s/\.pl$//g;
if (defined $ARGV[0] && $ARGV[0] eq '-h') {
print $HelpText;
exit 1;
}
if ($basename eq 'a2ls') {
$image_name = shift;
if (! defined $image_name) {
quit(18, "Usage: a2ls <disk_image>\n");
} else {
$command = \&a2ls;
}
} elsif ($basename eq 'a2out') {
if (defined $ARGV[0] && $ARGV[0] eq "-r") {
$rawmode = 1;
shift;
}
$image_name = shift;
if (! defined $image_name) {
quit(19, "Usage: a2out [-r] <disk_image> <a2file> [<destination>]\n");
} else {
$a2_name = shift;
if (! defined $image_name) {
quit(19, "Usage: a2out [-r] <disk_image> <a2file> [<destination>]\n");
} else {
$extfilename = shift;
if (! defined $extfilename) {
$to_file = \*STDOUT;
}
$command = \&a2out;
}
}
} elsif ($basename eq 'a2in') {
if (defined $ARGV[0] && $ARGV[0] eq "-r") {
$rawmode = 1;
shift;
}
$typestr = shift;
if (! defined $typestr) {
quit(20, "Usage: a2in [-r] <type>[.<hex_addr>] <disk_image> <a2file> [<source>]\n");
}
$a2_name = shift;
if (! defined $a2_name) {
$extfilename = $a2_name;
} else {
$from_file = \*STDIN;
}
$image_name = shift;
if (! defined $image_name) {
quit(20, "Usage: a2in [-r] <type>[.<hex_addr>] <disk_image> <a2file> [<source>]\n");
} else {
if ($typestr =~ /^[Tt]/) {
$filetype = $FILETYPE_T;
} elsif ($typestr =~ /^[Ii]/) {
$filetype = $FILETYPE_I;
} elsif ($typestr =~ /^[Aa]/) {
$filetype = $FILETYPE_A;
} elsif ($typestr =~ /^[Bb]/) {
$filetype = $FILETYPE_B;
if ($typestr =~ /,([0-9a-fA-F]+)/) {
$baseaddress = hex(lc($1));
}
} elsif ($typestr =~ /^[Ss]/) {
$filetype = $FILETYPE_S;
} elsif ($typestr =~ /^[Rr]/) {
$filetype = $FILETYPE_R;
} elsif ($typestr =~ /^[Xx]/) {
$filetype = $FILETYPE_X;
} elsif ($typestr =~ /^[Yy]/) {
$filetype = $FILETYPE_Y;
} else {
quit(21, "<type>: one of t,i,a,b,s,r,x,y without -\n");
}
$command = \&a2in;
}
} elsif ($basename eq 'a2rm') {
$image_name = shift;
if (! defined $image_name) {
quit(24, "Usage: a2rm <disk_image> <a2file>\n");
} else {
$a2_name = shift;
if (! defined $a2_name) {
quit(24, "Usage: a2rm <disk_image> <a2file>\n");
} else {
$command = \&a2rm;
}
}
} else {
quit(25, "Invoke as a2ls, a2in, a2out, or a2rm.\n");
}
if (!open($image_fp, "<$image_name")) {
print "Error in $image_name\n";
quit(26, "");
}
# prepare source filename by padding blanks
my @a2_name_bytes = split //, $a2_name;
$i = 0;
while ($i < $FILENAME_LENGTH && $a2_name_bytes[$i]) {
$padded_name[$i] = ord($a2_name_bytes[$i]) & 0x7f;
$i++;
}
while ($i < $FILENAME_LENGTH) {
$padded_name[$i++] = ord(' ') & 0x7f;
}
# get VTOC and check validity
read_sect(0x11, 0, $vtocbuffer);
my @vtoc_bytes = unpack "C*", $vtocbuffer;
$bad_vtoc = 0;
for ($i = 0; $i < $VTOC_CHK_NO; $i++) {
$bad_vtoc |= ($vtoc_bytes[$vtoc_chk_offset[$i]] != $vtoc_chk_value[$i]);
}
if ($bad_vtoc) {
quit(27, "Not an Apple DOS 3.3 .dsk image.\n");
}
$command->();
close($image_fp);
exit 1;
1;