#include #include #include #include #include #include #include #include #include "self.h" #ifndef u8 #define u8 unsigned char #define u16 unsigned short #endif #define INITIAL_BITS 9 #define MAX_BITS 12 #define MAX_TOKENS (1 << MAX_BITS) #define TOK_RESET 256 #define TOK_END 257 #define INIT_TOKEN 258 /* 256 = reset dict, 257 = token_bits++ */ #define MAX_INPUT_SIZE (1 << 24) u8 input_buf[MAX_INPUT_SIZE]; u8 output_buf[MAX_INPUT_SIZE]; u8 byte_tokens[256]; unsigned int input_len, output_len, out_bitpos; int opt_append = 0; int opt_overwrite = 0; struct stat in_file_stat; typedef struct s_token { u8 *start; u16 length; } token_t; token_t tokentab[MAX_TOKENS]; FILE *out_file, *in_file; const char *out_filename, *in_filename; int token_bits; int max_token; int curr_token; long hdr_compsize_pos; int in_pos; void init_table(void) { int i; memset(tokentab, 0, sizeof(tokentab)); token_bits = INITIAL_BITS; max_token = 1 << INITIAL_BITS; curr_token = INIT_TOKEN; for(i = 0; i < 256; i++) { byte_tokens[i] = (u8)i; tokentab[i].start = &byte_tokens[i]; tokentab[i].length = 1; } } 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], ext[4], *p; p = strrchr(in_filename, '/'); if(p) p++; else p = (char *)in_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(in_filename, '.'); 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_mtim.tv_sec; struct tm *tm; int msdos_year; u16 ms_date, ms_time; tm = localtime(&t); msdos_year = tm->tm_year + 1900 - 1980; ms_date = tm->tm_mday | (tm->tm_mon << 5) | (msdos_year << 9); ms_time = (tm->tm_min << 5) | (tm->tm_hour << 11); return ms_date | (ms_time << 16); } void create_header(void) { char hdr_filename[13]; atarify_filename(hdr_filename); printf("Crunching %s\n", hdr_filename); output_buf[0] = 0x1a; output_buf[1] = 0x0f; strncat((char *)&output_buf[2], hdr_filename, 13); output_buf[14] = 0x00; store_quad(15, 0); /* compressed size, fill in later */ store_quad(19, get_msdos_date_time()); store_cksum(); store_quad(25, input_len); output_len = 29; } void update_header(void) { store_quad(15, output_len - 29); } void open_input(const char *filename) { in_filename = filename; if(!(in_file = fopen(in_filename, "rb"))) { perror(in_filename); exit(1); } } void inc_output_len(void) { if(++output_len == MAX_INPUT_SIZE) { fprintf(stderr, "%s: fatal: compressed file would be >16MB.\n", self); exit(1); } } void append_bit(int bit) { output_buf[output_len] |= (bit << (7 - out_bitpos)); out_bitpos++; if(out_bitpos == 8) { out_bitpos = 0; inc_output_len(); } } void store_token(int tok) { int mask; for(mask = 1 << (token_bits - 1); mask; mask >>= 1) { append_bit(tok & mask ? 1 : 0); } } int match_token(int pos) { int i, bestmatch = -1; for(i = 0; i < curr_token; i++) { if(i == TOK_RESET || i == TOK_END) continue; if(memcmp(&input_buf[pos], tokentab[i].start, tokentab[i].length) == 0) bestmatch = i; } if(bestmatch == -1) { fprintf(stderr, "bestmatch is -1, this should never happen\n"); exit(1); } return bestmatch; } void make_token(int start, int end) { if(curr_token == max_token) { // printf("%d: curr_token %d == max_token, ", in_pos, curr_token); if(token_bits == MAX_BITS) { // printf("token_bits %d == MAX_BITS, reset\n", token_bits); store_token(TOK_RESET); /* stored at the *old* token size! */ token_bits = 9; init_table(); return; } else { // printf("token_bits %d < MAX_BITS, inc\n", token_bits); token_bits++; } max_token = 1 << token_bits; } tokentab[curr_token].start = &input_buf[start]; tokentab[curr_token].length = end - start + 1; curr_token++; } void crunch(void) { int new_pos; in_pos = 0; int token; out_bitpos = 0; store_token(TOK_RESET); while(in_pos < input_len) { token = match_token(in_pos); store_token(token); new_pos = in_pos + tokentab[token].length; // printf(" in_pos %d, new_pos %d\n", in_pos, new_pos); if(new_pos < input_len) make_token(in_pos, new_pos); in_pos = new_pos; } store_token(TOK_END); if(out_bitpos) inc_output_len(); update_header(); } void crunch_file(const char *filename) { init_table(); open_input(filename); /* 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(!feof(in_file)) { fprintf(stderr, "%s: warning: this file is too large; only compressing the first 16MB.\n", self); } output_len = 0; fstat(fileno(in_file), &in_file_stat); /* for timestamp */ fclose(in_file); memset(output_buf, 0, sizeof(output_buf)); create_header(); /* crunches the entire input to memory! */ crunch(); fwrite(output_buf, 1, output_len, out_file); } void make_backup(void) { char bak[PATH_MAX + 2]; strncpy(bak, out_filename, PATH_MAX); strcat(bak, "~"); rename(out_filename, bak); } void usage(void) { puts("alf (ALF compressor) v" VERSION " by B. Watson, WTFPL."); printf("Usage: %s [-a|-o] archive.alf file [file ...]\n", self); puts("Options:"); puts(" -a: append to archive"); puts(" -o: overwrite archive (don't make backup with ~)"); exit(0); } int main(int argc, char **argv) { int opt; set_self(argv[0]); if(argc < 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { usage(); } while((opt = getopt(argc, argv, "ao")) != -1) { switch(opt) { case 'a': opt_append = 1; opt_overwrite = 1; break; case 'o': opt_overwrite = 1; opt_append = 0; break; default: fprintf(stderr, "%s: fatal: invalid option '-%c' (try -h or --help)\n", self, optopt); exit(1); } } 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); } if(!opt_overwrite) make_backup(); out_file = fopen(out_filename, opt_append ? "ab" : "wb"); if(!out_file) { perror(*argv); exit(1); } while(optind < argc) { crunch_file(argv[optind++]); } fclose(out_file); exit(0); }