/* $VER: unlzx.c 1.0 (22.2.98) */
/* Created: 11.2.98 */
/* Added Pipe support to read from stdin (03.4.01, Erik Meusel)           */

/* LZX Extract in (supposedly) portable C.                                */

/* Compile with:                                                          */
/* gcc unlzx.c -ounlzx -O6                                                */

/* Thanks to Dan Fraser for decoding the coredumps and helping me track   */
/* down some HIDEOUSLY ANNOYING bugs.                                     */

/* Everything is accessed as unsigned char's to try and avoid problems    */
/* with byte order and alignment. Most of the decrunch functions          */
/* encourage overruns in the buffers to make things as fast as possible.  */
/* All the time is taken up in crc_calc() and decrunch() so they are      */
/* pretty damn optimized. Don't try to understand this program.           */

/* ---------------------------------------------------------------------- */

#include <stdlib.h>
#include <stdio.h>

/* ---------------------------------------------------------------------- */

static const unsigned char VERSION[]="$VER: unlzx 1.1 (03.4.01)";

/* ---------------------------------------------------------------------- */

struct work_data
{

    int mode;

    unsigned char info_header[10];
    unsigned char archive_header[31];
    unsigned char header_filename[256];
    unsigned char header_comment[256];

    unsigned int pack_size;
    unsigned int unpack_size;

    unsigned int crc;
    unsigned int year, month, day;
    unsigned int hour, minute, second;
    unsigned char attributes;
    unsigned char pack_mode;
}

/* ---------------------------------------------------------------------- */

struct filename_node
{
 struct filename_node *next;
 unsigned int length;
 unsigned int crc;
 char filename[256];
};

struct filename_node *filename_list;

/* ---------------------------------------------------------------------- */

unsigned char read_buffer[16384]; /* have a reasonable sized read buffer */
unsigned char decrunch_buffer[258+65536+258]; /* allow overrun for speed */

unsigned char *source;
unsigned char *destination;
unsigned char *source_end;
unsigned char *destination_end;

unsigned int decrunch_method;
unsigned int decrunch_length;
unsigned int last_offset;
unsigned int global_control;
int global_shift;

unsigned char offset_len[8];
unsigned short offset_table[128];
unsigned char huffman20_len[20];
unsigned short huffman20_table[96];
unsigned char literal_len[768];
unsigned short literal_table[5120];

/* ---------------------------------------------------------------------- */

static const char *month_str[16]=
{
 "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug",
 "sep", "oct", "nov", "dec", "?13", "?14", "?15", "?16"
};

/* ---------------------------------------------------------------------- */

unsigned int sum;

static const unsigned int crc_table[256]=
{
 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,
 0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,
 0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,
 0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
 0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,
 0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,
 0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,
 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,
 0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,
 0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,0x76DC4190,0x01DB7106,
 0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
 0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,
 0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,
 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,
 0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,
 0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,
 0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,0x5005713C,0x270241AA,
 0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
 0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,
 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,
 0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,
 0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,
 0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,
 0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8,0xA1D1937E,
 0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,
 0x316E8EEF,0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,
 0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,
 0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,
 0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,
 0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242,
 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
 0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,
 0x616BFFD3,0x166CCF45,0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,
 0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,
 0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,
 0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,
 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D
};

/* ---------------------------------------------------------------------- */

static const unsigned char table_one[32]=
{
 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14
};

static const unsigned int table_two[32]=
{
 0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,
 1536,2048,3072,4096,6144,8192,12288,16384,24576,32768,49152
};

static const unsigned int table_three[16]=
{
 0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767
};

static const unsigned char table_four[34]=
{
 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
};

/* ---------------------------------------------------------------------- */

/* Possible problems with 64 bit machines here. It kept giving warnings   */
/* for people so I changed back to ~.                                     */

void crc_calc(unsigned char *memory, unsigned int length)
{
 register unsigned int temp;

 if(length)
 {
  temp = ~sum; /* was (sum ^ 4294967295) */
  do
  {
   temp = crc_table[(*memory++ ^ temp) & 255] ^ (temp >> 8);
  } while(--length);
  sum = ~temp; /* was (temp ^ 4294967295) */
 }
}

/* ---------------------------------------------------------------------- */

/* Build a fast huffman decode table from the symbol bit lengths.         */
/* There is an alternate algorithm which is faster but also more complex. */

