/*----------------------------------------------------------------------------*/
/* UNCRunch/C - LZW uncruncher compatible with Z-80 CP/M program CRUNCH 2.3 */
/* (C) Copyright 1986 - Frank Prindle */
/* May be reproduced for non-profit use only */
/*----------------------------------------------------------------------------*/
/* This program is largely based on the previous works of Steven Greenberg, */
/* Joe Orost, James Woods, Ken Turkowski, Steve Davies, Jim McKie, Spencer */
/* Thomas, Terry Welch, and (of course) Lempel and Ziv. */
/*----------------------------------------------------------------------------*/
/* Portability related assumptions: */
/* 1. fopen called with "rb" or "wb" is sufficient to make getc/putc xfer */
/* bytes in binary, without newline or control-z translation (this is */
/* default for UNIX anyway, and the "b" has no effect). */
/* 2. size of type "long" is at least 4 bytes (for getbuf to work). */
/*----------------------------------------------------------------------------*/
#define VERSION "2.31D"
#define DATE "11 June 1987"
/*#define DUMBLINKER*/ /*define this if linker makes a huge executable image*/
/*containing mostly zeros (big tables will instead be*/
/*allocated at run time)*/
#define XLAT_FN_CHARS {'/','%'} /*FOR UNIX*/
/*define comma separated pairs of {undesirable char,*/
/*replacement char} for filenames - no need to include*/
/*control characters, as these are automatically*/
/*translated to question marks (?)*/
/*----------------------------------------------------------------------------*/
#include "stdio.h"
/*Macro definition - ensure letter is lower case*/
#define tolower(c) (((c)>='A' && (c)<='Z')?(c)-('A'-'a'):(c))
/*Macro definition - is character a control (non-printing) character?*/
#define iscntrl(c) (c<' ' || c=='\177')
#define TABLE_SIZE 4096 /*size of main lzw table for 12 bit codes*/
#define XLATBL_SIZE 5003 /*size of physical translation table*/
/*special values for predecessor in table*/
#define NOPRED 0x6fff /*no predecessor in table*/
#define EMPTY 0x8000 /*empty table entry (xlatbl only)*/
#define REFERENCED 0x2000 /*table entry referenced if this bit set*/
#define IMPRED 0x7fff /*impossible predecessor*/
#define EOFCOD 0x100 /*special code for end-of-file*/
#define RSTCOD 0x101 /*special code for adaptive reset*/
#define NULCOD 0x102 /*special filler code*/
#define SPRCOD 0x103 /*spare special code*/
#define REPEAT_CHARACTER 0x90 /*following byte is repeat count*/
#ifdef DUMBLINKER
/*main lzw table and it's structure*/
struct entry {
short predecessor; /*index to previous entry, if any*/
char suffix; /*character suffixed to previous entries*/
} *table;
/*auxilliary physical translation table*/
/*translates hash to main table index*/
short *xlatbl;
/*byte string stack used by decode*/
char *stack;
#else
struct entry {
short predecessor; /*index to previous entry, if any*/
char suffix; /*character suffixed to previous entries*/
} table[TABLE_SIZE];
/*auxilliary physical translation table*/
/*translates hash to main table index*/
short xlatbl[XLATBL_SIZE];
/*byte string stack used by decode*/
char stack[TABLE_SIZE];
#endif
/*other global variables*/
char codlen; /*variable code length in bits (9-12)*/
short trgmsk; /*mask for codes of current length*/
char fulflg; /*full flag - set once main table is full*/
short entry; /*next available main table entry*/
long getbuf; /*buffer used by getcode*/
short getbit; /*residual bit counter used by getcode*/
char entflg; /*inhibit main loop from entering this code*/
char repeat_flag; /*so send can remember if repeat required*/
int finchar; /*first character of last substring output*/
int lastpr; /*last predecessor (in main loop)*/
short cksum; /*checksum of all bytes written to output file*/
FILE *infd; /*currently open input file*/
FILE *outfd; /*currently open output file*/
main(argc,argv)
int argc;
char *argv[];
{
/*usage check*/
if (argc-- < 2)
{
printf("Usage : uncr <crunched_file_name> ...\n");
exit(0);
}
/*who am i*/
printf("UNCRunch/C Version %s (%s)\n\n",VERSION,DATE);
#ifdef DUMBLINKER
/*allocate storage now for the big tables (keeps load short)*/
table=(struct entry *)malloc(TABLE_SIZE*sizeof(struct entry));
xlatbl=(short *)malloc(XLATBL_SIZE*sizeof(short));
stack=(char *)malloc(TABLE_SIZE*sizeof(char));
if(table==NULL || xlatbl==NULL || stack==NULL)
{
printf("***** not enough memory to run this program!\n");
exit(0);
}
#endif
/*do all the files specified*/
while(argc--) uncrunch(*++argv);
/*all done all files*/
exit(0);
}
/*uncrunch a single file*/
uncrunch(filename)
char *filename;
{
int c;
char *p;
char *index();
char outfn[80]; /*space to build output file name*/
int pred; /*current predecessor (in main loop)*/
char reflevel; /*ref rev level from input file*/
char siglevel; /*sig rev level from input file*/
char errdetect; /*error detection flag from input file*/
short file_cksum; /*checksum read from input file*/
/*filename character translation array*/
static int xlat_fn_chars[][2] = {XLAT_FN_CHARS, {-1,-1}};
/*initialize variables for uncrunching a file*/
intram();
/*open input file*/
if ( 0 == (infd = fopen(filename,"rb")) )
{
printf("***** can't open %s\n", filename);
return;
}
/*verify this is a crunched file*/
if (getc(infd) != 0x76 || getc(infd) != 0xfe)
{
printf("***** %s is not a crunched file\n",filename);
fclose(infd);
return;
}
/*extract and build output file name*/
printf("%s --> ",filename);
for(p=outfn; (*p=getc(infd))!='\0'; p++)
{
int *q;
/*translate to lower case and strip high order bit*/
*p=tolower((*p)&0x7f);
/*make any control character into a question mark*/
if(iscntrl(*p)) *p = '?';
/*read the four info bytes*/
reflevel=getc(infd);
siglevel=getc(infd);
errdetect=getc(infd);
getc(infd); /*skip spare*/
/*make sure we can uncrunch this format file*/
/*note: this program does not support CRUNCH 1.x format*/
if(siglevel < 0x20 || siglevel > 0x2f)
{
printf("***** this version of UNCR cannot process %s!\n",
filename);
fclose(infd);
return;
}
else /*a normal code (nulls already deleted)*/
{
/*check for table full*/
if(fulflg!=2)
{
/*strategy if table not full*/
if(decode(pred)==0)enterx(lastpr,finchar);
else entflg=0;
}
else
{
/*strategy if table is full*/
decode(pred);
entfil(lastpr,finchar); /*attempt to reassign*/
}
}
}
/*verify checksum if required*/
if(errdetect==0)
{
file_cksum=getc(infd);
file_cksum|=getc(infd)<<8;
if((file_cksum&0xffff)!=(cksum&0xffff))
{
printf("***** checksum error detected in ");
printf("%s!\n",filename);
}
}
/*close files*/
fclose(infd);
fclose(outfd);
/*all done this file*/
return;
}
/*initialize the lzw and physical translation tables*/
initb2()
{
register int i;
register struct entry *p;
p=table;
/*first mark all entries of xlatbl as empty*/
for(i=0;i<XLATBL_SIZE;i++) xlatbl[i]=EMPTY;
/*enter atomic and reserved codes into lzw table*/
for(i=0;i<0x100;i++) enterx(NOPRED,i); /*first 256 atomic codes*/
for(i=0;i<4;i++) enterx(IMPRED,0); /*reserved codes*/
}
/*enter the next code into the lzw table*/
enterx(pred,suff)
int pred; /*table index of predecessor*/
int suff; /*suffix byte represented by this entry*/
{
register struct entry *ep;
ep = &table[entry];
/*update xlatbl to point to this entry*/
figure(pred,suff);
/*make the new entry*/
ep->predecessor = (short)pred;
ep->suffix = (char)suff;
entry++;
/*if only one entry of the current code length remains, update to*/
/*next code length because main loop is reading one code ahead*/
if(entry >= trgmsk)
{
if(codlen<12)
{
/*table not full, just make length one more bit*/
codlen++;
trgmsk=(trgmsk<<1)|1;
}
else
{
/*table almost full (fulflg==0) or full (fulflg==1)*/
/*just increment fulflg - when it gets to 2 we will*/
/*never be called again*/
fulflg++;
}
}
}
/*find an empty entry in xlatbl which hashes from this predecessor/suffix*/
/*combo, and store the index of the next available lzw table entry in it*/
figure(pred,suff)
int pred;
int suff;
{
short *hash();
auto int disp;
register short *p;
p=hash(pred,suff,&disp);
/*follow secondary hash chain as necessary to find an empty slot*/
while(((*p)&0xffff) != EMPTY)
{
p+=disp;
if(p<xlatbl)p+=XLATBL_SIZE;
}
/*stuff next available index into this slot*/
*p=entry;
}
/*hash pred/suff into xlatbl pointer*/
/*duplicates the hash algorithm used by CRUNCH 2.3*/
short *hash(pred,suff,disploc)
int pred;
int suff;
int *disploc;
{
register int hashval;
/*mark corresponding table entry as referenced*/
ep->predecessor |= REFERENCED;
/*walk back the lzw table starting with this code*/
stackp=stack;
while(ep > &table[255]) /*i.e. code not atomic*/
{
*stackp++ = ep->suffix;
ep = &table[(ep->predecessor)&0xfff];
}
/*then emit all bytes corresponding to this code in forward order*/
send(finchar=(ep->suffix)&0xff); /*first byte*/
/*write a byte to output file*/
/*repeat byte (0x90) expanded here*/
/*checksumming of output stream done here*/
send(c)
register int c;
{
static int savec; /*previous byte put to output*/
/*repeat flag may have been set by previous call*/
if(repeat_flag)
{
/*repeat flag was set - emit (c-1) copies of savec*/
/*or (if c is zero), emit the repeat byte itself*/
repeat_flag=0;
if(c)
{
cksum+=(savec)*(c-1);
while(--c) putc(savec,outfd);
}
else
{
putc(REPEAT_CHARACTER,outfd);
cksum+=REPEAT_CHARACTER;
}
}
else
{
/*normal case - emit c or set repeat flag*/
if(c==REPEAT_CHARACTER)
{
repeat_flag++;
}
else
{
putc(savec=c,outfd);
cksum+=(c&0xff);
}
}
}
/*attempt to reassign an existing code which has*/
/*been defined, but never referenced*/
entfil(pred,suff)
int pred; /*table index of predecessor*/
int suff; /*suffix byte represented by this entry*/
{
auto int disp;
register struct entry *ep;
short *hash();
short *p;
p=hash(pred,suff,&disp);
/*search the candidate codes (all those which hash from this new*/
/*predecessor and suffix) for an unreferenced one*/
while(*p!=(short)EMPTY)
{
/*candidate code*/
ep = &table[*p];
if(((ep->predecessor)&REFERENCED)==0)
{
/*entry reassignable, so do it!*/
ep->predecessor=pred;
ep->suffix=suff;