aboutsummaryrefslogtreecommitdiff
path: root/testing/alfls
diff options
context:
space:
mode:
Diffstat (limited to 'testing/alfls')
-rwxr-xr-xtesting/alfls240
1 files changed, 240 insertions, 0 deletions
diff --git a/testing/alfls b/testing/alfls
new file mode 100755
index 0000000..4b3d867
--- /dev/null
+++ b/testing/alfls
@@ -0,0 +1,240 @@
+#!/usr/bin/perl -w
+
+# Note: when/if I ever manage to reimplement the ALF decompressor in
+# C, this script will serve as the prototype for the unalf tool, which
+# will of course be in C also...
+
+use bytes;
+
+sub chrat {
+ my $offs = shift;
+ return substr($data, $offs, 1);
+}
+
+sub wordat {
+ my $offs = shift;
+ return ord(chrat($offs)) | (ord(chrat($offs + 1)) << 8);
+}
+
+sub longat {
+ my $offs = shift;
+ return wordat($offs) | (wordat($offs + 2) << 16);
+}
+
+sub header_ok {
+ my $pos = shift || 0;
+
+ return 0 unless chrat($pos) eq chr(0x1a);
+
+ my $c = ord(chrat($pos + 1 ));
+
+ if($c >= 2 && $c <= 9) {
+ warn "$SELF: this is an ARC archive (not ALF).\n" unless $arc_warn;
+ $arc_warn++;
+ return 1;
+ } elsif($c == 0x0f) {
+ return 1;
+ }
+
+ return 0;
+}
+
+@monthnames = qw/??? Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ??? ??? ???/;
+
+# see Arcinfo...
+sub extract_date {
+ my $pos = shift;
+ my $date = wordat($pos);
+
+ my $year = ($date >> 9) + 1980;
+ $year %= 100; # only ever print last 2 digits
+
+ my $month = ($date >> 5) & 0x0f;
+ $month = $monthnames[$month];
+
+ my $day = $date & 0x1f;
+
+ my $res = sprintf("%2d %3s %2d", $day, $month, $year);
+ return $res;
+}
+
+sub extract_time {
+ my $pos = shift;
+ my $time = wordat($pos);
+
+ my $hour = $time >> 11;
+ my $min = ($time >> 5) & 0x3f;
+ # don't bother with sec, we don't display them.
+
+ # special case: "arc v" displays 00:00 as 12:00p.
+ if($hour == 0 && $min == 0) {
+ $hour = 24;
+ }
+
+ my $ampm = ($hour > 11 ? 'p' : 'a');
+ $hour %= 12;
+ $hour = 12 if $hour == 0;
+
+ my $res = sprintf("%2d:%02d%s", $hour, $min, $ampm);
+ return $res;
+}
+
+sub list_file {
+ # each file header is 29 bytes.
+
+ # bytes 0-1 are 0x1a (ARCMARK) and 0x0f (ALF compression type)
+
+ # bytes 2-14 are filename, though the Atari max filename len
+ # is 12 (e.g. FOOBARXX.EXT).
+
+ # bytes 15-18 are 4-byte compressed length (LSB first).
+ # bytes 17-18 should always be 0, I don't think alfcrunch
+ # can handle a file >64K. even if it can, byte 18 should still
+ # always be 0, because *surely* it can't handle a file that's
+ # >16M. for that matter, most Atari DOSes can't handle a hard
+ # drive partition >16M...
+
+ # bytes 19-22 are date/time stamp, hopefully the same as ARC.
+
+ # bytes 23-24 are the checksum CRC-16 (?) checksum.
+
+ # bytes 25-28 are the 4-byte uncompressed length (LSB first).
+ # bytes 27-28 should always be 0 (as above).
+
+ # the 29-byte header is followed by the compressed data, whose length
+ # matches the compressed length in bytes 15-18.
+
+ # a lot of the files in the the Holmes archive have filler bytes at
+ # the end, put there by ancient dumb file transfer protocols, or
+ # dumb DOSes (doesn't CP/M do this?).
+
+ if(!header_ok($pos)) {
+ warn "$SELF: Junk at EOF (probably harmless).\n";
+ $pos += (1 << 31); # ludicrous size, makes main loop exit.
+ return;
+ }
+
+ # read the filename until we hit a null byte, or a space, or
+ # the max length. all the .alf files I have, do have a null byte
+ # terminator for the filename... followed by spaces to fill up the
+ # rest of the 13-byte field.
+ my $filename = "";
+ for(my $i = 2; $i < 15; $i++) {
+ my $b = chrat($pos + $i);
+ last if ord($b) == 0 || $b eq ' ';
+ $filename .= $b;
+ }
+
+ my $clen = longat($pos + 15);
+ my $ulen = longat($pos + 25);
+ my $crc = wordat($pos + 23);
+ my $date = extract_date($pos + 19);
+ my $time = extract_time($pos + 21);
+ my $pct = 100 - int($clen / $ulen * 100);
+
+ printf("%-12s ", $filename);
+ printf("%8d ", $ulen);
+ if(chrat($pos + 1) eq chr(0x0f)) {
+ print(" ALF ");
+ } else {
+ print(" ARC ");
+ }
+ printf("%3d%% ", $pct);
+ printf("%8d ", $clen);
+ printf("%9s ", $date);
+ printf("%6s ", $time);
+ printf("%04x\n", $crc);
+
+ if($ENV{DUMP}) {
+ my $bits;
+ for(my $i = 0; $i < $clen; $i++) {
+ $bits .= sprintf("%08b", ord(chrat($pos + 29 + $i)));
+ }
+ my $count = 0;
+ while($bits =~ s/^([01]{9})//) {
+ my $ctlbit = substr($1, 0, 1);
+ my $byte = substr($1, 1);
+ my $val = eval "0b$byte";
+ my $hex = sprintf('$%02x', $val);
+ printf("%3d: ", $count++);
+ print "$ctlbit $byte ; ";
+ if($ctlbit eq '0') {
+ print "literal $hex";
+ if($val > 32 && $val < 127) {
+ print " " . chr($val);
+ }
+ } else {
+ my $name = "(?)";
+ if($val == 0) {
+ $name = "start";
+ } elsif($val == 1) {
+ $name = "end";
+ }
+ print "$val $name";
+ }
+ print "\n";
+ }
+ print "junk: $bits\n" if length($bits);
+ }
+
+ $total_clen += $clen;
+ $total_ulen += $ulen;
+ $file_count++;
+
+ $pos += ($clen + 29);
+}
+
+# main()
+
+$total_clen = $total_ulen = $file_count = 0;
+
+($SELF = $0) =~ s,.*/,,;
+
+if(@ARGV != 1) {
+ die "$SELF requires exactly one ALF file as an argument.\n";
+} elsif($ARGV[0] =~ /--?h(elp)?/) {
+ print <<EOF;
+$SELF - list contents of an ALF (or ARC) archive file.
+
+Usage: $SELF <archive>
+
+For each file in the ALF or ARC archive, displays the filename,
+compressed and uncompressed sizes, compression amount, date/time,
+and CRC. After all files are listed, the total sizes and compression
+are shown.
+
+The output is intended to look like that of "arc v", except the
+Stowage column only ever says "ALF" or "ARC" (doesn't show e.g.
+"Squashed", "Crunched", etc for ARC files).
+
+Exit status is 0 on success, non-zero on failure.
+EOF
+ exit 0;
+}
+
+undef $/;
+
+$data = <>;
+
+#warn "read " . length($data) . " bytes\n";
+die("$SELF: Not an alfcrunch file.\n") unless header_ok();
+
+print <<EOF;
+Name Length Stowage SF Size now Date Time CRC
+============ ======== ======== ==== ======== ========= ====== ====
+EOF
+
+$pos = 0;
+while($pos < length($data)) {
+ list_file();
+}
+
+print " ==== ======== ==== ========\nTotal ";
+printf("%4d ", $file_count);
+printf("%8d", $total_ulen);
+print ' ' x 12;
+printf("%3d%% ", 99);
+printf("%8d\n", $total_clen);
+
+
+exit 0;