int make_decode_table(int number_symbols, int table_size,
                      unsigned char *length, unsigned short *table)
{
 register unsigned char bit_num = 0;
 register int symbol;
 unsigned int leaf; /* could be a register */
 unsigned int table_mask, bit_mask, pos, fill, next_symbol, reverse;
 int abort = 0;

 pos = 0; /* consistantly used as the current position in the decode table */

 bit_mask = table_mask = 1 << table_size;

 bit_mask >>= 1; /* don't do the first number */
 bit_num++;

 while((!abort) && (bit_num <= table_size))
 {
  for(symbol = 0; symbol < number_symbols; symbol++)
  {
   if(length[symbol] == bit_num)
   {
    reverse = pos; /* reverse the order of the position's bits */
    leaf = 0;
    fill = table_size;
    do /* reverse the position */
    {
     leaf = (leaf << 1) + (reverse & 1);
     reverse >>= 1;
    } while(--fill);
    if((pos += bit_mask) > table_mask)
    {
     abort = 1;
     break; /* we will overrun the table! abort! */
    }
    fill = bit_mask;
    next_symbol = 1 << bit_num;
    do
    {
     table[leaf] = symbol;
     leaf += next_symbol;
    } while(--fill);
   }
  }
  bit_mask >>= 1;
  bit_num++;
 }

 if((!abort) && (pos != table_mask))
 {
  for(symbol = pos; symbol < table_mask; symbol++) /* clear the rest of the table */
  {
   reverse = symbol; /* reverse the order of the position's bits */
   leaf = 0;
   fill = table_size;
   do /* reverse the position */
   {
    leaf = (leaf << 1) + (reverse & 1);
    reverse >>= 1;
   } while(--fill);
   table[leaf] = 0;
  }
  next_symbol = table_mask >> 1;
  pos <<= 16;
  table_mask <<= 16;
  bit_mask = 32768;

  while((!abort) && (bit_num <= 16))
  {
   for(symbol = 0; symbol < number_symbols; symbol++)
   {
    if(length[symbol] == bit_num)
    {
     reverse = pos >> 16; /* reverse the order of the position's bits */
     leaf = 0;
     fill = table_size;
     do /* reverse the position */
     {
      leaf = (leaf << 1) + (reverse & 1);
      reverse >>= 1;
     } while(--fill);
     for(fill = 0; fill < bit_num - table_size; fill++)
     {
      if(table[leaf] == 0)
      {
       table[(next_symbol << 1)] = 0;
       table[(next_symbol << 1) + 1] = 0;
       table[leaf] = next_symbol++;
      }
      leaf = table[leaf] << 1;
      leaf += (pos >> (15 - fill)) & 1;
     }
     table[leaf] = symbol;
     if((pos += bit_mask) > table_mask)
     {
      abort = 1;
      break; /* we will overrun the table! abort! */
     }
    }
   }
   bit_mask >>= 1;
   bit_num++;
  }
 }
 if(pos != table_mask) abort = 1; /* the table is incomplete! */

 return(abort);
}

/* ---------------------------------------------------------------------- */

/* Read and build the decrunch tables. There better be enough data in the */
/* source buffer or it's stuffed. */

