#!/usr/bin/perl -w # # dos33 version 0.1 # by Vince Weaver # Perl port 20190225 by Leeland Heins # use strict; use File::Basename; # For now hard-coded # Could be made dynamic if we want to be useful # On dos3.2 disks, or larger filesystems my $TRACKS_PER_DISK = 0x23; # 35 my $SECTORS_PER_TRACK = 0x10; # 16 my $BYTES_PER_SECTOR = 0x100; # 256 my $VTOC_TRACK = 0x11; # 17 my $VTOC_SECTOR = 0x00; # 0 # VTOC Values my $VTOC_CATALOG_T = 0x01; # 1 my $VTOC_CATALOG_S = 0x02; # 2 my $VTOC_DOS_RELEASE = 0x03; # 3 my $VTOC_DISK_VOLUME = 0x06; # 6 my $VTOC_MAX_TS_PAIRS = 0x27; # 39 my $VTOC_LAST_ALLOC_T = 0x30; # 48 my $VTOC_ALLOC_DIRECT = 0x31; # 49 my $VTOC_NUM_TRACKS = 0x34; # 52 my $VTOC_S_PER_TRACK = 0x35; # 53 my $VTOC_BYTES_PER_SL = 0x36; # 54 my $VTOC_BYTES_PER_SH = 0x37; # 55 my $VTOC_FREE_BITMAPS = 0x38; # 56 # CATALOG_VALUES my $CATALOG_NEXT_T = 0x01; # 1 my $CATALOG_NEXT_S = 0x02; # 2 my $CATALOG_FILE_LIST = 0x0b; # 11 my $CATALOG_ENTRY_SIZE = 0x23; # 35 # CATALOG ENTRY my $FILE_TS_LIST_T = 0x00; # 0 my $FILE_TS_LIST_S = 0x01; # 1 my $FILE_TYPE = 0x02; # 2 my $FILE_NAME = 0x03; # 3 my $FILE_SIZE_L = 0x21; # 33 my $FILE_SIZE_H = 0x22; # 34 my $FILE_NAME_SIZE = 0x1e; # 30 # TSL my $TSL_NEXT_TRACK = 0x01; # 1 my $TSL_NEXT_SECTOR = 0x02; # 2 my $TSL_OFFSET_L = 0x05; # 5 my $TSL_OFFSET_H = 0x06; # 6 my $TSL_LIST = 0x0c; # 12 my $TSL_ENTRY_SIZE = 0x02; # 2 my $TSL_MAX_NUMBER = 122; # 0x7a my $SEEK_SET = 0; my $VERSION = "0.1"; # Helper Subs sub TS_TO_INT { my ($x, $y) = @_; return (($x << 8) + $y); } sub DISK_OFFSET { my ($track, $sector) = @_; my $off = ((($track * $SECTORS_PER_TRACK) + $sector) * $BYTES_PER_SECTOR); return $off; } my $sector_buffer; my %ones_lookup = ( 0x00 => 0, # 0x0 = 0000 0 0x01 => 1, # 0x1 = 0001 1 0x02 => 1, # 0x2 = 0010 1 0x03 => 2, # 0x3 = 0011 2 0x04 => 1, # 0x4 = 0100 1 0x05 => 2, # 0x5 = 0101 2 0x06 => 2, # 0x6 = 0110 2 0x07 => 3, # 0x7 = 0111 3 0x08 => 1, # 0x8 = 1000 1 0x09 => 2, # 0x9 = 1001 2 0x0a => 2, # 0xA = 1010 2 0x0b => 3, # 0xB = 1011 3 0x0c => 2, # 0xC = 1100 2 0x0d => 3, # 0xd = 1101 3 0x0e => 3, # 0xe = 1110 3 0x0f => 4, # 0xf = 1111 4 ); sub get_high_byte { my ($value) = @_; return ($value >> 8) & 0xff; } sub get_low_byte { my ($value) = @_; return ($value & 0xff); } my $debug = 0; my $FILE_NORMAL = 0; my $FILE_DELETED = 1; sub dos33_file_type { my ($value) = @_; my $result = '?'; my $v2 = $value & 0x7f; if ($v2 == 0x00) { $result = 'T'; } elsif ($v2 == 0x01) { $result = 'I'; } elsif ($v2 == 0x02) { $result = 'A'; } elsif ($v2 == 0x04) { $result = 'B'; } elsif ($v2 == 0x08) { $result = 'S'; } elsif ($v2 == 0x10) { $result = 'R'; } elsif ($v2 == 0x20) { $result = 'N'; } elsif ($v2 == 0x40) { $result = 'L'; } else { $result = '?'; } return $result; } sub dos33_char_to_type { my ($type, $lock) = @_; my $result = 0x00; my $temp_type; # Covert to upper case $temp_type = uc($type); if ($temp_type eq 'T') { $result = 0x00; } elsif ($temp_type eq 'I') { $result = 0x01; } elsif ($temp_type eq 'A') { $result = 0x02; } elsif ($temp_type eq 'B') { $result = 0x04; } elsif ($temp_type eq 'S') { $result = 0x08; } elsif ($temp_type eq 'R') { $result = 0x10; } elsif ($temp_type eq 'N') { $result = 0x20; } elsif ($temp_type eq 'L') { $result = 0x40; } else { $result = 0x00; } if ($lock) { $result |= 0x80; } return $result; } # dos33 filenames have high bit set on ascii chars # and are padded with spaces sub dos33_filename_to_ascii { my ($dest, $src, $len) = @_; my @srcbytes = unpack "C*", $src; my @destbytes = (); my $l = 0; foreach my $byte (@srcbytes) { push @destbytes, $byte & 0x7f; $l++; last if $l > $len; } $dest = pack "C*", @destbytes; $_[0] = $dest; return $dest; } # Read VTOC into a buffer sub dos33_read_vtoc { my ($fd) = @_; # Seek to VTOC seek($fd, DISK_OFFSET($VTOC_TRACK, $VTOC_SECTOR), $SEEK_SET); # read in VTOC my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); if (! defined $result || $result < 0) { print STDERR "Error on I/O\n"; } return 0; } # Calculate available freespace sub dos33_free_space { my ($fd) = @_; my @bitmap = (); my $sectors_free = 0; # Read Vtoc dos33_read_vtoc($fd); # Unpack VTOC data. my @bytes = unpack "C*", $sector_buffer; for (my $i = 0; $i < $TRACKS_PER_DISK; $i++) { $bitmap[0] = $bytes[$VTOC_FREE_BITMAPS + ($i * 4)]; $bitmap[1] = $bytes[$VTOC_FREE_BITMAPS + ($i * 4) + 1]; $sectors_free += $ones_lookup{$bitmap[0] & 0x0f}; $sectors_free += $ones_lookup{($bitmap[0] >> 4) & 0x0f}; $sectors_free += $ones_lookup{$bitmap[1] & 0x0f}; $sectors_free += $ones_lookup{($bitmap[1] >> 4) & 0x0f}; } return $sectors_free * $BYTES_PER_SECTOR; } # Get a T/S value from a Catalog Sector sub dos33_get_catalog_ts { my ($fd) = @_; dos33_read_vtoc($fd); # Unpack VTOC data. my @bytes = unpack "C*", $sector_buffer; return TS_TO_INT($bytes[$VTOC_CATALOG_T], $bytes[$VTOC_CATALOG_S]); } # returns the next valid catalog entry # after the one passed in sub dos33_find_next_file { my ($fd, $catalog_tsf) = @_; my $catalog_file = $catalog_tsf >> 16; my $catalog_track = ($catalog_tsf >> 8) & 0xff; my $catalog_sector = ($catalog_tsf & 0xff); catalog_loop: # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); my @bytes = unpack "C*", $sector_buffer; my $i = $catalog_file; while ($i < 7) { my $file_track = $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)]; # 0xff means file deleted # 0x0 means empty if (($file_track != 0xff) && ($file_track != 0x00)) { return (($i << 16) + ($catalog_track << 8) + $catalog_sector); } $i++; } $catalog_track = $bytes[$CATALOG_NEXT_T]; $catalog_sector = $bytes[$CATALOG_NEXT_S]; if ($catalog_sector != 0) { $catalog_file = 0; goto catalog_loop; } if ($result < 0) { print STDERR "Error on I/O\n"; } return -1; } sub dos33_print_file_info { my ($fd, $catalog_tsf) = @_; my $temp_string; my $catalog_file = $catalog_tsf >> 16; my $catalog_track = ($catalog_tsf >> 8) & 0xff; my $catalog_sector = ($catalog_tsf & 0xff); # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); my @bytes = unpack "C*", $sector_buffer; # Print a * if the file locked if ($bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE] > 0x7f) { print "*"; } else { print " "; } # Print the file type printf("%s", dos33_file_type($bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE])); print " "; # Print the file size, stored in LO/HI printf("%.3i ", $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE + $FILE_SIZE_L)] + ($bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE + $FILE_SIZE_H)] << 8)); # Print filename. my $filenamestr = substr($sector_buffer, ($CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE + $FILE_NAME)), 30); dos33_filename_to_ascii($temp_string, $filenamestr, 30); print "$temp_string\n"; if ($result < 0) { print STDERR "Error on I/O\n"; } return 0; } # Checks if "filename" exists # returns entry/track/sector sub dos33_check_file_exists { my ($fd, $filename, $file_deleted) = @_; $filename =~ s/\s+$//g; # read the VTOC into buffer dos33_read_vtoc($fd); # Unpack VTOC data. my @vtoc_bytes = unpack "C*", $sector_buffer; # get the catalog track and sector from the VTOC my $catalog_track = $vtoc_bytes[$VTOC_CATALOG_T]; my $catalog_sector = $vtoc_bytes[$VTOC_CATALOG_S]; repeat_catalog: # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); my @bytes = unpack "C*", $sector_buffer; # scan all file entries in catalog sector for (my $i = 0; $i < 7; $i++) { my $file_track = $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)]; # 0xff means file deleted # 0x0 means empty if ($file_track != 0x00) { if ($file_track == 0xff) { my $filenamestr = substr($sector_buffer, ($CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE + $FILE_NAME)), 29); my $file_name = ''; dos33_filename_to_ascii($file_name, $filenamestr, 29); $file_name =~ s/\s+$//g; if ($file_deleted) { # return if we found the file if ($filename eq $file_name) { return (($i << 16) + ($catalog_track << 8) + $catalog_sector); } } } else { my $filenamestr = substr($sector_buffer, ($CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE +$FILE_NAME)), 30); my $file_name = ''; dos33_filename_to_ascii($file_name, $filenamestr, 30); $file_name =~ s/\s+$//g; # return if we found the file if ($filename eq $file_name) { return (($i << 16) + ($catalog_track << 8) + $catalog_sector); } } } } # point to next catalog track/sector $catalog_track = $bytes[$CATALOG_NEXT_T]; $catalog_sector = $bytes[$CATALOG_NEXT_S]; if ($catalog_sector != 0) { goto repeat_catalog; } if ($result < 0) { print STDERR "Error on I/O\n"; } return -1; } # could be replaced by "find leading 1" instruction # if available sub find_first_one { my ($byte) = @_; my $i = 0; if ($byte == 0) { return -1; } while (($byte & (0x01 << $i)) == 0) { $i++; } return $i; } sub dos33_free_sector { my ($fd, $track, $sector) = @_; my $vtoc; # Seek to VTOC seek($fd, DISK_OFFSET($VTOC_TRACK, $VTOC_SECTOR), $SEEK_SET); # read in VTOC my $result = read($fd, $vtoc, $BYTES_PER_SECTOR); # Unpack VTOC data. my @bytes = unpack "C*", $vtoc; # each bitmap is 32 bits. With 16-sector tracks only first 16 used # 1 indicates free, 0 indicates used if ($sector < 8) { $bytes[$VTOC_FREE_BITMAPS + ($track * 4) + 1] |= (0x01 << $sector); } else { $bytes[$VTOC_FREE_BITMAPS + ($track * 4)] |= (0x01 << ($sector - 8)); } # Re-pack VTOC data. $vtoc = pack "C*", @bytes; # write modified VTOC back out seek($fd, DISK_OFFSET($VTOC_TRACK, $VTOC_SECTOR), $SEEK_SET); print $fd $vtoc; return 0; } sub dos33_allocate_sector { my ($fd) = @_; my $found_track = 0; my $found_sector = 0; my @bitmap; my $start_track; my $track_dir; my $byte; dos33_read_vtoc($fd); # Unpack VTOC data. my @bytes = unpack "C*", $sector_buffer; # Originally used to keep things near center of disk for speed # We can use to avoid fragmentation possibly $start_track = $bytes[$VTOC_LAST_ALLOC_T] % $TRACKS_PER_DISK; $track_dir = $bytes[$VTOC_ALLOC_DIRECT]; if ($track_dir == 255) { $track_dir = -1; } if (($track_dir != 1) && ($track_dir != -1)) { print STDERR sprintf("ERROR! Invalid track dir %i\n", $track_dir); } if ((($start_track > $VTOC_TRACK) && ($track_dir != 1)) || (($start_track < $VTOC_TRACK) && ($track_dir != -1))) { print STDERR sprintf("Warning! Non-optimal values for track dir t=%i d=%i!\n", $start_track, $track_dir); } my $i = $start_track; do { for ($byte = 1; $byte > -1; $byte--) { $bitmap[$byte] = $bytes[$VTOC_FREE_BITMAPS + ($i * 4) + $byte]; if ($bitmap[$byte] != 0x00) { $found_sector = find_first_one($bitmap[$byte]); $found_track = $i; # clear bit indicating in use $bytes[$VTOC_FREE_BITMAPS + ($i * 4) + $byte] &= ~(0x01 << $found_sector); $found_sector += (8*(1 - $byte)); goto found_one; } } # Move to next track, handling overflows $i += $track_dir; if ($i < 0) { $i = $VTOC_TRACK; $track_dir = 1; } if ($i >= $TRACKS_PER_DISK) { $i = $VTOC_TRACK; $track_dir = -1; } } while ($i != $start_track); print STDERR "No room left!\n"; return -1; found_one: # store new track/direction info $bytes[$VTOC_LAST_ALLOC_T] = $found_track; if ($found_track > $VTOC_TRACK) { $bytes[$VTOC_ALLOC_DIRECT] = 1; } else { $bytes[$VTOC_ALLOC_DIRECT] = -1; } # Re-pack VTOC data. $sector_buffer = pack "C*", @bytes; # Seek to VTOC seek($fd, DISK_OFFSET($VTOC_TRACK, $VTOC_SECTOR), $SEEK_SET); # Write out VTOC print $fd $sector_buffer; return (($found_track << 8) + $found_sector); } my $track = 0; my $sector = 0; # FIXME: currently assume sector is going to be 0 sub dos33_force_allocate_sector { my ($fd) = @_; my $found_track = 0; my $found_sector = 0; #unsigned char bitmap[4]; my $i; my $start_track; #, track_dir, byte; my $result; my $so; dos33_read_vtoc($fd); # Unpack VTOC data. my @bytes = unpack "C*", $sector_buffer; # Originally used to keep things near center of disk for speed # We can use to avoid fragmentation possibly # $start_track = $bytes[$VTOC_LAST_ALLOC_T] % $TRACKS_PER_DISK; # $track_dir = $bytes[$VTOC_ALLOC_DIRECT]; $start_track = $track; $i = $start_track; $so = !(!$sector / 8); $found_sector = $sector % 8; # FIXME: check if free #$bitmap[$so] = $bytes[$VTOC_FREE_BITMAPS + ($i * 4) + $so]; # clear bit indicating in use $bytes[$VTOC_FREE_BITMAPS + ($i * 4) + $so] &= ~(0x01 << $found_sector); $found_sector += (8 * (1 - $so)); $found_track = $i; printf("VMW: want %d/%d, found %d/%d\n", $track, $sector, $found_track, $found_sector) if $debug; $sector++; if ($sector > 15) { $sector = 0; $track++; } # print STDERR "No room for raw-write!\n"; # return -1; #found_one: # store new track/direction info #$bytes[$VTOC_LAST_ALLOC_T] = $found_track; # # $bytes[$VTOC_ALLOC_DIRECT] = 1; # else $bytes[$VTOC_ALLOC_DIRECT] = -1; # Re-pack VTOC data. $sector_buffer = pack "C*", @bytes; # Seek to VTOC seek($fd, DISK_OFFSET($VTOC_TRACK, $VTOC_SECTOR), $SEEK_SET); # Write out VTOC print $fd $sector_buffer; printf("raw: T=%d S=%d\n", $found_track, $found_sector) if $debug; return (($found_track << 8) + $found_sector); } my $ERROR_INVALID_FILENAME = 1; my $ERROR_FILE_NOT_FOUND = 2; my $ERROR_NO_SPACE = 3; my $ERROR_IMAGE_NOT_FOUND = 4; my $ERROR_CATALOG_FULL = 5; my $ADD_RAW = 0; my $ADD_BINARY = 1; # creates file apple_filename on the image from local file filename # returns ?? sub dos33_add_file { my ($fd, $dos_type, $file_type, $address, $length, $filename, $apple_filename) = @_; my $free_space; my $file_size; my $needed_sectors; my $size_in_sectors = 0; my $initial_ts_list = 0; my $ts_list = 0; my $data_ts; my $bytes_read = 0; my $old_ts_list; my $catalog_track; my $catalog_sector; my $sectors_used = 0; my $input_fd; my $result; my $first_write = 1; if ($apple_filename !~ /^[A-Z]/) { print STDERR "Error! First char of filename must be ASCII 64 or above!\n"; return $ERROR_INVALID_FILENAME; } # Check for comma in filename if ($apple_filename =~ /,/) { print STDERR "Error! Cannot have , in a filename!\n"; return $ERROR_INVALID_FILENAME; } # FIXME # check type # and sanity check a/b filesize is set properly # Determine size of file to upload my @file_info = stat $filename; if (! @file_info) { print STDERR "Error! $filename not found!\n", $filename; return $ERROR_FILE_NOT_FOUND; } $file_size = $file_info[7]; print "Filesize: $file_size\n" if $debug; if ($file_type == $ADD_BINARY) { print "Adding 4 bytes for size/offset\n" if $debug; if ($length == 0) { $length = $file_size; } $file_size += 4; } # We need to round up to nearest sector size # Add an extra sector for the T/S list # Then add extra sector for a T/S list every 122*256 bytes (~31k) $needed_sectors = ($file_size / $BYTES_PER_SECTOR) + # round sectors (($file_size % $BYTES_PER_SECTOR) != 0) + # tail if needed 1 + # first T/S list ($file_size / (122 * $BYTES_PER_SECTOR)); # extra t/s lists # Get free space on device $free_space = dos33_free_space($fd); # Check for free space if ($needed_sectors * $BYTES_PER_SECTOR > $free_space) { print STDERR sprintf("Error! Not enough free space on disk image (need %d have %d)\n", ($needed_sectors * $BYTES_PER_SECTOR), $free_space); return $ERROR_NO_SPACE; } # plus one because we need a sector for the tail $size_in_sectors = ($file_size / $BYTES_PER_SECTOR) + (($file_size % $BYTES_PER_SECTOR) != 0); printf("Need to allocate %i data sectors\n", $size_in_sectors) if $debug; printf("Need to allocate %i total sectors\n", $needed_sectors) if $debug; my $ifh; # Open the local file if (!open($input_fd, "<$filename")) { print STDERR sprintf("Error! could not open %s\n", $filename); return $ERROR_IMAGE_NOT_FOUND; } my $i = 0; while ($i < $size_in_sectors) { # Create new T/S list if necessary if ($i % $TSL_MAX_NUMBER == 0) { $old_ts_list = $ts_list; # allocate a sector for the new list $ts_list = dos33_allocate_sector($fd); $sectors_used++; if ($ts_list < 0) { return -1; } # Initialize sector data. my @bytes = (); # clear the t/s sector for (my $x = 0; $x < $BYTES_PER_SECTOR; $x++) { $bytes[$x] = 0; } # Re-pack tslist data. $sector_buffer = pack "C*", @bytes; seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); print $fd $sector_buffer; if ($i == 0) { $initial_ts_list = $ts_list; } else { # we aren't the first t/s list so do special stuff # load in the old t/s list seek($fd, DISK_OFFSET(get_high_byte($old_ts_list), get_low_byte($old_ts_list)), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack tslist sector data. my @bytes = unpack "C*", $sector_buffer; # point from old ts list to new one we just made $bytes[$TSL_NEXT_TRACK] = get_high_byte($ts_list); $bytes[$TSL_NEXT_SECTOR] = get_low_byte($ts_list); # set offset into file $bytes[$TSL_OFFSET_H] = get_high_byte(($i - 122) * 256); $bytes[$TSL_OFFSET_L] = get_low_byte(($i - 122) * 256); # Re-pack tslist sector data. $sector_buffer = pack "C*", @bytes; # write out the old t/s list with updated info seek($fd, DISK_OFFSET(get_high_byte($old_ts_list), get_low_byte($old_ts_list)), $SEEK_SET); print $fd $sector_buffer; } } # Allocate a sector $data_ts = dos33_allocate_sector($fd); $sectors_used++; if ($data_ts < 0) { return -1; } # clear sector my @bytes = (); for (my $x = 0; $x < $BYTES_PER_SECTOR; $x++) { $bytes[$x] = 0; } # read from input if (($first_write) && ($file_type == $ADD_BINARY)) { $first_write = 0; $bytes[0] = $address & 0xff; $bytes[1] = ($address >> 8) & 0xff; $bytes[2] = ($length) & 0xff; $bytes[3] = (($length) >> 8) & 0xff; my $buf; $bytes_read = read($input_fd, $buf, ($BYTES_PER_SECTOR - 4)); $sector_buffer .= $buf; my @bytes2 = unpack "C*", $buf; foreach my $byte (@bytes2) { push @bytes, $byte; } $bytes_read += 4; } else { $bytes_read = read($input_fd, $sector_buffer, $BYTES_PER_SECTOR); @bytes = unpack "C*", $sector_buffer; } $first_write = 0; if ($bytes_read < 0) { print STDERR "Error reading bytes!\n"; } # Re-pack sector data. $sector_buffer = pack "C*", @bytes; # write to disk image seek($fd, DISK_OFFSET(($data_ts >> 8) & 0xff, $data_ts & 0xff), $SEEK_SET); print $fd $sector_buffer; printf("Writing %i bytes to %i/%i\n", $bytes_read, ($data_ts >> 8) & 0xff, $data_ts & 0xff) if $debug; # add to T/s table # read in t/s list seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); my @ts_bytes = unpack "C*", $sector_buffer; # point to new data sector $ts_bytes[(($i % $TSL_MAX_NUMBER) * 2) + $TSL_LIST] = ($data_ts >> 8) & 0xff; $ts_bytes[(($i % $TSL_MAX_NUMBER) * 2) + $TSL_LIST + 1] = ($data_ts & 0xff); # Re-pack sector data. $sector_buffer = pack "C*", @ts_bytes; # write t/s list back out seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); print $fd $sector_buffer; $i++; } # Add new file to Catalog # read in vtoc dos33_read_vtoc($fd); # Unpack VTOC data. my @vtoc_bytes = unpack "C*", $sector_buffer; $catalog_track = $vtoc_bytes[$VTOC_CATALOG_T]; $catalog_sector = $vtoc_bytes[$VTOC_CATALOG_S]; continue_parsing_catalog: # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; # Find empty directory entry $i = 0; while ($i < 7) { # for undelete purposes might want to skip 0xff # (deleted) files first and only use if no room if (($bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] == 0xff) || ($bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] == 0x00)) { goto got_a_dentry; } $i++; } if (($catalog_track == 0x11) && ($catalog_sector == 1)) { # in theory can only have 105 files # if full, we have no recourse! # can we allocate new catalog sectors # and point to them?? print STDERR "Error! No more room for files!\n"; return $ERROR_CATALOG_FULL; } $catalog_track = $bytes[$CATALOG_NEXT_T]; $catalog_sector = $bytes[$CATALOG_NEXT_S]; goto continue_parsing_catalog; got_a_dentry: # printf("Adding file at entry %i of catalog 0x%x:0x%x\n", $i, $catalog_track, $catalog_sector); # Point entry to initial t/s list $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] = ($initial_ts_list >> 8) & 0xff; $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + 1] = ($initial_ts_list & 0xff); # set file type $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_TYPE] = dos33_char_to_type($dos_type, 0); # printf("Pointing T/S to %x/%x\n", ($initial_ts_list >> 8) & 0xff, $initial_ts_list & 0xff); # copy over filename for (my $x = 0; $x < length($apple_filename); $x++) { $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(substr($apple_filename, $x, 1)) ^ 0x80; } # pad out the filename with spaces for (my $x = length($apple_filename); $x < $FILE_NAME_SIZE; $x++) { $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(' ') ^ 0x80; } # fill in filesize in sectors $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_SIZE_L] = $sectors_used & 0xff; $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_SIZE_H] = ($sectors_used >> 8) & 0xff; # Re-pack catalog sector data. $sector_buffer = pack "C*", @bytes; # write out catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } # Create raw file on disk starting at track/sector # returns ?? sub dos33_raw_file { my ($fd, $dos_type, $track, $sector, $filename, $apple_filename) = @_; my $free_space; my $file_size; my $needed_sectors; my $size_in_sectors = 0; my $initial_ts_list = 0; my $ts_list = 0; my $data_ts; my $bytes_read = 0; my $old_ts_list; my $catalog_track; my $catalog_sector; my $sectors_used = 0; my $input_fd; my $result; if ($apple_filename !~ /^[A-Z]/) { print STDERR "Error! First char of filename must be ASCII 64 or above!\n"; return $ERROR_INVALID_FILENAME; } # Check for comma in filename if ($apple_filename =~ /,/) { print STDERR "Error! Cannot have , in a filename!\n"; return $ERROR_INVALID_FILENAME; } # FIXME # check type # and sanity check a/b filesize is set properly # Determine size of file to upload my @file_info = stat $filename; if (! @file_info) { print STDERR "Error! $filename not found!\n"; return $ERROR_FILE_NOT_FOUND; } $file_size = $file_info[7]; print "Filesize: $file_size\n" if $debug; # We need to round up to nearest sector size # Add an extra sector for the T/S list # Then add extra sector for a T/S list every 122*256 bytes (~31k) $needed_sectors = ($file_size / $BYTES_PER_SECTOR) + # round sectors (($file_size % $BYTES_PER_SECTOR) != 0) + # tail if needed 1 + # first T/S list ($file_size / (122 * $BYTES_PER_SECTOR)); # extra t/s lists # Get free space on device $free_space = dos33_free_space($fd); # Check for free space if ($needed_sectors * $BYTES_PER_SECTOR > $free_space) { print STDERR sprintf("Error! Not enough free space on disk image (need %d have %d)\n", ($needed_sectors * $BYTES_PER_SECTOR), $free_space); return $ERROR_NO_SPACE; } # plus one because we need a sector for the tail $size_in_sectors = ($file_size / $BYTES_PER_SECTOR) + (($file_size % $BYTES_PER_SECTOR) != 0); print "Need to allocate $size_in_sectors data sectors\n" if $debug; print "Need to allocate $needed_sectors total sectors\n" if $debug; # Open the local file if (!open($input_fd, "<$filename")) { print STDERR "Error! could not open $filename\n"; return $ERROR_IMAGE_NOT_FOUND; } my $i = 0; while ($i < $size_in_sectors) { # Create new T/S list if necessary if ($i % $TSL_MAX_NUMBER == 0) { $old_ts_list = $ts_list; # allocate a sector for the new list $ts_list = dos33_allocate_sector($fd); $sectors_used++; if ($ts_list < 0) { return -1; } # clear the t/s sector my @bytes = (); for (my $x = 0; $x < $BYTES_PER_SECTOR; $x++) { $bytes[$x] = 0; } # Re-pack sector data. $sector_buffer = pack "C*", @bytes; seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); print $fd $sector_buffer; if ($i == 0) { $initial_ts_list = $ts_list; } else { # we aren't the first t/s list so do special stuff # load in the old t/s list seek($fd, DISK_OFFSET(get_high_byte($old_ts_list), get_low_byte($old_ts_list)), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack tslist sector data. my @bytes = unpack "C*", $sector_buffer; # point from old ts list to new one we just made $bytes[$TSL_NEXT_TRACK] = get_high_byte($ts_list); $bytes[$TSL_NEXT_SECTOR] = get_low_byte($ts_list); # set offset into file $bytes[$TSL_OFFSET_H] = get_high_byte(($i - 122) * 256); $bytes[$TSL_OFFSET_L] = get_low_byte(($i - 122) * 256); # Re-pack sector data. $sector_buffer = pack "C*", @bytes; # write out the old t/s list with updated info seek($fd, DISK_OFFSET(get_high_byte($old_ts_list), get_low_byte($old_ts_list)), $SEEK_SET); print $fd $sector_buffer; } } # force-allocate a sector # VMW $data_ts = dos33_force_allocate_sector($fd); $sectors_used++; if ($data_ts < 0) { return -1; } # clear sector my @bytes = (); for (my $x = 0; $x < $BYTES_PER_SECTOR; $x++) { $bytes[$x] = 0; } $bytes_read = read($input_fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack tslist sector data. @bytes = unpack "C*", $sector_buffer; if ($bytes_read < 0) { print STDERR "Error reading bytes!\n"; } # Re-pack sector data. $sector_buffer = pack "C*", @bytes; # write to disk image seek($fd, DISK_OFFSET(($data_ts >> 8) & 0xff, $data_ts & 0xff), $SEEK_SET); print $fd $sector_buffer; printf("Writing %i bytes to %i/%i\n", $bytes_read, (($data_ts >> 8) & 0xff), ($data_ts & 0xff)) if $debug; # add to T/s table # read in t/s list seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack tslist sector data. @bytes = unpack "C*", $sector_buffer; # point to new data sector $bytes[(($i % $TSL_MAX_NUMBER) * 2) + $TSL_LIST] = ($data_ts >> 8) & 0xff; $bytes[(($i % $TSL_MAX_NUMBER) * 2) + $TSL_LIST + 1] = ($data_ts & 0xff); # Re-pack sector data. $sector_buffer = pack "C*", @bytes; # write t/s list back out seek($fd, DISK_OFFSET(($ts_list >> 8) & 0xff, $ts_list & 0xff), $SEEK_SET); print $fd $sector_buffer; $i++; } # Add new file to Catalog # read in vtoc dos33_read_vtoc($fd); # Unpack VTOC data. my @vtoc_bytes = unpack "C*", $sector_buffer; $catalog_track = $vtoc_bytes[$VTOC_CATALOG_T]; $catalog_sector = $vtoc_bytes[$VTOC_CATALOG_S]; continue_parsing_catalog: # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; # Find empty directory entry $i = 0; while ($i < 7) { # for undelete purposes might want to skip 0xff # (deleted) files first and only use if no room if (($bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] == 0xff) || ($bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] == 0x00)) { goto got_a_dentry; } $i++; } if (($catalog_track == 0x11) && ($catalog_sector == 1)) { # in theory can only have 105 files # if full, we have no recourse! # can we allocate new catalog sectors # and point to them?? print STDERR "Error! No more room for files!\n"; return $ERROR_CATALOG_FULL; } $catalog_track = $bytes[$CATALOG_NEXT_T]; $catalog_sector = $bytes[$CATALOG_NEXT_S]; goto continue_parsing_catalog; got_a_dentry: # printf("Adding file at entry %i of catalog 0x%x:0x%x\n", $i, $catalog_track, $catalog_sector); # Point entry to initial t/s list $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE)] = ($initial_ts_list >> 8) & 0xff; $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + 1] = ($initial_ts_list & 0xff); # set file type $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_TYPE] = dos33_char_to_type($dos_type, 0); # printf("Pointing T/S to %x/%x\n", ($initial_ts_list >> 8) & 0xff, $initial_ts_list & 0xff); # copy over filename for (my $x = 0; $x < length($apple_filename); $x++) { $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(substr($apple_filename, $x, 1)) | 0x80; } # pad out the filename with spaces for (my $x = length($apple_filename); $x < $FILE_NAME_SIZE; $x++) { $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(' ') | 0x80; } # fill in filesize in sectors $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_SIZE_L] = $sectors_used & 0xff; $bytes[$CATALOG_FILE_LIST + ($i * $CATALOG_ENTRY_SIZE) + $FILE_SIZE_H] = ($sectors_used >> 8) & 0xff; # Re-pack sector data. $sector_buffer = pack "C*", @bytes; # write out catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } # load a file. fts=entry/track/sector sub dos33_load_file { my ($fd, $fts, $filename) = @_; #print "filename=$filename\n"; my $output_fd; my $file_size = -1; my $data_t; my $data_s; my $data_sector; my $tsl_pointer = 0; my $output_pointer = 0; # FIXME!Warn if overwriting file! if (!open($output_fd, ">$filename")) { print STDERR "Error! could not open $filename for local save\n"; return -1; } chmod 0666, $filename; my $catalog_file = $fts >> 16; my $catalog_track = ($fts >> 8) & 0xff; my $catalog_sector = ($fts & 0xff); # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; my $tsl_track = $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TS_LIST_T]; my $tsl_sector = $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TS_LIST_S]; my $file_type = dos33_file_type($bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE]); if ($file_type eq 'T') { $file_size = 0; } # printf("file_type: %s\n", $file_type); keep_saving: # Read in TSL Sector my $tsl_buf; seek($fd, DISK_OFFSET($tsl_track, $tsl_sector), $SEEK_SET); $result = read($fd, $tsl_buf, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @tsl_bytes = unpack "C*", $tsl_buf; $tsl_pointer = 0; # check each track/sector pair in the list while ($tsl_pointer < $TSL_MAX_NUMBER) { # get the t/s value #printf("data_t offset = %d\n", ($TSL_LIST + ($tsl_pointer * $TSL_ENTRY_SIZE))); #printf("data_s offset = %d\n", ($TSL_LIST + ($tsl_pointer * $TSL_ENTRY_SIZE) + 1)); $data_t = $tsl_bytes[$TSL_LIST + ($tsl_pointer * $TSL_ENTRY_SIZE)]; $data_s = $tsl_bytes[$TSL_LIST + ($tsl_pointer * $TSL_ENTRY_SIZE) + 1]; if (($data_s == 0) && ($data_t == 0)) { # empty last; } else { seek($fd, DISK_OFFSET($data_t, $data_s), $SEEK_SET); $result = read($fd, $data_sector, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @data_bytes = unpack "C*", $data_sector; # some file formats have the size in the first sector # so cheat and get real file size from file itself if ($output_pointer == 0) { if ($file_type eq 'A' || $file_type eq 'I') { $file_size = $data_bytes[0] + ($data_bytes[1] << 8) + 2; } elsif ($file_type eq 'B') { $file_size = $data_bytes[2] + ($data_bytes[3] << 8) + 4; } else { $file_size = -1; } } # Re-pack sector data. $data_sector = pack "C*", @data_bytes; # write the block read in out to the output file seek($output_fd, $output_pointer * $BYTES_PER_SECTOR, $SEEK_SET); print $output_fd $data_sector; if ($file_type eq 'T') { $file_size += length($data_sector); } } $output_pointer++; $tsl_pointer++; } # finished with TSL sector, see if we have another $tsl_track = $tsl_bytes[$TSL_NEXT_TRACK]; $tsl_sector = $tsl_bytes[$TSL_NEXT_SECTOR]; # printf("Next track/sector=%d/%d op=%d\n", $tsl_track, $tsl_sector, ($output_pointer * $BYTES_PER_SECTOR)); if (($tsl_track == 0) && ($tsl_sector == 0)) { } else { goto keep_saving; } # Correct the file size if ($file_size >= 0) { # print "Truncating file size to $file_size\n"; $result = truncate $output_fd, $file_size; } if ($result < 0) { print STDERR "Error on I/O\n"; } return 0; } # lock a file. fts=entry/track/sector sub dos33_lock_file { my ($fd, $fts, $lock) = @_; my $catalog_file = $fts >>16; my $catalog_track = ($fts >> 8) & 0xff; my $catalog_sector = ($fts & 0xff); # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; my $file_type = $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE]; if ($lock) { $file_type |= 0x80; } else { $file_type &= 0x7f; } $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE] = $file_type; # Re-pack catalog sector data. $sector_buffer = pack "C*", @bytes; # write back modified catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } # rename a file. fts=entry/track/sector # FIXME: can we rename a locked file? # FIXME: validate the new filename is valid sub dos33_rename_file { my ($fd, $fts, $new_name) = @_; my $catalog_file = $fts >> 16; my $catalog_track = ($fts >> 8) & 0xff; my $catalog_sector = ($fts & 0xff); # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; # copy over filename for (my $x = 0; $x < length($new_name); $x++) { $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(substr($new_name, $x, 1)) | 0x80; } # pad out the filename with spaces for (my $x = length($new_name); $x < $FILE_NAME_SIZE; $x++) { $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_NAME + $x] = ord(' ') | 0x80; } # Re-pack catalog sector data. $sector_buffer = pack "C*", @bytes; # write back modified catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } # undelete a file. fts=entry/track/sector # FIXME: validate the new filename is valid sub dos33_undelete_file { my ($fd, $fts, $new_name) = @_; my $catalog_file = $fts >> 16; my $catalog_track = ($fts>>8) & 0xff; my $catalog_sector = ($fts & 0xff); # Read in Catalog Sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; # get the stored track value, and put it back # FIXME: should walk file to see if T/s valild # by setting the track value to FF which indicates deleted file $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE)] = $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_NAME + 29]; # restore file name if possible my $replacement_char = 0xa0; if (length($new_name) > 29) { $replacement_char = ord(substr($new_name, 29, 1)) | 0x80; } $bytes[$CATALOG_FILE_LIST + ($catalog_file * $CATALOG_ENTRY_SIZE) + $FILE_NAME + 29] = $replacement_char; # Re-pack catalog sector data. $sector_buffer = pack "C*", @bytes; # write back modified catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } sub dos33_delete_file { my ($fd, $fsl) = @_; # unpack file/track/sector info my $catalog_entry = $fsl >> 16; my $catalog_track = ($fsl >> 8) & 0xff; my $catalog_sector = ($fsl & 0xff); # Load in the catalog table for the file seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @bytes = unpack "C*", $sector_buffer; my $file_type = $bytes[$CATALOG_FILE_LIST + ($catalog_entry * $CATALOG_ENTRY_SIZE) + $FILE_TYPE]; if ($file_type & 0x80) { print STDERR "File is locked! Unlock before deleting!\n"; exit(1); } # get pointer to t/s list my $ts_track = $bytes[$CATALOG_FILE_LIST + $catalog_entry*$CATALOG_ENTRY_SIZE + $FILE_TS_LIST_T]; my $ts_sector = $bytes[$CATALOG_FILE_LIST + $catalog_entry * $CATALOG_ENTRY_SIZE + $FILE_TS_LIST_S]; keep_deleting: # load in the t/s list info seek($fd, DISK_OFFSET($ts_track, $ts_sector), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @ts_bytes = unpack "C*", $sector_buffer; # Free each sector listed by t/s list for (my $i = 0; $i < $TSL_MAX_NUMBER; $i++) { # If t/s = 0/0 then no need to clear if (($ts_bytes[$TSL_LIST + 2 * $i] == 0) && ($ts_bytes[$TSL_LIST + 2 * $i + 1] == 0)) { } else { dos33_free_sector($fd, $ts_bytes[$TSL_LIST + 2 * $i], $ts_bytes[$TSL_LIST + 2 * $i + 1]); } } # free the t/s list dos33_free_sector($fd, $ts_track, $ts_sector); # Point to next t/s list $ts_track = $ts_bytes[$TSL_NEXT_TRACK]; $ts_sector = $ts_bytes[$TSL_NEXT_SECTOR]; # If more tsl lists, keep looping if (($ts_track == 0x0) && ($ts_sector == 0x0)) { } else { goto keep_deleting; } # Erase file from catalog entry # First reload proper catalog sector seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. my @cat_bytes = unpack "C*", $sector_buffer; # save track as last char of name, for undelete purposes $cat_bytes[$CATALOG_FILE_LIST + ($catalog_entry * $CATALOG_ENTRY_SIZE) + ($FILE_NAME + $FILE_NAME_SIZE - 1)] = $cat_bytes[$CATALOG_FILE_LIST + ($catalog_entry * $CATALOG_ENTRY_SIZE)]; # Actually delete the file # by setting the track value to FF which indicates deleted file $cat_bytes[$CATALOG_FILE_LIST + ($catalog_entry * $CATALOG_ENTRY_SIZE)] = 0xff; # Re-pack catalog sector data. $sector_buffer = pack "C*", @cat_bytes; # Re-seek to catalog position and write out changes seek($fd, DISK_OFFSET($catalog_track, $catalog_sector), $SEEK_SET); print $fd $sector_buffer; return 0; } sub dump_sector { # Unpack sector data. my @bytes = unpack "C*", $sector_buffer; for (my $i = 0; $i < 16; $i++) { printf("\$%02x : ", $i * 16); for (my $j = 0; $j < 16; $j++) { printf("%02x ", $bytes[$i * 16 + $j]); } print "\n"; } return 0; } sub dos33_dump { my ($fd) = @_; my $file; my $ts_t; my $ts_s; my $track; my $sector; my $deleted = 0; my $temp_string; my $tslist; # Read Track 1 Sector 9 seek($fd, DISK_OFFSET(1, 9), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack sector data. my @bytes = unpack "C*", $sector_buffer; print "Finding name of startup file, Track 1 Sector 9 offset \$75\n"; dump_sector(); print "Startup Filename: "; for (my $i = 0; $i < 30; $i++) { printf("%c", $bytes[0x75 + $i] & 0x7f); } print "\n"; dos33_read_vtoc($fd); # Unpack VTOC sector data. @bytes = unpack "C*", $sector_buffer; print "\nVTOC Sector:\n"; dump_sector(); print "\n\n"; print "VTOC INFORMATION:\n"; my $catalog_t = $bytes[$VTOC_CATALOG_T]; my $catalog_s = $bytes[$VTOC_CATALOG_S]; printf(" First Catalog = %02x/%02x\n", $catalog_t, $catalog_s); printf(" DOS RELEASE = 3.%i\n", $bytes[$VTOC_DOS_RELEASE]); printf(" DISK VOLUME = %i\n", $bytes[$VTOC_DISK_VOLUME]); my $ts_total = $bytes[$VTOC_MAX_TS_PAIRS]; printf(" T/S pairs that will fit in T/S List = %i\n", $ts_total); printf(" Last track where sectors were allocated = \$%02x\n", $bytes[$VTOC_LAST_ALLOC_T]); printf(" Direction of track allocation = %i\n", $bytes[$VTOC_ALLOC_DIRECT]); my $num_tracks = $bytes[$VTOC_NUM_TRACKS]; printf(" Number of tracks per disk = %i\n", $num_tracks); printf(" Number of sectors per track = %i\n", $bytes[$VTOC_S_PER_TRACK]); printf(" Number of bytes per sector = %i\n", ($bytes[$VTOC_BYTES_PER_SH] << 8) + $bytes[$VTOC_BYTES_PER_SL]); print "\nFree sector bitmap:\n"; print "Track FEDCBA98 76543210\n"; for (my $trk = 0; $trk < $num_tracks; $trk++) { printf(" \$%02x:", $trk); for (my $sec = 0; $sec < 8; $sec++) { if (($bytes[$VTOC_FREE_BITMAPS + ($trk * 4)] << $sec) & 0x80) { print "."; } else { print "U"; } } print " "; for (my $sec = 0; $sec < 8; $sec++) { if (($bytes[$VTOC_FREE_BITMAPS + ($trk * 4) + 1] << $sec) & 0x80) { print "."; } else { print "U"; } } print "\n"; } repeat_catalog: printf("\nCatalog Sector \$%02x/\$%02x\n", $catalog_t, $catalog_s); seek($fd, DISK_OFFSET($catalog_t, $catalog_s), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. @bytes = unpack "C*", $sector_buffer; dump_sector(); for ($file = 0; $file < 7; $file++) { #print "\n\n"; print "\n"; $ts_t = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_TS_LIST_T))]; $ts_s = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_TS_LIST_S))]; printf("%i+\$%02x/\$%02x - ", $file, $catalog_t, $catalog_s); $deleted = 0; if ($ts_t == 0xff) { print "**DELETED** "; $deleted = 1; $ts_t = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_NAME + 0x1e))]; } if ($ts_t == 0x00) { print "UNUSED!"; goto continue_dump; } my $filenamestr = substr($sector_buffer, ($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_NAME)), 30); dos33_filename_to_ascii($temp_string, $filenamestr, 30); print "$temp_string"; print "\n"; printf(" Locked = %s\n", $bytes[$CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE] > 0x7f ? "YES" : "NO"); printf(" Type = %s\n", dos33_file_type($bytes[$CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE) + $FILE_TYPE])); printf(" Size in sectors = %i\n", $bytes[$CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_SIZE_L)] + ($bytes[$CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_SIZE_H)] << 8)); repeat_tsl: printf(" T/S List \$%02x/\$%02x:\n", $ts_t, $ts_s); if ($deleted) { goto continue_dump; } seek($fd, DISK_OFFSET($ts_t, $ts_s), $SEEK_SET); $result = read($fd, $tslist, $BYTES_PER_SECTOR); # Unpack tslist sector data. my @tslist_bytes = unpack "C*", $tslist; for (my $i = 0; $i < $ts_total; $i++) { $track = $tslist_bytes[$TSL_LIST + ($i * $TSL_ENTRY_SIZE)]; $sector = $tslist_bytes[$TSL_LIST + ($i * $TSL_ENTRY_SIZE) + 1]; if (($track == 0) && ($sector == 0)) { print "."; } else { printf("\n %02x/%02x", $track, $sector); } } $ts_t = $tslist_bytes[$TSL_NEXT_TRACK]; $ts_s = $tslist_bytes[$TSL_NEXT_SECTOR]; if (!(($ts_s == 0) && ($ts_t == 0))) { goto repeat_tsl; } continue_dump:; } print "\n"; $catalog_t = $bytes[$CATALOG_NEXT_T]; $catalog_s = $bytes[$CATALOG_NEXT_S]; if ($catalog_s != 0) { $file = 0; goto repeat_catalog; } print "\n"; if ($result < 0) { print STDERR "Error on I/O\n"; } return 0; } sub dos33_showfree { my ($fd) = @_; my $file; my $ts_t; my $ts_s; my $track; my $sector; my $deleted = 0; my $temp_string; my $tslist; my $catalog_used; my $next_letter = ord('A'); my %file_key; my $num_files = 0; my @usage; for (my $i = 0; $i < 35; $i++) { for (my $j = 0; $j < 16; $j++) { $usage[$i][$j] = 0; } } # Read Track 1 Sector 9 seek($fd, DISK_OFFSET(1, 9), $SEEK_SET); my $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack sector data. my @bytes = unpack "C*", $sector_buffer; printf("Finding name of startup file, Track 1 Sector 9 offset \$75\n"); printf("Startup Filename: "); for (my $i = 0; $i < 30; $i++) { printf("%c", $bytes[0x75 + $i] & 0x7f); } printf("\n"); dos33_read_vtoc($fd); # Unpack VTOC sector data. @bytes = unpack "C*", $sector_buffer; printf("\n"); printf("VTOC INFORMATION:\n"); my $catalog_t = $bytes[$VTOC_CATALOG_T]; my $catalog_s = $bytes[$VTOC_CATALOG_S]; printf(" First Catalog = %02x/%02x\n", $catalog_t, $catalog_s); printf(" DOS RELEASE = 3.%i\n", $bytes[$VTOC_DOS_RELEASE]); printf(" DISK VOLUME = %i\n", $bytes[$VTOC_DISK_VOLUME]); my $ts_total = $bytes[$VTOC_MAX_TS_PAIRS]; printf(" T/S pairs that will fit in T/S List = %i\n", $ts_total); printf(" Last track where sectors were allocated = \$%02x\n", $bytes[$VTOC_LAST_ALLOC_T]); printf(" Direction of track allocation = %i\n", $bytes[$VTOC_ALLOC_DIRECT]); my $num_tracks = $bytes[$VTOC_NUM_TRACKS]; printf(" Number of tracks per disk = %i\n", $num_tracks); printf(" Number of sectors per track = %i\n", $bytes[$VTOC_S_PER_TRACK]); my $sectors_per_track = $bytes[$VTOC_S_PER_TRACK]; printf(" Number of bytes per sector = %i\n", ($bytes[$VTOC_BYTES_PER_SH] << 8) + $bytes[$VTOC_BYTES_PER_SL]); printf("\nFree sector bitmap:\n\n"); printf(" 1111111111111111222\n"); printf(" 0123456789ABCDEF0123456789ABCDEF012\n"); my $disp_sec = 0; for (my $sec = ($sectors_per_track - 1); $sec >= 0; $sec--) { printf("\$%01x: ", $disp_sec++); for (my $trk = 0; $trk < $num_tracks; $trk++) { if ($sec < 8) { if (($bytes[$VTOC_FREE_BITMAPS + ($trk * 4)] << $sec) & 0x80) { printf("."); } else { printf("U"); } } else { if (($bytes[$VTOC_FREE_BITMAPS + ($trk * 4) + 1] << ($sec - 8)) & 0x80) { printf("."); } else { printf("U"); } } } printf("\n"); } printf("Key: U=used, .=free\n\n"); # Reserve DOS for (my $i = 0; $i < 3; $i++) { for (my $j = 0; $j < 16; $j++) { $usage[$i][$j] = '$'; } } # Reserve CATALOG (not all used?) my $i = 0x11; for (my $j = 0; $j < 16; $j++) { $usage[$i][$j] = '#'; } repeat_catalog: $catalog_used = 0; seek($fd, DISK_OFFSET($catalog_t, $catalog_s), $SEEK_SET); $result = read($fd, $sector_buffer, $BYTES_PER_SECTOR); # Unpack catalog sector data. @bytes = unpack "C*", $sector_buffer; for ($file = 0; $file < 7; $file++) { $ts_t = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_TS_LIST_T))]; $ts_s = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_TS_LIST_S))]; $deleted = 0; if ($ts_t == 0xff) { printf("**DELETED** "); $deleted = 1; $ts_t = $bytes[($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_NAME + 0x1e))]; } if ($ts_t == 0x00) { goto continue_dump; } my $filenamestr = substr($sector_buffer, ($CATALOG_FILE_LIST + ($file * $CATALOG_ENTRY_SIZE + $FILE_NAME)), 30); dos33_filename_to_ascii($temp_string, $filenamestr, 30); printf("%s %s", chr($next_letter), $temp_string); printf("\n"); if (!$deleted) { $catalog_used++; $usage[$catalog_t][$catalog_s] = '@'; } repeat_tsl: if ($deleted) { goto continue_dump; } $usage[$ts_t][$ts_s] = chr($next_letter); $file_key{$temp_string} = chr($next_letter); $num_files++; seek($fd, DISK_OFFSET($ts_t, $ts_s), $SEEK_SET); $result = read($fd, $tslist, $BYTES_PER_SECTOR); # Unpack tslist sector data. my @tslist_bytes = unpack "C*", $tslist; for ($i = 0; $i < $ts_total; $i++) { $track = $tslist_bytes[$TSL_LIST + ($i * $TSL_ENTRY_SIZE)]; $sector = $tslist_bytes[$TSL_LIST + ($i * $TSL_ENTRY_SIZE) + 1]; if (($track == 0) && ($sector == 0)) { } else { $usage[$track][$sector] = chr($next_letter); } } $ts_t = $tslist_bytes[$TSL_NEXT_TRACK]; $ts_s = $tslist_bytes[$TSL_NEXT_SECTOR]; if (!(($ts_s == 0) && ($ts_t == 0))) { goto repeat_tsl; } continue_dump: if ($next_letter == ord('Z')) { $next_letter = ord('a'); } elsif ($next_letter == ord('z')) { $next_letter = ord('0'); } else { $next_letter++; } } $catalog_t = $bytes[$CATALOG_NEXT_T]; $catalog_s = $bytes[$CATALOG_NEXT_S]; if ($catalog_s != 0) { $file = 0; goto repeat_catalog; } print "\n"; if ($result < 0) { print STDERR "Error on I/O\n"; } print "\nDetailed sector bitmap:\n\n"; print " 1111111111111111222\n"; print " 0123456789ABCDEF0123456789ABCDEF012\n"; for (my $j = 0; $j < $sectors_per_track; $j++) { printf("\$%01x: ", $j); for ($i = 0; $i < $num_tracks; $i++) { if ($usage[$i][$j] eq '0') { print "."; } else { printf("%s", $usage[$i][$j]); } } print "\n"; } print "Key: \$=DOS, @=catalog used, #=catalog reserved, .=free\n\n"; foreach my $val (sort values %file_key) { if (defined $val) { if (defined $file_key{$val}) { printf(" %s %s\n", $val, $file_key{$val}); } } } return 0; } # ??? sub dos33_rename_hello { my ($fd, $new_name) = @_; my $buffer; seek($fd, DISK_OFFSET(1, 9), $SEEK_SET); read($fd, $buffer, $BYTES_PER_SECTOR); # Unpack sector data. my @bytes = unpack "C*", $buffer; for (my $i = 0; $i < 30; $i++) { if ($i < length($new_name)) { $bytes[0x75 + $i] = ord(substr($new_name, $i, 1)) | 0x80; } else { $bytes[0x75 + $i] = ord(' ') | 0x80; } } # Re-pack sector data. $buffer = pack "C*", @bytes; seek($fd, DISK_OFFSET(1, 9), $SEEK_SET); print $fd $buffer; return 0; } sub display_help { my ($name, $version_only) = @_; printf("\ndos33 version %s\n", $VERSION); printf("by Vince Weaver \n"); printf("Perl port by Leeland Heins \n"); printf("\n"); if ($version_only) { return; } printf("Usage: %s [-h] [-y] disk_image COMMAND [options]\n", $name); printf(" -h : this help message\n"); printf(" -y : always answer yes for anying warning questions\n"); printf("\n"); printf("Where disk_image is a valid dos3.3 disk image\nand COMMAND is one of the following:\n"); printf(" CATALOG\n"); printf(" LOAD apple_file \n"); printf(" SAVE type local_file \n"); printf(" BSAVE [-a addr] [-l len] local_file \n"); printf(" DELETE apple_file\n"); printf(" LOCK apple_file\n"); printf(" UNLOCK apple_file\n"); printf(" RENAME apple_file_old apple_file_new\n"); printf(" UNDELETE apple_file\n"); printf(" DUMP\n"); printf(" SHOWFREE\n"); printf(" HELLO apple_file\n"); #printf(" INIT\n"); #printf(" COPY\n"); printf("\n"); return; } sub truncate_filename { my ($out) = @_; my $truncated = 0; # Truncate filename if too long if (length($out) > 30) { $out = substr($out, 0, 30); print STDERR sprintf("Warning! Truncating %s to 30 chars\n", $out); $_[0] = $out; $truncated = 1; } return $truncated; } ## MAIN my $type = 'b'; my $catalog_entry; my $temp_string; my $apple_filename; my $new_filename; my $local_filename; my $always_yes = 0; my $address = 0; my $length = 0; # Process command line arguments. while (defined $ARGV[0] && $ARGV[0] =~ /^-/) { # Set base address in decimal. if ($ARGV[0] eq '-a' && defined $ARGV[1] && $ARGV[1] =~ /^\d+$/) { $address = $ARGV[1]; shift; shift; } elsif ($ARGV[0] eq '-l' && defined $ARGV[1] && $ARGV[1] =~ /^\d+$/) { $length = $ARGV[1]; shift; shift; } elsif ($ARGV[0] eq '-t' && defined $ARGV[1] && $ARGV[1] =~ /^\d+$/) { $track = $ARGV[1]; shift; shift; } elsif ($ARGV[0] eq '-v') { display_help($ARGV[0], 1); exit 1; } elsif ($ARGV[0] eq '-h') { display_help($ARGV[0], 0); exit 1; } else { die "Invalid argument $ARGV[0]\n"; } } # get argument 1, which is image name my $image = shift; if (! defined $image) { print STDERR "ERROR!Must specify disk image!\n\n"; exit 0; } my $dos_fd; if (!open($dos_fd, "+<$image")) { print STDERR "Error opening disk_image: $image\n"; exit 0; } # Grab command my $command = shift; if (! defined $command) { print STDERR "ERROR! Must specify command!\n\n"; exit 0; } # Make command be uppercase $command = uc($command); # Load a file from disk image to local machine if ($command eq "LOAD") { # check and make sure we have apple_filename my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need apple file_name\n"; print STDERR "$0 $image LOAD apple_filename\n"; } else { print " Apple filename: $apple_filename\n" if $debug; truncate_filename($apple_filename); # get output filename my $local_filename = shift; if (! defined $local_filename) { $local_filename = $apple_filename; print "Using $local_filename for filename\n" if $debug; } else { print "Using $apple_filename for filename\n" if $debug; } print " Output filename: $local_filename\n" if $debug; # get the entry/track/sector for file $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); if ($catalog_entry < 0) { print STDERR "Error! $apple_filename not found!\n"; } else { dos33_load_file($dos_fd, $catalog_entry, $local_filename); } } } elsif ($command eq "CATALOG") { # get first catalog $catalog_entry = dos33_get_catalog_ts($dos_fd); # Unpack sector data. my @bytes = unpack "C*", $sector_buffer; printf("\nDISK VOLUME %i\n\n", $bytes[$VTOC_DISK_VOLUME]); while ($catalog_entry > 0) { $catalog_entry = dos33_find_next_file($dos_fd, $catalog_entry); if ($catalog_entry > 0) { dos33_print_file_info($dos_fd, $catalog_entry); # why 1 << 16 ? $catalog_entry += (1 << 16); # dos33_find_next_file() handles wrapping issues } } print "\n"; } elsif ($command eq "SAVE") { # argv3 == type == A, B, T, I, N, L etc # argv4 == name of local file # argv5 == optional name of file on disk image my $type = shift; if (! defined $type || $type eq '') { print STDERR "Error! Need type\n"; print STDERR "$0 $image SAVE type file_name apple_filename\n\n"; } else { print " type=$type\n" if $debug; if ($type !~ /^[TIABSRNL]$/i) { print STDERR "Error! Invalied type - must be T, I, A, B, S, R, N or L\n"; } else { my $local_filename = shift; if (! defined $local_filename || $local_filename eq '') { print STDERR "Error! Need file_name\n"; print STDERR "$0 $image SAVE type file_name apple_filename\n\n"; } else { my $apple_filename = shift; if (! defined $apple_filename || $apple_filename eq '') { print STDERR "Error! Need apple_filename\n"; print STDERR "$0 $image SAVE type file_name apple_filename\n\n"; } else { printf(" Apple filename: %s\n", $apple_filename) if $debug; $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); my $result_string = 'y'; if ($catalog_entry >= 0) { print STDERR "Warning! $apple_filename exists!\n"; if (!$always_yes) { printf("Over-write (y/n)?"); $result_string = ; if (($result_string eq '') || ($result_string !~ /^[yY]/)) { printf("Exiting early...\n"); } } if ($result_string =~ /^[Yy]/) { print STDERR "Deleting previous version...\n"; dos33_delete_file($dos_fd, $catalog_entry); } } if ($result_string =~ /^[Yy]/) { dos33_add_file($dos_fd, $type, $ADD_RAW, $address, $length, $local_filename, $apple_filename); } } } } } } elsif ($command eq "BSAVE") { my $local_filename = shift; if (! defined $local_filename || $local_filename eq '') { print STDERR "Error! Need file_name\n"; print STDERR "$0 $image BSAVE file_name apple_filename\n\n"; } else { print " Local filename: $local_filename\n" if $debug; my $apple_filename = shift; if (! defined $apple_filename || $apple_filename eq '') { # apple filename specified print STDERR "Error! Need apple_filename\n"; print STDERR "$0 $image BSAVE file_name apple_filename\n\n"; } else { truncate_filename($apple_filename); # If no filename specified for apple name # Then use the input name.Note, we strip # everything up to the last slash so useless # path info isn't used $apple_filename = basename($local_filename); truncate_filename($apple_filename); } printf(" Apple filename: %s\n", $apple_filename) if $debug; $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); my $result_string = 'y'; if ($catalog_entry >= 0) { print STDERR "Warning! $apple_filename exists!\n"; if (!$always_yes) { printf("Over-write (y/n)?"); $result_string = ; if (($result_string eq '') || ($result_string !~ /^[yY]/)) { printf("Exiting early...\n"); } } if ($result_string =~ /^[Yy]/) { print STDERR "Deleting previous version...\n"; dos33_delete_file($dos_fd, $catalog_entry); } } if ($result_string =~ /^[Yy]/) { dos33_add_file($dos_fd, $type, $ADD_BINARY, $address, $length, $local_filename, $apple_filename); } } } elsif ($command eq "RAWWRITE") { # ??? printf(" type=%s\n", $type) if $debug; my $local_filename = shift; if (! defined $local_filename) { print STDERR "Error! Need file_name\n"; print STDERR "$0 $image RAWWRITE file_name apple_filename\n\n"; } else { printf(" Local filename: %s\n", $local_filename) if $debug; my $apple_filename = shift; if (defined $apple_filename) { # apple filename specified truncate_filename($apple_filename); } else { $apple_filename = basename($local_filename); # If no filename specified for apple name # Then use the input name. Note, we strip # everything up to the last slash so useless # path info isn't used truncate_filename($apple_filename); } printf(" Apple filename: %s\n", $apple_filename) if $debug; $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); my $result_string = 'y'; if ($catalog_entry >= 0) { print STDERR "Warning! $apple_filename exists!\n"; if (!$always_yes) { print "Over-write (y/n)?"; $result_string = ; if (($result_string eq '') || ($result_string !~ /[yY]/)) { print "Exiting early...\n"; } } if ($result_string =~ /^[Yy]/) { print STDERR "Deleting previous version...\n"; dos33_delete_file($dos_fd, $catalog_entry); } } if ($result_string =~ /^[Yy]/) { dos33_raw_file($dos_fd, $type, $track, $sector, $local_filename, $apple_filename); } } } elsif ($command eq "DELETE") { my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need file_name\n"; print STDERR "$0 $image DELETE apple_filename\n"; } else { truncate_filename($apple_filename); $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); if ($catalog_entry < 0) { print STDERR "Error! File $apple_filename does not exist\n"; } else { dos33_delete_file($dos_fd, $catalog_entry); } } } elsif ($command eq "DUMP") { printf("Dumping %s!\n", $image); dos33_dump($dos_fd); } elsif ($command eq "SHOWFREE") { printf("Showing Free %s!\n", $image); dos33_showfree($dos_fd); } elsif ($command eq "LOCK" || $command eq "UNLOCK") { # check and make sure we have apple_filename my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need apple file_name\n"; print STDERR "$0 $image $command apple_filename\n"; } else { truncate_filename($apple_filename); # get the entry/track/sector for file $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); if ($catalog_entry < 0) { print STDERR "Error! $apple_filename not found!\n"; } else { dos33_lock_file($dos_fd, $catalog_entry, $command eq "LOCK"); } } } elsif ($command eq "RENAME") { # check and make sure we have apple_filename my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need two filenames\n"; print STDERR "$0 $image LOCK apple_filename_old apple_filename_new\n"; } else { # Truncate filename if too long truncate_filename($apple_filename); my $new_filename = shift; if (! defined $new_filename) { print STDERR "Error! Need two filenames\n"; print STDERR "$0 $image LOCK apple_filename_old apple_filename_new\n"; } else { truncate_filename($new_filename); # get the entry/track/sector for file $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); if ($catalog_entry < 0) { print STDERR "Error! $apple_filename not found!\n"; } else { dos33_rename_file($dos_fd, $catalog_entry, $new_filename); } } } } elsif ($command eq "UNDELETE") { # check and make sure we have apple_filename my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need apple file_name\n"; print STDERR "$0 $image UNDELETE apple_filename\n\n"; } else { truncate_filename($apple_filename); # get the entry/track/sector for file $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_DELETED); if ($catalog_entry < 0) { print STDERR "Error! $apple_filename not found!\n"; } else { dos33_undelete_file($dos_fd, $catalog_entry, $apple_filename); } } } elsif ($command eq "HELLO") { my $apple_filename = shift; if (! defined $apple_filename) { print STDERR "Error! Need file_name\n"; print "$0 $image HELLO apple_filename\n\n"; } else { truncate_filename($apple_filename); $catalog_entry = dos33_check_file_exists($dos_fd, $apple_filename, $FILE_NORMAL); if ($catalog_entry < 0) { print STDERR "Warning! File $apple_filename does not exist\n"; } dos33_rename_hello($dos_fd, $apple_filename); } } elsif ($command eq "INIT") { # use common code from mkdos33fs? } elsif ($command eq "COPY") { # use temp file? Walking a sector at a time seems a pain } else { print STDERR "ERROR! Unknown command $command\n"; print STDERR " Try \"$0 -h\" for help.\n\n"; } close($dos_fd); exit 1; 1;