diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 25 | ||||
| -rw-r--r-- | src/addrs.h | 86 | ||||
| -rw-r--r-- | src/alfsum.1 | 75 | ||||
| -rw-r--r-- | src/alfsum.c | 53 | ||||
| -rw-r--r-- | src/alfsum.rst | 65 | ||||
| -rw-r--r-- | src/extract.c | 732 | ||||
| -rw-r--r-- | src/io.c | 92 | ||||
| -rw-r--r-- | src/listalf.c | 118 | ||||
| -rw-r--r-- | src/unalf.1 | 136 | ||||
| -rw-r--r-- | src/unalf.c | 66 | ||||
| -rw-r--r-- | src/unalf.h | 40 | ||||
| -rw-r--r-- | src/unalf.rst | 124 |
12 files changed, 1612 insertions, 0 deletions
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 |