int read_literal_table()
{
 register unsigned int control;
 register int shift;
 unsigned int temp; /* could be a register */
 unsigned int symbol, pos, count, fix, max_symbol;
 int abort = 0;

 control = global_control;
 shift = global_shift;

 if(shift < 0) /* fix the control word if necessary */
 {
  shift += 16;
  control += *source++ << (8 + shift);
  control += *source++ << shift;
 }

/* read the decrunch method */

 decrunch_method = control & 7;
 control >>= 3;
 if((shift -= 3) < 0)
 {
  shift += 16;
  control += *source++ << (8 + shift);
  control += *source++ << shift;
 }

/* Read and build the offset huffman table */

 if((!abort) && (decrunch_method == 3))
 {
  for(temp = 0; temp < 8; temp++)
  {
   offset_len[temp] = control & 7;
   control >>= 3;
   if((shift -= 3) < 0)
   {
    shift += 16;
    control += *source++ << (8 + shift);
    control += *source++ << shift;
   }
  }
  abort = make_decode_table(8, 7, offset_len, offset_table);
 }

/* read decrunch length */

 if(!abort)
 {
  decrunch_length = (control & 255) << 16;
  control >>= 8;
  if((shift -= 8) < 0)
  {
   shift += 16;
   control += *source++ << (8 + shift);
   control += *source++ << shift;
  }
  decrunch_length += (control & 255) << 8;
  control >>= 8;
  if((shift -= 8) < 0)
  {
   shift += 16;
   control += *source++ << (8 + shift);
   control += *source++ << shift;
  }
  decrunch_length += (control & 255);
  control >>= 8;
  if((shift -= 8) < 0)
  {
   shift += 16;
   control += *source++ << (8 + shift);
   control += *source++ << shift;
  }
 }

/* read and build the huffman literal table */

 if((!abort) && (decrunch_method != 1))
 {
  pos = 0;
  fix = 1;
  max_symbol = 256;

  do
  {
   for(temp = 0; temp < 20; temp++)
   {
    huffman20_len[temp] = control & 15;
    control >>= 4;
    if((shift -= 4) < 0)
    {
     shift += 16;
     control += *source++ << (8 + shift);
     control += *source++ << shift;
    }
   }
   abort = make_decode_table(20, 6, huffman20_len, huffman20_table);

   if(abort) break; /* argh! table is corrupt! */

   do
   {
    if((symbol = huffman20_table[control & 63]) >= 20)
    {
     do /* symbol is longer than 6 bits */
     {
      symbol = huffman20_table[((control >> 6) & 1) + (symbol << 1)];
      if(!shift--)
      {
       shift += 16;
       control += *source++ << 24;
       control += *source++ << 16;
      }
      control >>= 1;
     } while(symbol >= 20);
     temp = 6;
    }
    else
    {
     temp = huffman20_len[symbol];
    }
    control >>= temp;
    if((shift -= temp) < 0)
    {
     shift += 16;
     control += *source++ << (8 + shift);
     control += *source++ << shift;
    }
    switch(symbol)
    {
     case 17:
     case 18:
     {
      if(symbol == 17)
      {
       temp = 4;
       count = 3;
      }
      else /* symbol == 18 */
      {
       temp = 6 - fix;
       count = 19;
      }
      count += (control & table_three[temp]) + fix;
      control >>= temp;
      if((shift -= temp) < 0)
      {
       shift += 16;
       control += *source++ << (8 + shift);
       control += *source++ << shift;
      }
      while((pos < max_symbol) && (count--))
       literal_len[pos++] = 0;
      break;
     }
     case 19:
     {
      count = (control & 1) + 3 + fix;
      if(!shift--)
      {
       shift += 16;
       control += *source++ << 24;
       control += *source++ << 16;
      }
      control >>= 1;
      if((symbol = huffman20_table[control & 63]) >= 20)
      {
       do /* symbol is longer than 6 bits */
       {
        symbol = huffman20_table[((control >> 6) & 1) + (symbol << 1)];
        if(!shift--)
        {
         shift += 16;
         control += *source++ << 24;
         control += *source++ << 16;
        }
        control >>= 1;
       } while(symbol >= 20);
       temp = 6;
      }
      else
      {
       temp = huffman20_len[symbol];
      }
      control >>= temp;
      if((shift -= temp) < 0)
      {
       shift += 16;
       control += *source++ << (8 + shift);
       control += *source++ << shift;
      }
      symbol = table_four[literal_len[pos] + 17 - symbol];
      while((pos < max_symbol) && (count--))
       literal_len[pos++] = symbol;
      break;
     }
     default:
     {
      symbol = table_four[literal_len[pos] + 17 - symbol];
      literal_len[pos++] = symbol;
      break;
     }
    }
   } while(pos < max_symbol);
   fix--;
   max_symbol += 512;
  } while(max_symbol == 768);

  if(!abort)
   abort = make_decode_table(768, 12, literal_len, literal_table);
 }

 global_control = control;
 global_shift = shift;

 return(abort);
}

/* ---------------------------------------------------------------------- */

/* Fill up the decrunch buffer. Needs lots of overrun for both destination */
/* and source buffers. Most of the time is spent in this routine so it's  */
/* pretty damn optimized. */

