#include "unalf.h" #include "addrs.h" static int headers_read = 0; static int convert_eols = 0; static void die_arc(void) { fprintf(stderr, "%s: fatal: this is an ARC file, not ALF\n", self); exit(1); } static void die_not_alf(void) { fprintf(stderr, "%s: fatal: not an ALF file\n", self); exit(1); } /* truncating the file by name could be a race condition, but I don't think it's going to be a real-world problem for anyone. */ static void eof_junk(long pos) { fprintf(stderr, "%s: junk at EOF (%s)\n", self, opts.fixjunk ? "removing" : "ignoring"); if(opts.fixjunk) { if(truncate(in_filename, pos) < 0) { fprintf(stderr, "%s: could not remove junk: ", self); perror(in_filename); } } } static void check_hdr_size(const char *name, unsigned long size) { const char *desc; int fatal = 0; /* fits on a double-density disk? */ if(size < 184320) return; /* >= 16MB files are impossible because the decrunch algorithm ignores the high byte of the 4-byte length. */ if(size >= 16777216L) { desc = "impossibly large (>=16MB)"; fatal = !opts.listonly; /* listing the file isn't an error, extracting it is. */ /* >1MB files are possible (e.g. with a hard drive on SpartaDOS X) but exceedingly rare in the Atari world. */ } else if(size > 1048576L) { desc = "suspiciously large (>1MB)"; } else { desc = "too large for a floppy disk (>180KB)"; } fprintf(stderr, "%s: %s: header #%d %s size is %s.\n", self, (fatal ? "fatal" : "warning"), headers_read, name, desc); if(fatal) exit(1); } static void sanity_check_header(long pos) { struct stat s; unsigned long origsize, compsize; int fatal; origsize = getquad(alf_hdr_origsize0); compsize = getquad(alf_hdr_compsize0); check_hdr_size("original", origsize); check_hdr_size("compressed", compsize); pos += 29; /* skip header */ if(fstat(fileno(in_file), &s) < 0) { fprintf(stderr, "%s: fatal: fstat on %s ", self, in_filename); perror("failed"); return; } if(compsize > (s.st_size - pos)) { fatal = !(opts.force || opts.listonly); fprintf(stderr, "%s: %s: compressed size for header #%d is bigger than the rest of the file (truncated?)", self, fatal ? "fatal" : "warning", headers_read); if(fatal) { fputs(", use -F to override.\n", stderr); exit(1); } fputs("\n", stderr); } /* 0 byte input gives a 2-byte output, 1 byte input gives 4, 2 bytes gives 5. don't complain about these. starting with any 3 byte input, the compressed size will always be under 2x the input size. */ if(origsize > 2 && compsize > 5 && compsize > origsize * 2) { fatal = !(opts.force || opts.listonly); fprintf(stderr, "%s: %s: compressed size for header #%d is over twice the uncompressed size (corrupt?)", self, fatal ? "fatal": "warning", headers_read); if(fatal) { fputs(", use -F to override.\n", stderr); exit(1); } fputs("\n", stderr); } } /* return 1 if a header is read, 0 if not */ int read_alf_header(void) { u8 h1, h2; int bytes; long read_pos; read_pos = ftell(in_file); 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(read_pos); 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++; sanity_check_header(read_pos); return 1; /* signature matches */ } } if(headers_read) eof_junk(read_pos); 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. EOF is handled like the Atari does: you get a partial buffer *and* EOF status. */ void readblock(void) { int bytes, len, bufadr; u8 *buf; bufadr = dpeek(buf_adr_l); buf = mem + bufadr; len = dpeek(buf_len_l); bytes = fread(buf, 1, len, in_file); /* mimic CIO's behaviour: Y=1 means OK, Y>=0x80 means error */ if(feof(in_file)) { ldy_i(0x88); } else { ldy_i(1); } dpoke(buf_len_l, bytes); } /* Atari-specific (can't use isprint()) */ static int is_printable(u8 c) { return (c == 0x9b || (c >= ' ' && c <= 124)); } static int is_text_file(u8 *buf) { return is_printable(buf[0]) && is_printable(buf[1]); } /* mirror of readblock(), plus EOL conversion if needed. With -a, a file is considered text if its first 2 bytes are printable ATASCII, including EOLs. With -aa, all files are converted. */ void writeblock(void) { int i, bytes, len, bufadr; u8 *buf; bufadr = dpeek(buf_adr_l); buf = mem + bufadr; len = dpeek(buf_len_l); if(new_file) { if(opts.txtconv > 1) { convert_eols = 1; } else if(opts.txtconv == 1 && len > 1) { convert_eols = is_text_file(buf); } else { convert_eols = 0; } } new_file = 0; if(convert_eols) { for(i = 0; i < len; i++) { if(buf[i] == 0x9b) buf[i] = '\n'; if(buf[i] == 0x7f) buf[i] = '\t'; } } bytes = fwrite(buf, 1, len, out_file); if(bytes < 0) { extern char *out_filename; /* extract.c */ fprintf(stderr, "%s: fatal: ", self); perror(out_filename); exit(1); } bytes_written += bytes; dpoke(buf_len_l, bytes); }