#include #include #include #include #include #include #include #include #include #include "u816.h" #include "sanity.h" #include "self.h" #include "crunch.h" #include "dupname.h" int opt_append = 0; int opt_overwrite = 0; int opt_zerotime = 0; int opt_alftime = 0; int opt_gmtime = 0; int opt_txtconv = 0; int opt_verbose = 0; int opt_quiet = 0; int backup_msg = 0; int file_count = 0; unsigned int total_in = 0; unsigned int total_out = 0; struct timeval start_time; struct stat in_file_stat; long hdr_compsize_pos; FILE *out_file, *in_file; const char *out_filename, *atari_filename; /* pcc needs this */ #ifndef PATH_MAX #define PATH_MAX 4096 #endif char in_filename[PATH_MAX + 1]; char hdr_filename[13]; void store_quad(int pos, unsigned long data) { int i; for(i = 0; i < 4; i++) { output_buf[pos++] = data & 0xff; data >>= 8; } } void store_cksum(void) { int i; u16 cksum = 0; for(i = 0; i < input_len; i++) cksum += input_buf[i]; output_buf[23] = cksum & 0xff; output_buf[24] = cksum >> 8; } /* examples: foo => FOO. toolongfile => TOOLONGF. (really should be TOOLONGF.ILE) regular.txt => REGULAR.TXT too.many.dots => TOO.MAN */ void atarify_filename(char *result) { int i; char name[9] = { 0 }, ext[4] = { 0 }, *p; p = strrchr(atari_filename, '/'); if(p) p++; else p = (char *)atari_filename; strncpy(name, p, 8); for(i = 0; i < 8; i++) { if(!name[i]) break; if(name[i] == '.') { name[i] = '\0'; break; } } strcpy(result, name); strcat(result, "."); p = strchr(p, '.'); if(p) { p++; strncpy(ext, p, 3); for(p = ext; *p; p++) if(*p == '.') *p = 0; strcat(result, ext); } for(p = result; *p; p++) *p = toupper(*p); } /* see Arcinfo for the gory details. */ unsigned long get_msdos_date_time(void) { time_t t = in_file_stat.st_mtime; struct tm *tm; int msdos_year; u16 ms_date, ms_time; if(opt_gmtime) tm = gmtime(&t); else tm = localtime(&t); tm->tm_isdst = 0; /* use a 0 timestamp if the year's out of range, unalf will display it as . */ if(tm->tm_year < 80 || tm->tm_year > 207) return 0; msdos_year = tm->tm_year + 1900 - 1980; /* tm_mon + 1 because MS uses 1-12 for months, and struct tm uses 0-11 */ ms_date = (tm->tm_mday) | (((tm->tm_mon + 1) << 5)) | (msdos_year << 9); ms_time = (tm->tm_sec >> 1) | (tm->tm_min << 5) | (tm->tm_hour << 11); return ms_date | (ms_time << 16); } void create_header(void) { unsigned long time; atarify_filename(hdr_filename); sanity_check_filename(hdr_filename); if(opt_alftime) time = 0x03130588; else if(opt_zerotime) time = 0; else time = get_msdos_date_time(); output_buf[0] = 0x1a; output_buf[1] = 0x0f; output_buf[2] = 0x00; memset(&output_buf[3], 0x20, 13); /* LZ.COM space-fills this field */ strncat((char *)&output_buf[2], hdr_filename, 13); store_quad(15, 0); /* compressed size, fill in later */ store_quad(19, time); store_cksum(); store_quad(25, input_len); output_len = 29; } void update_header(void) { store_quad(15, output_len - 29); } void open_input(void) { if(!(in_file = fopen(in_filename, "rb"))) { perror(in_filename); exit(1); } } void make_backup(void) { char bak[PATH_MAX + 2]; strncpy(bak, out_filename, PATH_MAX); strcat(bak, "~"); if(rename(out_filename, bak) >= 0) { backup_msg = 1; } } void convert_eols(void) { int i; for(i = 0; i < input_len; i++) { if(input_buf[i] == '\n') input_buf[i] = 0x9b; else if(input_buf[i] == '\t') input_buf[i] = 0x7f; } } int percent(unsigned int out, unsigned int in) { if(!in) return 0; return 100 - (int)((float)out / (float)in * 100.0); } void crunch_file(const char *filename_arg) { strncpy(in_filename, filename_arg, PATH_MAX); /* 1st strtok() returns in_filename itself, whether or not there's an "=", *and* changes the '=' to '\0' if there is one. */ strtok(in_filename, "="); /* 2nd returns everything after the "=", if there is one (or null if not, *or* if it's the last char of the string) */ atari_filename = strtok(NULL, "="); if(!atari_filename) atari_filename = in_filename; open_input(); /* read in entire input, couldn't do it this way on the Atari */ input_len = fread(input_buf, 1, MAX_INPUT_SIZE - 1, in_file); if(input_len == MAX_INPUT_SIZE - 1) { if(fgetc(in_file) != EOF) fprintf(stderr, "%s: %s: this file is too large; only compressing the first 16MB.\n", self, in_filename); } if(opt_txtconv) convert_eols(); output_len = 0; fstat(fileno(in_file), &in_file_stat); /* for timestamp */ fclose(in_file); create_header(); if(!opt_quiet) { if(opt_verbose) { printf("Crunching %s as %s", in_filename, hdr_filename); } else { printf("Crunching %s", hdr_filename); } fflush(stdout); if(!opt_verbose) putchar('\n'); } /* crunches the entire input to memory! */ crunch(); update_header(); /* don't open the output file until crunch() has succeeded once. this avoids leaving 0-byte turds */ if(!out_file) { if(!opt_overwrite) make_backup(); out_file = fopen(out_filename, opt_append ? "ab" : "wb"); if(!out_file) { fprintf(stderr, "%s: fatal: ", self); perror(out_filename); exit(1); } /* so -vv will correctly say "Created" if trying to append to a non-existent file. */ if(ftell(out_file) == 0) opt_append = 0; } if(fwrite(output_buf, 1, output_len, out_file) < 0) { fprintf(stderr, "%s: fatal: ", self); perror(out_filename); exit(1); } if(opt_verbose) { printf(" %u/%u (%d%%)\n", input_len, output_len, percent(output_len, input_len)); } total_in += input_len; total_out += output_len; } void usage(void) { extern char *usage_msg[]; char **line; puts("alf (ALF compressor) v" VERSION " by B. Watson, WTFPL."); printf("Usage: %s -[options] archive.alf file[=NAME] [file[=NAME] ...]\n", self); puts("Use file=NAME to set the filenames in the ALF archive."); puts("Options:"); for(line = usage_msg; *line; line++) puts(*line); exit(0); } float tv_to_float(struct timeval *tv) { return ((float)tv->tv_sec) + (((float)tv->tv_usec) / 1000000.0); } void print_elapsed_time(void) { struct timeval end_time; float s, e, l, mbs; gettimeofday(&end_time, 0); end_time.tv_sec -= start_time.tv_sec; start_time.tv_sec = 0; s = tv_to_float(&start_time); e = tv_to_float(&end_time); l = e - s; mbs = ((float)total_in) / (1048576.0) / l; printf("Elapsed time: %.3fs (%.2fMB/s).\n", l, mbs); } int main(int argc, char **argv) { int opt; gettimeofday(&start_time, 0); set_self(argv[0]); if(argc < 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { usage(); } if(!strcmp(argv[1], "--version")) { puts(VERSION); exit(0); } /* don't let getopt() print error message for us. */ opterr = 0; while((opt = getopt(argc, argv, "aAt:oqvV")) != -1) { switch(opt) { case 'A': opt_txtconv = 1; break; case 'a': opt_append = 1; opt_overwrite = 1; break; case 'o': opt_overwrite = 1; opt_append = 0; break; case 'q': opt_quiet = 1; break; case 't': opt_zerotime = opt_alftime = opt_gmtime = 0; switch(*optarg) { case 'z': opt_zerotime = 1; break; case 'd': opt_alftime = 1; break; case 'u': opt_gmtime = 1; break; default: fprintf(stderr, "%s: fatal: invalid -t suboption '-%c' (try -h or --help)\n", self, *optarg); exit(1); } break; case 'v': opt_verbose++; break; case 'V': puts(VERSION); exit(0); break; default: fprintf(stderr, "%s: fatal: invalid option '-%c' (try -h or --help)\n", self, optopt); exit(1); } } if(opt_quiet) opt_verbose = 0; if(optind >= argc) { fprintf(stderr, "%s: fatal: missing alf file argument (try -h or --help)\n", self); exit(1); } out_filename = argv[optind]; optind++; if(optind >= argc) { fprintf(stderr, "%s: fatal: no filenames (nothing to compress) (try -h or --help)\n", self); exit(1); } while(optind < argc) { crunch_file(argv[optind++]); file_count++; } if(out_file) fclose(out_file); if(opt_verbose) { if(file_count > 1) { printf("Compressed %d file%s: ", file_count, file_count == 1 ? "" : "s"); printf("%u/%u (%d%%)\n", total_in, total_out, percent(total_out, total_in)); } print_elapsed_time(); if(backup_msg) { printf("Backed up old '%s' to '%s~'.\n", out_filename, out_filename); } else if(opt_append) { printf("Appended to '%s'.\n", out_filename); } else { printf("Created '%s'.\n", out_filename); } } exit(0); }