void decrunch()
{
 register unsigned int control;
 register int shift;
 unsigned int temp; /* could be a register */
 unsigned int symbol, count;
 unsigned char *string;

 control = global_control;
 shift = global_shift;

 do
 {
  if((symbol = literal_table[control & 4095]) >= 768)
  {
   control >>= 12;
   if((shift -= 12) < 0)
   {
    shift += 16;
    control += *source++ << (8 + shift);
    control += *source++ << shift;
   }
   do /* literal is longer than 12 bits */
   {
    symbol = literal_table[(control & 1) + (symbol << 1)];
    if(!shift--)
    {
     shift += 16;
     control += *source++ << 24;
     control += *source++ << 16;
    }
    control >>= 1;
   } while(symbol >= 768);
  }
  else
  {
   temp = literal_len[symbol];
   control >>= temp;
   if((shift -= temp) < 0)
   {
    shift += 16;
    control += *source++ << (8 + shift);
    control += *source++ << shift;
   }
  }
  if(symbol < 256)
  {
   *destination++ = symbol;
  }
  else
  {
   symbol -= 256;
   count = table_two[temp = symbol & 31];
   temp = table_one[temp];
   if((temp >= 3) && (decrunch_method == 3))
   {
    temp -= 3;
    count += ((control & table_three[temp]) << 3);
    control >>= temp;
    if((shift -= temp) < 0)
    {
     shift += 16;
     control += *source++ << (8 + shift);
     control += *source++ << shift;
    }
    count += (temp = offset_table[control & 127]);
    temp = offset_len[temp];
   }
   else
   {
    count += control & table_three[temp];
    if(!count) count = last_offset;
   }
   control >>= temp;
   if((shift -= temp) < 0)
   {
    shift += 16;
    control += *source++ << (8 + shift);
    control += *source++ << shift;
   }
   last_offset = count;

   count = table_two[temp = (symbol >> 5) & 15] + 3;
   temp = table_one[temp];
   count += (control & table_three[temp]);
   control >>= temp;
   if((shift -= temp) < 0)
   {
    shift += 16;
    control += *source++ << (8 + shift);
    control += *source++ << shift;
   }
   string = (decrunch_buffer + last_offset < destination) ?
            destination - last_offset : destination + 65536 - last_offset;
   do
   {
    *destination++ = *string++;
   } while(--count);
  }
 } while((destination < destination_end) && (source < source_end));

 global_control = control;
 global_shift = shift;
}

/* ---------------------------------------------------------------------- */

/* Opens a file for writing & creates the full path if required. */

FILE *open_output(char *filename)
{
 unsigned int temp;
 FILE *file;

 if(!(file = fopen(filename, "wb")))
 {
/* couldn't open the file. try and create directories */
  for(temp = 0; filename[temp]; temp++)
  {
   if(filename[temp] == '/')
   {
    filename[temp] = 0;
    mkdir(filename, 511); /* I don't care if it works or not */
    filename[temp] = '/';
   }
  }
  if(!(file = fopen(filename, "wb")))
  {
   perror("FOpen");
  }
 }
 return(file);
}

/* ---------------------------------------------------------------------- */

/* Trying to understand this function is hazardous. */

int extract_normal(FILE *in_file)
{
 struct filename_node *node;
 FILE *out_file = 0;
 unsigned char *pos;
 unsigned char *temp;
 unsigned int count;
 int abort = 0;

 global_control = 0; /* initial control word */
 global_shift = -16;
 last_offset = 1;
 unpack_size = 0;
 decrunch_length = 0;

 for(count = 0; count < 8; count++)
  offset_len[count] = 0;
 for(count = 0; count < 768; count ++)
  literal_len[count] = 0;

 source_end = (source = read_buffer + 16384) - 1024;
 pos = destination_end = destination = decrunch_buffer + 258 + 65536;

 for(node = filename_list; (!abort) && node; node = node->next)
 {
  printf("Extracting \"%s\"...", node->filename);
  fflush(stdout);

  out_file = open_output(node->filename);

  sum = 0; /* reset CRC */

  unpack_size = node->length;

  while(unpack_size > 0)
  {

   if(pos == destination) /* time to fill the buffer? */
   {
/* check if we have enough data and read some if not */
    if(source >= source_end) /* have we exhausted the current read buffer? */
    {
     temp = read_buffer;
     if(count = temp - source + 16384)
     {
      do /* copy the remaining overrun to the start of the buffer */
      {
       *temp++ = *source++;
      } while(--count);
     }
     source = read_buffer;
     count = source - temp + 16384;

     if(pack_size < count) count = pack_size; /* make sure we don't read too much */

     if(fread(temp, 1, count, in_file) != count)
     {
      printf("\n");
      if(ferror(in_file))
       perror("FRead(Data)");
      else
       fprintf(stderr, "EOF: Data\n");
      abort = 1;
      break; /* fatal error */
     }
     pack_size -= count;

     temp += count;
     if(source >= temp) break; /* argh! no more data! */
    } /* if(source >= source_end) */

/* check if we need to read the tables */
    if(decrunch_length <= 0)
    {
     if(read_literal_table()) break; /* argh! can't make huffman tables! */
    }

/* unpack some data */
    if(destination >= decrunch_buffer + 258 + 65536)
    {
     if(count = destination - decrunch_buffer - 65536)
     {
      temp = (destination = decrunch_buffer) + 65536;
      do /* copy the overrun to the start of the buffer */
      {
       *destination++ = *temp++;
      } while(--count);
     }
     pos = destination;
    }
    destination_end = destination + decrunch_length;
    if(destination_end > decrunch_buffer + 258 + 65536)
     destination_end = decrunch_buffer + 258 + 65536;
    temp = destination;

    decrunch();

    decrunch_length -= (destination - temp);
   }

/* calculate amount of data we can use before we need to fill the buffer again */
   count = destination - pos;
   if(count > unpack_size) count = unpack_size; /* take only what we need */

   crc_calc(pos, count);

   if(out_file) /* Write the data to the file */
   {
    if(fwrite(pos, 1, count, out_file) != count)
    {
     perror("FWrite"); /* argh! write error */
     fclose(out_file);
     out_file = 0;
    }
   }
   unpack_size -= count;
   pos += count;
  }

  if(out_file)
  {
   fclose(out_file);
   if(!abort) printf(" crc %s\n", (node->crc == sum) ? "good" : "bad");
  }
 } /* for */

 return(abort);
}

