#!/usr/bin/perl # # Copyright © 2015-2020 by Vincent Slyngstad # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the names of the authors above # shall not be used in advertising or otherwise to promote the sale, use # or other dealings in this Software without prior written authorization # from those authors. # # Read the .xml descriptions of disk volumes, # re-create them. (We don't try to be tolerant # of free format XML.) # # Write a block to the new image. sub writeblk { local($blk, @buf) = @_; seek(IMAGE, ($blk+$fsbase)*512, 0) || die "seek($iname): $!"; $buf = pack("S*", @buf); return unless length($buf); syswrite(IMAGE, $buf) || die "write($iname) $f: $!"; # FEATURE: Should book-keep which blocks were written, # and which were forgotten. } # # Ensure at least enough words remain in the directory # segment for the next directory entry. If not, wrap # up (and output) the directory block, and start a new # one. sub reserve { local($needed) = $_[0]; return unless $doff + $needed > 255; $dbuf[0] = 07777 & -$dent; $dbuf[2] = $dblk + 1; # Link to next block. # Write the directory block. &writeblk($dblk, @dbuf); $dblk++; die "$fsname: directory full: $file" if $dblk > 6; $dbuf[0] = $dent = 0; # No entries yet $dbuf[1] = $sblk; # Start here $dbuf[2] = 0; # No link yet $doff = 5; # New buffer ptr } # # Trim a date to 12 bits in OS/8 format. sub os8date { local($file) = $_[0]; local($date) = (stat$file)[9]; local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($date); $mon++; #warn "os8date($f:$date): $mday-$mon-$year\n"; $year -= 1970; $year %= 8; return ($mon<<8) + ($mday<<3) + $year; } # # Process a sequence. sub file { $_ = ; return 0 unless /^<\/file>$/; ($file, $start, $end, $mode) = ($1, $2, $3, $4); $start = oct($start) if $start =~ /^0/; $end = oct($end) if $end =~ /^0/; # Uppercase a copy of the file name and extension. $f = $file; $f =~ y/a-z/A-Z/; $f =~ s/.*\///; # Copy the data for the file into the disk image. # An empty area of the disk has a file name .NNN, # where NNN is the starting block number. if ($f eq ".$start") { # Don't bother to actually write empty areas. #©out($file, $start, $end); # Output 00000, -length &reserve(2); # Need room for 2 words $dbuf[$doff+0] = 0; $dbuf[$doff+1] = 07777 & ($start-$end-1); $doff += 2; $sblk = $end + 1; $dent++; } else { ©out($file, $start, $end); # Seperate name, extension $ext = $f; $ext =~ s/^.*[.]//; $f =~ s/[.]$ext$//; # Pad with spaces. $f = pack("a6", $f); $ext = pack("a2", $ext); # Pack character pairs into 6bit. @f = split(//, $f); @ext = split(//, $ext); $datew = &os8date($file); &reserve(5); # Need room for 2 words $dbuf[$doff+0] = ((ord($f[0])&077)<<6) + (ord($f[1])&077); $dbuf[$doff+1] = ((ord($f[2])&077)<<6) + (ord($f[3])&077); $dbuf[$doff+2] = ((ord($f[4])&077)<<6) + (ord($f[5])&077); $dbuf[$doff+3] = ((ord($ext[0])&077)<<6) + (ord($ext[1])&077); $dbuf[$doff+4] = $datew; $dbuf[$doff+5] = 07777 & ($start-$end-1); $doff += 6; $sblk = $end + 1; $dent++; } return 1; } sub copyout { local($f, $start, $last) = @_; open(FILE, "$f") || die "$f: $!"; binmode(FILE); local($buf, @ibuf, @obuf); local($mode, $eof, $eofpos) = (0, 0, 0); # Check that the file fits in the allocated space! local($blks) = int(((-s $f)+383) / 384); if ($blks != $last-$start+1) { die "$f doesn't fit the allocated space!\n" if $blks != $last-$start+1; } # Unpack the file as if binary. for ($first = $start; $first <= $last; $first++) { # Read a (packed, 384 byte) block. read(FILE, $buf, 384) || die "read($f): $!"; @ibuf = unpack("C*", $buf); # Must be text if not a multiple of block size # Add an EOF marker, if so. push(@ibuf, 032) unless $#ibuf%384 == 383; $foo = ".rsvd"; #warn "$f: ibuf[$#ibuf] = $ibuf[$#ibuf]\n" if $f =~ /$foo/; # Pad last block to a multiple of 3 bytes. push(@ibuf, 0) unless $#ibuf%3 == 2; push(@ibuf, 0) unless $#ibuf%3 == 2; # Unpack the block. while (@ibuf) { $c1 = shift @ibuf; $c2 = shift @ibuf; $c3 = shift @ibuf; #warn "$f: $c1 $c2 $c3\n" if $f =~ /$foo/; # Worry about it is truly text mode. $mode = 1 if $c1 & 0200; # Must be binary $eofpos++ unless $eof; $eof++ if $c1 == 032; $mode = 1 if $c2 & 0200; # Must be binary $eofpos++ unless $eof; $eof++ if $c2 == 032; $mode = 1 if $c3 & 0200; # Must be binary $eofpos++ unless $eof; $eof++ if $c3 == 032; $mode = 1 if $eof > 1; # Another EOF, must be binary $c1 |= ($c3&0360)<<4; $c2 |= ($c3&0017)<<8; #warn "$f: pushing $c1 $c2\n" if $f =~ /$foo/; push(@obuf, $c1, $c2); } } # Force binary treatment for un-named files. $mode = 1 if $f =~ /^\..../; #warn "$f: mode is $mode\n" if $f =~ /$foo/; #warn "$f: obuf is $obuf[0] $obuf[1]\n" if $f =~ /$foo/; # We walked the file, so we know if it is truly binary. if (!$mode) { # Text file, more mangling is needed! # NOTE: We don't fix your line endings, so # ~/.exrc: set fileformats=unix,dos # or equivalent is probably needed to perserve them.. # # Each word in needs 0200 set, and the first # in each pair also need 04000. for ($i = 0; $i < $#obuf; $i += 2) { $obuf[$i] |= 04200; $obuf[$i+1] |= 00200; } # Pad the length to an even block. while ($#obuf%256 != 255) { push(@obuf, 04200, 00200); } } #warn "$f: obuf is $obuf[0] $obuf[1]\n" if $f =~ /$foo/; # Write @obuf as the result. &writeblk($start, @obuf); return $mode; } sub os8fs { $_ = ; return 0 unless /^$/; ($fsname, $fsbase, $fssize) = ($1, $2, $3); $fsbase = oct($fsbase) if $fsbase =~ /^0/; ©out("$fsname/.boot", 0, 0); $sblk = 07; if (-f "$fsname/.kmon") { # System disk, copy out .kmon, etc. ©out("$fsname/.kmon", 07, 012); ©out("$fsname/.usr", 013, 015); ©out("$fsname/.dhand", 016, 025); ©out("$fsname/.ent", 026, 026); ©out("$fsname/.sblks", 027, 050); ©out("$fsname/.cdec", 051, 053); ©out("$fsname/.sdate", 054, 055); ©out("$fsname/.merr", 056, 056); ©out("$fsname/.chain", 057, 057); ©out("$fsname/.sodt", 060, 063); ©out("$fsname/.rsvd", 064, 064); ©out("$fsname/.cclr", 065, 065); ©out("$fsname/.td8e", 066, 066); ©out("$fsname/.cclo", 067, 067); $sblk = 070; } $dblk = 1; # Start directory here $dbuf[0] = $dent = 0; # No entries yet $dbuf[1] = $sblk; # Files start here $dbuf[2] = 0; # No link yet $dbuf[3] = 0; # No flag $dbuf[4] = 07777; # 1 additional data word (date) $doff = 5; # Start file entries with word 5 $_ = ; die "no " unless /^$/; while (&file) {} die "no " unless /^<\/directory>$/; # # Write out the last directory block. $dbuf[0] = 07777 & -$dent; $dbuf[2] = 0; # Zero link to next block. &writeblk($dblk, @dbuf); # Skip over .kmon, etc.! while (($_ = )) { last if /^<\/os8fs>$/; } die "no " unless /^<\/os8fs>$/; } foreach $x (@ARGV) { open(XML, "$x") || die "$x: $!"; $_ = ; die "no " unless /^$/; ($iname, $isize) = ($1, $2); open(IMAGE, ">$iname") || die "$iname: $!"; # Write the last block to force the size. for ($i = 0; $i < 256; $i++) { $dbuf[$i] = 0; } &writeblk($isize-1, @dbuf); while (&os8fs) {} die "no " unless /^<\/image>$/; }