aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2025-11-13 05:39:38 -0500
committerB. Watson <urchlay@slackware.uk>2025-11-13 05:39:38 -0500
commite2da2bffe58a76c091d3496bd3ca2d2f18ea2eb6 (patch)
tree5195b221457842d781fadcb94331c93058046744
downloadunalf-e2da2bffe58a76c091d3496bd3ca2d2f18ea2eb6.tar.gz
initial commit
-rw-r--r--README.txt47
-rw-r--r--TODO.txt20
-rw-r--r--disasm/Makefile3
-rw-r--r--disasm/unalf14.combin0 -> 3683 bytes
-rw-r--r--disasm/unalf14.notes.txt248
-rw-r--r--disasm/unalf14.osequates15
-rw-r--r--disasm/unalf14.rawbin0 -> 3691 bytes
-rw-r--r--disasm/unalf14.raw.info221
-rw-r--r--disasm/unalf14.s1932
-rw-r--r--doc/Arcinfo124
-rw-r--r--doc/alf14.atrbin0 -> 92176 bytes
-rw-r--r--doc/alf14_doc.txt266
-rw-r--r--doc/fileformat.txt45
-rw-r--r--doc/review.txt44
-rw-r--r--examples/aprog.alfbin0 -> 25728 bytes
-rw-r--r--examples/atutor.alfbin0 -> 37888 bytes
-rw-r--r--examples/bbsmio.alfbin0 -> 4096 bytes
-rw-r--r--examples/examples.txt22
-rw-r--r--examples/gotcha.alfbin0 -> 78208 bytes
-rw-r--r--examples/infocom1.alfbin0 -> 66160 bytes
-rw-r--r--examples/infocom2.alfbin0 -> 69295 bytes
-rw-r--r--examples/mtosv3.alfbin0 -> 20253 bytes
-rw-r--r--f65/Makefile10
-rw-r--r--f65/README32
-rwxr-xr-xf65/asm2fake65.pl72
-rw-r--r--f65/f65.c68
-rw-r--r--f65/f65.h129
-rw-r--r--src/Makefile25
-rw-r--r--src/addrs.h86
-rw-r--r--src/alfsum.175
-rw-r--r--src/alfsum.c53
-rw-r--r--src/alfsum.rst65
-rw-r--r--src/extract.c732
-rw-r--r--src/io.c92
-rw-r--r--src/listalf.c118
-rw-r--r--src/unalf.1136
-rw-r--r--src/unalf.c66
-rw-r--r--src/unalf.h40
-rw-r--r--src/unalf.rst124
-rwxr-xr-xtesting/alfls240
40 files changed, 5150 insertions, 0 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..5062247
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,47 @@
+unalf project README.
+
+The ultimate goal of this project is to reimplement the ALF
+compression and decompression algorithms on modern systems aka
+Linux and anything else that's POSIX-ish. It's also intended to
+be a repository of information about the ALF archiver (and UNALF
+dearchiver).
+
+What's here so far:
+
+README.txt - you're reading it now.
+
+TODO.txt - plans for the future.
+
+src/ - the source. should be able to build with "cd src; make",
+followed by "make install" if you want.
+
+f65/ - "fake 6502" porting layer. Not for the faint of heart. The
+unalf algorithm was ported from a disassembly of the 6502 code, using
+a perl script to convert the 6502 mnemonics to C macros. This means I
+was able to port the code without fully understanding how it works...
+
+doc/Arcinfo - describes the format of ARC compressed files. The ALF
+file structure is almost identical to ARC's. This file was taken from
+the arc-5.21q source.
+
+doc/alf14.atr - the distribution disk for ALF version 1.4, as an Atari
+8-bit single-density floppy disk image. This likely isn't the original
+distribution disk, but it's the only one I've found on the various
+archive sites.
+
+doc/alf14_doc.txt - the documentation for ALF and UNALF, extracted
+from the disk image and converted from ATASCII to standard ASCII. Note
+that the filenames are different: LZ.COM for ALF14.COM and DZ.COM for
+UNALF14.COM.
+
+doc/fileformat.txt - documents how the ALF file format differs from ARC.
+
+doc/review.txt - a review of the original ALFCrunch, from an Atari magazine.
+
+examples/* - ALF files found in the wild.
+
+testing/alfls - a Perl script that lists the contents of an ALF
+archive. Run it with --help for more information. If you're packaging
+unalf for a distribution, there's no need to include this script in
+the package: I wrote it for testing purposes only. You can use "unalf
+-l" to list .alf files, so this is redundant.
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..9050278
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,20 @@
+- selective extraction, possibly with wildcards
+
+- rename old file to flle~ rather than overwriting
+
+- implement a few unzip/arc style options:
+ -a = convert text files (Atari EOL => UNIX NL), -aa = all files
+ -e = extract (already the default action)
+ -t = test checksums
+ -k = keep trailing dot for extensionless filenames
+ -l = list (filenames only)
+ -L = lowercase filenames
+ -d <dir> = set output dir (create if needed)
+ -o = overwrite (do not create file~ backups)
+ -p = extract to stdout
+ -x <pattern> = do not extract files matching pattern
+ -v = verbose list
+
+- reverse-engineer the ALF14.COM binary, so we have a compressor.
+ this would be more of "just to say I did it" thing, there's no real
+ need for it.
diff --git a/disasm/Makefile b/disasm/Makefile
new file mode 100644
index 0000000..a7ec9e7
--- /dev/null
+++ b/disasm/Makefile
@@ -0,0 +1,3 @@
+
+all:
+ da65 -i unalf14.raw.info
diff --git a/disasm/unalf14.com b/disasm/unalf14.com
new file mode 100644
index 0000000..7a91ef0
--- /dev/null
+++ b/disasm/unalf14.com
Binary files differ
diff --git a/disasm/unalf14.notes.txt b/disasm/unalf14.notes.txt
new file mode 100644
index 0000000..92fd218
--- /dev/null
+++ b/disasm/unalf14.notes.txt
@@ -0,0 +1,248 @@
+uunalf was *not* written in C. no C compiler for the 6502 emits such
+tight code, especially not the ones that were available in 1988. This
+explains why it outperforms arc (which actually was in C) while having
+a smaller executable size.
+
+unalf14.com is a standard Atari segmented executable. For disassembly
+purposes, I converted it to a single chunk of raw object code:
+
+ataricom -n -m 1-2 unalf14.com unalf14.raw
+
+IOCB usage:
+
+#0 is E: as usual.
+#1 is the input file(s).
+#2 is K: (not sure if it's actually used).
+#3 is the output file.
+
+When the program exits (print_msg_complete), it checks the byte at
+$0700 (BOOTRG). If it's equal to $53 (ASCII S, for SpartaDOS), it
+exits via an RTS. Otherwise it exits via JMP (DOSVEC), which reloads
+the DOS menu on DOS 2 and compatibles.
+
+Bugs:
+
+ALF14.COM won't actually compress any files at all, when run from
+the DOS on the disk image I keep finding in archives. The DOS is
+"XDOS". Whatever I try to compress, I always end up with a 0-byte
+output file. It works better in DOS 2.0S, but "*.TXT" doesn't catch
+all the files it should, either.
+
+Trying to compress a file with the one-letter name D causes a an error
+and the program exits. D:D works OK.
+
+Compressed files:
+-----------------------------------------------------------------------
+original data:
+ff ff ff ff 00 ff ff ff ff 01 ff ff ff ff 02
+in binary:
+00000000: 11111111 11111111 11111111 11111111 00000000 11111111 ......
+00000006: 11111111 11111111 11111111 00000001 11111111 11111111 ......
+0000000c: 11111111 11111111 00000010
+
+compressed form:
+10000000 00111111 11100000 01001111 11110000 00000100 00001101 11111110 00000001 10000011 00000000 10100000 00100000
+
+-----------------------------------------------------------------------
+orig in hex:
+ aa aa aa aa 00 aa aa aa aa 01 aa aa aa aa 02
+
+orig in bin:
+ 0: 10101010
+ 1: 10101010
+ 2: 10101010
+ 3: 10101010
+ 4: 00000000
+ 5: 10101010
+ 6: 10101010
+ 7: 10101010
+ 8: 10101010
+ 9: 00000001
+10: 10101010
+11: 10101010
+12: 10101010
+13: 10101010
+14: 00000010
+
+compressed:
+ 10000000 00101010 10100000 01001010 10100000 00000100 00001101 01010100 00000001 10000011 00000000 10100000 00100000
+in groups of 9 bits:
+ 1 00000000 0 10101010 1 00000010 0 10101010 0 00000000 1 00000011 0 10101010 0 00000001 1 00000110 0 00000010 1 00000001 0 0000
+
+ 0: 1 00000000 ; 0 start
+ 1: 0 10101010 ; literal $aa
+ 2: 1 00000010 ; 2: $aa $aa
+ 3: 0 10101010 ; literal $aa
+ 4: 0 00000000 ; literal $00
+ 5: 1 00000011 ; 3: $aa $aa $aa
+ 6: 0 10101010 ; literal $aa
+ 7: 0 00000001 ; literal $01
+ 8: 1 00000110 ; 6: $aa $aa $aa $aa
+ 9: 0 00000010 ; literal $02
+ 10: 1 00000001 ; 1 end
+
+-----------------------------------------------------------------------
+orig (sam.txt):
+00000000: 49 20 61 6d 20 53 61 6d 0a 0a 53 61 6d 20 49 20 I am Sam..Sam I
+00000010: 61 6d 0a 0a 54 68 61 74 20 53 61 6d 2d 49 2d 61 am..That Sam-I-a
+00000020: 6d 21 0a 54 68 61 74 20 53 61 6d 2d 49 2d 61 6d m!.That Sam-I-am
+00000030: 21 0a 49 20 64 6f 20 6e 6f 74 20 6c 69 6b 65 0a !.I do not like.
+00000040: 74 68 61 74 20 53 61 6d 2d 49 2d 61 6d 21 0a 0a that Sam-I-am!..
+00000050: 44 6f 20 79 6f 75 20 6c 69 6b 65 20 67 72 65 65 Do you like gree
+00000060: 6e 20 65 67 67 73 20 61 6e 64 20 68 61 6d 3f 0a n eggs and ham?.
+00000070: 0a 49 20 64 6f 20 6e 6f 74 20 6c 69 6b 65 20 74 .I do not like t
+00000080: 68 65 6d 2c 20 53 61 6d 2d 49 2d 61 6d 2e 0a 49 hem, Sam-I-am..I
+00000090: 20 64 6f 20 6e 6f 74 20 6c 69 6b 65 20 67 72 65 do not like gre
+000000a0: 65 6e 20 65 67 67 73 20 61 6e 64 20 68 61 6d 2e en eggs and ham.
+000000b0: 0a .
+
+Name Length Stowage SF Size now Date Time CRC
+============ ======== ======== ==== ======== ========= ====== ====
+SAM.TXT 177 ALF 28% 128 8 Dec 82 12:24a 3690
+ 0: 1 00000000 ; 0 start
+ 1: 0 01001001 ; literal $49 I
+ 2: 0 00100000 ; literal $20
+ 3: 0 01100001 ; literal $61 a
+ 4: 0 01101101 ; literal $6d m
+ 5: 0 00100000 ; literal $20
+ 6: 0 01010011 ; literal $53 S
+ 7: 1 00000100 ; 4 (?)
+ 8: 0 00001010 ; literal $0a
+ 9: 0 00001010 ; literal $0a
+ 10: 1 00000111 ; 7 (?)
+ 11: 1 00000101 ; 5 (?)
+ 12: 1 00000010 ; 2 (?)
+ 13: 1 00001000 ; 8 (?)
+ 14: 0 00001010 ; literal $0a
+ 15: 0 01010100 ; literal $54 T
+ 16: 0 01101000 ; literal $68 h
+ 17: 0 01100001 ; literal $61 a
+ 18: 0 01110100 ; literal $74 t
+ 19: 1 00000110 ; 6 (?)
+ 20: 1 00000100 ; 4 (?)
+ 21: 0 00101101 ; literal $2d -
+ 22: 0 01001001 ; literal $49 I
+ 23: 0 00101101 ; literal $2d -
+ 24: 1 00000100 ; 4 (?)
+ 25: 0 00100001 ; literal $21 !
+ 26: 1 00001111 ; 15 (?)
+ 27: 1 00010001 ; 17 (?)
+ 28: 1 00010011 ; 19 (?)
+ 29: 1 00001011 ; 11 (?)
+ 30: 1 00010110 ; 22 (?)
+ 31: 1 00011000 ; 24 (?)
+ 32: 0 01101101 ; literal $6d m
+ 33: 1 00011010 ; 26 (?)
+ 34: 1 00000010 ; 2 (?)
+ 35: 0 01100100 ; literal $64 d
+ 36: 0 01101111 ; literal $6f o
+ 37: 0 00100000 ; literal $20
+ 38: 0 01101110 ; literal $6e n
+ 39: 0 01101111 ; literal $6f o
+ 40: 1 00010011 ; 19 (?)
+ 41: 0 01101100 ; literal $6c l
+ 42: 0 01101001 ; literal $69 i
+ 43: 0 01101011 ; literal $6b k
+ 44: 0 01100101 ; literal $65 e
+ 45: 0 00001010 ; literal $0a
+ 46: 0 01110100 ; literal $74 t
+ 47: 1 00011100 ; 28 (?)
+ 48: 1 00010100 ; 20 (?)
+ 49: 0 01101101 ; literal $6d m
+ 50: 1 00011111 ; 31 (?)
+ 51: 1 00011001 ; 25 (?)
+ 52: 1 00001001 ; 9 (?)
+ 53: 0 01000100 ; literal $44 D
+ 54: 1 00100101 ; 37 (?)
+ 55: 0 01111001 ; literal $79 y
+ 56: 0 01101111 ; literal $6f o
+ 57: 0 01110101 ; literal $75 u
+ 58: 0 00100000 ; literal $20
+ 59: 1 00101010 ; 42 (?)
+ 60: 1 00101100 ; 44 (?)
+ 61: 0 00100000 ; literal $20
+ 62: 0 01100111 ; literal $67 g
+ 63: 0 01110010 ; literal $72 r
+ 64: 0 01100101 ; literal $65 e
+ 65: 0 01100101 ; literal $65 e
+ 66: 0 01101110 ; literal $6e n
+ 67: 0 00100000 ; literal $20
+ 68: 0 01100101 ; literal $65 e
+ 69: 0 01100111 ; literal $67 g
+ 70: 0 01100111 ; literal $67 g
+ 71: 0 01110011 ; literal $73 s
+ 72: 1 00000011 ; 3 (?)
+ 73: 0 01101110 ; literal $6e n
+ 74: 0 01100100 ; literal $64 d
+ 75: 0 00100000 ; literal $20
+ 76: 1 00010001 ; 17 (?)
+ 77: 0 01101101 ; literal $6d m
+ 78: 0 00111111 ; literal $3f ?
+ 79: 1 00001001 ; 9 (?)
+ 80: 1 00100011 ; 35 (?)
+ 81: 1 00100101 ; 37 (?)
+ 82: 1 00100111 ; 39 (?)
+ 83: 1 00101001 ; 41 (?)
+ 84: 1 00101011 ; 43 (?)
+ 85: 0 01100101 ; literal $65 e
+ 86: 0 00100000 ; literal $20
+ 87: 1 00101111 ; 47 (?)
+ 88: 0 01100101 ; literal $65 e
+ 89: 0 01101101 ; literal $6d m
+ 90: 0 00101100 ; literal $2c ,
+ 91: 1 00110001 ; 49 (?)
+ 92: 1 00110011 ; 51 (?)
+ 93: 0 01101101 ; literal $6d m
+ 94: 0 00101110 ; literal $2e .
+ 95: 0 00001010 ; literal $0a
+ 96: 1 01010001 ; 81 (?)
+ 97: 1 00100110 ; 38 (?)
+ 98: 1 00101000 ; 40 (?)
+ 99: 1 00111011 ; 59 (?)
+100: 1 01010101 ; 85 (?)
+101: 1 00111110 ; 62 (?)
+102: 1 01000000 ; 64 (?)
+103: 1 01000010 ; 66 (?)
+104: 1 01000100 ; 68 (?)
+105: 1 01000110 ; 70 (?)
+106: 1 01001000 ; 72 (?)
+107: 0 01100001 ; literal $61 a
+108: 1 01001010 ; 74 (?)
+109: 1 01001100 ; 76 (?)
+110: 1 00000100 ; 4 (?)
+111: 1 01011111 ; 95 (?)
+112: 1 00000001 ; 1 end
+junk: 0000000
+ ==== ======== ==== ========
+Total 1 177 99% 128
+
+-----------------------------------------------------------------------
+orig: ABCDEFABBCCDDEEFABCABCDABCDEBCDBCDEBCDEF
+
+ 0: 1 00000000 ; 0 start
+ 1: 0 01000001 ; literal $41 A
+ 2: 0 01000010 ; literal $42 B
+ 3: 0 01000011 ; literal $43 C
+ 4: 0 01000100 ; literal $44 D
+ 5: 0 01000101 ; literal $45 E
+ 6: 0 01000110 ; literal $46 F
+ 7: 1 00000010 ; 2 AB
+ 8: 1 00000011 ; 3 BC
+ 9: 1 00000100 ; 4 CD
+ 10: 1 00000101 ; 5 DE
+ 11: 1 00000110 ; 6 EF
+ 12: 1 00000010 ; 2 AB
+ 13: 0 01000011 ; literal $43 C
+ 14: 1 00001101 ; 13 ABC
+ 15: 0 01000100 ; literal $44 D
+ 16: 1 00001111 ; 15 ABCD
+ 17: 0 01000101 ; literal $45 E
+ 18: 1 00000011 ; 3 BC
+ 19: 0 01000100 ; literal $44 D
+ 20: 1 00010011 ; 19 BCD?
+ 21: 1 00010010 ; 18 EB?
+ 22: 1 00000100 ; 4 CD
+ 23: 1 00000110 ; 6 EF
+ 24: 1 00000001 ; 1 end
+junk: 0000000
+
diff --git a/disasm/unalf14.osequates b/disasm/unalf14.osequates
new file mode 100644
index 0000000..79b2b77
--- /dev/null
+++ b/disasm/unalf14.osequates
@@ -0,0 +1,15 @@
+DOSVEC_lo := $000A ; 10 PROGRAM RUN VECTOR
+DOSVEC_hi := $000B
+SDMCTL := $022F ; 559 DMACTL SHADOW
+SHFLOC := $02BE ; 702
+MEMTOP_lo := $02E5 ; 741 END OF FREE RAM
+MEMTOP_hi := $02E6
+ICCOM := $0342 ; 834 ; Command byte (see C_* constants) (set by user)
+ICBAL := $0344 ; 836 ; Buffer address, LSB (set by user)
+ICBAH := $0345 ; 837 ; Buffer address, MSB (set by user)
+ICBLL := $0348 ; 840 ; Buffer length, LSB (set by user)
+ICBLH := $0349 ; 841 ; Buffer length, MSB (set by user)
+ICAX1 := $034A ; 842 ; AUX1 byte (2nd param in BASIC OPEN) (set by user)
+ICAX2 := $034B ; 843 ; AUX2 byte (4rd param in BASIC OPEN) (set by user)
+BOOTRG := $0700 ; 1792 PROGRAM AREA
+CIOV := $E456 ; 58454 ; Main CIO entry point!
diff --git a/disasm/unalf14.raw b/disasm/unalf14.raw
new file mode 100644
index 0000000..7ef8395
--- /dev/null
+++ b/disasm/unalf14.raw
Binary files differ
diff --git a/disasm/unalf14.raw.info b/disasm/unalf14.raw.info
new file mode 100644
index 0000000..eef7c80
--- /dev/null
+++ b/disasm/unalf14.raw.info
@@ -0,0 +1,221 @@
+global {
+ outputname "unalf14.s";
+ inputname "unalf14.raw";
+ startaddr $7173;
+ cpu "6502";
+ comments 3;
+ commentcolumn 32;
+};
+asminc { file "unalf14.osequates"; };
+
+label { name "zp_b0"; addr $b0; };
+label { name "zp_b1"; addr $b1; };
+label { name "acc16_l"; addr $b2; };
+label { name "acc16_h"; addr $b3; };
+label { name "zp_b4"; addr $b4; };
+label { name "zp_b5"; addr $b5; };
+label { name "stackptr_l"; addr $b6; };
+label { name "stackptr_h"; addr $b7; };
+label { name "zp_b8"; addr $b8; };
+label { name "zp_b9"; addr $b9; };
+label { name "outbuf_ptr_l"; addr $ba; };
+label { name "outbuf_ptr_h"; addr $bb; };
+label { name "zp_bc"; addr $bc; };
+label { name "zp_bd"; addr $bd; };
+label { name "zp_be"; addr $be; };
+label { name "zp_bf"; addr $bf; };
+
+range { type bytetable; start $7173; end $71b7; };
+range { type texttable; start $71b8; end $7359; };
+range { type texttable; start $7c32; end $7ca7; };
+range { type texttable; start $7f1d; end $7f3c; };
+range { type bytetable; start $7a79; end $7a83; };
+range { type texttable; start $7a84; end $7a8a; };
+range { type texttable; start $7925; end $7943; };
+range { type texttable; start $7d82; end $7d85; };
+range { type bytetable; start $7e38; end $7e4d; };
+range { type bytetable; start $7fca; end $7fdd; };
+range { type texttable; start $7e07; end $7e0b; };
+
+label { name "copy_cli_arg"; comment "copy sparta cli arg to linbuf, append EOL ($9b)"; addr $7c11; };
+label { name "cca_loop"; addr $7c16; };
+label { name "cca_append_eol"; addr $7c29; };
+label { name "have_infile"; comment "either a filename was passed as an argument (sparta), or enterd at the prompt"; addr $7386; };
+label { name "have_outdir"; comment "either a dir was passed as an argument (sparta), or enterd at the prompt"; addr $73ee; };
+label { name "get_cli_arg"; comment "spartados only; returns with C clear if there's an arg, or set if not"; addr $7bce; };
+label { name "sparta_exit"; addr $73b9; };
+label { name "shift_counter"; addr $71ac; };
+label { name "init_counters"; addr $780c; };
+label { name "out_ptr_hi_ok"; addr $7841; };
+label { name "out_len_hi_ok"; addr $7849; };
+label { name "outbuf_not_full"; addr $7898; };
+label { name "push_acc16"; comment "push 2 byte 'register' to software stack"; addr $78f6; };
+label { name "pop_acc16"; comment "pop 2 byte 'register' from software stack"; addr $7944; };
+label { name "store_outbyte"; comment "save decrunched byte in outbuf, update checksum, write outbuf if full"; addr $7826; };
+label { name "setup_io_bufs"; addr $7978; };
+label { name "cleanup_and_exit"; addr $787f; };
+label { name "init_outbuf"; addr $7886; };
+label { name "alf_header"; comment "arc/alf signature, $1a"; addr $718d; };
+label { name "alf_hdr_sig"; comment "alf signature, $0f"; addr $718e; };
+label { name "alf_hdr_filename"; comment "null-terminated compressed filename, 13 bytes"; addr $718f; };
+label { name "alf_hdr_compsize0"; comment "compressed size, 4 bytes, LSB first"; addr $719c; };
+label { name "alf_hdr_compsize1"; addr $719d; };
+label { name "alf_hdr_compsize2"; addr $719e; };
+label { name "alf_hdr_compsize3"; comment "always 0, archived file never >= 16MB"; addr $719f; };
+label { name "alf_hdr_date0"; comment "2 bytes, MS-DOS date format (see Arcinfo)"; addr $71a0; };
+label { name "alf_hdr_date1"; addr $71a1; };
+label { name "alf_hdr_time0"; comment "2 bytes, MS-DOS time format (see Arcinfo)"; addr $71a2; };
+label { name "alf_hdr_time1"; addr $71a3; };
+label { name "alf_hdr_cksum_l"; comment "CRC stored in file header"; addr $71a4; };
+label { name "alf_hdr_cksum_h"; addr $71a5; };
+label { name "alf_hdr_origsize0"; comment "uncompressed size, 4 bytes, LSB first"; addr $71a6; };
+label { name "alf_hdr_origsize1"; addr $71a7; };
+label { name "alf_hdr_origsize2"; addr $71a8; };
+label { name "alf_hdr_origsize3"; comment "last byte in alf header"; addr $71a9; };
+
+label { name "output_dir"; addr $7050; };
+label { name "copy_outdir"; addr $740d; };
+label { name "cod_loop"; addr $7410; };
+
+label { name "cksum_ok"; addr $75da; };
+label { name "uncrunch_blk"; addr $75db; };
+label { name "alfext"; addr $7e07; };
+label { name "entrypoint"; addr $7fc7; };
+label { name "startup"; addr $735a; };
+label { name "open_read"; comment "X = IOCB<<4, A=<buf, Y=>buf"; addr $7b76; };
+label { name "open_write"; addr $7b7b; };
+label { name "open_update"; comment "not used?"; addr $7b80; };
+label { name "open_append"; comment "not used?"; addr $7b85; };
+label { name "open_dir"; addr $7b8a; };
+label { name "finish_open"; comment "#$03 in A = CIO OPEN command"; addr $7b8d; };
+label { name "close_iocb"; addr $7b48; };
+label { name "putchar"; comment "print character in A, saves A/X/Y regs"; addr $7a9f; };
+label { name "stardotstar"; comment "filespec for directory"; addr $7d82; };
+label { name "exit"; addr $739a; };
+label { name "printstr"; comment "X = LSB, Y = MSB of string. SELF MODIFYING!"; addr $7a8b; };
+label { name "printstr_done"; addr $7a9e; };
+label { name "printstr_loop"; comment "$B9 = LDA abs,y"; addr $7a93; };
+label { name "printstr_op_lo"; comment "gets modified"; addr $7a94; };
+label { name "printstr_op_hi"; comment "gets modified"; addr $7a95; };
+label { name "msg_skipping"; addr $7f1d; };
+label { name "emsg_locate"; addr $7f28; };
+label { name "pskp_loop"; addr $7f08; };
+label { name "pskp_done"; addr $7f13; };
+label { name "check_arc_sig"; comment "first byte of header is $1A, just like ARC"; addr $748c; };
+label { name "check_alf_sig"; comment "2nd byte of header (compression type) is $0F"; addr $749d; };
+
+label { name "readblock"; addr $7acf; };
+label { name "writeblock"; addr $7ad3; };
+label { name "do_block_io"; addr $7ad5; };
+label { name "save_SDMCTL"; addr $7173; };
+label { name "MEMLO_hi"; addr $02e8; };
+
+label { name "buf_adr_l"; addr $7a7b; };
+label { name "buf_adr_h"; addr $7a7c; };
+label { name "buf_len_l"; addr $7a7d; };
+label { name "buf_len_h"; addr $7a7e; };
+label { name "save_x"; addr $7a81; };
+label { name "save_y"; addr $7a82; };
+label { name "save_a"; addr $7a83; };
+label { name "toupper"; addr $7b51; };
+label { name "le_z"; addr $7b56; };
+label { name "ge_a"; addr $7b5b; };
+label { name "open_kdev"; addr $7bb4; };
+label { name "close_kdev"; addr $7bc3; };
+label { name "getc0"; comment "read 1 byte from E: (IOCB0)"; addr $7b06; };
+label { name "getcx"; addr $7b08; };
+label { name "getline"; comment "read a line of input from E:"; addr $7b19; };
+label { name "nextchar"; addr $7b1e; };
+label { name "do_backspace"; addr $7b40; };
+label { name "getline_done"; addr $7b39; };
+label { name "linbuf_idx"; addr $7a79; };
+label { name "linbuf"; addr $7120; };
+label { name "ucase_linbuf"; addr $7b5e; };
+label { name "ucase_linbuf_loop"; addr $7b69; };
+label { name "ucase_linbuf_done"; addr $7b75; };
+label { name "disable_screen"; addr $7432; };
+label { name "open_input"; addr $743b; };
+label { name "disable_screen_flag"; addr $71b3; };
+label { name "read_alf_header"; comment "29 ($1D) bytes, read into alf_header"; addr $7450; };
+
+label { name "copy_filename"; addr $73c4; };
+label { name "copy_filename_loop"; addr $73d0; };
+label { name "ensure_d_prefix"; addr $7dcf; };
+label { name "ensure_suffix"; addr $7e0c; };
+label { name "input_file"; addr $7000; };
+label { name "lday_input_file"; comment "lda #<input_file ; ldy #>input_file"; addr $743d; };
+label { name "open_fileadr_l"; addr $7a7f; };
+label { name "open_fileadr_h"; addr $7a80; };
+label { name "kdev"; addr $7a84; };
+label { name "p3dev"; comment "unused?"; addr $7a87; };
+label { name "lday_kdev"; comment "lda #<kdev ; ldy #>kdev"; addr $7bbb; };
+label { name "outfile_l"; addr $70a0; };
+label { name "outfile_h"; addr $70a1; };
+label { name "open_outfile"; comment "lda #<outfile ; ldy #>outfile"; addr $750d; };
+label { name "uncrunch_file"; addr $754e; };
+label { name "next_header"; addr $7546; };
+label { name "cksum_l"; comment "calculated"; addr $717f; };
+label { name "cksum_h"; addr $7180; };
+label { name "write_output"; addr $79c0; };
+label { name "have_output"; addr $79c9; };
+label { name "outbuf_adr_l"; addr $7187; };
+label { name "outbuf_adr_h"; addr $7188; };
+label { name "outbuf_len_l"; addr $71b1; };
+label { name "outbuf_len_h"; addr $71b2; };
+label { name "inbuf_adr_l"; addr $7185; };
+label { name "inbuf_adr_h"; addr $7186; };
+label { name "inbuf_len_l"; addr $7183; };
+label { name "inbuf_len_h"; addr $7184; };
+
+# text strings:
+label { name "msg_banner"; addr $71b8; };
+label { name "prompt_infile"; addr $71dd; };
+label { name "prompt_outdir"; addr $71f2; };
+label { name "msg_complete"; addr $7206; };
+label { name "msg_uncrunching"; addr $721b; };
+label { name "prompt_screen_off"; addr $7229; };
+label { name "emsg_not_alf"; addr $7241; };
+label { name "emsg_outdir_too_long"; addr $7259; };
+label { name "emsg_outdir_invalid"; addr $7277; };
+label { name "emsg_open_input"; addr $7294; };
+label { name "emsg_eof_extra"; addr $72ae; };
+label { name "emsg_write_output"; addr $72d5; };
+label { name "emsg_read_input"; addr $72f0; };
+label { name "emsg_input_overrun"; addr $730a; };
+label { name "emsg_checksum"; addr $7320; };
+label { name "emsg_memlo"; addr $7338; };
+label { name "emsg_stk_overrun"; addr $7925; };
+label { name "emsg_stk_underrun"; addr $7934; };
+
+label { name "emsg_read_main_dir"; addr $7c32; };
+label { name "emsg_credir_failed"; addr $7c50; };
+label { name "emsg_outpath_build"; addr $7c72; };
+label { name "msg_credir_issued"; addr $7c8f; };
+
+# code that prints text strings:
+label { name "print_emsg_locate"; comment "ldx #<emsg_locate : ldy #>emsg_locate"; addr $7f9f; };
+label { name "print_msg_skipping"; comment "ldx #<msg_skipping : ldy #>msg_skipping"; addr $7eff; };
+label { name "print_emsg_outpath_build"; comment "ldx #<emsg_outpath_build : ldy #>emsg_outpath_build"; addr $7cca; };
+label { name "print_emsg_read_main_dir"; comment "ldx #<emsg_read_main_dir : ldy #>emsg_read_main_dir"; addr $7cd4; };
+label { name "print_msg_credir_issued"; comment "ldx #<msg_credir_issued : ldy #>msg_credir_issued"; addr $7d2d; };
+label { name "print_emsg_credir_failed"; comment "ldx #<emsg_credir_failed : ldy #>emsg_credir_failed"; addr $7d61; };
+
+label { name "print_msg_banner"; comment "ldx #<msg_banner : ldy #>msg_banner"; addr $736a; };
+label { name "print_prompt_infile"; comment "ldx #<prompt_infile : ldy #>prompt_infile"; addr $738b; };
+label { name "print_msg_complete"; comment "ldx #<msg_complete : ldy #>msg_complete"; addr $739d; };
+label { name "print_emsg_memlo"; comment "ldx #<emsg_memlo : ldy #>emsg_memlo"; addr $73ba; };
+label { name "print_prompt_outdir"; comment "ldx #<prompt_outdir : ldy #>prompt_outdir"; addr $73e4; };
+label { name "print_emsg_outdir_invalid2"; comment "ldx #<emsg_outdir_invalid : ldy #>emsg_outdir_invalid"; addr $7403; };
+label { name "print_prompt_screen_off"; comment "ldx #<prompt_screen_off : ldy #>prompt_screen_off"; addr $741e; };
+label { name "print_emsg_open_input"; comment "ldx #<emsg_open_input : ldy #>emsg_open_input"; addr $7446; };
+label { name "print_emsg_eof_extra"; comment "ldx #<emsg_eof_extra : ldy #>emsg_eof_extra"; addr $7482; };
+label { name "print_emsg_not_alf"; comment "ldx #<emsg_not_alf : ldy #>emsg_not_alf"; addr $7493; };
+label { name "print_emsg_outdir_too_long"; comment "ldx #<emsg_outdir_too_long : ldy #>emsg_outdir_too_long"; addr $74bd; };
+label { name "print_emsg_outdir_invalid"; comment "ldx #<emsg_outdir_invalid : ldy #>emsg_outdir_invalid"; addr $74d5; };
+label { name "print_emsg_write_output"; comment "ldx #<emsg_write_output : ldy #>emsg_write_output"; addr $7518; };
+label { name "print_msg_uncrunching"; comment "ldx #<msg_uncrunching : ldy #>msg_uncrunching"; addr $7522; };
+label { name "print_filename"; comment "ldx #<outfile ; ldy #>outfile"; addr $7529; };
+label { name "print_emsg_checksum"; comment "ldx #<emsg_checksum : ldy #>emsg_checksum"; addr $75d3; };
+label { name "print_emsg_write_output_2"; comment "ldx #<emsg_checksum : ldy #>emsg_checksum"; addr $7878; };
+label { name "print_emsg_stk_overrun"; comment "ldx #<emsg_stk_overrun : ldy #>emsg_stk_overrun"; addr $7912; };
+label { name "print_emsg_stk_underrun"; comment "ldx #<emsg_stk_underrun : ldy #>emsg_stk_underrun"; addr $7962; };
diff --git a/disasm/unalf14.s b/disasm/unalf14.s
new file mode 100644
index 0000000..ecf8147
--- /dev/null
+++ b/disasm/unalf14.s
@@ -0,0 +1,1932 @@
+; da65 V2.19 - N/A
+; Created: 2025-11-04 02:06:02
+; Input file: unalf14.raw
+; Page: 1
+
+
+ .setcpu "6502"
+
+; ----------------------------------------------------------------------------
+DOSVEC_lo := $000A ; 10 PROGRAM RUN VECTOR
+DOSVEC_hi := $000B
+zp_b0 := $00B0
+zp_b1 := $00B1
+acc16_l := $00B2
+acc16_h := $00B3
+zp_b4 := $00B4
+zp_b5 := $00B5
+stackptr_l := $00B6
+stackptr_h := $00B7
+zp_b8 := $00B8
+zp_b9 := $00B9
+outbuf_ptr_l := $00BA
+outbuf_ptr_h := $00BB
+zp_bc := $00BC
+zp_bd := $00BD
+zp_be := $00BE
+zp_bf := $00BF
+SDMCTL := $022F ; 559 DMACTL SHADOW
+SHFLOC := $02BE ; 702
+MEMTOP_lo := $02E5 ; 741 END OF FREE RAM
+MEMTOP_hi := $02E6
+MEMLO_hi := $02E8
+ICCOM := $0342 ; 834 ; Command byte (see C_* constants) (set by user)
+ICBAL := $0344 ; 836 ; Buffer address, LSB (set by user)
+ICBAH := $0345 ; 837 ; Buffer address, MSB (set by user)
+ICBLL := $0348 ; 840 ; Buffer length, LSB (set by user)
+ICBLH := $0349 ; 841 ; Buffer length, MSB (set by user)
+ICAX1 := $034A ; 842 ; AUX1 byte (2nd param in BASIC OPEN) (set by user)
+ICAX2 := $034B ; 843 ; AUX2 byte (4rd param in BASIC OPEN) (set by user)
+BOOTRG := $0700 ; 1792 PROGRAM AREA
+input_file := $7000
+output_dir := $7050
+outfile_l := $70A0
+outfile_h := $70A1
+linbuf := $7120
+CIOV := $E456 ; 58454 ; Main CIO entry point!
+; ----------------------------------------------------------------------------
+save_SDMCTL:
+ .byte $00 ; 7173 00
+L7174: .byte $00 ; 7174 00
+L7175: .byte $00 ; 7175 00
+L7176: .byte $00 ; 7176 00
+L7177: .byte $00 ; 7177 00
+L7178: .byte $00 ; 7178 00
+L7179: .byte $00 ; 7179 00
+L717A: .byte $00 ; 717A 00
+L717B: .byte $00 ; 717B 00
+L717C: .byte $00 ; 717C 00
+L717D: .byte $00 ; 717D 00
+L717E: .byte $00 ; 717E 00
+; calculated
+cksum_l:.byte $00 ; 717F 00
+cksum_h:.byte $00 ; 7180 00
+L7181: .byte $00 ; 7181 00
+L7182: .byte $30 ; 7182 30
+inbuf_len_l:
+ .byte $00 ; 7183 00
+inbuf_len_h:
+ .byte $00 ; 7184 00
+inbuf_adr_l:
+ .byte $00 ; 7185 00
+inbuf_adr_h:
+ .byte $00 ; 7186 00
+outbuf_adr_l:
+ .byte $00 ; 7187 00
+outbuf_adr_h:
+ .byte $00 ; 7188 00
+L7189: .byte $00 ; 7189 00
+L718A: .byte $00 ; 718A 00
+L718B: .byte $00 ; 718B 00
+L718C: .byte $00 ; 718C 00
+; arc/alf signature, $1a
+alf_header:
+ .byte $00 ; 718D 00
+; alf signature, $0f
+alf_hdr_sig:
+ .byte $00 ; 718E 00
+; null-terminated compressed filename, 13 bytes
+alf_hdr_filename:
+ .byte $20,$20,$20,$20,$20,$20,$20,$20; 718F 20 20 20 20 20 20 20 20
+ .byte $20,$20,$20,$20,$20; 7197 20 20 20 20 20
+; compressed size, 4 bytes, LSB first
+alf_hdr_compsize0:
+ .byte $00 ; 719C 00
+alf_hdr_compsize1:
+ .byte $00 ; 719D 00
+alf_hdr_compsize2:
+ .byte $00 ; 719E 00
+; always 0, archived file never >= 16MB
+alf_hdr_compsize3:
+ .byte $00 ; 719F 00
+; 2 bytes, MS-DOS date format (see Arcinfo)
+alf_hdr_date0:
+ .byte $00 ; 71A0 00
+alf_hdr_date1:
+ .byte $00 ; 71A1 00
+; 2 bytes, MS-DOS time format (see Arcinfo)
+alf_hdr_time0:
+ .byte $00 ; 71A2 00
+alf_hdr_time1:
+ .byte $00 ; 71A3 00
+; CRC stored in file header
+alf_hdr_cksum_l:
+ .byte $00 ; 71A4 00
+alf_hdr_cksum_h:
+ .byte $00 ; 71A5 00
+; uncompressed size, 4 bytes, LSB first
+alf_hdr_origsize0:
+ .byte $00 ; 71A6 00
+alf_hdr_origsize1:
+ .byte $00 ; 71A7 00
+alf_hdr_origsize2:
+ .byte $00 ; 71A8 00
+; last byte in alf header
+alf_hdr_origsize3:
+ .byte $00 ; 71A9 00
+L71AA: .byte $00 ; 71AA 00
+L71AB: .byte $00 ; 71AB 00
+shift_counter:
+ .byte $09 ; 71AC 09
+L71AD: .byte $00 ; 71AD 00
+L71AE: .byte $02 ; 71AE 02
+L71AF: .byte $00 ; 71AF 00
+L71B0: .byte $00 ; 71B0 00
+outbuf_len_l:
+ .byte $00 ; 71B1 00
+outbuf_len_h:
+ .byte $00 ; 71B2 00
+disable_screen_flag:
+ .byte $00 ; 71B3 00
+L71B4: .byte $00 ; 71B4 00
+L71B5: .byte $00 ; 71B5 00
+L71B6: .byte $00 ; 71B6 00
+L71B7: .byte $00 ; 71B7 00
+; ----------------------------------------------------------------------------
+msg_banner:
+ .byte "}V1.4 ALFUNCRUNCH 07/10"; 71B8 7D 56 31 2E 34 20 20 20
+ ; 71C0 20 20 41 4C 46 55 4E 43
+ ; 71C8 52 55 4E 43 48 20 20 20
+ ; 71D0 20 20 30 37 2F 31 30
+ .byte "/88" ; 71D7 2F 38 38
+ .byte $9B,$9B,$00 ; 71DA 9B 9B 00
+prompt_infile:
+ .byte "File to decompress:"; 71DD 46 69 6C 65 20 74 6F 20
+ ; 71E5 64 65 63 6F 6D 70 72 65
+ ; 71ED 73 73 3A
+ .byte $9B,$00 ; 71F0 9B 00
+prompt_outdir:
+ .byte "Output Directory: "; 71F2 4F 75 74 70 75 74 20 44
+ ; 71FA 69 72 65 63 74 6F 72 79
+ ; 7202 3A 20
+ .byte $9B,$00 ; 7204 9B 00
+msg_complete:
+ .byte "Processing complete"; 7206 50 72 6F 63 65 73 73 69
+ ; 720E 6E 67 20 63 6F 6D 70 6C
+ ; 7216 65 74 65
+ .byte $9B,$00 ; 7219 9B 00
+msg_uncrunching:
+ .byte "Uncrunching: "; 721B 55 6E 63 72 75 6E 63 68
+ ; 7223 69 6E 67 3A 20
+ .byte $00 ; 7228 00
+prompt_screen_off:
+ .byte "Screen off for speed ? "; 7229 53 63 72 65 65 6E 20 6F
+ ; 7231 66 66 20 66 6F 72 20 73
+ ; 7239 70 65 65 64 20 3F 20
+ .byte $00 ; 7240 00
+emsg_not_alf:
+ .byte "Not an AlfCrunch file!"; 7241 4E 6F 74 20 61 6E 20 41
+ ; 7249 6C 66 43 72 75 6E 63 68
+ ; 7251 20 66 69 6C 65 21
+ .byte $9B,$00 ; 7257 9B 00
+emsg_outdir_too_long:
+ .byte "Output Directory Is Too Long"; 7259 4F 75 74 70 75 74 20 44
+ ; 7261 69 72 65 63 74 6F 72 79
+ ; 7269 20 49 73 20 54 6F 6F 20
+ ; 7271 4C 6F 6E 67
+ .byte $9B,$00 ; 7275 9B 00
+emsg_outdir_invalid:
+ .byte "Output Directory Is Invalid"; 7277 4F 75 74 70 75 74 20 44
+ ; 727F 69 72 65 63 74 6F 72 79
+ ; 7287 20 49 73 20 49 6E 76 61
+ ; 728F 6C 69 64
+ .byte $9B,$00 ; 7292 9B 00
+emsg_open_input:
+ .byte "Error Opening Input File"; 7294 45 72 72 6F 72 20 4F 70
+ ; 729C 65 6E 69 6E 67 20 49 6E
+ ; 72A4 70 75 74 20 46 69 6C 65
+ .byte $9B,$00 ; 72AC 9B 00
+emsg_eof_extra:
+ .byte "Extra bytes at EOF. Don't add t"; 72AE 45 78 74 72 61 20 62 79
+ ; 72B6 74 65 73 20 61 74 20 45
+ ; 72BE 4F 46 2E 20 44 6F 6E 27
+ ; 72C6 74 20 61 64 64 20 74
+ .byte "o file" ; 72CD 6F 20 66 69 6C 65
+ .byte $9B,$00 ; 72D3 9B 00
+emsg_write_output:
+ .byte "Error writing output file"; 72D5 45 72 72 6F 72 20 77 72
+ ; 72DD 69 74 69 6E 67 20 6F 75
+ ; 72E5 74 70 75 74 20 66 69 6C
+ ; 72ED 65
+ .byte $9B,$00 ; 72EE 9B 00
+emsg_read_input:
+ .byte "Error reading input file"; 72F0 45 72 72 6F 72 20 72 65
+ ; 72F8 61 64 69 6E 67 20 69 6E
+ ; 7300 70 75 74 20 66 69 6C 65
+ .byte $9B,$00 ; 7308 9B 00
+emsg_input_overrun:
+ .byte "Input buffer overrun"; 730A 49 6E 70 75 74 20 62 75
+ ; 7312 66 66 65 72 20 6F 76 65
+ ; 731A 72 72 75 6E
+ .byte $9B,$00 ; 731E 9B 00
+emsg_checksum:
+ .byte "File checksum in error"; 7320 46 69 6C 65 20 63 68 65
+ ; 7328 63 6B 73 75 6D 20 69 6E
+ ; 7330 20 65 72 72 6F 72
+ .byte $9B,$00 ; 7336 9B 00
+emsg_memlo:
+ .byte "Error, memlo must be under $300"; 7338 45 72 72 6F 72 2C 20 6D
+ ; 7340 65 6D 6C 6F 20 6D 75 73
+ ; 7348 74 20 62 65 20 75 6E 64
+ ; 7350 65 72 20 24 33 30 30
+ .byte "0" ; 7357 30
+ .byte $9B,$00 ; 7358 9B 00
+; ----------------------------------------------------------------------------
+startup:jsr open_kdev ; 735A 20 B4 7B
+ lda SDMCTL ; 735D AD 2F 02
+ sta save_SDMCTL ; 7360 8D 73 71
+ lda MEMLO_hi ; 7363 AD E8 02
+ cmp #$30 ; 7366 C9 30
+ bcs print_emsg_memlo; 7368 B0 50
+; ldx #<msg_banner : ldy #>msg_banner
+print_msg_banner:
+ ldx #$B8 ; 736A A2 B8
+ ldy #$71 ; 736C A0 71
+ jsr printstr ; 736E 20 8B 7A
+ lda #$40 ; 7371 A9 40
+ sta SHFLOC ; 7373 8D BE 02
+ jsr get_cli_arg ; 7376 20 CE 7B
+ bcs have_infile ; 7379 B0 0B
+ jsr copy_cli_arg ; 737B 20 11 7C
+ lda #$01 ; 737E A9 01
+ sta disable_screen_flag; 7380 8D B3 71
+ jmp L7395 ; 7383 4C 95 73
+
+; ----------------------------------------------------------------------------
+; either a filename was passed as an argument (sparta), or enterd at the prompt
+have_infile:
+ lda #$9B ; 7386 A9 9B
+ jsr putchar ; 7388 20 9F 7A
+; ldx #<prompt_infile : ldy #>prompt_infile
+print_prompt_infile:
+ ldx #$DD ; 738B A2 DD
+ ldy #$71 ; 738D A0 71
+ jsr printstr ; 738F 20 8B 7A
+ jsr getline ; 7392 20 19 7B
+L7395: lda linbuf_idx ; 7395 AD 79 7A
+ bne copy_filename ; 7398 D0 2A
+exit: jsr close_kdev ; 739A 20 C3 7B
+; ldx #<msg_complete : ldy #>msg_complete
+print_msg_complete:
+ ldx #$06 ; 739D A2 06
+ ldy #$72 ; 739F A0 72
+ jsr printstr ; 73A1 20 8B 7A
+ lda save_SDMCTL ; 73A4 AD 73 71
+ sta SDMCTL ; 73A7 8D 2F 02
+ ldx #$30 ; 73AA A2 30
+ jsr close_iocb ; 73AC 20 48 7B
+ lda BOOTRG ; 73AF AD 00 07
+ cmp #$53 ; 73B2 C9 53
+ beq sparta_exit ; 73B4 F0 03
+ jmp (DOSVEC_lo) ; 73B6 6C 0A 00
+
+; ----------------------------------------------------------------------------
+sparta_exit:
+ rts ; 73B9 60
+
+; ----------------------------------------------------------------------------
+; ldx #<emsg_memlo : ldy #>emsg_memlo
+print_emsg_memlo:
+ ldx #$38 ; 73BA A2 38
+ ldy #$73 ; 73BC A0 73
+ jsr printstr ; 73BE 20 8B 7A
+ jmp exit ; 73C1 4C 9A 73
+
+; ----------------------------------------------------------------------------
+copy_filename:
+ jsr ucase_linbuf ; 73C4 20 5E 7B
+ jsr ensure_d_prefix; 73C7 20 CF 7D
+ jsr ensure_suffix ; 73CA 20 0C 7E
+ ldx linbuf_idx ; 73CD AE 79 7A
+copy_filename_loop:
+ lda linbuf,x ; 73D0 BD 20 71
+ sta input_file,x ; 73D3 9D 00 70
+ dex ; 73D6 CA
+ bpl copy_filename_loop; 73D7 10 F7
+ jsr get_cli_arg ; 73D9 20 CE 7B
+ bcs print_prompt_outdir; 73DC B0 06
+ jsr copy_cli_arg ; 73DE 20 11 7C
+ jmp have_outdir ; 73E1 4C EE 73
+
+; ----------------------------------------------------------------------------
+; ldx #<prompt_outdir : ldy #>prompt_outdir
+print_prompt_outdir:
+ ldx #$F2 ; 73E4 A2 F2
+ ldy #$71 ; 73E6 A0 71
+ jsr printstr ; 73E8 20 8B 7A
+ jsr getline ; 73EB 20 19 7B
+; either a dir was passed as an argument (sparta), or enterd at the prompt
+have_outdir:
+ lda linbuf_idx ; 73EE AD 79 7A
+ beq exit ; 73F1 F0 A7
+ lda #$00 ; 73F3 A9 00
+ sta L7174 ; 73F5 8D 74 71
+ jsr ucase_linbuf ; 73F8 20 5E 7B
+ jsr ensure_d_prefix; 73FB 20 CF 7D
+ jsr L7D86 ; 73FE 20 86 7D
+ bcc copy_outdir ; 7401 90 0A
+; ldx #<emsg_outdir_invalid : ldy #>emsg_outdir_invalid
+print_emsg_outdir_invalid2:
+ ldx #$77 ; 7403 A2 77
+ ldy #$72 ; 7405 A0 72
+ jsr printstr ; 7407 20 8B 7A
+ jmp exit ; 740A 4C 9A 73
+
+; ----------------------------------------------------------------------------
+copy_outdir:
+ ldx linbuf_idx ; 740D AE 79 7A
+cod_loop:
+ lda linbuf,x ; 7410 BD 20 71
+ sta output_dir,x ; 7413 9D 50 70
+ dex ; 7416 CA
+ bpl cod_loop ; 7417 10 F7
+ ldx disable_screen_flag; 7419 AE B3 71
+ bne disable_screen ; 741C D0 14
+; ldx #<prompt_screen_off : ldy #>prompt_screen_off
+print_prompt_screen_off:
+ ldx #$29 ; 741E A2 29
+ ldy #$72 ; 7420 A0 72
+ jsr printstr ; 7422 20 8B 7A
+ jsr getline ; 7425 20 19 7B
+ lda linbuf ; 7428 AD 20 71
+ jsr toupper ; 742B 20 51 7B
+ cmp #$59 ; 742E C9 59
+ bne open_input ; 7430 D0 09
+disable_screen:
+ ldx #$00 ; 7432 A2 00
+ sta SDMCTL ; 7434 8D 2F 02
+ inx ; 7437 E8
+ stx disable_screen_flag; 7438 8E B3 71
+open_input:
+ ldx #$10 ; 743B A2 10
+; lda #<input_file ; ldy #>input_file
+lday_input_file:
+ lda #$00 ; 743D A9 00
+ ldy #$70 ; 743F A0 70
+ jsr open_read ; 7441 20 76 7B
+ bpl read_alf_header; 7444 10 0A
+; ldx #<emsg_open_input : ldy #>emsg_open_input
+print_emsg_open_input:
+ ldx #$94 ; 7446 A2 94
+ ldy #$72 ; 7448 A0 72
+ jsr printstr ; 744A 20 8B 7A
+ jmp exit ; 744D 4C 9A 73
+
+; ----------------------------------------------------------------------------
+; 29 ($1D) bytes, read into alf_header
+read_alf_header:
+ lda #$8D ; 7450 A9 8D
+ sta buf_adr_l ; 7452 8D 7B 7A
+ lda #$71 ; 7455 A9 71
+ sta buf_adr_h ; 7457 8D 7C 7A
+ lda #$1D ; 745A A9 1D
+ sta buf_len_l ; 745C 8D 7D 7A
+ lda #$00 ; 745F A9 00
+ sta buf_len_h ; 7461 8D 7E 7A
+ ldx #$10 ; 7464 A2 10
+ jsr readblock ; 7466 20 CF 7A
+ bpl check_arc_sig ; 7469 10 21
+ cpy #$88 ; 746B C0 88
+ bne print_emsg_open_input; 746D D0 D7
+ lda buf_len_h ; 746F AD 7E 7A
+ bne check_arc_sig ; 7472 D0 18
+ lda buf_len_l ; 7474 AD 7D 7A
+ beq L7489 ; 7477 F0 10
+ cmp #$1D ; 7479 C9 1D
+ bcs check_arc_sig ; 747B B0 0F
+ lda L71B4 ; 747D AD B4 71
+ beq print_emsg_open_input; 7480 F0 C4
+; ldx #<emsg_eof_extra : ldy #>emsg_eof_extra
+print_emsg_eof_extra:
+ ldx #$AE ; 7482 A2 AE
+ ldy #$72 ; 7484 A0 72
+ jsr printstr ; 7486 20 8B 7A
+L7489: jmp exit ; 7489 4C 9A 73
+
+; ----------------------------------------------------------------------------
+; first byte of header is $1A, just like ARC
+check_arc_sig:
+ lda alf_header ; 748C AD 8D 71
+ cmp #$1A ; 748F C9 1A
+ beq check_alf_sig ; 7491 F0 0A
+; ldx #<emsg_not_alf : ldy #>emsg_not_alf
+print_emsg_not_alf:
+ ldx #$41 ; 7493 A2 41
+ ldy #$72 ; 7495 A0 72
+ jsr printstr ; 7497 20 8B 7A
+ jmp exit ; 749A 4C 9A 73
+
+; ----------------------------------------------------------------------------
+; 2nd byte of header (compression type) is $0F
+check_alf_sig:
+ lda alf_hdr_sig ; 749D AD 8E 71
+ cmp #$0F ; 74A0 C9 0F
+ bne print_emsg_not_alf; 74A2 D0 EF
+ lda L7174 ; 74A4 AD 74 71
+ bne L74B1 ; 74A7 D0 08
+ jsr L7CA9 ; 74A9 20 A9 7C
+ lda #$01 ; 74AC A9 01
+ sta L7174 ; 74AE 8D 74 71
+L74B1: ldx #$00 ; 74B1 A2 00
+L74B3: lda output_dir,x ; 74B3 BD 50 70
+ cmp #$9B ; 74B6 C9 9B
+ beq L74C7 ; 74B8 F0 0D
+ inx ; 74BA E8
+ bpl L74B3 ; 74BB 10 F6
+; ldx #<emsg_outdir_too_long : ldy #>emsg_outdir_too_long
+print_emsg_outdir_too_long:
+ ldx #$59 ; 74BD A2 59
+ ldy #$72 ; 74BF A0 72
+ jsr printstr ; 74C1 20 8B 7A
+ jmp exit ; 74C4 4C 9A 73
+
+; ----------------------------------------------------------------------------
+L74C7: lda output_dir,x ; 74C7 BD 50 70
+ cmp #$3E ; 74CA C9 3E
+ beq L74DF ; 74CC F0 11
+ cmp #$3A ; 74CE C9 3A
+ beq L74DF ; 74D0 F0 0D
+ dex ; 74D2 CA
+ bpl L74C7 ; 74D3 10 F2
+; ldx #<emsg_outdir_invalid : ldy #>emsg_outdir_invalid
+print_emsg_outdir_invalid:
+ ldx #$77 ; 74D5 A2 77
+ ldy #$72 ; 74D7 A0 72
+ jsr printstr ; 74D9 20 8B 7A
+ jmp exit ; 74DC 4C 9A 73
+
+; ----------------------------------------------------------------------------
+L74DF: txa ; 74DF 8A
+ tay ; 74E0 A8
+L74E1: lda output_dir,x ; 74E1 BD 50 70
+ sta outfile_l,x ; 74E4 9D A0 70
+ dex ; 74E7 CA
+ bpl L74E1 ; 74E8 10 F7
+ iny ; 74EA C8
+ ldx #$00 ; 74EB A2 00
+L74ED: lda alf_hdr_filename,x; 74ED BD 8F 71
+ sta outfile_l,y ; 74F0 99 A0 70
+ beq L74FB ; 74F3 F0 06
+ inx ; 74F5 E8
+ iny ; 74F6 C8
+ bpl L74ED ; 74F7 10 F4
+ bmi print_emsg_not_alf; 74F9 30 98
+L74FB: lda #$9B ; 74FB A9 9B
+ sta outfile_l,y ; 74FD 99 A0 70
+ lda #$00 ; 7500 A9 00
+ sta outfile_h,y ; 7502 99 A1 70
+ jsr L7EE4 ; 7505 20 E4 7E
+ bcc open_outfile ; 7508 90 03
+ jmp read_alf_header; 750A 4C 50 74
+
+; ----------------------------------------------------------------------------
+; lda #<outfile ; ldy #>outfile
+open_outfile:
+ ldx #$30 ; 750D A2 30
+ lda #$A0 ; 750F A9 A0
+ ldy #$70 ; 7511 A0 70
+ jsr open_write ; 7513 20 7B 7B
+ bpl print_msg_uncrunching; 7516 10 0A
+; ldx #<emsg_write_output : ldy #>emsg_write_output
+print_emsg_write_output:
+ ldx #$D5 ; 7518 A2 D5
+ ldy #$72 ; 751A A0 72
+ jsr printstr ; 751C 20 8B 7A
+ jmp exit ; 751F 4C 9A 73
+
+; ----------------------------------------------------------------------------
+; ldx #<msg_uncrunching : ldy #>msg_uncrunching
+print_msg_uncrunching:
+ ldx #$1B ; 7522 A2 1B
+ ldy #$72 ; 7524 A0 72
+ jsr printstr ; 7526 20 8B 7A
+; ldx #<outfile ; ldy #>outfile
+print_filename:
+ ldx #$A0 ; 7529 A2 A0
+ ldy #$70 ; 752B A0 70
+ jsr printstr ; 752D 20 8B 7A
+ lda #$01 ; 7530 A9 01
+ sta L71B4 ; 7532 8D B4 71
+ lda alf_hdr_compsize0; 7535 AD 9C 71
+ ora alf_hdr_compsize1; 7538 0D 9D 71
+ ora alf_hdr_compsize2; 753B 0D 9E 71
+ ora alf_hdr_compsize3; 753E 0D 9F 71
+ beq next_header ; 7541 F0 03
+ jsr uncrunch_file ; 7543 20 4E 75
+next_header:
+ ldx #$30 ; 7546 A2 30
+ jsr close_iocb ; 7548 20 48 7B
+ jmp read_alf_header; 754B 4C 50 74
+
+; ----------------------------------------------------------------------------
+uncrunch_file:
+ lda #$00 ; 754E A9 00
+ sta L71AF ; 7550 8D AF 71
+ sta L71B0 ; 7553 8D B0 71
+ sta outbuf_len_l ; 7556 8D B1 71
+ sta outbuf_len_h ; 7559 8D B2 71
+ sta L71B6 ; 755C 8D B6 71
+ sta L71B7 ; 755F 8D B7 71
+ sta L718C ; 7562 8D 8C 71
+ sta cksum_l ; 7565 8D 7F 71
+ sta cksum_h ; 7568 8D 80 71
+ lda #$09 ; 756B A9 09
+ sta shift_counter ; 756D 8D AC 71
+ lda #$00 ; 7570 A9 00
+ sta L71AD ; 7572 8D AD 71
+ lda #$02 ; 7575 A9 02
+ sta L71AE ; 7577 8D AE 71
+ lda #$00 ; 757A A9 00
+ sta stackptr_l ; 757C 85 B6
+ lda #$60 ; 757E A9 60
+ sta stackptr_h ; 7580 85 B7
+ jsr setup_io_bufs ; 7582 20 78 79
+ ldx #$10 ; 7585 A2 10
+ lda inbuf_adr_l ; 7587 AD 85 71
+ sta buf_adr_l ; 758A 8D 7B 7A
+ lda inbuf_adr_h ; 758D AD 86 71
+ sta buf_adr_h ; 7590 8D 7C 7A
+ lda inbuf_len_l ; 7593 AD 83 71
+ sta buf_len_l ; 7596 8D 7D 7A
+ lda inbuf_len_h ; 7599 AD 84 71
+ sta buf_len_h ; 759C 8D 7E 7A
+ jsr L7A19 ; 759F 20 19 7A
+ sty L718C ; 75A2 8C 8C 71
+ jsr L79E7 ; 75A5 20 E7 79
+ lda buf_len_l ; 75A8 AD 7D 7A
+ ora buf_len_h ; 75AB 0D 7E 7A
+ bne L75B1 ; 75AE D0 01
+ rts ; 75B0 60
+
+; ----------------------------------------------------------------------------
+L75B1: jsr L76D0 ; 75B1 20 D0 76
+ lda acc16_h ; 75B4 A5 B3
+ cmp #$01 ; 75B6 C9 01
+ bne uncrunch_blk ; 75B8 D0 21
+ lda acc16_l ; 75BA A5 B2
+ cmp #$01 ; 75BC C9 01
+ bne uncrunch_blk ; 75BE D0 1B
+ jsr write_output ; 75C0 20 C0 79
+ lda alf_hdr_cksum_l; 75C3 AD A4 71
+ cmp cksum_l ; 75C6 CD 7F 71
+ bne print_emsg_checksum; 75C9 D0 08
+ lda alf_hdr_cksum_h; 75CB AD A5 71
+ cmp cksum_h ; 75CE CD 80 71
+ beq cksum_ok ; 75D1 F0 07
+; ldx #<emsg_checksum : ldy #>emsg_checksum
+print_emsg_checksum:
+ ldx #$20 ; 75D3 A2 20
+ ldy #$73 ; 75D5 A0 73
+ jsr printstr ; 75D7 20 8B 7A
+cksum_ok:
+ rts ; 75DA 60
+
+; ----------------------------------------------------------------------------
+uncrunch_blk:
+ lda acc16_l ; 75DB A5 B2
+ cmp #$00 ; 75DD C9 00
+ bne L760B ; 75DF D0 2A
+ lda acc16_h ; 75E1 A5 B3
+ cmp #$01 ; 75E3 C9 01
+ bne L760B ; 75E5 D0 24
+ jsr init_counters ; 75E7 20 0C 78
+ jsr L76D0 ; 75EA 20 D0 76
+ lda acc16_l ; 75ED A5 B2
+ sta L717D ; 75EF 8D 7D 71
+ sta L7179 ; 75F2 8D 79 71
+ lda acc16_h ; 75F5 A5 B3
+ sta L717E ; 75F7 8D 7E 71
+ sta L717A ; 75FA 8D 7A 71
+ lda acc16_l ; 75FD A5 B2
+ sta L7177 ; 75FF 8D 77 71
+ sta L7178 ; 7602 8D 78 71
+ jsr store_outbyte ; 7605 20 26 78
+ jmp L75B1 ; 7608 4C B1 75
+
+; ----------------------------------------------------------------------------
+L760B: lda acc16_l ; 760B A5 B2
+ sta L717D ; 760D 8D 7D 71
+ sta L7175 ; 7610 8D 75 71
+ lda acc16_h ; 7613 A5 B3
+ sta L717E ; 7615 8D 7E 71
+ sta L7176 ; 7618 8D 76 71
+ lda acc16_h ; 761B A5 B3
+ cmp L717C ; 761D CD 7C 71
+ bcc L7641 ; 7620 90 1F
+ lda acc16_l ; 7622 A5 B2
+ cmp L717B ; 7624 CD 7B 71
+ bcc L7641 ; 7627 90 18
+ lda L7179 ; 7629 AD 79 71
+ sta acc16_l ; 762C 85 B2
+ sta L717D ; 762E 8D 7D 71
+ lda L717A ; 7631 AD 7A 71
+ sta acc16_h ; 7634 85 B3
+ sta L717E ; 7636 8D 7E 71
+ lda L7178 ; 7639 AD 78 71
+ sta acc16_l ; 763C 85 B2
+ jsr push_acc16 ; 763E 20 F6 78
+L7641: lda L717E ; 7641 AD 7E 71
+ beq L7670 ; 7644 F0 2A
+ lda L717D ; 7646 AD 7D 71
+ sta zp_b4 ; 7649 85 B4
+ lda L717E ; 764B AD 7E 71
+ sta zp_b5 ; 764E 85 B5
+ jsr L7899 ; 7650 20 99 78
+ ldy #$02 ; 7653 A0 02
+ lda (zp_b0),y ; 7655 B1 B0
+ sta acc16_l ; 7657 85 B2
+ jsr push_acc16 ; 7659 20 F6 78
+ ldy #$00 ; 765C A0 00
+ lda (zp_b0),y ; 765E B1 B0
+ sta acc16_l ; 7660 85 B2
+ sta L717D ; 7662 8D 7D 71
+ iny ; 7665 C8
+ lda (zp_b0),y ; 7666 B1 B0
+ sta acc16_h ; 7668 85 B3
+ sta L717E ; 766A 8D 7E 71
+ jmp L7641 ; 766D 4C 41 76
+
+; ----------------------------------------------------------------------------
+L7670: lda L717D ; 7670 AD 7D 71
+ sta acc16_l ; 7673 85 B2
+ sta L7178 ; 7675 8D 78 71
+ sta L7177 ; 7678 8D 77 71
+ lda L717E ; 767B AD 7E 71
+ sta acc16_h ; 767E 85 B3
+ jsr push_acc16 ; 7680 20 F6 78
+L7683: lda L71AF ; 7683 AD AF 71
+ ora L71B0 ; 7686 0D B0 71
+ beq L7694 ; 7689 F0 09
+ jsr pop_acc16 ; 768B 20 44 79
+ jsr store_outbyte ; 768E 20 26 78
+ jmp L7683 ; 7691 4C 83 76
+
+; ----------------------------------------------------------------------------
+L7694: jsr L78C2 ; 7694 20 C2 78
+ lda L7175 ; 7697 AD 75 71
+ sta acc16_l ; 769A 85 B2
+ sta L7179 ; 769C 8D 79 71
+ lda L7176 ; 769F AD 76 71
+ sta acc16_h ; 76A2 85 B3
+ sta L717A ; 76A4 8D 7A 71
+ lda L717B ; 76A7 AD 7B 71
+ sta zp_b4 ; 76AA 85 B4
+ lda L717C ; 76AC AD 7C 71
+ sta zp_b5 ; 76AF 85 B5
+ cmp L71AE ; 76B1 CD AE 71
+ bcc L76CD ; 76B4 90 17
+ lda zp_b4 ; 76B6 A5 B4
+ cmp L71AD ; 76B8 CD AD 71
+ bcc L76CD ; 76BB 90 10
+ lda shift_counter ; 76BD AD AC 71
+ cmp #$0C ; 76C0 C9 0C
+ beq L76CD ; 76C2 F0 09
+ inc shift_counter ; 76C4 EE AC 71
+ asl L71AD ; 76C7 0E AD 71
+ rol L71AE ; 76CA 2E AE 71
+L76CD: jmp L75B1 ; 76CD 4C B1 75
+
+; ----------------------------------------------------------------------------
+L76D0: lda L71B6 ; 76D0 AD B6 71
+ sta zp_b8 ; 76D3 85 B8
+ lda L71B7 ; 76D5 AD B7 71
+ sta zp_b9 ; 76D8 85 B9
+ ldx #$02 ; 76DA A2 02
+L76DC: lsr zp_b9 ; 76DC 46 B9
+ ror zp_b8 ; 76DE 66 B8
+ dex ; 76E0 CA
+ bpl L76DC ; 76E1 10 F9
+ lda zp_b8 ; 76E3 A5 B8
+ sta L71AA ; 76E5 8D AA 71
+ lda zp_b9 ; 76E8 A5 B9
+ sta L71AB ; 76EA 8D AB 71
+ lda inbuf_len_l ; 76ED AD 83 71
+ sec ; 76F0 38
+ sbc zp_b8 ; 76F1 E5 B8
+ sta zp_bc ; 76F3 85 BC
+ lda inbuf_len_h ; 76F5 AD 84 71
+ sbc zp_b9 ; 76F8 E5 B9
+ sta zp_bd ; 76FA 85 BD
+ lda zp_bd ; 76FC A5 BD
+ bne L770F ; 76FE D0 0F
+ lda zp_bc ; 7700 A5 BC
+ cmp #$03 ; 7702 C9 03
+ bcs L770F ; 7704 B0 09
+ ldx L718C ; 7706 AE 8C 71
+ bpl L771C ; 7709 10 11
+ cmp #$02 ; 770B C9 02
+ bcc L7712 ; 770D 90 03
+L770F: jmp L779B ; 770F 4C 9B 77
+
+; ----------------------------------------------------------------------------
+L7712: ldx #$0A ; 7712 A2 0A
+ ldy #$0A ; 7714 A0 0A
+ jsr printstr ; 7716 20 8B 7A
+ jmp cleanup_and_exit; 7719 4C 7F 78
+
+; ----------------------------------------------------------------------------
+L771C: tay ; 771C A8
+ dey ; 771D 88
+ ldx inbuf_adr_l ; 771E AE 85 71
+ stx zp_be ; 7721 86 BE
+ ldx inbuf_adr_h ; 7723 AE 86 71
+ stx zp_bf ; 7726 86 BF
+ lda inbuf_adr_l ; 7728 AD 85 71
+ clc ; 772B 18
+ adc zp_b8 ; 772C 65 B8
+ sta zp_b8 ; 772E 85 B8
+ lda inbuf_adr_h ; 7730 AD 86 71
+ adc zp_b9 ; 7733 65 B9
+ sta zp_b9 ; 7735 85 B9
+L7737: lda (zp_b8),y ; 7737 B1 B8
+ sta (zp_be),y ; 7739 91 BE
+ dey ; 773B 88
+ bpl L7737 ; 773C 10 F9
+ lda inbuf_len_l ; 773E AD 83 71
+ sec ; 7741 38
+ sbc zp_bc ; 7742 E5 BC
+ sta buf_len_l ; 7744 8D 7D 7A
+ lda inbuf_len_h ; 7747 AD 84 71
+ sbc #$00 ; 774A E9 00
+ sta buf_len_h ; 774C 8D 7E 7A
+ lda inbuf_adr_l ; 774F AD 85 71
+ clc ; 7752 18
+ adc zp_bc ; 7753 65 BC
+ sta buf_adr_l ; 7755 8D 7B 7A
+ lda inbuf_adr_h ; 7758 AD 86 71
+ adc #$00 ; 775B 69 00
+ sta buf_adr_h ; 775D 8D 7C 7A
+ ldx #$10 ; 7760 A2 10
+ jsr L7A19 ; 7762 20 19 7A
+ sty L718C ; 7765 8C 8C 71
+ bpl L7771 ; 7768 10 07
+ cpy #$88 ; 776A C0 88
+ beq L7771 ; 776C F0 03
+ jmp cleanup_and_exit; 776E 4C 7F 78
+
+; ----------------------------------------------------------------------------
+L7771: jsr L79E7 ; 7771 20 E7 79
+ lda L71AA ; 7774 AD AA 71
+ sta zp_b8 ; 7777 85 B8
+ lda L71AB ; 7779 AD AB 71
+ sta zp_b9 ; 777C 85 B9
+ ldx #$02 ; 777E A2 02
+L7780: asl zp_b8 ; 7780 06 B8
+ rol zp_b9 ; 7782 26 B9
+ dex ; 7784 CA
+ bpl L7780 ; 7785 10 F9
+ lda L71B6 ; 7787 AD B6 71
+ sec ; 778A 38
+ sbc zp_b8 ; 778B E5 B8
+ sta L71B6 ; 778D 8D B6 71
+ lda L71B7 ; 7790 AD B7 71
+ sbc zp_b9 ; 7793 E5 B9
+ sta L71B7 ; 7795 8D B7 71
+ jmp L76D0 ; 7798 4C D0 76
+
+; ----------------------------------------------------------------------------
+L779B: lda zp_b8 ; 779B A5 B8
+ sta zp_bc ; 779D 85 BC
+ clc ; 779F 18
+ adc inbuf_adr_l ; 77A0 6D 85 71
+ sta zp_b8 ; 77A3 85 B8
+ lda zp_b9 ; 77A5 A5 B9
+ sta zp_bd ; 77A7 85 BD
+ adc inbuf_adr_h ; 77A9 6D 86 71
+ sta zp_b9 ; 77AC 85 B9
+ ldy #$00 ; 77AE A0 00
+ lda L71B6 ; 77B0 AD B6 71
+ and #$07 ; 77B3 29 07
+ bne L77E1 ; 77B5 D0 2A
+ lda (zp_b8),y ; 77B7 B1 B8
+ sta acc16_h ; 77B9 85 B3
+ iny ; 77BB C8
+ lda (zp_b8),y ; 77BC B1 B8
+ sta acc16_l ; 77BE 85 B2
+L77C0: lda #$0F ; 77C0 A9 0F
+ sec ; 77C2 38
+ sbc shift_counter ; 77C3 ED AC 71
+ tax ; 77C6 AA
+L77C7: lsr acc16_h ; 77C7 46 B3
+ ror acc16_l ; 77C9 66 B2
+ dex ; 77CB CA
+ bpl L77C7 ; 77CC 10 F9
+ lda shift_counter ; 77CE AD AC 71
+ clc ; 77D1 18
+ adc L71B6 ; 77D2 6D B6 71
+ sta L71B6 ; 77D5 8D B6 71
+ lda #$00 ; 77D8 A9 00
+ adc L71B7 ; 77DA 6D B7 71
+ sta L71B7 ; 77DD 8D B7 71
+ rts ; 77E0 60
+
+; ----------------------------------------------------------------------------
+L77E1: ldx #$02 ; 77E1 A2 02
+L77E3: lda (zp_b8),y ; 77E3 B1 B8
+ sta L7189,x ; 77E5 9D 89 71
+ iny ; 77E8 C8
+ dex ; 77E9 CA
+ bpl L77E3 ; 77EA 10 F7
+ lda L71B6 ; 77EC AD B6 71
+ and #$07 ; 77EF 29 07
+ tax ; 77F1 AA
+ dex ; 77F2 CA
+L77F3: asl L7189 ; 77F3 0E 89 71
+ rol L718A ; 77F6 2E 8A 71
+ rol L718B ; 77F9 2E 8B 71
+ dex ; 77FC CA
+ bpl L77F3 ; 77FD 10 F4
+ lda L718A ; 77FF AD 8A 71
+ sta acc16_l ; 7802 85 B2
+ lda L718B ; 7804 AD 8B 71
+ sta acc16_h ; 7807 85 B3
+ jmp L77C0 ; 7809 4C C0 77
+
+; ----------------------------------------------------------------------------
+init_counters:
+ lda #$09 ; 780C A9 09
+ sta shift_counter ; 780E 8D AC 71
+ lda #$00 ; 7811 A9 00
+ sta L71AD ; 7813 8D AD 71
+ lda #$02 ; 7816 A9 02
+ sta L71AE ; 7818 8D AE 71
+ lda #$02 ; 781B A9 02
+ sta L717B ; 781D 8D 7B 71
+ lda #$01 ; 7820 A9 01
+ sta L717C ; 7822 8D 7C 71
+ rts ; 7825 60
+
+; ----------------------------------------------------------------------------
+; save decrunched byte in outbuf, update checksum, write outbuf if full
+store_outbyte:
+ ldy #$00 ; 7826 A0 00
+ lda acc16_l ; 7828 A5 B2
+ sta (outbuf_ptr_l),y; 782A 91 BA
+ clc ; 782C 18
+ adc cksum_l ; 782D 6D 7F 71
+ sta cksum_l ; 7830 8D 7F 71
+ lda #$00 ; 7833 A9 00
+ adc cksum_h ; 7835 6D 80 71
+ sta cksum_h ; 7838 8D 80 71
+ inc outbuf_ptr_l ; 783B E6 BA
+ bne out_ptr_hi_ok ; 783D D0 02
+ inc outbuf_ptr_h ; 783F E6 BB
+out_ptr_hi_ok:
+ inc outbuf_len_l ; 7841 EE B1 71
+ bne out_len_hi_ok ; 7844 D0 03
+ inc outbuf_len_h ; 7846 EE B2 71
+out_len_hi_ok:
+ lda outbuf_len_h ; 7849 AD B2 71
+ cmp inbuf_len_h ; 784C CD 84 71
+ bcc outbuf_not_full; 784F 90 47
+ lda outbuf_len_l ; 7851 AD B1 71
+ cmp inbuf_len_l ; 7854 CD 83 71
+ bcc outbuf_not_full; 7857 90 3F
+ lda outbuf_adr_l ; 7859 AD 87 71
+ sta buf_adr_l ; 785C 8D 7B 7A
+ lda outbuf_adr_h ; 785F AD 88 71
+ sta buf_adr_h ; 7862 8D 7C 7A
+ lda outbuf_len_l ; 7865 AD B1 71
+ sta buf_len_l ; 7868 8D 7D 7A
+ lda outbuf_len_h ; 786B AD B2 71
+ sta buf_len_h ; 786E 8D 7E 7A
+ ldx #$30 ; 7871 A2 30
+ jsr writeblock ; 7873 20 D3 7A
+ bpl init_outbuf ; 7876 10 0E
+; ldx #<emsg_checksum : ldy #>emsg_checksum
+print_emsg_write_output_2:
+ ldx #$D5 ; 7878 A2 D5
+ ldy #$72 ; 787A A0 72
+ jsr printstr ; 787C 20 8B 7A
+cleanup_and_exit:
+ pla ; 787F 68
+ pla ; 7880 68
+ pla ; 7881 68
+ pla ; 7882 68
+ jmp exit ; 7883 4C 9A 73
+
+; ----------------------------------------------------------------------------
+init_outbuf:
+ lda outbuf_adr_l ; 7886 AD 87 71
+ sta outbuf_ptr_l ; 7889 85 BA
+ lda outbuf_adr_h ; 788B AD 88 71
+ sta outbuf_ptr_h ; 788E 85 BB
+ lda #$00 ; 7890 A9 00
+ sta outbuf_len_l ; 7892 8D B1 71
+ sta outbuf_len_h ; 7895 8D B2 71
+outbuf_not_full:
+ rts ; 7898 60
+
+; ----------------------------------------------------------------------------
+L7899: lda zp_b4 ; 7899 A5 B4
+ sta zp_b0 ; 789B 85 B0
+ lda zp_b5 ; 789D A5 B5
+ sta zp_b1 ; 789F 85 B1
+ asl zp_b0 ; 78A1 06 B0
+ rol zp_b1 ; 78A3 26 B1
+ lda zp_b0 ; 78A5 A5 B0
+ clc ; 78A7 18
+ adc zp_b4 ; 78A8 65 B4
+ sta zp_b0 ; 78AA 85 B0
+ lda zp_b1 ; 78AC A5 B1
+ adc zp_b5 ; 78AE 65 B5
+ sta zp_b1 ; 78B0 85 B1
+ lda zp_b0 ; 78B2 A5 B0
+ clc ; 78B4 18
+ adc L7181 ; 78B5 6D 81 71
+ sta zp_b0 ; 78B8 85 B0
+ lda zp_b1 ; 78BA A5 B1
+ adc L7182 ; 78BC 6D 82 71
+ sta zp_b1 ; 78BF 85 B1
+ rts ; 78C1 60
+
+; ----------------------------------------------------------------------------
+L78C2: lda L717B ; 78C2 AD 7B 71
+ sta zp_b4 ; 78C5 85 B4
+ lda L717C ; 78C7 AD 7C 71
+ sta zp_b5 ; 78CA 85 B5
+ jsr L7899 ; 78CC 20 99 78
+ lda L7177 ; 78CF AD 77 71
+ sta acc16_l ; 78D2 85 B2
+ ldy #$02 ; 78D4 A0 02
+ sta (zp_b0),y ; 78D6 91 B0
+ lda L7179 ; 78D8 AD 79 71
+ sta acc16_l ; 78DB 85 B2
+ lda L717A ; 78DD AD 7A 71
+ sta acc16_h ; 78E0 85 B3
+ ldy #$00 ; 78E2 A0 00
+ lda acc16_l ; 78E4 A5 B2
+ sta (zp_b0),y ; 78E6 91 B0
+ iny ; 78E8 C8
+ lda acc16_h ; 78E9 A5 B3
+ sta (zp_b0),y ; 78EB 91 B0
+ inc L717B ; 78ED EE 7B 71
+ bne L78F5 ; 78F0 D0 03
+ inc L717C ; 78F2 EE 7C 71
+L78F5: rts ; 78F5 60
+
+; ----------------------------------------------------------------------------
+; push 2 byte 'register' to software stack
+push_acc16:
+ ldy #$00 ; 78F6 A0 00
+ lda acc16_l ; 78F8 A5 B2
+ sta (stackptr_l),y ; 78FA 91 B6
+ iny ; 78FC C8
+ lda acc16_h ; 78FD A5 B3
+ sta (stackptr_l),y ; 78FF 91 B6
+ lda stackptr_l ; 7901 A5 B6
+ clc ; 7903 18
+ adc #$02 ; 7904 69 02
+ sta stackptr_l ; 7906 85 B6
+ bcc L790C ; 7908 90 02
+ inc stackptr_h ; 790A E6 B7
+L790C: lda stackptr_h ; 790C A5 B7
+ cmp #$70 ; 790E C9 70
+ bcc L791C ; 7910 90 0A
+; ldx #<emsg_stk_overrun : ldy #>emsg_stk_overrun
+print_emsg_stk_overrun:
+ ldx #$25 ; 7912 A2 25
+ ldy #$79 ; 7914 A0 79
+ jsr printstr ; 7916 20 8B 7A
+ jmp cleanup_and_exit; 7919 4C 7F 78
+
+; ----------------------------------------------------------------------------
+L791C: inc L71AF ; 791C EE AF 71
+ bne L7924 ; 791F D0 03
+ inc L71B0 ; 7921 EE B0 71
+L7924: rts ; 7924 60
+
+; ----------------------------------------------------------------------------
+emsg_stk_overrun:
+ .byte "STACK OVERRUN"; 7925 53 54 41 43 4B 20 4F 56
+ ; 792D 45 52 52 55 4E
+ .byte $9B,$00 ; 7932 9B 00
+emsg_stk_underrun:
+ .byte "STACK UNDERRUN"; 7934 53 54 41 43 4B 20 55 4E
+ ; 793C 44 45 52 52 55 4E
+ .byte $9B,$00 ; 7942 9B 00
+; ----------------------------------------------------------------------------
+; pop 2 byte 'register' from software stack
+pop_acc16:
+ lda stackptr_l ; 7944 A5 B6
+ sec ; 7946 38
+ sbc #$02 ; 7947 E9 02
+ sta stackptr_l ; 7949 85 B6
+ lda stackptr_h ; 794B A5 B7
+ sbc #$00 ; 794D E9 00
+ sta stackptr_h ; 794F 85 B7
+ ldy #$00 ; 7951 A0 00
+ lda (stackptr_l),y ; 7953 B1 B6
+ sta acc16_l ; 7955 85 B2
+ iny ; 7957 C8
+ lda (stackptr_l),y ; 7958 B1 B6
+ sta acc16_h ; 795A 85 B3
+ lda stackptr_h ; 795C A5 B7
+ cmp #$60 ; 795E C9 60
+ bcs L796C ; 7960 B0 0A
+; ldx #<emsg_stk_underrun : ldy #>emsg_stk_underrun
+print_emsg_stk_underrun:
+ ldx #$34 ; 7962 A2 34
+ ldy #$79 ; 7964 A0 79
+ jsr printstr ; 7966 20 8B 7A
+ jmp cleanup_and_exit; 7969 4C 7F 78
+
+; ----------------------------------------------------------------------------
+L796C: lda L71AF ; 796C AD AF 71
+ bne L7974 ; 796F D0 03
+ dec L71B0 ; 7971 CE B0 71
+L7974: dec L71AF ; 7974 CE AF 71
+ rts ; 7977 60
+
+; ----------------------------------------------------------------------------
+setup_io_bufs:
+ lda MEMTOP_lo ; 7978 AD E5 02
+ sec ; 797B 38
+ sbc #$DC ; 797C E9 DC
+ sta inbuf_len_l ; 797E 8D 83 71
+ lda MEMTOP_hi ; 7981 AD E6 02
+ sbc #$7F ; 7984 E9 7F
+ sta inbuf_len_h ; 7986 8D 84 71
+ lsr inbuf_len_h ; 7989 4E 84 71
+ ror inbuf_len_l ; 798C 6E 83 71
+ lda inbuf_len_h ; 798F AD 84 71
+ cmp #$1F ; 7992 C9 1F
+ bcc L79A0 ; 7994 90 0A
+ lda #$00 ; 7996 A9 00
+ sta inbuf_len_l ; 7998 8D 83 71
+ lda #$1F ; 799B A9 1F
+ sta inbuf_len_h ; 799D 8D 84 71
+L79A0: lda #$DC ; 79A0 A9 DC
+ sta inbuf_adr_l ; 79A2 8D 85 71
+ lda #$7F ; 79A5 A9 7F
+ sta inbuf_adr_h ; 79A7 8D 86 71
+ lda #$DC ; 79AA A9 DC
+ clc ; 79AC 18
+ adc inbuf_len_l ; 79AD 6D 83 71
+ sta outbuf_adr_l ; 79B0 8D 87 71
+ sta outbuf_ptr_l ; 79B3 85 BA
+ lda #$7F ; 79B5 A9 7F
+ adc inbuf_len_h ; 79B7 6D 84 71
+ sta outbuf_adr_h ; 79BA 8D 88 71
+ sta outbuf_ptr_h ; 79BD 85 BB
+ rts ; 79BF 60
+
+; ----------------------------------------------------------------------------
+write_output:
+ lda outbuf_len_l ; 79C0 AD B1 71
+ ora outbuf_len_h ; 79C3 0D B2 71
+ bne have_output ; 79C6 D0 01
+ rts ; 79C8 60
+
+; ----------------------------------------------------------------------------
+have_output:
+ lda outbuf_adr_l ; 79C9 AD 87 71
+ sta buf_adr_l ; 79CC 8D 7B 7A
+ lda outbuf_adr_h ; 79CF AD 88 71
+ sta buf_adr_h ; 79D2 8D 7C 7A
+ lda outbuf_len_l ; 79D5 AD B1 71
+ sta buf_len_l ; 79D8 8D 7D 7A
+ lda outbuf_len_h ; 79DB AD B2 71
+ sta buf_len_h ; 79DE 8D 7E 7A
+ ldx #$30 ; 79E1 A2 30
+ jsr writeblock ; 79E3 20 D3 7A
+ rts ; 79E6 60
+
+; ----------------------------------------------------------------------------
+L79E7: lda buf_adr_l ; 79E7 AD 7B 7A
+ clc ; 79EA 18
+ adc buf_len_l ; 79EB 6D 7D 7A
+ sta zp_be ; 79EE 85 BE
+ lda buf_adr_h ; 79F0 AD 7C 7A
+ adc buf_len_h ; 79F3 6D 7E 7A
+ sta zp_bf ; 79F6 85 BF
+ ldx #$02 ; 79F8 A2 02
+ bne L7A0A ; 79FA D0 0E
+L79FC: ldy #$00 ; 79FC A0 00
+ tya ; 79FE 98
+ sta (zp_be),y ; 79FF 91 BE
+ inc zp_be ; 7A01 E6 BE
+ bne L7A07 ; 7A03 D0 02
+ inc zp_bf ; 7A05 E6 BF
+L7A07: dex ; 7A07 CA
+ bmi L7A18 ; 7A08 30 0E
+L7A0A: lda zp_bf ; 7A0A A5 BF
+ cmp outbuf_adr_h ; 7A0C CD 88 71
+ bcc L79FC ; 7A0F 90 EB
+ lda zp_be ; 7A11 A5 BE
+ cmp outbuf_adr_l ; 7A13 CD 87 71
+ bcc L79FC ; 7A16 90 E4
+L7A18: rts ; 7A18 60
+
+; ----------------------------------------------------------------------------
+L7A19: lda alf_hdr_compsize2; 7A19 AD 9E 71
+ ora alf_hdr_compsize3; 7A1C 0D 9F 71
+ beq L7A28 ; 7A1F F0 07
+L7A21: jsr readblock ; 7A21 20 CF 7A
+ jsr L7A5D ; 7A24 20 5D 7A
+ rts ; 7A27 60
+
+; ----------------------------------------------------------------------------
+L7A28: lda alf_hdr_compsize1; 7A28 AD 9D 71
+ cmp buf_len_h ; 7A2B CD 7E 7A
+ bcc L7A40 ; 7A2E 90 10
+ beq L7A34 ; 7A30 F0 02
+ bcs L7A21 ; 7A32 B0 ED
+L7A34: lda alf_hdr_compsize0; 7A34 AD 9C 71
+ cmp buf_len_l ; 7A37 CD 7D 7A
+ bcc L7A40 ; 7A3A 90 04
+ beq L7A40 ; 7A3C F0 02
+ bcs L7A21 ; 7A3E B0 E1
+L7A40: lda alf_hdr_compsize0; 7A40 AD 9C 71
+ sta buf_len_l ; 7A43 8D 7D 7A
+ lda alf_hdr_compsize1; 7A46 AD 9D 71
+ sta buf_len_h ; 7A49 8D 7E 7A
+ lda buf_len_l ; 7A4C AD 7D 7A
+ ora buf_len_h ; 7A4F 0D 7E 7A
+ beq L7A57 ; 7A52 F0 03
+ jsr readblock ; 7A54 20 CF 7A
+L7A57: ldy #$88 ; 7A57 A0 88
+ jsr L7A5D ; 7A59 20 5D 7A
+ rts ; 7A5C 60
+
+; ----------------------------------------------------------------------------
+L7A5D: lda alf_hdr_compsize0; 7A5D AD 9C 71
+ sec ; 7A60 38
+ sbc buf_len_l ; 7A61 ED 7D 7A
+ sta alf_hdr_compsize0; 7A64 8D 9C 71
+ lda alf_hdr_compsize1; 7A67 AD 9D 71
+ sbc buf_len_h ; 7A6A ED 7E 7A
+ sta alf_hdr_compsize1; 7A6D 8D 9D 71
+ lda alf_hdr_compsize2; 7A70 AD 9E 71
+ sbc #$00 ; 7A73 E9 00
+ sta alf_hdr_compsize2; 7A75 8D 9E 71
+ rts ; 7A78 60
+
+; ----------------------------------------------------------------------------
+linbuf_idx:
+ .byte $00,$00 ; 7A79 00 00
+buf_adr_l:
+ .byte $00 ; 7A7B 00
+buf_adr_h:
+ .byte $00 ; 7A7C 00
+buf_len_l:
+ .byte $00 ; 7A7D 00
+buf_len_h:
+ .byte $00 ; 7A7E 00
+open_fileadr_l:
+ .byte $00 ; 7A7F 00
+open_fileadr_h:
+ .byte $00 ; 7A80 00
+save_x: .byte $00 ; 7A81 00
+save_y: .byte $00 ; 7A82 00
+save_a: .byte $00 ; 7A83 00
+; ----------------------------------------------------------------------------
+kdev: .byte "K:" ; 7A84 4B 3A
+ .byte $9B ; 7A86 9B
+; unused?
+p3dev: .byte "P3:" ; 7A87 50 33 3A
+ .byte $9B ; 7A8A 9B
+; ----------------------------------------------------------------------------
+; X = LSB, Y = MSB of string. SELF MODIFYING!
+printstr:
+ stx printstr_op_lo ; 7A8B 8E 94 7A
+ sty printstr_op_hi ; 7A8E 8C 95 7A
+ ldy #$00 ; 7A91 A0 00
+; $B9 = LDA abs,y
+printstr_loop:
+ .byte $B9 ; 7A93 B9
+; gets modified
+printstr_op_lo:
+ .byte $93 ; 7A94 93
+; gets modified
+printstr_op_hi:
+ .byte $7A ; 7A95 7A
+ beq printstr_done ; 7A96 F0 06
+ jsr putchar ; 7A98 20 9F 7A
+ iny ; 7A9B C8
+ bne printstr_loop ; 7A9C D0 F5
+printstr_done:
+ rts ; 7A9E 60
+
+; ----------------------------------------------------------------------------
+; print character in A, saves A/X/Y regs
+putchar:stx save_x ; 7A9F 8E 81 7A
+ sty save_y ; 7AA2 8C 82 7A
+ sta save_a ; 7AA5 8D 83 7A
+ ldx #$00 ; 7AA8 A2 00
+ lda #$0B ; 7AAA A9 0B
+ sta ICCOM,x ; 7AAC 9D 42 03
+ lda #$00 ; 7AAF A9 00
+ sta ICBLL,x ; 7AB1 9D 48 03
+ sta ICBLH,x ; 7AB4 9D 49 03
+ lda save_a ; 7AB7 AD 83 7A
+ jsr CIOV ; 7ABA 20 56 E4
+ lda save_a ; 7ABD AD 83 7A
+ ldx save_x ; 7AC0 AE 81 7A
+ ldy save_y ; 7AC3 AC 82 7A
+ rts ; 7AC6 60
+
+; ----------------------------------------------------------------------------
+ lda #$05 ; 7AC7 A9 05
+ bne do_block_io ; 7AC9 D0 0A
+ lda #$09 ; 7ACB A9 09
+ bne do_block_io ; 7ACD D0 06
+readblock:
+ lda #$07 ; 7ACF A9 07
+ bne do_block_io ; 7AD1 D0 02
+writeblock:
+ lda #$0B ; 7AD3 A9 0B
+do_block_io:
+ sta ICCOM,x ; 7AD5 9D 42 03
+ lda buf_adr_l ; 7AD8 AD 7B 7A
+ sta ICBAL,x ; 7ADB 9D 44 03
+ lda buf_adr_h ; 7ADE AD 7C 7A
+ sta ICBAH,x ; 7AE1 9D 45 03
+ lda buf_len_l ; 7AE4 AD 7D 7A
+ sta ICBLL,x ; 7AE7 9D 48 03
+ lda buf_len_h ; 7AEA AD 7E 7A
+ sta ICBLH,x ; 7AED 9D 49 03
+ jsr CIOV ; 7AF0 20 56 E4
+ php ; 7AF3 08
+ lda ICBLL,x ; 7AF4 BD 48 03
+ sta buf_len_l ; 7AF7 8D 7D 7A
+ lda ICBLH,x ; 7AFA BD 49 03
+ sta buf_len_h ; 7AFD 8D 7E 7A
+ plp ; 7B00 28
+ rts ; 7B01 60
+
+; ----------------------------------------------------------------------------
+ ldx #$20 ; 7B02 A2 20
+ bne getcx ; 7B04 D0 02
+; read 1 byte from E: (IOCB0)
+getc0: ldx #$00 ; 7B06 A2 00
+getcx: lda #$07 ; 7B08 A9 07
+ sta ICCOM,x ; 7B0A 9D 42 03
+ lda #$00 ; 7B0D A9 00
+ sta ICBLL,x ; 7B0F 9D 48 03
+ sta ICBLH,x ; 7B12 9D 49 03
+ jsr CIOV ; 7B15 20 56 E4
+ rts ; 7B18 60
+
+; ----------------------------------------------------------------------------
+; read a line of input from E:
+getline:lda #$00 ; 7B19 A9 00
+ sta linbuf_idx ; 7B1B 8D 79 7A
+nextchar:
+ jsr getc0 ; 7B1E 20 06 7B
+ cmp #$7E ; 7B21 C9 7E
+ beq do_backspace ; 7B23 F0 1B
+ cmp #$9B ; 7B25 C9 9B
+ beq getline_done ; 7B27 F0 10
+ ldy linbuf_idx ; 7B29 AC 79 7A
+ cpy #$4E ; 7B2C C0 4E
+ bcs nextchar ; 7B2E B0 EE
+ sta linbuf,y ; 7B30 99 20 71
+ inc linbuf_idx ; 7B33 EE 79 7A
+ jmp nextchar ; 7B36 4C 1E 7B
+
+; ----------------------------------------------------------------------------
+getline_done:
+ ldy linbuf_idx ; 7B39 AC 79 7A
+ sta linbuf,y ; 7B3C 99 20 71
+ rts ; 7B3F 60
+
+; ----------------------------------------------------------------------------
+do_backspace:
+ dec linbuf_idx ; 7B40 CE 79 7A
+ lda #$7E ; 7B43 A9 7E
+ jmp nextchar ; 7B45 4C 1E 7B
+
+; ----------------------------------------------------------------------------
+close_iocb:
+ lda #$0C ; 7B48 A9 0C
+ sta ICCOM,x ; 7B4A 9D 42 03
+ jsr CIOV ; 7B4D 20 56 E4
+ rts ; 7B50 60
+
+; ----------------------------------------------------------------------------
+toupper:cmp #$7B ; 7B51 C9 7B
+ bcc le_z ; 7B53 90 01
+ rts ; 7B55 60
+
+; ----------------------------------------------------------------------------
+le_z: cmp #$61 ; 7B56 C9 61
+ bcs ge_a ; 7B58 B0 01
+ rts ; 7B5A 60
+
+; ----------------------------------------------------------------------------
+ge_a: sbc #$20 ; 7B5B E9 20
+ rts ; 7B5D 60
+
+; ----------------------------------------------------------------------------
+ucase_linbuf:
+ ldy linbuf_idx ; 7B5E AC 79 7A
+ beq ucase_linbuf_done; 7B61 F0 12
+ dey ; 7B63 88
+ tya ; 7B64 98
+ cmp #$52 ; 7B65 C9 52
+ bcs ucase_linbuf_done; 7B67 B0 0C
+ucase_linbuf_loop:
+ lda linbuf,y ; 7B69 B9 20 71
+ jsr toupper ; 7B6C 20 51 7B
+ sta linbuf,y ; 7B6F 99 20 71
+ dey ; 7B72 88
+ bpl ucase_linbuf_loop; 7B73 10 F4
+ucase_linbuf_done:
+ rts ; 7B75 60
+
+; ----------------------------------------------------------------------------
+; X = IOCB<<4, A=<buf, Y=>buf
+open_read:
+ pha ; 7B76 48
+ lda #$04 ; 7B77 A9 04
+ bne finish_open ; 7B79 D0 12
+open_write:
+ pha ; 7B7B 48
+ lda #$08 ; 7B7C A9 08
+ bne finish_open ; 7B7E D0 0D
+; not used?
+open_update:
+ pha ; 7B80 48
+ lda #$0C ; 7B81 A9 0C
+ bne finish_open ; 7B83 D0 08
+; not used?
+open_append:
+ pha ; 7B85 48
+ lda #$09 ; 7B86 A9 09
+ bne finish_open ; 7B88 D0 03
+open_dir:
+ pha ; 7B8A 48
+ lda #$06 ; 7B8B A9 06
+; #$03 in A = CIO OPEN command
+finish_open:
+ sta open_fileadr_l ; 7B8D 8D 7F 7A
+ sty open_fileadr_h ; 7B90 8C 80 7A
+ jsr close_iocb ; 7B93 20 48 7B
+ lda #$03 ; 7B96 A9 03
+ sta ICCOM,x ; 7B98 9D 42 03
+ lda open_fileadr_l ; 7B9B AD 7F 7A
+ sta ICAX1,x ; 7B9E 9D 4A 03
+ lda open_fileadr_h ; 7BA1 AD 80 7A
+ sta ICBAH,x ; 7BA4 9D 45 03
+ pla ; 7BA7 68
+ sta ICBAL,x ; 7BA8 9D 44 03
+ lda #$00 ; 7BAB A9 00
+ sta ICAX2,x ; 7BAD 9D 4B 03
+ jsr CIOV ; 7BB0 20 56 E4
+ rts ; 7BB3 60
+
+; ----------------------------------------------------------------------------
+open_kdev:
+ ldx #$10 ; 7BB4 A2 10
+ jsr close_iocb ; 7BB6 20 48 7B
+ ldx #$20 ; 7BB9 A2 20
+; lda #<kdev ; ldy #>kdev
+lday_kdev:
+ lda #$84 ; 7BBB A9 84
+ ldy #$7A ; 7BBD A0 7A
+ jsr open_read ; 7BBF 20 76 7B
+ rts ; 7BC2 60
+
+; ----------------------------------------------------------------------------
+close_kdev:
+ ldx #$10 ; 7BC3 A2 10
+ jsr close_iocb ; 7BC5 20 48 7B
+ ldx #$20 ; 7BC8 A2 20
+ jsr close_iocb ; 7BCA 20 48 7B
+ rts ; 7BCD 60
+
+; ----------------------------------------------------------------------------
+; spartados only; returns with C clear if there's an arg, or set if not
+get_cli_arg:
+ lda DOSVEC_lo ; 7BCE A5 0A
+ clc ; 7BD0 18
+ adc #$3F ; 7BD1 69 3F
+ sta zp_be ; 7BD3 85 BE
+ lda DOSVEC_hi ; 7BD5 A5 0B
+ adc #$00 ; 7BD7 69 00
+ sta zp_bf ; 7BD9 85 BF
+ lda BOOTRG ; 7BDB AD 00 07
+ cmp #$53 ; 7BDE C9 53
+ bne L7BF9 ; 7BE0 D0 17
+ lda L7CA8 ; 7BE2 AD A8 7C
+ beq L7BF9 ; 7BE5 F0 12
+ ldy L71B5 ; 7BE7 AC B5 71
+L7BEA: lda (zp_be),y ; 7BEA B1 BE
+ cmp #$9B ; 7BEC C9 9B
+ beq L7BF9 ; 7BEE F0 09
+ cmp #$20 ; 7BF0 C9 20
+ beq L7BFB ; 7BF2 F0 07
+ iny ; 7BF4 C8
+ cpy #$40 ; 7BF5 C0 40
+ bcc L7BEA ; 7BF7 90 F1
+L7BF9: sec ; 7BF9 38
+ rts ; 7BFA 60
+
+; ----------------------------------------------------------------------------
+L7BFB: lda (zp_be),y ; 7BFB B1 BE
+ cmp #$9B ; 7BFD C9 9B
+ beq L7BF9 ; 7BFF F0 F8
+ cmp #$20 ; 7C01 C9 20
+ bne L7C0C ; 7C03 D0 07
+ iny ; 7C05 C8
+ cpy #$40 ; 7C06 C0 40
+ bcc L7BFB ; 7C08 90 F1
+ bcs L7BF9 ; 7C0A B0 ED
+L7C0C: sty L71B5 ; 7C0C 8C B5 71
+ clc ; 7C0F 18
+ rts ; 7C10 60
+
+; ----------------------------------------------------------------------------
+; copy sparta cli arg to linbuf, append EOL ($9b)
+copy_cli_arg:
+ ldx #$00 ; 7C11 A2 00
+ ldy L71B5 ; 7C13 AC B5 71
+cca_loop:
+ lda (zp_be),y ; 7C16 B1 BE
+ sta linbuf,x ; 7C18 9D 20 71
+ cmp #$9B ; 7C1B C9 9B
+ beq cca_append_eol ; 7C1D F0 0A
+ cmp #$20 ; 7C1F C9 20
+ beq cca_append_eol ; 7C21 F0 06
+ inx ; 7C23 E8
+ iny ; 7C24 C8
+ cpy #$40 ; 7C25 C0 40
+ bcc cca_loop ; 7C27 90 ED
+cca_append_eol:
+ lda #$9B ; 7C29 A9 9B
+ sta linbuf,x ; 7C2B 9D 20 71
+ stx linbuf_idx ; 7C2E 8E 79 7A
+ rts ; 7C31 60
+
+; ----------------------------------------------------------------------------
+emsg_read_main_dir:
+ .byte "Error reading main directory"; 7C32 45 72 72 6F 72 20 72 65
+ ; 7C3A 61 64 69 6E 67 20 6D 61
+ ; 7C42 69 6E 20 64 69 72 65 63
+ ; 7C4A 74 6F 72 79
+ .byte $9B,$00 ; 7C4E 9B 00
+emsg_credir_failed:
+ .byte "CREDIR failed for the above pat"; 7C50 43 52 45 44 49 52 20 66
+ ; 7C58 61 69 6C 65 64 20 66 6F
+ ; 7C60 72 20 74 68 65 20 61 62
+ ; 7C68 6F 76 65 20 70 61 74
+ .byte "h" ; 7C6F 68
+ .byte $9B,$00 ; 7C70 9B 00
+emsg_outpath_build:
+ .byte "Output path cannot be built"; 7C72 4F 75 74 70 75 74 20 70
+ ; 7C7A 61 74 68 20 63 61 6E 6E
+ ; 7C82 6F 74 20 62 65 20 62 75
+ ; 7C8A 69 6C 74
+ .byte $9B,$00 ; 7C8D 9B 00
+msg_credir_issued:
+ .byte "CREDIR issued for:"; 7C8F 43 52 45 44 49 52 20 69
+ ; 7C97 73 73 75 65 64 20 66 6F
+ ; 7C9F 72 3A
+ .byte $9B,$00 ; 7CA1 9B 00
+L7CA3: .byte "*.*" ; 7CA3 2A 2E 2A
+ .byte $9B ; 7CA6 9B
+L7CA7: .byte $00 ; 7CA7 00
+; ----------------------------------------------------------------------------
+L7CA8: .byte $01 ; 7CA8 01
+L7CA9: lda #$00 ; 7CA9 A9 00
+ sta L7CA7 ; 7CAB 8D A7 7C
+ sta L7CA8 ; 7CAE 8D A8 7C
+ jmp L7D6B ; 7CB1 4C 6B 7D
+
+; ----------------------------------------------------------------------------
+L7CB4: ldx L7CA7 ; 7CB4 AE A7 7C
+L7CB7: lda output_dir,x ; 7CB7 BD 50 70
+ cmp #$3E ; 7CBA C9 3E
+ beq L7CE0 ; 7CBC F0 22
+ cmp #$9B ; 7CBE C9 9B
+ beq L7CC5 ; 7CC0 F0 03
+ inx ; 7CC2 E8
+ bpl L7CB7 ; 7CC3 10 F2
+L7CC5: lda L7CA7 ; 7CC5 AD A7 7C
+ beq print_emsg_read_main_dir; 7CC8 F0 0A
+; ldx #<emsg_outpath_build : ldy #>emsg_outpath_build
+print_emsg_outpath_build:
+ ldx #$72 ; 7CCA A2 72
+ ldy #$7C ; 7CCC A0 7C
+ jsr printstr ; 7CCE 20 8B 7A
+ jmp L7CDB ; 7CD1 4C DB 7C
+
+; ----------------------------------------------------------------------------
+; ldx #<emsg_read_main_dir : ldy #>emsg_read_main_dir
+print_emsg_read_main_dir:
+ ldx #$32 ; 7CD4 A2 32
+ ldy #$7C ; 7CD6 A0 7C
+ jsr printstr ; 7CD8 20 8B 7A
+L7CDB: pla ; 7CDB 68
+ pla ; 7CDC 68
+ jmp exit ; 7CDD 4C 9A 73
+
+; ----------------------------------------------------------------------------
+L7CE0: stx L7CA7 ; 7CE0 8E A7 7C
+L7CE3: lda output_dir,x ; 7CE3 BD 50 70
+ sta outfile_l,x ; 7CE6 9D A0 70
+ dex ; 7CE9 CA
+ bpl L7CE3 ; 7CEA 10 F7
+ ldx L7CA7 ; 7CEC AE A7 7C
+ inx ; 7CEF E8
+ stx L7CA7 ; 7CF0 8E A7 7C
+ ldy #$00 ; 7CF3 A0 00
+L7CF5: lda L7CA3,y ; 7CF5 B9 A3 7C
+ sta outfile_l,x ; 7CF8 9D A0 70
+ inx ; 7CFB E8
+ iny ; 7CFC C8
+ cpy #$04 ; 7CFD C0 04
+ bcc L7CF5 ; 7CFF 90 F4
+ ldx #$40 ; 7D01 A2 40
+ lda #$A0 ; 7D03 A9 A0
+ ldy #$70 ; 7D05 A0 70
+ jsr open_dir ; 7D07 20 8A 7B
+ php ; 7D0A 08
+ ldx #$40 ; 7D0B A2 40
+ jsr close_iocb ; 7D0D 20 48 7B
+ plp ; 7D10 28
+ bmi L7D16 ; 7D11 30 03
+ jmp L7D6B ; 7D13 4C 6B 7D
+
+; ----------------------------------------------------------------------------
+L7D16: ldx L7CA7 ; 7D16 AE A7 7C
+ dex ; 7D19 CA
+ dex ; 7D1A CA
+ lda output_dir,x ; 7D1B BD 50 70
+ cmp #$3A ; 7D1E C9 3A
+ beq print_emsg_read_main_dir; 7D20 F0 B2
+ inx ; 7D22 E8
+ lda #$9B ; 7D23 A9 9B
+ sta outfile_l,x ; 7D25 9D A0 70
+ lda #$00 ; 7D28 A9 00
+ sta outfile_h,x ; 7D2A 9D A1 70
+; ldx #<msg_credir_issued : ldy #>msg_credir_issued
+print_msg_credir_issued:
+ ldx #$8F ; 7D2D A2 8F
+ ldy #$7C ; 7D2F A0 7C
+ jsr printstr ; 7D31 20 8B 7A
+ ldx #$A0 ; 7D34 A2 A0
+ ldy #$70 ; 7D36 A0 70
+ jsr printstr ; 7D38 20 8B 7A
+ ldx #$40 ; 7D3B A2 40
+ lda #$2A ; 7D3D A9 2A
+ sta ICCOM,x ; 7D3F 9D 42 03
+ lda #$A0 ; 7D42 A9 A0
+ sta ICBAL,x ; 7D44 9D 44 03
+ lda #$70 ; 7D47 A9 70
+ sta ICBAH,x ; 7D49 9D 45 03
+ lda #$80 ; 7D4C A9 80
+ sta ICBLL,x ; 7D4E 9D 48 03
+ lda #$00 ; 7D51 A9 00
+ sta ICBLH,x ; 7D53 9D 49 03
+ sta ICAX1,x ; 7D56 9D 4A 03
+ sta ICAX2,x ; 7D59 9D 4B 03
+ jsr CIOV ; 7D5C 20 56 E4
+ bpl L7D6B ; 7D5F 10 0A
+; ldx #<emsg_credir_failed : ldy #>emsg_credir_failed
+print_emsg_credir_failed:
+ ldx #$50 ; 7D61 A2 50
+ ldy #$7C ; 7D63 A0 7C
+ jsr printstr ; 7D65 20 8B 7A
+ jmp L7CDB ; 7D68 4C DB 7C
+
+; ----------------------------------------------------------------------------
+L7D6B: ldx #$40 ; 7D6B A2 40
+ lda #$50 ; 7D6D A9 50
+ ldy #$70 ; 7D6F A0 70
+ jsr open_dir ; 7D71 20 8A 7B
+ php ; 7D74 08
+ ldx #$40 ; 7D75 A2 40
+ jsr close_iocb ; 7D77 20 48 7B
+ plp ; 7D7A 28
+ bmi L7D7F ; 7D7B 30 02
+ clc ; 7D7D 18
+ rts ; 7D7E 60
+
+; ----------------------------------------------------------------------------
+L7D7F: jmp L7CB4 ; 7D7F 4C B4 7C
+
+; ----------------------------------------------------------------------------
+; filespec for directory
+stardotstar:
+ .byte "*.*" ; 7D82 2A 2E 2A
+ .byte $9B ; 7D85 9B
+; ----------------------------------------------------------------------------
+L7D86: jsr L7DBF ; 7D86 20 BF 7D
+ bcc L7D8C ; 7D89 90 01
+ rts ; 7D8B 60
+
+; ----------------------------------------------------------------------------
+L7D8C: dex ; 7D8C CA
+L7D8D: lda linbuf,x ; 7D8D BD 20 71
+ cmp #$3E ; 7D90 C9 3E
+ beq L7D9D ; 7D92 F0 09
+ cmp #$3A ; 7D94 C9 3A
+ beq L7D9D ; 7D96 F0 05
+ dex ; 7D98 CA
+ bpl L7D8D ; 7D99 10 F2
+ sec ; 7D9B 38
+ rts ; 7D9C 60
+
+; ----------------------------------------------------------------------------
+L7D9D: ldy #$00 ; 7D9D A0 00
+ inx ; 7D9F E8
+ lda linbuf,x ; 7DA0 BD 20 71
+ cmp #$9B ; 7DA3 C9 9B
+ beq L7DAD ; 7DA5 F0 06
+ cmp #$20 ; 7DA7 C9 20
+ beq L7DAD ; 7DA9 F0 02
+ clc ; 7DAB 18
+ rts ; 7DAC 60
+
+; ----------------------------------------------------------------------------
+L7DAD: lda stardotstar,y ; 7DAD B9 82 7D
+ sta linbuf,x ; 7DB0 9D 20 71
+ inx ; 7DB3 E8
+ iny ; 7DB4 C8
+ cpy #$04 ; 7DB5 C0 04
+ bcc L7DAD ; 7DB7 90 F4
+ dex ; 7DB9 CA
+ stx linbuf_idx ; 7DBA 8E 79 7A
+ clc ; 7DBD 18
+ rts ; 7DBE 60
+
+; ----------------------------------------------------------------------------
+L7DBF: ldx #$00 ; 7DBF A2 00
+L7DC1: lda linbuf,x ; 7DC1 BD 20 71
+ cmp #$9B ; 7DC4 C9 9B
+ beq L7DCD ; 7DC6 F0 05
+ inx ; 7DC8 E8
+ bpl L7DC1 ; 7DC9 10 F6
+ sec ; 7DCB 38
+ rts ; 7DCC 60
+
+; ----------------------------------------------------------------------------
+L7DCD: clc ; 7DCD 18
+ rts ; 7DCE 60
+
+; ----------------------------------------------------------------------------
+ensure_d_prefix:
+ lda linbuf ; 7DCF AD 20 71
+ cmp #$44 ; 7DD2 C9 44
+ bne L7DE2 ; 7DD4 D0 0C
+ lda #$3A ; 7DD6 A9 3A
+ cmp $7121 ; 7DD8 CD 21 71
+ beq L7E06 ; 7DDB F0 29
+ cmp $7122 ; 7DDD CD 22 71
+ beq L7E06 ; 7DE0 F0 24
+L7DE2: lda linbuf_idx ; 7DE2 AD 79 7A
+ tax ; 7DE5 AA
+ clc ; 7DE6 18
+ adc #$03 ; 7DE7 69 03
+ sta linbuf_idx ; 7DE9 8D 79 7A
+ tay ; 7DEC A8
+L7DED: lda linbuf,x ; 7DED BD 20 71
+ sta linbuf,y ; 7DF0 99 20 71
+ dey ; 7DF3 88
+ dex ; 7DF4 CA
+ bpl L7DED ; 7DF5 10 F6
+ lda #$44 ; 7DF7 A9 44
+ ldx #$31 ; 7DF9 A2 31
+ ldy #$3A ; 7DFB A0 3A
+ sta linbuf ; 7DFD 8D 20 71
+ stx $7121 ; 7E00 8E 21 71
+ sty $7122 ; 7E03 8C 22 71
+L7E06: rts ; 7E06 60
+
+; ----------------------------------------------------------------------------
+alfext: .byte ".ALF" ; 7E07 2E 41 4C 46
+ .byte $9B ; 7E0B 9B
+; ----------------------------------------------------------------------------
+ensure_suffix:
+ ldx linbuf_idx ; 7E0C AE 79 7A
+ ldy #$00 ; 7E0F A0 00
+L7E11: lda linbuf,x ; 7E11 BD 20 71
+ cmp #$2E ; 7E14 C9 2E
+ beq L7E21 ; 7E16 F0 09
+ iny ; 7E18 C8
+ dex ; 7E19 CA
+ bpl L7E11 ; 7E1A 10 F5
+ ldx linbuf_idx ; 7E1C AE 79 7A
+ bne L7E25 ; 7E1F D0 04
+L7E21: cpy #$00 ; 7E21 C0 00
+ bne L7E37 ; 7E23 D0 12
+L7E25: ldy #$00 ; 7E25 A0 00
+L7E27: lda alfext,y ; 7E27 B9 07 7E
+ sta linbuf,x ; 7E2A 9D 20 71
+ inx ; 7E2D E8
+ iny ; 7E2E C8
+ cpy #$05 ; 7E2F C0 05
+ bcc L7E27 ; 7E31 90 F4
+ dex ; 7E33 CA
+ stx linbuf_idx ; 7E34 8E 79 7A
+L7E37: rts ; 7E37 60
+
+; ----------------------------------------------------------------------------
+L7E38: .byte $00,$00,$00,$00,$00,$00,$00,$00; 7E38 00 00 00 00 00 00 00 00
+ .byte $00,$00,$00 ; 7E40 00 00 00
+L7E43: .byte $00,$00,$00,$00,$00,$00,$00,$00; 7E43 00 00 00 00 00 00 00 00
+ .byte $00,$00,$00 ; 7E4B 00 00 00
+; ----------------------------------------------------------------------------
+L7E4E: lda #$20 ; 7E4E A9 20
+ ldx #$0A ; 7E50 A2 0A
+L7E52: sta L7E38,x ; 7E52 9D 38 7E
+ sta L7E43,x ; 7E55 9D 43 7E
+ dex ; 7E58 CA
+ bpl L7E52 ; 7E59 10 F7
+ ldx #$00 ; 7E5B A2 00
+ ldy #$00 ; 7E5D A0 00
+L7E5F: lda alf_hdr_filename,x; 7E5F BD 8F 71
+ beq L7E75 ; 7E62 F0 11
+ cmp #$2E ; 7E64 C9 2E
+ bne L7E6C ; 7E66 D0 04
+ ldy #$07 ; 7E68 A0 07
+ bne L7E6F ; 7E6A D0 03
+L7E6C: sta L7E38,y ; 7E6C 99 38 7E
+L7E6F: inx ; 7E6F E8
+ iny ; 7E70 C8
+ cpy #$0B ; 7E71 C0 0B
+ bcc L7E5F ; 7E73 90 EA
+L7E75: rts ; 7E75 60
+
+; ----------------------------------------------------------------------------
+L7E76: ldx #$00 ; 7E76 A2 00
+L7E78: lda output_dir,x ; 7E78 BD 50 70
+ cmp #$9B ; 7E7B C9 9B
+ beq L7E82 ; 7E7D F0 03
+ inx ; 7E7F E8
+ bne L7E78 ; 7E80 D0 F6
+L7E82: lda output_dir,x ; 7E82 BD 50 70
+ cmp #$3E ; 7E85 C9 3E
+ beq L7E90 ; 7E87 F0 07
+ cmp #$3A ; 7E89 C9 3A
+ beq L7E90 ; 7E8B F0 03
+ dex ; 7E8D CA
+ bpl L7E82 ; 7E8E 10 F2
+L7E90: ldy #$00 ; 7E90 A0 00
+ inx ; 7E92 E8
+L7E93: lda output_dir,x ; 7E93 BD 50 70
+ cmp #$2A ; 7E96 C9 2A
+ bne L7EB3 ; 7E98 D0 19
+ lda #$3F ; 7E9A A9 3F
+L7E9C: sta L7E43,y ; 7E9C 99 43 7E
+ iny ; 7E9F C8
+ cpy #$08 ; 7EA0 C0 08
+ bcc L7E9C ; 7EA2 90 F8
+ inx ; 7EA4 E8
+L7EA5: lda output_dir,x ; 7EA5 BD 50 70
+ cmp #$2E ; 7EA8 C9 2E
+ beq L7EC1 ; 7EAA F0 15
+ cmp #$9B ; 7EAC C9 9B
+ beq L7EC0 ; 7EAE F0 10
+ inx ; 7EB0 E8
+ bne L7EA5 ; 7EB1 D0 F2
+L7EB3: cmp #$2E ; 7EB3 C9 2E
+ beq L7EC1 ; 7EB5 F0 0A
+ sta L7E43,y ; 7EB7 99 43 7E
+ inx ; 7EBA E8
+ iny ; 7EBB C8
+ cpy #$08 ; 7EBC C0 08
+ bcc L7E93 ; 7EBE 90 D3
+L7EC0: rts ; 7EC0 60
+
+; ----------------------------------------------------------------------------
+L7EC1: inx ; 7EC1 E8
+ ldy #$08 ; 7EC2 A0 08
+L7EC4: lda output_dir,x ; 7EC4 BD 50 70
+ cmp #$9B ; 7EC7 C9 9B
+ beq L7EC0 ; 7EC9 F0 F5
+ cmp #$2A ; 7ECB C9 2A
+ bne L7EDA ; 7ECD D0 0B
+ lda #$3F ; 7ECF A9 3F
+L7ED1: sta L7E43,y ; 7ED1 99 43 7E
+ iny ; 7ED4 C8
+ cpy #$0B ; 7ED5 C0 0B
+ bcc L7ED1 ; 7ED7 90 F8
+ rts ; 7ED9 60
+
+; ----------------------------------------------------------------------------
+L7EDA: sta L7E43,y ; 7EDA 99 43 7E
+ inx ; 7EDD E8
+ iny ; 7EDE C8
+ cpy #$0B ; 7EDF C0 0B
+ bcc L7EC4 ; 7EE1 90 E1
+ rts ; 7EE3 60
+
+; ----------------------------------------------------------------------------
+L7EE4: jsr L7E4E ; 7EE4 20 4E 7E
+ jsr L7E76 ; 7EE7 20 76 7E
+ ldx #$00 ; 7EEA A2 00
+L7EEC: lda L7E43,x ; 7EEC BD 43 7E
+ cmp #$3F ; 7EEF C9 3F
+ beq L7EF8 ; 7EF1 F0 05
+ cmp L7E38,x ; 7EF3 DD 38 7E
+ bne print_msg_skipping; 7EF6 D0 07
+L7EF8: inx ; 7EF8 E8
+ cpx #$0B ; 7EF9 E0 0B
+ bcc L7EEC ; 7EFB 90 EF
+ clc ; 7EFD 18
+ rts ; 7EFE 60
+
+; ----------------------------------------------------------------------------
+; ldx #<msg_skipping : ldy #>msg_skipping
+print_msg_skipping:
+ ldx #$1D ; 7EFF A2 1D
+ ldy #$7F ; 7F01 A0 7F
+ jsr printstr ; 7F03 20 8B 7A
+ ldx #$00 ; 7F06 A2 00
+pskp_loop:
+ lda alf_hdr_filename,x; 7F08 BD 8F 71
+ beq pskp_done ; 7F0B F0 06
+ jsr putchar ; 7F0D 20 9F 7A
+ inx ; 7F10 E8
+ bpl pskp_loop ; 7F11 10 F5
+pskp_done:
+ lda #$9B ; 7F13 A9 9B
+ jsr putchar ; 7F15 20 9F 7A
+ jsr L7F3D ; 7F18 20 3D 7F
+ sec ; 7F1B 38
+ rts ; 7F1C 60
+
+; ----------------------------------------------------------------------------
+msg_skipping:
+ .byte "Skipping: " ; 7F1D 53 6B 69 70 70 69 6E 67
+ ; 7F25 3A 20
+ .byte $00 ; 7F27 00
+emsg_locate:
+ .byte "Error During Locate"; 7F28 45 72 72 6F 72 20 44 75
+ ; 7F30 72 69 6E 67 20 4C 6F 63
+ ; 7F38 61 74 65
+ .byte $9B,$00 ; 7F3B 9B 00
+; ----------------------------------------------------------------------------
+L7F3D: lda #$DC ; 7F3D A9 DC
+ sta buf_adr_l ; 7F3F 8D 7B 7A
+ lda #$7F ; 7F42 A9 7F
+ sta buf_adr_h ; 7F44 8D 7C 7A
+ lda MEMTOP_lo ; 7F47 AD E5 02
+ sec ; 7F4A 38
+ sbc #$DC ; 7F4B E9 DC
+ sta inbuf_len_l ; 7F4D 8D 83 71
+ lda MEMTOP_hi ; 7F50 AD E6 02
+ sbc #$7F ; 7F53 E9 7F
+ sta inbuf_len_h ; 7F55 8D 84 71
+L7F58: lda alf_hdr_compsize0; 7F58 AD 9C 71
+ ora alf_hdr_compsize1; 7F5B 0D 9D 71
+ ora alf_hdr_compsize2; 7F5E 0D 9E 71
+ bne L7F64 ; 7F61 D0 01
+ rts ; 7F63 60
+
+; ----------------------------------------------------------------------------
+L7F64: lda alf_hdr_compsize2; 7F64 AD 9E 71
+ bne L7F8C ; 7F67 D0 23
+ lda inbuf_len_h ; 7F69 AD 84 71
+ cmp alf_hdr_compsize1; 7F6C CD 9D 71
+ bcc L7F8C ; 7F6F 90 1B
+ beq L7F75 ; 7F71 F0 02
+ bcs L7F7D ; 7F73 B0 08
+L7F75: lda inbuf_len_l ; 7F75 AD 83 71
+ cmp alf_hdr_compsize0; 7F78 CD 9C 71
+ bcc L7F8C ; 7F7B 90 0F
+L7F7D: lda alf_hdr_compsize0; 7F7D AD 9C 71
+ sta buf_len_l ; 7F80 8D 7D 7A
+ lda alf_hdr_compsize1; 7F83 AD 9D 71
+ sta buf_len_h ; 7F86 8D 7E 7A
+ jmp L7F98 ; 7F89 4C 98 7F
+
+; ----------------------------------------------------------------------------
+L7F8C: lda inbuf_len_l ; 7F8C AD 83 71
+ sta buf_len_l ; 7F8F 8D 7D 7A
+ lda inbuf_len_h ; 7F92 AD 84 71
+ sta buf_len_h ; 7F95 8D 7E 7A
+L7F98: ldx #$10 ; 7F98 A2 10
+ jsr readblock ; 7F9A 20 CF 7A
+ bpl L7FA9 ; 7F9D 10 0A
+; ldx #<emsg_locate : ldy #>emsg_locate
+print_emsg_locate:
+ ldx #$28 ; 7F9F A2 28
+ ldy #$7F ; 7FA1 A0 7F
+ jsr printstr ; 7FA3 20 8B 7A
+ jmp cleanup_and_exit; 7FA6 4C 7F 78
+
+; ----------------------------------------------------------------------------
+L7FA9: lda alf_hdr_compsize0; 7FA9 AD 9C 71
+ sec ; 7FAC 38
+ sbc buf_len_l ; 7FAD ED 7D 7A
+ sta alf_hdr_compsize0; 7FB0 8D 9C 71
+ lda alf_hdr_compsize1; 7FB3 AD 9D 71
+ sbc buf_len_h ; 7FB6 ED 7E 7A
+ sta alf_hdr_compsize1; 7FB9 8D 9D 71
+ lda alf_hdr_compsize2; 7FBC AD 9E 71
+ sbc #$00 ; 7FBF E9 00
+ sta alf_hdr_compsize2; 7FC1 8D 9E 71
+ jmp L7F58 ; 7FC4 4C 58 7F
+
+; ----------------------------------------------------------------------------
+entrypoint:
+ jmp startup ; 7FC7 4C 5A 73
+
+; ----------------------------------------------------------------------------
+ .byte $4C,$4E,$75,$4C,$00,$00,$20,$71; 7FCA 4C 4E 75 4C 00 00 20 71
+ .byte $00,$70,$A0,$70,$50,$70,$00,$00; 7FD2 00 70 A0 70 50 70 00 00
+ .byte $01,$04,$C7,$7F; 7FDA 01 04 C7 7F
diff --git a/doc/Arcinfo b/doc/Arcinfo
new file mode 100644
index 0000000..6c9d500
--- /dev/null
+++ b/doc/Arcinfo
@@ -0,0 +1,124 @@
+
+ARC-FILE.INF, created by Keith Petersen, W8SDZ, 21-Sep-86, extracted
+from UNARC.INF by Robert A. Freed.
+
+From: Robert A. Freed
+Subject: Technical Information for ARC files
+Date: June 24, 1986
+
+Note: In the following discussion, UNARC refers to my CP/M-80 program
+for extracting files from MSDOS ARCs. The definitions of the ARC file
+format are based on MSDOS ARC512.EXE.
+
+ARCHIVE FILE FORMAT
+-------------------
+
+Component files are stored sequentially within an archive. Each entry
+is preceded by a 29-byte header, which contains the directory
+information. There is no wasted space between entries. (This is in
+contrast to the centralized directory used by Novosielski libraries.
+Although random access to subfiles within an archive can be noticeably
+slower than with libraries, archives do have the advantage of not
+requiring pre-allocation of directory space.)
+
+Archive entries are normally maintained in sorted name order. The
+format of the 29-byte archive header is as follows:
+
+Byte 1: 1A Hex.
+ This marks the start of an archive header. If this byte is not found
+ when expected, UNARC will scan forward in the file (up to 64K bytes)
+ in an attempt to find it (followed by a valid compression version).
+ If a valid header is found in this manner, a warning message is
+ issued and archive file processing continues. Otherwise, the file is
+ assumed to be an invalid archive and processing is aborted. (This is
+ compatible with MS-DOS ARC version 5.12). Note that a special
+ exception is made at the beginning of an archive file, to accomodate
+ "self-unpacking" archives (see below).
+
+Byte 2: Compression version, as follows:
+
+ 0 = end of file marker (remaining bytes not present)
+ 1 = unpacked (obsolete)
+ 2 = unpacked
+ 3 = packed
+ 4 = squeezed (after packing)
+ 5 = crunched (obsolete)
+ 6 = crunched (after packing) (obsolete)
+ 7 = crunched (after packing, using faster hash algorithm) (obsolete)
+ 8 = crunched (after packing, using dynamic LZW variations)
+
+Bytes 3-15: ASCII file name, nul-terminated.
+
+(All of the following numeric values are stored low-byte first.)
+
+Bytes 16-19: Compressed file size in bytes.
+
+Bytes 20-21: File date, in 16-bit MS-DOS format:
+ Bits 15:9 = year - 1980
+ Bits 8:5 = month of year
+ Bits 4:0 = day of month
+ (All zero means no date.)
+
+Bytes 22-23: File time, in 16-bit MS-DOS format:
+ Bits 15:11 = hour (24-hour clock)
+ Bits 10:5 = minute
+ Bits 4:0 = second/2 (not displayed by UNARC)
+
+Bytes 24-25: Cyclic redundancy check (CRC) value (see below).
+
+Bytes 26-29: Original (uncompressed) file length in bytes.
+ (This field is not present for version 1 entries, byte 2 = 1.
+ I.e., in this case the header is only 25 bytes long. Because
+ version 1 files are uncompressed, the value normally found in
+ this field may be obtained from bytes 16-19.)
+
+
+SELF-UNPACKING ARCHIVES
+-----------------------
+
+A "self-unpacking" archive is one which can be renamed to a .COM file
+and executed as a program. An example of such a file is the MS-DOS
+program ARC512.COM, which is a standard archive file preceded by a
+three-byte jump instruction. The first entry in this file is a simple
+"bootstrap" program in uncompressed form, which loads the subfile
+ARC.EXE (also uncompressed) into memory and passes control to it. In
+anticipation of a similar scheme for future distribution of UNARC, the
+program permits up to three bytes to precede the first header in an
+archive file (with no error message).
+
+
+CRC COMPUTATION
+---------------
+
+Archive files use a 16-bit cyclic redundancy check (CRC) for error
+control. The particular CRC polynomial used is x^16 + x^15 + x^2 + 1,
+which is commonly known as "CRC-16" and is used in many data
+transmission protocols (e.g. DEC DDCMP and IBM BSC), as well as by
+most floppy disk controllers. Note that this differs from the CCITT
+polynomial (x^16 + x^12 + x^5 + 1), which is used by the XMODEM-CRC
+protocol and the public domain CHEK program (although these do not
+adhere strictly to the CCITT standard). The MS-DOS ARC program does
+perform a mathematically sound and accurate CRC calculation. (We
+mention this because it contrasts with some unfortunately popular
+public domain programs we have witnessed, which from time immemorial
+have based their calculation on an obscure magazine article which
+contained a typographical error!)
+
+Additional note (while we are on the subject of CRC's): The validity
+of using a 16-bit CRC for checking an entire file is somewhat
+questionable. Many people quote the statistics related to these
+functions (e.g. "all two-bit errors, all single burst errors of 16 or
+fewer bits, 99.997% of all single 17-bit burst errors, etc."), without
+realizing that these claims are valid only if the total number of bits
+checked is less than 32767 (which is why they are used in small-packet
+data transmission protocols). I.e., for file sizes in excess of about
+4K bytes, a 16-bit CRC is not really as good as what is often claimed.
+This is not to say that it is bad, but there are more reliable methods
+available (e.g. the 32-bit AUTODIN-II polynomial). (End of lecture!)
+
+ Bob Freed
+ 62 Miller Road
+ Newton Centre, MA 02159
+ Telephone (617) 332-3533
+
+
diff --git a/doc/alf14.atr b/doc/alf14.atr
new file mode 100644
index 0000000..7fc3021
--- /dev/null
+++ b/doc/alf14.atr
Binary files differ
diff --git a/doc/alf14_doc.txt b/doc/alf14_doc.txt
new file mode 100644
index 0000000..832f59d
--- /dev/null
+++ b/doc/alf14_doc.txt
@@ -0,0 +1,266 @@
+ AlfCrunch Documentation Revised 7/10/88
+ -----------------------
+
+ AlfCrunch is an implementation of the Lempel-Ziv compression
+ algorithm. Although it produces files that have the same structure as
+ those produced by the Arc program, the two are not compatible. Arc
+ cannot uncrunch AlfCrunch files, nor can AlfUnCrunch unarc normal Arc
+ files.
+
+ The current version of the LZ/DZ files is 1.4. Versions 1.1 through 1.3
+ are compatible, but not with 1.0. If you have 1.0, you should discard it
+ and use 1.4. The reason for this is that 1.0 used the same header as
+ normal Arc crunch. Because of possible confusion over this, the header
+ used by AlfCrunch was changed. Since 1.0 had very limited distribution,
+ this situation should not often arise. For those who wish to be able to
+ detect the AlfCrunch format, the first two bytes of the file will always
+ be $1A $0F.
+
+ This version fixes an annoying bug in both v1.2 and 1.3. If you had a
+subdirectory entry amongst the filenames you were crunching, LZ would
+stop at the subdir entry. Also the stack errors will now cause a proper
+exit to Dos rather than re-execution.
+
+ Enhancements to v1.4 are the addition of time/date support. If you
+are running under Sparta 3.2, LZ will store the Sparta date/time from each
+file into the header. DZ does not use this information, it's just there to
+provide a reference point.
+
+ When running either LZ.COM or DZ.COM, Memlo must be under $3000. This
+ should not normally be a problem unless you have a lot of handlers
+installed.
+ A cartridge may be present, as it only affects the size of the buffer
+ available to AlfCrunch. Maximum speed will be achieved without a
+ cartridge being present.
+
+ A final note
+ ------------
+
+ Well I think this is about as far as AlfCrunch is going to get for now. I
+don't really believe there are any more features to add without modifying the
+command line parameters. So this version (1.4) will be the last for
+some time to come. Except for bug fixes (few if any I hope) the 1.x line will
+not change. I hope to add command line parameters similar to ARC and maybe
+add the ARC compression methods to finally resolve the compatibility issue.
+
+ Alfred
+ Programmer's Aid BBS
+ (416) 465-4182
+
+ Running AlfCrunch
+ -----------------
+
+ To crunch files, load LZ.COM. The title will be displayed, along
+ with the version which should be 1.4. You will then be prompted for
+ the output filename. This may be up to 80 characters long,
+ including subdirectory names.
+
+ If the output file already exists, it is checked to see if it is an
+AlfCrunch file. If the first header is correct, then the new files will be
+appended to it. If the header is wrong the program will print an error
+message and exit to Dos. If the file is shorter than the header length
+(29 bytes), then it is simply opened for normal output, which erases it.
+
+ Next you will be prompted for the input filemask. This is what will
+ be used to select the files. This may also be up to 80 characters long,
+ including any subdirectory names. Wildcards are allowed. If selecting
+ all files, the mask must end in *.* .
+
+ Finally, you have the option of turning the screen off. Selecting
+ this option will speed up the program by 15-20%. Once selected, you will
+ not again be prompted for this option. If you do not elect to turn the
+ screen off, the program will continue to present this prompt until it is
+ selected.
+
+ The program will then select files using the mask and compress them,
+ displaying the filenames as it progresses. When it has finished, it will
+ prompt you for additional input filemasks. You may either enter another
+ mask or simply press return to exit back to Dos.
+
+ LZ and SpartaDos 3.2
+ --------------------
+
+ If you are using SpartaDos 3.2, you may invoke LZ.COM and specify
+ the output file and input filemask on the command line. The format is:
+
+ [Dn:]LZ Dn:[path>]filename[.ext] [Dn:[path>]filename[.ext] ]
+
+ The square brackets denote optional parameters which may be omitted.
+ The first filename is the output file. The second is the input
+ filemask. If you do not specify the input filemask, the program will
+ prompt you for it. The program will automatically turn the screen off.
+ When it is finished it will prompt you for more input filemasks.
+
+ To invoke LZ as part of a batch file, the format is almost identical.
+ The lines in the batch file would be:
+
+ [Dn:]LZ Dn:[path>]filename[.ext] [Dn:[path>]filename[.ext] ]
+ Dn:[path>]filename[.ext] <- Additional
+ Dn:[path>]filename[.ext] input masks
+
+ The program will read each input filemask, compress the files
+ selected and continue until all the input masks have been used. You will
+ then be prompted for more input masks. If this is part of a larger batch
+ file, leave a single return after the last input mask to force LZ to
+ return control back to the batch file. Example:
+
+ [Dn:]LZ Dn:[path>]filename[.ext] [Dn:[path>]filename[.ext] ]
+ Dn:[path>]filename[.ext]
+ Dn:[path>]filename[.ext]
+ (single return here)
+ [Dn:]LZ Dn:[path>]filename[.ext] [Dn:[path>]filename[.ext] ]
+ Dn:[path>]filename[.ext]
+ Dn:[path>]filename[.ext]
+ (single return here)
+
+ At the end of this, you will be left at the Dos prompt. Because of
+ the way i/o redirection is handled, an alternative form is available:
+
+ [Dn:]LZ
+ Dn:[path>]filename[.ext] <- The output file
+ Dn:[path>]filename[.ext] <- The input filemask
+ Y <- Turn the screen off
+ Dn:[path>]filename[.ext] <- Additional
+ Dn:[path>]filename[.ext] <- input filemasks
+ (single return here)
+
+ Notice that the Y was only supplied once. When LZ is run in this
+ manner, it behaves exactly as if you were pressing the keys yourself. If
+ you turn the screen off, then you need only enter the Y once. If you
+ said N, then you would need an N after every input filemask until you
+ said Y. Example:
+
+ [Dn:]LZ
+ Dn:[path>]filename[.ext] <- The output file
+ Dn:[path>]filename[.ext] <- The input filemask
+ N <- Leave the screen on
+ Dn:[path>]filename[.ext] <- Additional mask
+ N <- Leave the screen on
+ Dn:[path>]filename[.ext] <- Additional mask
+ Y <- Screen off now
+ Dn:[path>]filename[.ext] <- Additional masks, but no Y
+ Dn:[path>]filename[.ext] <- is necessary
+ (single return here)
+
+ Getting Them Back
+ -----------------
+
+ To extract the files from an Alfcrunch file, load DZ.COM The title
+ will be displayed, along with the version number.
+
+ The first prompt is for the name of the file to uncrunch. This
+ filename may be up to 80 characters long, including subdirectory names.
+ Wildcards are not allowed.
+
+ The next prompt is the output directory. This is the directory where
+ the files will be placed when extracted from the crunch file. If the
+ directory does not exist, an attempt will be made to create the
+ directory. This may involve creating a number of subdirectories to get
+ to the last one, so care should exercised with this feature. If
+ errors occur during the directory build stage, an error message will be
+ displayed, and the program will return to DOS. You may specify a wildcard to
+only extract certain files or use '*.*' to extract them all. *.* is the default.
+
+ Auto directory creation is only available under SpartaDos. Under
+ any other Dos, if you specify a subdirectory, you will probably get
+a single file with the name of the first pathname.
+
+ Assuming all is well, you again have the option of turning the screen
+ off while files are being extracted.
+
+ The program will then extract each file and place it in the output
+ directory specified. If any errors occur, an error message is printed
+ and the program returns to Dos. When all files have been extracted, you
+ will be prompted for another input file. You may enter another filename
+ or press Return to exit to Dos.
+
+ The situation may arise where the crunch file has been corrupted.
+ This may occur due to errors during download, or failure of the disk on
+ which the file resides. There are several error messages which are
+ associated with bit errors.
+
+ Msg: Not An AlfCrunch File!
+ ---------------------------
+ If this message is issued before any files were extracted, then
+ either the first two bytes of the file are corrupt, or else the file was
+ not created by AlfCrunch. If the message is issued after several files
+ were extracted, then the file has been damaged somewhere in the last
+ file extracted. You may also get the message which is described next.
+
+ Msg: File Checksum In Error
+ ---------------------------
+ DZ has detected that the checksum calculated for the filename just
+ extracted does not agree with the checksum in the header block. Either
+ the header block has been damaged or more likely, the file itself has
+ been corrupted. If the file is a text file, it may be partially correct.
+ Object file types should be discarded, as it must be assumed they are
+ corrupt.
+
+ Msg: Stack Overrun
+ ------------------
+ This is an internal DZ error. The file being processed has been
+ corrupted, and DZ has exhausted all free memory in attempting to extract
+ the data. The output file produced is incomplete, corrupt, and should be
+ discarded.
+
+ Msg: Extra Bytes At Eof, Don't Add To File
+ ------------------------------------------
+ This means that the file has extra data at the end which is not valid.
+This may arise from downloading where the last block is padded. Do not add
+new files to it with LZ as you will not be able to get them back when you run
+DZ again. You will get the 'Not An AlfCrunch File!' message at that time.
+
+ DZ and SpartaDos 3.2
+ --------------------
+ If you are using SpartaDos 3.2, you may invoke DZ.COM and specify
+ the input file and output directory on the command line. The format is:
+
+ [Dn:]DZ Dn:[path>]filename[.ext] [Dn:[path>][*.*]
+
+ The square brackets denote optional parameters which may be omiited
+ if you wish. The first filename is the file to be processed. The second
+ filename is the directory in which the output files are to be placed.
+ Remember, if any of the directories in the output path do not exist, an
+ attempt will be made to create them. Remember, you can use a wildcard to
+limit the files or take the default
+which is '*.*'.
+
+ The program will automatically turn the screen off, and extract
+ the files. If any errors occur, the appropriate error message will
+ be printed and control will return to Dos.
+
+ When DZ is finished with the current input file, it will again prompt
+ you for another input file. You may continue uncrunching files, or
+ simply press return to exit back to Dos.
+
+ As part of a batch file, the form for DZ is almost identical to the
+ LZ form. Accordingly, only brief examples will be shown:
+
+ [Dn:]DZ Dn:[path>]filename[.ext] [Dn:[path>][*.*]
+ Dn:[path>]filename[.ext] <- Second input file
+ Dn:[path>][*.*] <- Second output path
+ Dn:[path>]filename[.ext] <- Third input file
+ Dn:[path>][*.*] <- Third output path
+ (single return) <- Return to Dos
+
+ The second format is:
+
+ [Dn:]DZ Dn:[path>]filename[.ext] <- First input file
+ Dn:[path>][*.*] <- First output path
+ Dn:[path>]filename[.ext] <- Second input file
+ Dn:[path>][*.*] <- Second output path
+ Dn:[path>]filename[.ext] <- Third input file
+ Dn:[path>][*.*] <- Third output path
+ (single return) <- Return to Dos
+
+ The third format is:
+
+ [Dn:]DZ
+ Dn:[path>]filename[.ext] <- First input file
+ Dn:[path>][*.*] <- First output path
+ Y <- Screen off
+ Dn:[path>]filename[.ext] <- Second input file
+ Dn:[path>][*.*] <- Second output path
+ Dn:[path>]filename[.ext] <- Third input file
+ Dn:[path>][*.*] <- Third output path
+ (single return) <- Exit to Dos
diff --git a/doc/fileformat.txt b/doc/fileformat.txt
new file mode 100644
index 0000000..eb08d13
--- /dev/null
+++ b/doc/fileformat.txt
@@ -0,0 +1,45 @@
+An ALF archive is laid out exactly like an ARC version 2 or greater
+archive, with a 29-byte header for each file, followed by the
+compressed data, followed by either EOF or the next file's header.
+
+See the file Arcinfo for the original ARC file format. For ALF files,
+"Byte 2: Compression version" will always be $0F.
+
+The differences are:
+
+- ALF files use $0F for the 'compression type' (Byte 1), whereas
+ ARC files use compression types 2 through 8 (or 1, for ARC v1).
+
+- The actual compressed data is incompatible with any of the
+ compression types supported by ARC. Although ALF uses an
+ implementation of Lempel-Zev, it's not the same implementation
+ as any of the ones that ARC uses.
+
+- For ARC, the last file's compressed data is followed by a 0 byte
+ (in place of the $1A header), to signal "end of archive". For
+ ALF, there's no data after the last byte of the last compressed
+ file;
+
+Other things caused by the limitations of the Atari:
+
+- Not really a file format difference, but the dates stored inside
+ ALF files might be wrong or gibberish, if they were created on
+ an Atari OS other than SpartaDOS (or, on SpartaDOS, but without
+ the time set correctly).
+
+- Atari filenames are generally limited to 12 characters, e.g.
+ PROGRAM1.BAS, so the filename field (Bytes 3-15, 13 bytes long)
+ will never be completely filled. ALF uses a null (0) byte for
+ the filename terminator, and any remaining bytes in the field
+ will be set to $20 (ASCII spaces, *not* more nulls).
+
+- ALF files are never embedded inside a self-extracting executable,
+ so the first file's header always starts at the first byte of
+ the file.
+
+- ARC and ALF both store the compressed and uncompressed file lengths
+ as 32-bit unsigned integers... but for ALF, the maximum file size
+ that can be compressed is probably under 64KB, so both the lengths
+ should have their last 2 bytes set fo 0. I haven't yet attempted
+ to compress a file larger than 64KB with ALF on the Atari, so I
+ may be wrong...
diff --git a/doc/review.txt b/doc/review.txt
new file mode 100644
index 0000000..f56e4c3
--- /dev/null
+++ b/doc/review.txt
@@ -0,0 +1,44 @@
+The following review was published in the Atari H.A.C.K. magazine,
+in the August 1988 issue (Volume II, Issue IIX) [1]:
+
+---------------------------------------------------------------------
+Those of us who are experienced telecommunicators are quite familiar
+with the ARC family of disk file compression programs. The most
+widely used of the 8-bit versions of the ARC program has been,
+and remains to be, ARC version 1.2 (the archiver) and ARCX version
+1.2 (the dearchiver). Two very excellent programs written in C by
+Ralph Walden of the Atari Computer Enthusiasts of Eugene, aka ACE.
+Almost every BBS worth its salt uses this program to compress its
+files not only to make them take up less space, but also to save time
+on file transfers. A smaller program simply takes less time to send
+or receive. Of course, since the file is compressed, or archived, it
+isn't runnable until it's dearced with the ARCX program.
+
+ARC and ARCX are great programs but they have their small problems.
+They are slow and sometimes show unexplainable CRC errors when
+dearcing. This frustrates and detracts from what is otherwise a great
+program. There was none better, that is, until now.
+
+ALFCRUNCH is here. Despite its cute name it has nothing to do with
+the furry wise guy from the planet Melmac. ALFCRUNCH consists of two
+programs, LZ.COM, the archiver, and DZ.COM, the dearchiver. Files are
+manipulated the same way as the ARC programs do it but they are not
+compatible. The LZ program compresses programs slightly more than does
+ARC.COM, or anywhere from a few percent to almost 70%, all depending
+on file type and save method used. The DZ program works as claimed-
+there isn't much to say except that it works. All of this sounds good
+but so what? Why change for a few percent?
+
+The reason to change is speed. ALF programs are at least 10 times
+faster than the ARC programs. Sometimes they are even quicker!
+Programs which may have taken several minutes to process are done
+in seconds with ALF. In fact the first time I tried ALF I thought it
+didn't work... but it does! Reason enough to change? Not yet? Well,
+ALF is free. Get it from your club PD library or download it from
+SLOWPOKE! [2]
+---------------------------------------------------------------------
+
+[1] The full issue of HACK can be found here:
+ https://archive.org/details/AtariHACKNewsAugust1988
+
+[2] SLOWPOKE was an Atari BBS in the Salem/Portland, Oregon area.
diff --git a/examples/aprog.alf b/examples/aprog.alf
new file mode 100644
index 0000000..253565e
--- /dev/null
+++ b/examples/aprog.alf
Binary files differ
diff --git a/examples/atutor.alf b/examples/atutor.alf
new file mode 100644
index 0000000..d9aad92
--- /dev/null
+++ b/examples/atutor.alf
Binary files differ
diff --git a/examples/bbsmio.alf b/examples/bbsmio.alf
new file mode 100644
index 0000000..a05a2f7
--- /dev/null
+++ b/examples/bbsmio.alf
Binary files differ
diff --git a/examples/examples.txt b/examples/examples.txt
new file mode 100644
index 0000000..a576d37
--- /dev/null
+++ b/examples/examples.txt
@@ -0,0 +1,22 @@
+These .alf archives all came from the Holmes Archive, CD 2.
+
+Atari Archives/Dos/:
+ mtosv3.alf
+
+Atari Archives/Telecomm/PRO_BBS/:
+ bbsmio.alf [*]
+ gotcha.alf
+
+Atari Archives/Text/:
+ infocom1.alf
+ infocom2.alf
+
+Programming/Misc/:
+ aprog.alf [*]
+
+Programming/Action!/:
+ atutor.alf [*]
+
+Files marked with [*] have garbage appended to them. These are
+^Z ($1A) bytes, probably put there by a dumb BBS file transfer
+program or a dumb OS (CP/M? MS-DOS 1.0?).
diff --git a/examples/gotcha.alf b/examples/gotcha.alf
new file mode 100644
index 0000000..eb40399
--- /dev/null
+++ b/examples/gotcha.alf
Binary files differ
diff --git a/examples/infocom1.alf b/examples/infocom1.alf
new file mode 100644
index 0000000..3e9c37f
--- /dev/null
+++ b/examples/infocom1.alf
Binary files differ
diff --git a/examples/infocom2.alf b/examples/infocom2.alf
new file mode 100644
index 0000000..f48072e
--- /dev/null
+++ b/examples/infocom2.alf
Binary files differ
diff --git a/examples/mtosv3.alf b/examples/mtosv3.alf
new file mode 100644
index 0000000..5e36988
--- /dev/null
+++ b/examples/mtosv3.alf
Binary files differ
diff --git a/f65/Makefile b/f65/Makefile
new file mode 100644
index 0000000..91adf06
--- /dev/null
+++ b/f65/Makefile
@@ -0,0 +1,10 @@
+CFLAGS=-Wall
+
+all: test
+
+f65.o: f65.c f65.h
+
+f65test: f65.o f65test.c
+
+test: f65test
+ ./f65test
diff --git a/f65/README b/f65/README
new file mode 100644
index 0000000..0f7d0a3
--- /dev/null
+++ b/f65/README
@@ -0,0 +1,32 @@
+f65 - "fake 6502" porting layer
+
+This is a set of C macros that implements most of the 6502 assembly
+language instructions, plus a perl script to convert 6502 assembly
+source to C code that calls the macros. You can use it to assist in
+porting 6502 assembly routines to C, using either the original asm
+source, or a disassembly created with da65.
+
+What's implemented: 64K of memory. The A/X/Y/S registers. The carry,
+zero, and negative flags. Most arithmetic and logic instructions. The
+stack. Conditional branches and absolute jumps are implemented as C
+goto's. JSR is implemented as a real C function call, and RTS is a
+real C return. Labels in the assembly source become C labels (aka goto
+targets). Equates in the asm source become C variables.
+
+What's not implemented: The D flag, and decimal mode in general. The
+V flag, and branches based on it. The I and B flags. Interrupts
+and the RTI instruction. The Program Counter (though branches, JMP,
+JSR, and RTS are implemented without it). ROM routines (including
+I/O). Indirect JMP. The "CPU bug" that causes e.g. 'LDA ($FF),y' to
+take its high byte from $00 rather than $100. (zeropage, x) addressing
+mode.
+
+I wrote this specifically to port the decompression algorithm from
+UnAlf 1.4. Instructions not used by UnAlf probably aren't implemented,
+or if they are, they're untested.
+
+The perl script doesn't magically convert a whole 6502 program to C
+source. You'll have to figure out which parts of the 6502 program are
+subroutines, and put them in their own C functions. Any data (.byte,
+.word, etc) won't be in the C program. Anything that does I/O must be
+rewritten in C.
diff --git a/f65/asm2fake65.pl b/f65/asm2fake65.pl
new file mode 100755
index 0000000..78031a7
--- /dev/null
+++ b/f65/asm2fake65.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl -w
+
+LINE: while(<>) {
+ chomp;
+
+ next if /^\s*$/;
+ next if /^\s+\.byte/;
+ next if /^\s+\.setcpu/;
+ next if /^\s+;/;
+
+ # join lone label: with next line
+ if(/^[a-zA-Z0-9_]+:\s*$/) {
+ $_ .= <>;
+ redo LINE;
+ }
+
+ # comment-only lines:
+ if(/^;\s+(.*)/) {
+ print "/* $1 */\n";
+ next;
+ }
+
+ if(s/(^[a-zA-Z0-9_]+):?//) {
+ my $label = $1;
+ if(/:=\s*\$([0-9A-F]{4})/) {
+ print "u16 $label = 0x$1;\n";
+ next;
+ } elsif(/\.byte/) {
+ /; ([0-9A-F]{4})\s/;
+ print "u16 $label = 0x$1;\n";
+ next;
+ } else {
+ print "$label:\n";
+ }
+ }
+
+ s/\s+;.*//;
+ s/^\s*([a-z]{3})\s*//;
+ my $mnem = $1;
+ s/\s*$//;
+ my $operand = $_;
+ my $arg = "";
+
+ print "\t";
+
+ if(!$operand) {
+ $mnem .= "_a" if $mnem =~ /asl|lsr|rol|ror/;
+ print $mnem . "();\n";
+ next;
+ }
+
+ $operand =~ s,\$,0x,;
+ $operand =~ s,;.*,,;
+
+ #print "mnem '$mnem', operand '$operand'\n";
+ if($operand =~ /#(.*)/) {
+ print $mnem . "_i(" . $1 . ");\n";
+ next;
+ }
+
+ if($operand =~ /\(([^)]+)\),y/) {
+ print $mnem . "_ind_y(" . $1 . ");\n";
+ next;
+ }
+
+ if($operand =~ /(.+),([xy])/) {
+ print $mnem . "_abs_" . $2 . "(" . $1 . ");\n";
+ next;
+ }
+
+ print $mnem . "(" . $operand . ");\n";
+}
diff --git a/f65/f65.c b/f65/f65.c
new file mode 100644
index 0000000..952eac3
--- /dev/null
+++ b/f65/f65.c
@@ -0,0 +1,68 @@
+/* fake 6502. a perl script converts this:
+
+ ldy #10
+loop:
+ lda blah,y
+ sta (p1),y
+ dey
+ bpl loop
+
+to something like:
+ ldy_i(10)
+loop:
+ lda_absy(blah)
+ sta_zpy(p1)
+ dey();
+ bpl(loop);
+
+where e.g.
+
+ #define ldy_i(x) Y=x; ZF=!Y; NF=Y&0x80;
+
+ #define lda_absy(x) A=mem[x+y]; ZF=!A; NF=A&0x80;
+
+ #define sta_zpy(x) mem[(mem[x] | mem[x+1] << 8) + y] = A;
+
+ #define dey() Y--; ZF=!Y; NF=Y&0x80;
+
+ #define bpl(x) if(!NF) goto x
+
+...and the 'loop:' is a real C line label.
+
+we don't worry about the V flag since unalf doesn't use it.
+
+This isn't emulation exactly, and it isn't dynamic recompilation because
+it's static. "Static recompilation" maybe?
+
+*/
+
+#include "f65.h"
+
+u8 A, X, Y, S = 0xff, tmp; /* tmp is result for cmp/cpx/cpy */
+u8 mem[1 << 16]; /* 64K */
+u8 *stack = mem + 0x100; /* page 1 */
+
+u8 CF, ZF, NF; /* flags should really be a bitfield, *shrug* */
+
+unsigned int wtmp;
+
+void dump_state(void) {
+ fprintf(stderr,
+ "A = $%02x X = $%02x Y = $%02x S = $%02x | "
+ "CF = %d ZF=%d NF=%d\n",
+ A, X, Y, S, (CF ? 1 : 0), (ZF ? 1 : 0), (NF ? 1 : 0));
+}
+
+void dump_mem(int start, int end) {
+ int count = 0;
+ while(start <= end) {
+ if(count % 16 == 0) {
+ if(count) fputc('\n', stderr);
+ fprintf(stderr, "%04x:", start);
+ }
+ fprintf(stderr, " %02x", mem[start]);
+ count++;
+ start++;
+ }
+ fputc('\n', stderr);
+}
diff --git a/f65/f65.h b/f65/f65.h
new file mode 100644
index 0000000..d1a3b2d
--- /dev/null
+++ b/f65/f65.h
@@ -0,0 +1,129 @@
+#include <stdio.h>
+
+#define u8 unsigned char
+#define u16 unsigned short
+
+extern u8 A, X, Y, S, tmp; /* tmp is result for cmp/cpx/cpy */
+extern u8 mem[1 << 16]; /* 64K */
+extern u8 *stack; /* page 1 */
+
+extern unsigned int wtmp; /* wide temporary for adc/sbc */
+
+extern u8 CF, ZF, NF; /* flags should really be a bitfield, *shrug* */
+
+extern void dump_state(void);
+extern void dump_mem(int start, int end);
+
+#define dump_zp() dump_mem(0, 0xff);
+#define dump_stack() dump_mem(0x100, 0x1ff);
+#define dump_page(x) dump_mem(x * 0x100, x * 0x100 + 0x1ff);
+
+#define _ind_y(x) mem[Y + (mem[x] | (mem[x+1] << 8))]
+
+#define setnz(x) ZF=!x; NF=(x&0x80)!=0;
+
+#define pha() stack[S--] = A;
+#define pla() A = stack[++S]; setnz(A);
+#define php() stack[S--] = (NF<<7 | ZF<<1 | CF);
+#define plp() tmp = stack[++S]; NF=(tmp&0x80)!=0 ; ZF=(tmp&0x02)!=0; CF=tmp&0x01;
+
+#define lda_i(x) A=x; setnz(A);
+#define ldy_i(x) Y=x; setnz(Y);
+#define ldx_i(x) X=x; setnz(X);
+#define lda(x) A=mem[x]; setnz(A);
+#define ldx(x) X=mem[x]; setnz(X);
+#define ldy(x) Y=mem[x]; setnz(Y);
+#define lda_abs_x(x) A=mem[x+X]; setnz(A);
+#define lda_abs_y(x) A=mem[x+Y]; setnz(A);
+#define lda_ind_y(x) A=_ind_y(x); setnz(A);
+
+#define sta(x) mem[x] = A;
+#define sta_abs_x(x) mem[x + X] = A;
+#define sta_abs_y(x) mem[x + Y] = A;
+#define sta_ind_y(x) _ind_y(x) = A;
+
+#define stx(x) mem[x] = X;
+#define sty(x) mem[x] = Y;
+
+#define sec() CF=1;
+#define clc() CF=0;
+
+#define tax() X=A; setnz(X);
+#define tay() Y=A; setnz(Y);
+#define txa() A=X; setnz(A);
+#define tya() A=Y; setnz(A);
+#define txs() S=X; setnz(S);
+/* note: tsx doesn't affect flags */
+#define tsx() X=S;
+
+#define _ror(x) tmp=x&1; x>>=1; x|=(CF<<7); CF=tmp; setnz(x);
+#define _rol(x) tmp=(x&0x80)!=0; x<<=1; x|=CF; CF=tmp; setnz(x);
+
+#define ror_a() _ror(A);
+#define rol_a() _rol(A);
+#define ror(x) _ror(mem[x]);
+#define rol(x) _rol(mem[x]);
+
+#define _lsr(x) CF=x&1; x>>=1; setnz(x);
+#define _asl(x) CF=(x&0x80)!=0; x<<=1; setnz(x);
+
+#define lsr_a() _lsr(A);
+#define asl_a() _asl(A);
+#define lsr(x) _lsr(mem[x]);
+#define asl(x) _asl(mem[x]);
+
+#define inx() X++; setnz(X);
+#define iny() Y++; setnz(Y);
+#define dex() X--; setnz(X);
+#define dey() Y--; setnz(Y);
+
+#define inc(x) mem[x]++; setnz(mem[x]);
+#define dec(x) mem[x]--; setnz(mem[x]);
+
+#define jsr(x) x();
+#define rts() return;
+
+#define jmp(x) goto x;
+#define bne(x) if(!ZF) goto x;
+#define beq(x) if(ZF) goto x;
+#define bcc(x) if(!CF) goto x;
+#define bcs(x) if(CF) goto x;
+#define bpl(x) if(!NF) goto x;
+#define bmi(x) if(NF) goto x;
+
+#define ora_i(x) A|=x; setnz(A);
+#define ora(x) A|=mem[x]; setnz(A);
+
+#define and_i(x) A&=x; setnz(A);
+#define and(x) A&=mem[x]; setnz(A);
+#define and_abx_x(x) A&=mem[x+X]; setnz(A);
+#define and_abs_y(x) A&=mem[x+Y]; setnz(A);
+#define and_ind_y(x) A&=_ind_y(x); setnz(A);
+
+#define eor_i(x) A^=x; setnz(A);
+#define eor(x) A^=mem[x]; setnz(A);
+
+#define adc_i(x) wtmp=A+x+CF; A=wtmp&0xff; CF=(wtmp&0x100)!=0; setnz(A);
+#define adc(x) adc_i(mem[x]);
+#define adc_abs_x(x) adc_i(mem[x+X]);
+#define adc_abs_y(x) adc_i(mem[x+Y]);
+#define adc_ind_y(x) adc_i(_ind_y(x));
+
+/* TODO: actually check this logic! */
+#define _sub(dst,orig,c,operand) wtmp=orig+(operand^0xff)+c; dst=wtmp&0xff; CF=(wtmp&0x100)!=0; setnz(dst);
+
+#define sbc_i(x) _sub(A,A,CF,x)
+#define sbc(x) _sub(A,A,CF,mem[x])
+#define cmp_i(x) _sub(tmp,A,1,x)
+#define cpy_i(x) _sub(tmp,Y,1,x)
+#define cpx_i(x) _sub(tmp,X,1,x)
+#define cmp(x) cmp_i(mem[x])
+#define cmp_abs_x(x) cmp_i(mem[x+X])
+#define cpy(x) cpy_i(mem[x])
+#define cpx(x) cpx_i(mem[x])
+
+#define nop() {}
+
+/* this should produce a compile error instead */
+#define brk() nop()
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..56951f2
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,25 @@
+COPT=-O3
+CFLAGS=-Wall -Wno-unused-label -I../f65 $(COPT)
+
+all: unalf unalf.1 alfsum alfsum.1
+
+unalf: unalf.o io.o listalf.o extract.o ../f65/f65.o
+
+unalf.o: unalf.c unalf.h ../f65/f65.h
+
+io.o: io.c unalf.h addrs.h ../f65/f65.h
+
+listalf.o: listalf.c addrs.h unalf.h ../f65/f65.h
+
+extract.o: extract.c addrs.h unalf.h ../f65/f65.h
+
+../f65/f65.o: ../f65/f65.c ../f65/f65.h
+
+unalf.1: unalf.rst
+ rst2man unalf.rst > unalf.1
+
+alfsum.1: alfsum.rst
+ rst2man alfsum.rst > alfsum.1
+
+clean:
+ rm -f *.o unalf alfsum ../f65/f65.o
diff --git a/src/addrs.h b/src/addrs.h
new file mode 100644
index 0000000..47e0e32
--- /dev/null
+++ b/src/addrs.h
@@ -0,0 +1,86 @@
+
+#define buf_adr_l 0x7A7B
+#define buf_adr_h 0x7A7C
+#define buf_len_l 0x7A7D
+#define buf_len_h 0x7A7E
+#define zp_b0 0x00B0
+#define zp_b1 0x00B1
+#define acc16_l 0x00B2
+#define acc16_h 0x00B3
+#define zp_b4 0x00B4
+#define zp_b5 0x00B5
+#define stackptr_l 0x00B6
+#define stackptr_h 0x00B7
+#define zp_b8 0x00B8
+#define zp_b9 0x00B9
+#define outbuf_ptr_l 0x00BA
+#define outbuf_ptr_h 0x00BB
+#define zp_bc 0x00BC
+#define zp_bd 0x00BD
+#define zp_be 0x00BE
+#define zp_bf 0x00BF
+#define MEMTOP 0x02E5
+#define MEMTOP_lo 0x02E5
+#define MEMTOP_hi 0x02E6
+#define MEMLO 0x02E7
+#define MEMLO_hi 0x02E8
+#define input_file 0x7000
+#define output_dir 0x7050
+#define outfile_l 0x70A0
+#define outfile_h 0x70A1
+#define linbuf 0x7120
+#define L7174 0x7174
+#define L7175 0x7175
+#define L7176 0x7176
+#define L7177 0x7177
+#define L7178 0x7178
+#define L7179 0x7179
+#define L717A 0x717A
+#define L717B 0x717B
+#define L717C 0x717C
+#define L717D 0x717D
+#define L717E 0x717E
+#define cksum_l 0x717F
+#define cksum_h 0x7180
+#define L7181 0x7181
+#define L7182 0x7182
+#define inbuf_len_l 0x7183
+#define inbuf_len_h 0x7184
+#define inbuf_adr_l 0x7185
+#define inbuf_adr_h 0x7186
+#define outbuf_adr_l 0x7187
+#define outbuf_adr_h 0x7188
+#define L7189 0x7189
+#define L718A 0x718A
+#define L718B 0x718B
+#define L718C 0x718C
+#define alf_header 0x718D
+#define alf_hdr_sig 0x718E
+#define alf_hdr_filename 0x718F
+#define alf_hdr_compsize0 0x719C
+#define alf_hdr_compsize1 0x719D
+#define alf_hdr_compsize2 0x719E
+#define alf_hdr_compsize3 0x719F
+#define alf_hdr_date0 0x71A0
+#define alf_hdr_date1 0x71A1
+#define alf_hdr_time0 0x71A2
+#define alf_hdr_time1 0x71A3
+#define alf_hdr_cksum_l 0x71A4
+#define alf_hdr_cksum_h 0x71A5
+#define alf_hdr_origsize0 0x71A6
+#define alf_hdr_origsize1 0x71A7
+#define alf_hdr_origsize2 0x71A8
+#define alf_hdr_origsize3 0x71A9
+#define L71AA 0x71AA
+#define L71AB 0x71AB
+#define shift_counter 0x71AC
+#define L71AD 0x71AD
+#define L71AE 0x71AE
+#define L71AF 0x71AF
+#define L71B0 0x71B0
+#define outbuf_len_l 0x71B1
+#define outbuf_len_h 0x71B2
+#define L71B4 0x71B4
+#define L71B5 0x71B5
+#define L71B6 0x71B6
+#define L71B7 0x71B7
diff --git a/src/alfsum.1 b/src/alfsum.1
new file mode 100644
index 0000000..1f4ec5b
--- /dev/null
+++ b/src/alfsum.1
@@ -0,0 +1,75 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.TH "ALFSUM" 1 "2025-11-04" "0.0.0" "Urchlay's Atari 8-bit Tools"
+.SH NAME
+alfsum \- calculate ALF checksums
+.\" RST source for alfsum(1) man page. Convert with:
+.
+.\" rst2man.py alfsum.rst > alfsum.1
+.
+.SH SYNOPSIS
+.sp
+alfsum \fBfile\fP [\fBfile\fP ...]
+.SH DESCRIPTION
+.sp
+\fBalfsum\fP calculates the checksums used by the \fBALF\fP compression
+utility on the Atari 8\-bit platform.
+.sp
+There are no options. Use \fB\-\fP to read from standard input. Use
+\fB\&./\-file\fP to read a file whose name begins with \fB\-\fP\&.
+.SH NOTES
+.sp
+The checksum algorithm is very simple: all the bytes in the file are
+added together, and the low 16 bits of the result are the checksum.
+.SH EXIT STATUS
+.INDENT 0.0
+.TP
+.B 0
+Success.
+.TP
+.B 1 to 254
+File I/O error count. If there are more than 254 I/O errors, 254 is returned.
+.TP
+.B 255
+Error in command\-line arguments.
+.UNINDENT
+.SH COPYRIGHT
+.sp
+\fBunalf\fP is released under the WTPFL: Do WTF you want with this.
+.SH AUTHORS
+.INDENT 0.0
+.IP B. 3
+Watson <\fI\%urchlay@slackware.uk\fP>
+.UNINDENT
+.SH SEE ALSO
+.sp
+TODO
+.\" Generated by docutils manpage writer.
+.
diff --git a/src/alfsum.c b/src/alfsum.c
new file mode 100644
index 0000000..7f30c7c
--- /dev/null
+++ b/src/alfsum.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <unistd.h>
+
+/* 20251104 bkw: implement the simple checksum used by ALF.
+ Dumbest possible algo: all the bytes are added together and
+ the bottom 16 bits of the result is the checksum. */
+
+char *self;
+
+void alfsum(const char *file, FILE *f) {
+ int c;
+ unsigned long sum = 0;
+
+ while((c = fgetc(f)) != EOF)
+ sum += c;
+
+ printf("%8s\t%04x\n", file, (unsigned int)(sum & 0xffff));
+}
+
+int main(int argc, char **argv) {
+ int errs = 0;
+ char *file;
+ FILE *f;
+
+ self = argv[0];
+
+ /* if the first arg is a - followed by anything at all, assume --help */
+ if(argc < 2 || (argc == 2 && argv[1][0] == '-' && argv[1][1])) {
+ fprintf(stderr,
+ "usage: %s filename [filename ...]\n"
+ "\t(use - to read from standard input)\n",
+ self);
+ return -1;
+ }
+
+ while((file = *++argv)) {
+ if(argv[0][0] == '-' && !argv[0][1]) {
+ if(isatty(0))
+ fprintf(stderr, "%s: reading from stdin...\n", self);
+ f = stdin;
+ file = " (stdin)";
+ } else if(!(f = fopen(file, "rb"))) {
+ fprintf(stderr, "%s: ", self);
+ perror(file);
+ errs++;
+ continue;
+ }
+ alfsum(file, f);
+ fclose(f);
+ }
+
+ return errs > 254 ? 254 : errs;
+}
diff --git a/src/alfsum.rst b/src/alfsum.rst
new file mode 100644
index 0000000..1d1ee87
--- /dev/null
+++ b/src/alfsum.rst
@@ -0,0 +1,65 @@
+.. RST source for alfsum(1) man page. Convert with:
+.. rst2man.py alfsum.rst > alfsum.1
+
+.. |version| replace:: 0.0.0
+.. |date| date::
+
+======
+alfsum
+======
+
+-----------------------
+calculate ALF checksums
+-----------------------
+
+:Manual section: 1
+:Manual group: Urchlay's Atari 8-bit Tools
+:Date: |date|
+:Version: |version|
+
+SYNOPSIS
+========
+
+alfsum **file** [**file** ...]
+
+DESCRIPTION
+===========
+
+**alfsum** calculates the checksums used by the **ALF** compression
+utility on the Atari 8-bit platform.
+
+There are no options. Use **-** to read from standard input. Use
+**./-file** to read a file whose name begins with **-**.
+
+NOTES
+=====
+
+The checksum algorithm is very simple: all the bytes in the file are
+added together, and the low 16 bits of the result are the checksum.
+
+EXIT STATUS
+===========
+
+0
+ Success.
+
+1 to 254
+ File I/O error count. If there are more than 254 I/O errors, 254 is returned.
+
+255
+ Error in command-line arguments.
+
+COPYRIGHT
+=========
+
+**unalf** is released under the WTPFL: Do WTF you want with this.
+
+AUTHORS
+=======
+
+B. Watson <urchlay@slackware.uk>
+
+SEE ALSO
+========
+
+TODO
diff --git a/src/extract.c b/src/extract.c
new file mode 100644
index 0000000..079fe7f
--- /dev/null
+++ b/src/extract.c
@@ -0,0 +1,732 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <f65.h>
+#include "unalf.h"
+#include "addrs.h"
+
+void dpoke(int addr, u16 value) {
+ mem[addr] = value & 0xff;
+ mem[addr + 1] = value >> 8;
+}
+
+u16 dpeek(int addr) {
+ return mem[addr] | (mem[addr + 1] << 8);
+}
+
+void extract_alf(void) {
+ char *filename;
+
+ /* get ready to call fake 6502 stuff. set up memory like the Atari. */
+ dpoke(MEMTOP, 0xbc1f);
+
+ while(read_alf_header()) {
+ filename = (char *)(mem + alf_hdr_filename);
+ printf("Uncrunching %s\n", filename);
+
+ if(!(out_file = fopen(filename, "wb"))) {
+ fprintf(stderr, "%s: ", self);
+ perror(filename);
+ exit(1);
+ }
+
+ uncrunch_file();
+ fclose(out_file);
+ }
+}
+
+static void chksum_err(void) {
+ fprintf(stderr, "%s: checksum error on file %s\n", self, in_filename);
+}
+
+void uncrunch_file(void) {
+ lda_i(0x00);
+ sta(L71AF);
+ sta(L71B0);
+ sta(outbuf_len_l);
+ sta(outbuf_len_h);
+ sta(L71B6);
+ sta(L71B7);
+ sta(L718C);
+ sta(cksum_l);
+ sta(cksum_h);
+ lda_i(0x09);
+ sta(shift_counter);
+ lda_i(0x00);
+ sta(L71AD);
+ lda_i(0x02);
+ sta(L71AE);
+ lda_i(0x00);
+ sta(stackptr_l);
+ lda_i(0x60);
+ sta(stackptr_h);
+ jsr(setup_io_bufs);
+ ldx_i(0x10);
+ lda(inbuf_adr_l);
+ sta(buf_adr_l);
+ lda(inbuf_adr_h);
+ sta(buf_adr_h);
+ lda(inbuf_len_l);
+ sta(buf_len_l);
+ lda(inbuf_len_h);
+ sta(buf_len_h);
+ jsr(L7A19);
+ sty(L718C);
+ jsr(L79E7);
+ lda(buf_len_l);
+ ora(buf_len_h);
+ bne(L75B1);
+ rts();
+
+L75B1:
+ jsr(L76D0);
+ lda(acc16_h);
+ cmp_i(0x01);
+ bne(uncrunch_blk);
+ lda(acc16_l);
+ cmp_i(0x01);
+ bne(uncrunch_blk);
+ jsr(write_output);
+ lda(alf_hdr_cksum_l);
+ cmp(cksum_l);
+ bne(print_emsg_checksum);
+ lda(alf_hdr_cksum_h);
+ cmp(cksum_h);
+ beq(cksum_ok);
+print_emsg_checksum:
+ chksum_err();
+cksum_ok:
+ rts();
+
+uncrunch_blk:
+ lda(acc16_l);
+ cmp_i(0x00);
+ bne(L760B);
+ lda(acc16_h);
+ cmp_i(0x01);
+ bne(L760B);
+ jsr(init_counters);
+ jsr(L76D0);
+ lda(acc16_l);
+ sta(L717D);
+ sta(L7179);
+ lda(acc16_h);
+ sta(L717E);
+ sta(L717A);
+ lda(acc16_l);
+ sta(L7177);
+ sta(L7178);
+ jsr(store_outbyte);
+ jmp(L75B1);
+
+L760B:
+ lda(acc16_l);
+ sta(L717D);
+ sta(L7175);
+ lda(acc16_h);
+ sta(L717E);
+ sta(L7176);
+ lda(acc16_h);
+ cmp(L717C);
+ bcc(L7641);
+ lda(acc16_l);
+ cmp(L717B);
+ bcc(L7641);
+ lda(L7179);
+ sta(acc16_l);
+ sta(L717D);
+ lda(L717A);
+ sta(acc16_h);
+ sta(L717E);
+ lda(L7178);
+ sta(acc16_l);
+ jsr(push_acc16);
+
+L7641:
+ lda(L717E);
+ beq(L7670);
+ lda(L717D);
+ sta(zp_b4);
+ lda(L717E);
+ sta(zp_b5);
+ jsr(L7899);
+ ldy_i(0x02);
+ lda_ind_y(zp_b0);
+ sta(acc16_l);
+ jsr(push_acc16);
+ ldy_i(0x00);
+ lda_ind_y(zp_b0);
+ sta(acc16_l);
+ sta(L717D);
+ iny();
+ lda_ind_y(zp_b0);
+ sta(acc16_h);
+ sta(L717E);
+ jmp(L7641);
+
+L7670:
+ lda(L717D);
+ sta(acc16_l);
+ sta(L7178);
+ sta(L7177);
+ lda(L717E);
+ sta(acc16_h);
+ jsr(push_acc16);
+L7683:
+ lda(L71AF);
+ ora(L71B0);
+ beq(L7694);
+ jsr(pop_acc16);
+ jsr(store_outbyte);
+ jmp(L7683);
+
+L7694:
+ jsr(L78C2);
+ lda(L7175);
+ sta(acc16_l);
+ sta(L7179);
+ lda(L7176);
+ sta(acc16_h);
+ sta(L717A);
+ lda(L717B);
+ sta(zp_b4);
+ lda(L717C);
+ sta(zp_b5);
+ cmp(L71AE);
+ bcc(L76CD);
+ lda(zp_b4);
+ cmp(L71AD);
+ bcc(L76CD);
+ lda(shift_counter);
+ cmp_i(0x0C);
+ beq(L76CD);
+ inc(shift_counter);
+ asl(L71AD);
+ rol(L71AE);
+L76CD:
+ jmp(L75B1);
+}
+
+void L76D0(void) {
+L76D0:
+ lda(L71B6);
+ sta(zp_b8);
+ lda(L71B7);
+ sta(zp_b9);
+ ldx_i(0x02);
+L76DC:
+ lsr(zp_b9);
+ ror(zp_b8);
+ dex();
+ bpl(L76DC);
+ lda(zp_b8);
+ sta(L71AA);
+ lda(zp_b9);
+ sta(L71AB);
+ lda(inbuf_len_l);
+ sec();
+ sbc(zp_b8);
+ sta(zp_bc);
+ lda(inbuf_len_h);
+ sbc(zp_b9);
+ sta(zp_bd);
+ lda(zp_bd);
+ bne(L770F);
+ lda(zp_bc);
+ cmp_i(0x03);
+ bcs(L770F);
+ ldx(L718C);
+ bpl(L771C);
+ cmp_i(0x02);
+ bcc(L7712);
+L770F:
+ jmp(L779B);
+/* ---------------------------------------------------------------------------- */
+L7712:
+ #if 0
+ ldx_i(0x0A);
+ ldy_i(0x0A);
+ jsr(printstr);
+ jmp(cleanup_and_exit);
+ #endif
+ fprintf(stderr, "%s: unknown error (L7712)\n", self);
+ exit(1);
+/* ---------------------------------------------------------------------------- */
+L771C:
+ tay();
+ dey();
+ ldx(inbuf_adr_l);
+ stx(zp_be);
+ ldx(inbuf_adr_h);
+ stx(zp_bf);
+ lda(inbuf_adr_l);
+ clc();
+ adc(zp_b8);
+ sta(zp_b8);
+ lda(inbuf_adr_h);
+ adc(zp_b9);
+ sta(zp_b9);
+L7737:
+ lda_ind_y(zp_b8);
+ sta_ind_y(zp_be);
+ dey();
+ bpl(L7737);
+ lda(inbuf_len_l);
+ sec();
+ sbc(zp_bc);
+ sta(buf_len_l);
+ lda(inbuf_len_h);
+ sbc_i(0x00);
+ sta(buf_len_h);
+ lda(inbuf_adr_l);
+ clc();
+ adc(zp_bc);
+ sta(buf_adr_l);
+ lda(inbuf_adr_h);
+ adc_i(0x00);
+ sta(buf_adr_h);
+ ldx_i(0x10);
+ jsr(L7A19);
+ sty(L718C);
+ bpl(L7771);
+ cpy_i(0x88);
+ beq(L7771);
+ // jmp(cleanup_and_exit);
+ exit(0);
+/* ---------------------------------------------------------------------------- */
+L7771:
+ jsr(L79E7);
+ lda(L71AA);
+ sta(zp_b8);
+ lda(L71AB);
+ sta(zp_b9);
+ ldx_i(0x02);
+L7780:
+ asl(zp_b8);
+ rol(zp_b9);
+ dex();
+ bpl(L7780);
+ lda(L71B6);
+ sec();
+ sbc(zp_b8);
+ sta(L71B6);
+ lda(L71B7);
+ sbc(zp_b9);
+ sta(L71B7);
+ jmp(L76D0);
+/* ---------------------------------------------------------------------------- */
+L779B:
+ lda(zp_b8);
+ sta(zp_bc);
+ clc();
+ adc(inbuf_adr_l);
+ sta(zp_b8);
+ lda(zp_b9);
+ sta(zp_bd);
+ adc(inbuf_adr_h);
+ sta(zp_b9);
+ ldy_i(0x00);
+ lda(L71B6);
+ and_i(0x07);
+ bne(L77E1);
+ lda_ind_y(zp_b8);
+ sta(acc16_h);
+ iny();
+ lda_ind_y(zp_b8);
+ sta(acc16_l);
+L77C0:
+ lda_i(0x0F);
+ sec();
+ sbc(shift_counter);
+ tax();
+L77C7:
+ lsr(acc16_h);
+ ror(acc16_l);
+ dex();
+ bpl(L77C7);
+ lda(shift_counter);
+ clc();
+ adc(L71B6);
+ sta(L71B6);
+ lda_i(0x00);
+ adc(L71B7);
+ sta(L71B7);
+ rts();
+
+/* ---------------------------------------------------------------------------- */
+L77E1:
+ ldx_i(0x02);
+L77E3:
+ lda_ind_y(zp_b8);
+ sta_abs_x(L7189);
+ iny();
+ dex();
+ bpl(L77E3);
+ lda(L71B6);
+ and_i(0x07);
+ tax();
+ dex();
+L77F3:
+ asl(L7189);
+ rol(L718A);
+ rol(L718B);
+ dex();
+ bpl(L77F3);
+ lda(L718A);
+ sta(acc16_l);
+ lda(L718B);
+ sta(acc16_h);
+ jmp(L77C0);
+}
+
+void L7A19(void) {
+L7A19:
+ lda(alf_hdr_compsize2);
+ ora(alf_hdr_compsize3);
+ beq(L7A28);
+L7A21:
+ jsr(readblock);
+ Y = 1; /* CIO would set this */
+ jsr(L7A5D);
+ rts();
+L7A28:
+ lda(alf_hdr_compsize1);
+ cmp(buf_len_h);
+ bcc(L7A40);
+ beq(L7A34);
+ bcs(L7A21);
+L7A34:
+ lda(alf_hdr_compsize0);
+ cmp(buf_len_l);
+ bcc(L7A40);
+ beq(L7A40);
+ bcs(L7A21);
+L7A40:
+ lda(alf_hdr_compsize0);
+ sta(buf_len_l);
+ lda(alf_hdr_compsize1);
+ sta(buf_len_h);
+ lda(buf_len_l);
+ ora(buf_len_h);
+ beq(L7A57);
+ jsr(readblock);
+ Y = 1; /* CIO would set this */
+L7A57:
+ ldy_i(0x88);
+ jsr(L7A5D);
+ rts();
+}
+
+void L7A5D(void) {
+L7A5D:
+ lda(alf_hdr_compsize0);
+ sec();
+ sbc(buf_len_l);
+ sta(alf_hdr_compsize0);
+ lda(alf_hdr_compsize1);
+ sbc(buf_len_h);
+ sta(alf_hdr_compsize1);
+ lda(alf_hdr_compsize2);
+ sbc_i(0x00);
+ sta(alf_hdr_compsize2);
+ rts();
+}
+
+void setup_io_bufs(void) {
+setup_io_bufs:
+ lda(MEMTOP_lo);
+ sec();
+ sbc_i(0xDC);
+ sta(inbuf_len_l);
+ lda(MEMTOP_hi);
+ sbc_i(0x7F);
+ sta(inbuf_len_h);
+ lsr(inbuf_len_h);
+ ror(inbuf_len_l);
+ lda(inbuf_len_h);
+ cmp_i(0x1F);
+ bcc(L79A0);
+ lda_i(0x00);
+ sta(inbuf_len_l);
+ lda_i(0x1F);
+ sta(inbuf_len_h);
+L79A0:
+ lda_i(0xDC);
+ sta(inbuf_adr_l);
+ lda_i(0x7F);
+ sta(inbuf_adr_h);
+ lda_i(0xDC);
+ clc();
+ adc(inbuf_len_l);
+ sta(outbuf_adr_l);
+ sta(outbuf_ptr_l);
+ lda_i(0x7F);
+ adc(inbuf_len_h);
+ sta(outbuf_adr_h);
+ sta(outbuf_ptr_h);
+ rts();
+}
+
+void init_counters(void) {
+init_counters:
+ lda_i(0x09);
+ sta(shift_counter);
+ lda_i(0x00);
+ sta(L71AD);
+ lda_i(0x02);
+ sta(L71AE);
+ lda_i(0x02);
+ sta(L717B);
+ lda_i(0x01);
+ sta(L717C);
+ rts();
+}
+
+/* save decrunched byte in outbuf, update checksum, write outbuf if full */
+void store_outbyte(void) {
+ ldy_i(0x00);
+ lda(acc16_l);
+ sta_ind_y(outbuf_ptr_l);
+ clc();
+ adc(cksum_l);
+ sta(cksum_l);
+ lda_i(0x00);
+ adc(cksum_h);
+ sta(cksum_h);
+ inc(outbuf_ptr_l);
+ bne(out_ptr_hi_ok);
+ inc(outbuf_ptr_h);
+out_ptr_hi_ok:
+ inc(outbuf_len_l);
+ bne(out_len_hi_ok);
+ inc(outbuf_len_h);
+out_len_hi_ok:
+ lda(outbuf_len_h);
+ cmp(inbuf_len_h);
+ bcc(outbuf_not_full);
+ lda(outbuf_len_l);
+ cmp(inbuf_len_l);
+ bcc(outbuf_not_full);
+ lda(outbuf_adr_l);
+ sta(buf_adr_l);
+ lda(outbuf_adr_h);
+ sta(buf_adr_h);
+ lda(outbuf_len_l);
+ sta(buf_len_l);
+ lda(outbuf_len_h);
+ sta(buf_len_h);
+ /* TODO: call C writeblock() */
+ ldx_i(0x30);
+ jsr(writeblock);
+ bpl(init_outbuf);
+ #if 0
+/* ldx #<emsg_checksum : ldy #>emsg_checksum */
+print_emsg_write_output_2:
+ ldx_i(0xD5);
+ ldy_i(0x72);
+ jsr(printstr);
+cleanup_and_exit:
+ pla();
+ pla();
+ pla();
+ pla();
+ jmp(exit);
+ #endif
+ chksum_err();
+ exit(1);
+/* ---------------------------------------------------------------------------- */
+init_outbuf:
+ lda(outbuf_adr_l);
+ sta(outbuf_ptr_l);
+ lda(outbuf_adr_h);
+ sta(outbuf_ptr_h);
+ lda_i(0x00);
+ sta(outbuf_len_l);
+ sta(outbuf_len_h);
+outbuf_not_full:
+ rts();
+}
+
+
+/* ---------------------------------------------------------------------------- */
+/* push 2 byte 'register' to software stack */
+void push_acc16(void) {
+push_acc16:
+ ldy_i(0x00);
+ lda(acc16_l);
+ sta_ind_y(stackptr_l);
+ iny();
+ lda(acc16_h);
+ sta_ind_y(stackptr_l);
+ lda(stackptr_l);
+ clc();
+ adc_i(0x02);
+ sta(stackptr_l);
+ bcc(L790C);
+ inc(stackptr_h);
+L790C:
+ lda(stackptr_h);
+ cmp_i(0x70);
+ bcc(L791C);
+ #if 0
+/* ldx #<emsg_stk_overrun : ldy #>emsg_stk_overrun */
+print_emsg_stk_overrun:
+ ldx_i(0x25);
+ ldy_i(0x79);
+ jsr(printstr);
+ jmp(cleanup_and_exit);
+ #endif
+ fprintf(stderr, "%s: stack overrun\n", self);
+ exit(1);
+/* ---------------------------------------------------------------------------- */
+L791C:
+ inc(L71AF);
+ bne(L7924);
+ inc(L71B0);
+L7924:
+ rts();
+}
+
+void pop_acc16(void) {
+/* pop 2 byte 'register' from software stack */
+pop_acc16:
+ lda(stackptr_l);
+ sec();
+ sbc_i(0x02);
+ sta(stackptr_l);
+ lda(stackptr_h);
+ sbc_i(0x00);
+ sta(stackptr_h);
+ ldy_i(0x00);
+ lda_ind_y(stackptr_l);
+ sta(acc16_l);
+ iny();
+ lda_ind_y(stackptr_l);
+ sta(acc16_h);
+ lda(stackptr_h);
+ cmp_i(0x60);
+ bcs(L796C);
+ #if 0
+/* ldx #<emsg_stk_underrun : ldy #>emsg_stk_underrun */
+print_emsg_stk_underrun:
+ ldx_i(0x34);
+ ldy_i(0x79);
+ jsr(printstr);
+ jmp(cleanup_and_exit);
+ #endif
+ fprintf(stderr, "%s: stack underrun\n", self);
+ exit(1);
+/* ---------------------------------------------------------------------------- */
+L796C:
+ lda(L71AF);
+ bne(L7974);
+ dec(L71B0);
+L7974:
+ dec(L71AF);
+ rts();
+}
+
+void L79E7(void) {
+L79E7:
+ lda(buf_adr_l);
+ clc();
+ adc(buf_len_l);
+ sta(zp_be);
+ lda(buf_adr_h);
+ adc(buf_len_h);
+ sta(zp_bf);
+ ldx_i(0x02);
+ bne(L7A0A);
+L79FC:
+ ldy_i(0x00);
+ tya();
+ sta_ind_y(zp_be);
+ inc(zp_be);
+ bne(L7A07);
+ inc(zp_bf);
+L7A07:
+ dex();
+ bmi(L7A18);
+L7A0A:
+ lda(zp_bf);
+ cmp(outbuf_adr_h);
+ bcc(L79FC);
+ lda(zp_be);
+ cmp(outbuf_adr_l);
+ bcc(L79FC);
+L7A18:
+ rts();
+}
+
+void L7899(void) {
+L7899:
+ lda(zp_b4);
+ sta(zp_b0);
+ lda(zp_b5);
+ sta(zp_b1);
+ asl(zp_b0);
+ rol(zp_b1);
+ lda(zp_b0);
+ clc();
+ adc(zp_b4);
+ sta(zp_b0);
+ lda(zp_b1);
+ adc(zp_b5);
+ sta(zp_b1);
+ lda(zp_b0);
+ clc();
+ adc(L7181);
+ sta(zp_b0);
+ lda(zp_b1);
+ adc(L7182);
+ sta(zp_b1);
+ rts();
+}
+
+void L78C2(void) {
+L78C2:
+ lda(L717B);
+ sta(zp_b4);
+ lda(L717C);
+ sta(zp_b5);
+ jsr(L7899);
+ lda(L7177);
+ sta(acc16_l);
+ ldy_i(0x02);
+ sta_ind_y(zp_b0);
+ lda(L7179);
+ sta(acc16_l);
+ lda(L717A);
+ sta(acc16_h);
+ ldy_i(0x00);
+ lda(acc16_l);
+ sta_ind_y(zp_b0);
+ iny();
+ lda(acc16_h);
+ sta_ind_y(zp_b0);
+ inc(L717B);
+ bne(L78F5);
+ inc(L717C);
+L78F5:
+ rts();
+}
+
+void write_output(void) {
+write_output:
+ lda(outbuf_len_l);
+ ora(outbuf_len_h);
+ bne(have_output);
+ rts();
+/* ---------------------------------------------------------------------------- */
+have_output:
+ lda(outbuf_adr_l);
+ sta(buf_adr_l);
+ lda(outbuf_adr_h);
+ sta(buf_adr_h);
+ lda(outbuf_len_l);
+ sta(buf_len_l);
+ lda(outbuf_len_h);
+ sta(buf_len_h);
+ ldx_i(0x30);
+ jsr(writeblock);
+ rts();
+}
diff --git a/src/io.c b/src/io.c
new file mode 100644
index 0000000..9b8e40e
--- /dev/null
+++ b/src/io.c
@@ -0,0 +1,92 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <f65.h>
+#include "unalf.h"
+#include "addrs.h"
+
+static int headers_read = 0;
+
+static void die_arc(void) {
+ fprintf(stderr, "%s: this is an ARC file, not ALF\n", self);
+ exit(1);
+}
+
+static void die_not_alf(void) {
+ fprintf(stderr, "%s: not an ALF file\n", self);
+ exit(1);
+}
+
+static void eof_junk(void) {
+ fprintf(stderr, "%s: junk at EOF (ignoring)\n", self);
+}
+
+/* return 1 if a header is read, 0 if not */
+int read_alf_header(void) {
+ u8 h1, h2;
+ int bytes;
+
+ bytes = fread(mem + alf_header, 1, 29, in_file);
+
+ if(!bytes) {
+ if(headers_read)
+ return 0;
+ else
+ die_not_alf();
+ } else if(bytes < 29) {
+ if(headers_read) {
+ eof_junk();
+ return 0;
+ } else {
+ die_not_alf();
+ }
+ }
+
+ h1 = mem[alf_header];
+ h2 = mem[alf_hdr_sig];
+
+ if(h1 == 0x1a) {
+ if(h2 < 0x0f) die_arc();
+ if(h2 == 0x0f) {
+ headers_read++;
+ return 1; /* signature matches */
+ }
+ }
+
+ if(headers_read)
+ eof_junk();
+ else
+ die_not_alf();
+
+ return 0;
+}
+
+/* read buf_len_l/h bytes into buf_adr_l/h, then store the number
+ of bytes actually read in buf_len_l/h. TODO: what about EOF? */
+void readblock(void) {
+ int bytes, len, bufadr;
+ u8 *buf;
+
+ bufadr = dpeek(buf_adr_l);
+ buf = mem + bufadr;
+ len = dpeek(buf_len_l);
+
+ // fprintf(stderr, "readblock, bufadr = $%04x, len = $%04x\n", bufadr, len);
+
+ bytes = fread(buf, 1, len, in_file);
+ dpoke(buf_len_l, bytes);
+}
+
+/* mirror of readblock() */
+void writeblock(void) {
+ int bytes, len, bufadr;
+ u8 *buf;
+
+ bufadr = dpeek(buf_adr_l);
+ buf = mem + bufadr;
+ len = dpeek(buf_len_l);
+
+ // fprintf(stderr, "writeblock, bufadr = $%04x, len = $%04x\n", bufadr, len);
+
+ bytes = fwrite(buf, 1, len, out_file);
+ dpoke(buf_len_l, bytes);
+}
diff --git a/src/listalf.c b/src/listalf.c
new file mode 100644
index 0000000..051f2fe
--- /dev/null
+++ b/src/listalf.c
@@ -0,0 +1,118 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <f65.h>
+#include "unalf.h"
+#include "addrs.h"
+
+static const char *monthnames[] = {
+ "???",
+ "Jan", "Feb", "Mar", "Apr",
+ "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec",
+ "???", "???", "???",
+};
+
+u16 getword(int offs) {
+ return mem[offs] | (mem[offs + 1] << 8);
+}
+
+unsigned int getquad(int offs) {
+ return getword(offs) | ((getword(offs + 2) << 16));
+}
+
+/* see Arcinfo for details */
+void format_msdos_time(char *buf) {
+ u16 t, hour, min;
+ char ampm;
+
+ t = getword(alf_hdr_time0);
+
+ /* don't bother with seconds (arc doesn't print them either) */
+ hour = t >> 11;
+ min = (t >> 5) & 0x3f;
+
+ /* midnight prints as 12pm, not 12am... but 0:01 (1 min past
+ midnight) prints as 12:01am */
+ if(hour == 0 && min == 0) hour = 24;
+
+ ampm = (hour > 11 ? 'p' : 'a');
+ hour %= 12;
+
+ /* midnight and noon print as 12, not 0 */
+ if(hour == 0) hour = 12;
+
+ sprintf(buf, "%2d:%02d%c", hour, min, ampm);
+}
+
+/* see Arcinfo for details */
+void format_msdos_date(char *buf) {
+ u16 d, year, month, day;
+
+ d = getword(alf_hdr_date0);
+
+ /* year actually ranges 1980 to 2107... */
+ year = (d >> 9) + 1980;
+ year %= 100; /* ...but only print last 2 digits */
+
+ /* valid months range 1 to 12. values 0, 13, 14, 15 are possible
+ but invalid (print as ???) */
+ month = (d >> 5) && 0x0f;
+
+ /* valid day range is 1 to 31. 0 is invalid, print as-is. */
+ day = d & 0x1f;
+
+ sprintf(buf, "%2d %3s %02d", day, monthnames[month], year);
+}
+
+/* small files may be "compressed" larger than the original, so this
+ has to return a signed type. */
+static int comp_percent(unsigned int orig, unsigned int comp) {
+ if(orig == 0) return 0; /* no division by zero please */
+ return 100 - (int)((float)comp / (float)orig * 100.0);
+}
+
+/* output similar to "arc v", except we omit the compression type column
+ since there's only one type. also, don't call the checksum a CRC, it
+ isn't. */
+void list_alf(void) {
+ unsigned int c = 0, orig_size, comp_size, total_osize = 0, total_csize = 0;
+ char buf[100];
+ char *filename;
+
+ puts("Name Length Size now Comp Date Time CkSum");
+ puts("============ ======== ======== ==== ========= ====== =====");
+
+ while(read_alf_header()) {
+ c++;
+ orig_size = getquad(alf_hdr_origsize0);
+ comp_size = getquad(alf_hdr_compsize0);
+ filename = (char *)(mem + alf_hdr_filename);
+
+ total_osize += orig_size;
+ total_csize += comp_size;
+
+ printf("%-12s ", filename);
+ printf("%8d ", orig_size);
+ printf("%8d ", comp_size);
+ printf("%3d%% ", comp_percent(orig_size, comp_size));
+ format_msdos_date(buf);
+ printf("%9s ", buf);
+ format_msdos_time(buf);
+ printf("%6s ", buf);
+ printf(" %04x", getword(alf_hdr_cksum_l));
+ putchar('\n');
+
+ if(fseek(in_file, comp_size, SEEK_CUR) != 0) {
+ fputs(self, stderr);
+ perror(": fseek");
+ exit(1);
+ }
+ }
+
+ fputs(" ==== ======== ======== ====\nTotal ", stdout);
+ printf("%4d ", c);
+ printf("%8d ", total_osize);
+ printf("%8d ", total_csize);
+ printf("%3d%% ", comp_percent(total_osize, total_csize));
+ putchar('\n');
+}
diff --git a/src/unalf.1 b/src/unalf.1
new file mode 100644
index 0000000..3ed769b
--- /dev/null
+++ b/src/unalf.1
@@ -0,0 +1,136 @@
+.\" Man page generated from reStructuredText.
+.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.TH "UNALF" 1 "2025-11-12" "0.0.0" "Urchlay's Atari 8-bit Tools"
+.SH NAME
+unalf \- extract Atari 8-bit ALF archives
+.\" RST source for unalf(1) man page. Convert with:
+.
+.\" rst2man.py unalf.rst > unalf.1
+.
+.SH SYNOPSIS
+.sp
+unalf [\fB\-l\fP] \fBalf\-file\fP
+.SH DESCRIPTION
+.sp
+\fBunalf\fP lists or extracts the contents of an \fIALF\fP archive.
+.sp
+\fIALF\fP is a compressed archive format similar to \fBarc\fP(1), though
+not compatible with it. It was used on the Atari 8\-bit platform
+beginning in the late 1980s.
+.sp
+Extracted files are written to the current directory. Existing files
+are overwritten without warning.
+.SH OPTIONS
+.sp
+For compatibility with \fBarc\fP(1) usage, the \fB\-\fP in front of
+option letters is optional.
+.INDENT 0.0
+.TP
+.B \-l
+List contents of archive, but do not extract. The output format
+is similar to that of \fBarc \-v\fP, minus the \fIStowage\fP column, since
+\fIALF\fP doesn\(aqt support multiple compression types. The date and time
+are displayed, but in most .alf files these are the default values
+of "8 Jan 82 12:24a".
+.TP
+.B \-v
+Same as \fB\-l\fP\&.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \fB\-e\fP, \fB\-x\fP
+Extract files. This is actually a no\-op, as extraction is the
+default action.
+.UNINDENT
+.SH EXIT STATUS
+.sp
+\fB0\fP for success, \fB1\fP for failure.
+.SH DIAGNOSTICS
+.sp
+Besides the standard error messages such as "no such file or directory":
+.INDENT 0.0
+.TP
+.B \fBunalf: this is an ARC file, not ALF\fP
+Self\-explanatory. Use the \fBarc\fP(1) utility for this file.
+.TP
+.B \fBunalf: not an ALF file\fP
+Self\-explanatory. Either the file is too small (less than 29 bytes)
+or its first two bytes don\(aqt match the \fIALF\fP signature \fB0x1a\fP \fB0x0f\fP\&.
+.TP
+.B \fBunalf: junk at EOF (ignored)\fP
+Usually this is caused by the .alf file being stored on a CP/M disk
+at some time, or by a dumb file transfer protocol. Either way, the
+file gets padded to the block size of the filesystem or protocol.
+Usually, the padding characters are \fB0x1a\fP, aka ASCII control\-Z.
+.sp
+If you see this message, you can ignore it. It\(aqs intended to let
+you know that this .alf file can\(aqt be appended to by the \fBALF.COM\fP
+aka \fBLZ.COM\fP Atari utility.
+.TP
+.B \fBunalf: checksum error on\fP \fI<file>\fP
+The archive is corrupt. If \fI<file>\fP is a text file, it may be
+partially readable. If it\(aqs an executable or other binary file, it\(aqs
+probably unrecoverable.
+.UNINDENT
+.SH NOTES
+.sp
+This \fBunalf\fP is 100% compatible with the original Atari \fBUNALF.COM\fP
+aka \fBDZ.COM\fP, with the following differences:
+.INDENT 0.0
+.IP \(bu 2
+There is no interactive mode. The file to extract must be given as
+a command\-line argument.
+.IP \(bu 2
+Files are always extracted to the current directory. The original
+\fBUNALF.COM\fP accepted a 2nd argument or prompted for the output directory.
+.IP \(bu 2
+This \fBunalf\fP is capable of listing the contents of an archive
+without extracting it.
+.IP \(bu 2
+Turning the screen off for speed makes no sense on modern operating
+systems, so there\(aqs no option for that.
+.UNINDENT
+.sp
+Neither this \fBunalf\fP nor \fBUNALF.COM\fP actually use the dates/times
+stored in the archive. Extracted files will have their timestamps set
+to the current date/time.
+.SH COPYRIGHT
+.sp
+\fBunalf\fP is released under the WTPFL: Do WTF you want with this.
+.SH AUTHORS
+.INDENT 0.0
+.IP B. 3
+Watson <\fI\%urchlay@slackware.uk\fP>
+.UNINDENT
+.SH SEE ALSO
+.sp
+TODO
+.\" Generated by docutils manpage writer.
+.
diff --git a/src/unalf.c b/src/unalf.c
new file mode 100644
index 0000000..ba6405e
--- /dev/null
+++ b/src/unalf.c
@@ -0,0 +1,66 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <f65.h>
+#include "unalf.h"
+
+FILE *in_file, *out_file;
+char *in_filename, *self;
+
+static void usage(void) {
+ printf("usage: %s [-l] file.alf\n", self);
+ exit(1);
+}
+
+static void set_self(char *argv0) {
+ char *p;
+
+ self = argv0;
+ p = strrchr(self, '/');
+ if(p) self = p + 1;
+}
+
+int main(int argc, char **argv) {
+ char opt = 0;
+ int listonly = 0;
+
+ set_self(argv[0]);
+
+ if(argc == 1) {
+ fprintf(stderr, "%s: missing required argument(s)\n", self);
+ usage();
+ } else if(argc > 3) {
+ fprintf(stderr, "%s: too many arguments\n", self);
+ usage();
+ } else if(argc == 2) {
+ in_filename = argv[1];
+ } else { /* argc == 3 */
+ in_filename = argv[2];
+ if(argv[1][0] == '-')
+ opt = argv[1][1];
+ else
+ opt = argv[1][0];
+ if(opt == 'l' || opt == 'v') {
+ listonly = 1;
+ } else if(opt == 'x' || opt == 'e') {
+ /* NOP */
+ } else {
+ fprintf(stderr, "%s: unknown option '-%c'\n", self, opt);
+ usage();
+ }
+ }
+
+ if(!(in_file = fopen(in_filename, "rb"))) {
+ fprintf(stderr, "%s: ", self);
+ perror(in_filename);
+ exit(1);
+ }
+
+ if(listonly)
+ list_alf();
+ else
+ extract_alf();
+
+ exit(0);
+}
diff --git a/src/unalf.h b/src/unalf.h
new file mode 100644
index 0000000..855ca80
--- /dev/null
+++ b/src/unalf.h
@@ -0,0 +1,40 @@
+/* converted from asm */
+void uncrunch_file(void);
+void setup_io_bufs(void);
+void L7A19(void);
+void L79E7(void);
+void L76D0(void);
+void write_output(void);
+void init_counters(void);
+void store_outbyte(void);
+void push_acc16(void);
+void L7899(void);
+void pop_acc16(void);
+void L78C2(void);
+void L7A19(void);
+void L79E7(void);
+void writeblock(void);
+void L7A5D(void);
+
+/* io.c - asm rewritten in C */
+int read_alf_header(void);
+void readblock(void);
+void writeblock(void);
+void write_output(void);
+
+/* io.c - just C */
+void open_output(void);
+
+/* listalf.c */
+void list_alf(void);
+
+/* extract.c */
+void extract_alf(void);
+void dpoke(int addr, u16 value);
+u16 dpeek(int addr);
+
+/* unalf.c */
+extern char *self;
+extern FILE *out_file;
+extern FILE *in_file;
+extern char *in_filename;
diff --git a/src/unalf.rst b/src/unalf.rst
new file mode 100644
index 0000000..774ddf2
--- /dev/null
+++ b/src/unalf.rst
@@ -0,0 +1,124 @@
+.. RST source for unalf(1) man page. Convert with:
+.. rst2man.py unalf.rst > unalf.1
+
+.. |version| replace:: 0.0.0
+.. |date| date::
+
+=====
+unalf
+=====
+
+--------------------------------
+extract Atari 8-bit ALF archives
+--------------------------------
+
+:Manual section: 1
+:Manual group: Urchlay's Atari 8-bit Tools
+:Date: |date|
+:Version: |version|
+
+SYNOPSIS
+========
+
+unalf [**-l**] **alf-file**
+
+DESCRIPTION
+===========
+
+**unalf** lists or extracts the contents of an *ALF* archive.
+
+*ALF* is a compressed archive format similar to **arc**\(1), though
+not compatible with it. It was used on the Atari 8-bit platform
+beginning in the late 1980s.
+
+Extracted files are written to the current directory. Existing files
+are overwritten without warning.
+
+OPTIONS
+=======
+
+For compatibility with **arc**\(1) usage, the **-** in front of
+option letters is optional.
+
+-l
+ List contents of archive, but do not extract. The output format
+ is similar to that of **arc -v**, minus the *Stowage* column, since
+ *ALF* doesn't support multiple compression types. The date and time
+ are displayed, but in most .alf files these are the default values
+ of "8 Jan 82 12:24a".
+
+-v
+ Same as **-l**.
+
+**-e**, **-x**
+ Extract files. This is actually a no-op, as extraction is the
+ default action.
+
+EXIT STATUS
+===========
+
+**0** for success, **1** for failure.
+
+DIAGNOSTICS
+===========
+
+Besides the standard error messages such as "no such file or directory":
+
+**unalf: this is an ARC file, not ALF**
+ Self-explanatory. Use the **arc**\(1) utility for this file.
+
+**unalf: not an ALF file**
+ Self-explanatory. Either the file is too small (less than 29 bytes)
+ or its first two bytes don't match the *ALF* signature **0x1a** **0x0f**\.
+
+**unalf: junk at EOF (ignored)**
+ Usually this is caused by the .alf file being stored on a CP/M disk
+ at some time, or by a dumb file transfer protocol. Either way, the
+ file gets padded to the block size of the filesystem or protocol.
+ Usually, the padding characters are **0x1a**, aka ASCII control-Z.
+
+ If you see this message, you can ignore it. It's intended to let
+ you know that this .alf file can't be appended to by the **ALF.COM**
+ aka **LZ.COM** Atari utility.
+
+**unalf: checksum error on** *<file>*
+ The archive is corrupt. If *<file>* is a text file, it may be
+ partially readable. If it's an executable or other binary file, it's
+ probably unrecoverable.
+
+NOTES
+=====
+
+This **unalf** is 100% compatible with the original Atari **UNALF.COM**
+aka **DZ.COM**, with the following differences:
+
+- There is no interactive mode. The file to extract must be given as
+ a command-line argument.
+
+- Files are always extracted to the current directory. The original
+ **UNALF.COM** accepted a 2nd argument or prompted for the output directory.
+
+- This **unalf** is capable of listing the contents of an archive
+ without extracting it.
+
+- Turning the screen off for speed makes no sense on modern operating
+ systems, so there's no option for that.
+
+Neither this **unalf** nor **UNALF.COM** actually use the dates/times
+stored in the archive. Extracted files will have their timestamps set
+to the current date/time.
+
+COPYRIGHT
+=========
+
+**unalf** is released under the WTPFL: Do WTF you want with this.
+
+AUTHORS
+=======
+
+B. Watson <urchlay@slackware.uk>
+
+SEE ALSO
+========
+
+TODO
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;