/* ---------------------------------------------------------------------- */

/* This is less complex than extract_normal. Almost decipherable. */

int extract_store(FILE *in_file)
{
 struct filename_node *node;
 FILE *out_file;
 unsigned int count;
 int abort = 0;

 for(node = filename_list; (!abort) && node; node = node->next)
 {
  printf("Storing \"%s\"...", node->filename);
  fflush(stdout);

  out_file = open_output(node->filename);

  sum = 0; /* reset CRC */

  unpack_size = node->length;
  if(unpack_size > pack_size) unpack_size = pack_size;

  while(unpack_size > 0)
  {
   count = (unpack_size > 16384) ? 16384 : unpack_size;

   if(fread(read_buffer, 1, count, in_file) != count)
   {
    printf("\n");
    if(ferror(in_file))
     perror("FRead(Data)");
    else
     fprintf(stderr, "EOF: Data\n");
    abort = 1;
    break; /* fatal error */
   }
   pack_size -= count;

   crc_calc(read_buffer, count);

   if(out_file) /* Write the data to the file */
   {
    if(fwrite(read_buffer, 1, count, out_file) != count)
    {
     perror("FWrite"); /* argh! write error */
     fclose(out_file);
     out_file = 0;
    }
   }
   unpack_size -= count;
  }

  if(out_file)
  {
   fclose(out_file);
   if(!abort) printf(" crc %s\n", (node->crc == sum) ? "good" : "bad");
  }
 } /* for */

 return(abort);
}

/* ---------------------------------------------------------------------- */

/* Easiest of the three. Just print the file(s) we didn't understand. */

int extract_unknown(FILE *in_file)
{
 struct filename_node *node;
 int abort = 0;

 for(node = filename_list; node; node = node->next)
 {
  printf("Unknown \"%s\"\n", node->filename);
 }

 return(abort);
}

/* ---------------------------------------------------------------------- */

/* Read the archive and build a linked list of names. Merged files is     */
/* always assumed. Will fail if there is no memory for a node. Sigh.      */

int extract_archive(FILE *in_file)
{
 unsigned int temp;
 struct filename_node **filename_next;
 struct filename_node *node;
 struct filename_node *temp_node;
 int actual;
 int abort;
 int result = 1; /* assume an error */

 filename_list = 0; /* clear the list */
 filename_next = &filename_list;

 do
 {
  abort = 1; /* assume an error */
  actual = fread(archive_header, 1, 31, in_file);
  if(!ferror(in_file))
  {
   if(actual) /* 0 is normal and means EOF */
   {
    if(actual == 31)
    {
     sum = 0; /* reset CRC */
     crc = (archive_header[29] << 24) + (archive_header[28] << 16) + (archive_header[27] << 8) + archive_header[26]; /* header crc */
     archive_header[29] = 0; /* Must set the field to 0 before calculating the crc */
     archive_header[28] = 0;
     archive_header[27] = 0;
     archive_header[26] = 0;
     crc_calc(archive_header, 31);
     temp = archive_header[30]; /* filename length */
     actual = fread(header_filename, 1, temp, in_file);
     if(!ferror(in_file))
     {
      if(actual == temp)
      {
       header_filename[temp] = 0;
       crc_calc(header_filename, temp);
       temp = archive_header[14]; /* comment length */
       actual = fread(header_comment, 1, temp, in_file);
       if(!ferror(in_file))
       {
        if(actual == temp)
        {
         header_comment[temp] = 0;
         crc_calc(header_comment, temp);
         if(sum == crc)
         {
          unpack_size = (archive_header[5] << 24) + (archive_header[4] << 16) + (archive_header[3] << 8) + archive_header[2]; /* unpack size */
          pack_size = (archive_header[9] << 24) + (archive_header[8] << 16) + (archive_header[7] << 8) + archive_header[6]; /* packed size */
          pack_mode = archive_header[11]; /* pack mode */
          crc = (archive_header[25] << 24) + (archive_header[24] << 16) + (archive_header[23] << 8) + archive_header[22]; /* data crc */

          if(node = (struct filename_node *)malloc(sizeof(struct filename_node))) /* allocate a filename node */
          {
           *filename_next = node; /* add this node to the list */
           filename_next = &(node->next);
           node->next = 0;
           node->length = unpack_size;
           node->crc = crc;
           for(temp = 0; node->filename[temp] = header_filename[temp]; temp++);

           if(pack_size)
           {
            switch(pack_mode)
            {
             case 0: /* store */
             {
              abort = extract_store(in_file);
              break;
             }
             case 2: /* normal */
             {
              abort = extract_normal(in_file);
              break;
             }
             default: /* unknown */
             {
              abort = extract_unknown(in_file);
              break;
             }
            }
            if(abort) break; /* a read error occured */

            temp_node = filename_list; /* free the list now */
            while(node = temp_node)
            {
             temp_node = node->next;
             free(node);
            }
            filename_list = 0; /* clear the list */
            filename_next = &filename_list;

            if(fseek(in_file, pack_size, SEEK_CUR))
            {
             perror("FSeek(Data)");
             break;
            }
           }
           else
            abort = 0; /* continue */
          }
          else
           fprintf(stderr, "MAlloc(Filename_node)\n");
         }
         else
          fprintf(stderr, "CRC: Archive_Header\n");
        }
        else
         fprintf(stderr, "EOF: Header_Comment\n");
       }
       else
        perror("FRead(Header_Comment)");
      }
      else
       fprintf(stderr, "EOF: Header_Filename\n");
     }
     else
      perror("FRead(Header_Filename)");
    }
    else
     fprintf(stderr, "EOF: Archive_Header\n");
   }
   else
   {
    result = 0; /* normal termination */
   }
  }
  else
   perror("FRead(Archive_Header)");
 } while(!abort);

/* free the filename list in case an error occured */
 temp_node = filename_list;
 while(node = temp_node)
 {
  temp_node = node->next;
  free(node);
 }

 return(result);
}

/* ---------------------------------------------------------------------- */

/* List the contents of an archive in a nice formatted kinda way.         */

int view_archive(FILE *in_file)
{
 unsigned int temp;
 unsigned int total_pack = 0;
 unsigned int total_unpack = 0;
 unsigned int total_files = 0;
 unsigned int merge_size = 0;
 int actual;
 int abort;
 int result = 1; /* assume an error */

 printf("Unpacked   Packed Time     Date        Attrib   Name\n");
 printf("-------- -------- -------- ----------- -------- ----\n");

 do
 {
  abort = 1; /* assume an error */
  actual = fread(archive_header, 1, 31, in_file);
  if(!ferror(in_file))
  {
   if(actual) /* 0 is normal and means EOF */
   {
    if(actual == 31)
    {
     sum = 0; /* reset CRC */
     crc = (archive_header[29] << 24) + (archive_header[28] << 16) + (archive_header[27] << 8) + archive_header[26];
     archive_header[29] = 0; /* Must set the field to 0 before calculating the crc */
     archive_header[28] = 0;
     archive_header[27] = 0;
     archive_header[26] = 0;
     crc_calc(archive_header, 31);
     temp = archive_header[30]; /* filename length */
     actual = fread(header_filename, 1, temp, in_file);
     if(!ferror(in_file))
     {
      if(actual == temp)
      {
       header_filename[temp] = 0;
       crc_calc(header_filename, temp);
       temp = archive_header[14]; /* comment length */
       actual = fread(header_comment, 1, temp, in_file);
       if(!ferror(in_file))
       {
        if(actual == temp)
        {
         header_comment[temp] = 0;
         crc_calc(header_comment, temp);
         if(sum == crc)
         {
          attributes = archive_header[0]; /* file protection modes */
          unpack_size = (archive_header[5] << 24) + (archive_header[4] << 16) + (archive_header[3] << 8) + archive_header[2]; /* unpack size */
          pack_size = (archive_header[9] << 24) + (archive_header[8] << 16) + (archive_header[7] << 8) + archive_header[6]; /* packed size */
          temp = (archive_header[18] << 24) + (archive_header[19] << 16) + (archive_header[20] << 8) + archive_header[21]; /* date */
          year = ((temp >> 17) & 63) + 1970;
          month = (temp >> 23) & 15;
          day = (temp >> 27) & 31;
          hour = (temp >> 12) & 31;
          minute = (temp >> 6) & 63;
          second = temp & 63;

          total_pack += pack_size;
          total_unpack += unpack_size;
          total_files++;
          merge_size += unpack_size;

          printf("%8ld ", unpack_size);
          if(archive_header[12] & 1)
           printf("     n/a ");
          else
           printf("%8ld ", pack_size);
          printf("%02ld:%02ld:%02ld ", hour, minute, second);
          printf("%2ld-%s-%4ld ", day, month_str[month], year);
          printf("%c%c%c%c%c%c%c%c ",
                 (attributes & 32) ? 'h' : '-',
                 (attributes & 64) ? 's' : '-',
                 (attributes & 128) ? 'p' : '-',
                 (attributes & 16) ? 'a' : '-',
                 (attributes & 1) ? 'r' : '-',
                 (attributes & 2) ? 'w' : '-',
                 (attributes & 8) ? 'e' : '-',
                 (attributes & 4) ? 'd' : '-');
          printf("\"%s\"\n", header_filename);
          if(header_comment[0])
           printf(": \"%s\"\n", header_comment);
          if((archive_header[12] & 1) && pack_size)
          {
           printf("%8ld %8ld Merged\n", merge_size, pack_size);
          }

          if(pack_size) /* seek past the packed data */
          {
           merge_size = 0;
           if(!fseek(in_file, pack_size, SEEK_CUR))
           {
            abort = 0; /* continue */
           }
           else
            perror("FSeek()");
          }
          else
           abort = 0; /* continue */
         }
         else
          fprintf(stderr, "CRC: Archive_Header\n");
        }
        else
         fprintf(stderr, "EOF: Header_Comment\n");
       }
       else
        perror("FRead(Header_Comment)");
      }
      else
       fprintf(stderr, "EOF: Header_Filename\n");
     }
     else
      perror("FRead(Header_Filename)");
    }
    else
     fprintf(stderr, "EOF: Archive_Header\n");
   }
   else
   {
    printf("-------- -------- -------- ----------- -------- ----\n");
    printf("%8ld %8ld ", total_unpack, total_pack);
    printf("%ld file%s\n", total_files, ((total_files == 1) ? "" : "s"));

    result = 0; /* normal termination */
   }
  }
  else
   perror("FRead(Archive_Header)");
 } while(!abort);

 return(result);
}

/* ---------------------------------------------------------------------- */

/* Process a single archive. */

int process_archive(char *filename)
{
 int result = 1; /* assume an error */
 FILE *in_file;
 int actual;

 if(NULL == filename)
  in_file = stdin;
 else if(NULL == (in_file = fopen(filename,"rb")))
 {
  perror("FOpen(Archive)");
  return(result);
 }

 actual = fread(info_header, 1, 10, in_file);
 if(!ferror(in_file))
 {
  if(actual == 10)
  {
   if((info_header[0] == 76) && (info_header[1] == 90) && (info_header[2] == 88)) /* LZX */
   {
    switch(mode)
    {
     case 1: /* extract archive */
     {
      result = extract_archive(in_file);
      break;
     }
     case 2: /* view archive */
     {
      result = view_archive(in_file);
      break;
     }
    }
   }
   else
    fprintf(stderr, "Info_Header: Bad ID\n");
  }
  else
   fprintf(stderr, "EOF: Info_Header\n");
 }
 else
  perror("FRead(Info_Header)");
 fclose(in_file);

 return(result);
}

/* ---------------------------------------------------------------------- */

/* Handle options & multiple filenames. */

int main(int argc, char **argv)
{
 int result = 0;
 int read_from_stdin = 0;
 int option;
 extern int optind;

 mode = 1; /* default mode is extract */
 while ((option = getopt(argc, argv, "vxc")) != EOF)
 {
  switch(option)
  {
   case 'v': /* (v)iew archive */
   {
    mode = 2;
    break;
   }
   case 'x': /* e(x)tract archive */
   {
    mode = 1;
    break;
   }
   case 'c': /* use stdin to extract/view from */
   {
    read_from_stdin = 1;
    break;
   }
   case '?': /* unknown option */
   {
    result = 1;
    break;
   }
  }
 }
 if(!read_from_stdin && optind >= argc) result = 1;
 /* gotta have a filename or read from stdin */

 if(!result)
 {
  if (read_from_stdin)
  {
   printf("\nReading from stdin...\n\n");
   process_archive(NULL);
   result = 0;
  }
  if((argc - optind) > 1)
  {
   for(; optind < argc; optind++)
   {
    printf("\nArchive \"%s\"...\n\n", argv[optind]);
    process_archive(argv[optind]);
   }
   result = 0; /* Can't give a reliable result for multiple archives */
  }
  else
  {
   result = process_archive(argv[optind]); /* do a single archive */
  }
 }
 else
 {
  fprintf(stderr, "Usage: unlzx [-v][-x][-c] [archive...]\n");
  fprintf(stderr, "\t-c : extract/list from stdin\n");
  fprintf(stderr, "\t-v : list archive(s)\n");
  fprintf(stderr, "\t-x : extract (default)\n");
  result = 2;
 }

 return(result);
}

/* ---------------------------------------------------------------------- */

/* Some info for the reader only. This is unused by the program and can   */
/* safely be deleted.                                                     */

#define INFO_DAMAGE_PROTECT 1
#define INFO_FLAG_LOCKED 2

/* STRUCTURE Info_Header
{
  UBYTE ID[3]; 0 - "LZX"
  UBYTE flags; 3 - INFO_FLAG_#?
  UBYTE[6]; 4
} */ /* SIZE = 10 */

#define HDR_FLAG_MERGED 1

#define HDR_PROT_READ    1
#define HDR_PROT_WRITE   2
#define HDR_PROT_DELETE  4
#define HDR_PROT_EXECUTE 8
#define HDR_PROT_ARCHIVE 16
#define HDR_PROT_HOLD    32
#define HDR_PROT_SCRIPT  64
#define HDR_PROT_PURE    128

#define HDR_TYPE_MSDOS   0
#define HDR_TYPE_WINDOWS 1
#define HDR_TYPE_OS2     2
#define HDR_TYPE_AMIGA   10
#define HDR_TYPE_UNIX    20

#define HDR_PACK_STORE    0
#define HDR_PACK_NORMAL   2
#define HDR_PACK_EOF      32

/* STRUCTURE Archive_Header
{
  UBYTE attributes; 0 - HDR_PROT_#?
  UBYTE; 1
  ULONG unpacked_length; 2 - FUCKED UP LITTLE ENDIAN SHIT
  ULONG packed_length; 6 - FUCKED UP LITTLE ENDIAN SHIT
  UBYTE machine_type; 10 - HDR_TYPE_#?
  UBYTE pack_mode; 11 - HDR_PACK_#?
  UBYTE flags; 12 - HDR_FLAG_#?
  UBYTE; 13
  UBYTE len_comment; 14 - comment length [0,79]
  UBYTE extract_ver; 15 - version needed to extract
  UBYTE; 16
  UBYTE; 17
  ULONG date; 18 - Packed_Date
  ULONG data_crc; 22 - FUCKED UP LITTLE ENDIAN SHIT
  ULONG header_crc; 26 - FUCKED UP LITTLE ENDIAN SHIT
  UBYTE filename_len; 30 - filename length
} */ /* SIZE = 31 */

#define DATE_SHIFT_YEAR   17
#define DATE_SHIFT_MONTH  23
#define DATE_SHIFT_DAY    27
#define DATE_SHIFT_HOUR   12
#define DATE_SHIFT_MINUTE 6
#define DATE_SHIFT_SECOND 0
#define DATE_MASK_YEAR    0x007E0000
#define DATE_MASK_MONTH   0x07800000
#define DATE_MASK_DAY     0xF8000000
#define DATE_MASK_HOUR    0x0001F000
#define DATE_MASK_MINUTE  0x00000FC0
#define DATE_MASK_SECOND  0x0000003F

/* STRUCTURE DATE_Unpacked
{
  UBYTE year; 80 - Year 0=1970 1=1971 63=2033
  UBYTE month; 81 - 0=january 1=february .. 11=december
  UBYTE day; 82
  UBYTE hour; 83
  UBYTE minute; 84
  UBYTE second; 85
} */ /* SIZE = 6 */

/* STRUCTURE DATE_Packed
{
  UBYTE packed[4]; bit 0 is MSB, 31 is LSB
; bit # 0-4=Day 5-8=Month 9-14=Year 15-19=Hour 20-25=Minute 26-31=Second
} */ /* SIZE = 4 */
