{$C-}
program Unsqueeze;      { unsqueeze file from in_file to out_file }

{ This program unsqueezes a file which has been squeezed or compressed
  to  reduce  the space required to store it on disk.  The program was
  converted from the original  version  written  for  CP/M  in  the  C
  language.   This  program  can be used to unsqueeze files which have
  been downloaded from RCP/M systems where almost all files are  saved
  in this squeezed format.

  The  technique used is the Huffman encoding technique which converts
  the most common characters in the input file  to  a  compressed  bit
  stream  of  data.   This  program  unsqueezes such a Huffman encoded
  file.

  PUBLIC  DOMAIN  -  Feel  free  to  distribute  this program.  Do not
  distribute it by commercial means or make any charge for this pgm.

  Version 1.0  - 09/05/82  Scott Loftesness
  Version 1.1  - 01/06/83  Added capability to strip off parity bit if
                           output file is text. Ernie LeMay 71435,730
  Version 1.2  - 07/20/84  converted to Turbo Pascal. Steve Freeman
  Version 1.3  - 12/30/84  changed file I/O to run on CP/M as well as
                           MS-DOS.  Changed filetypes to 'file' and
                           used blockread/write for file I/O.
                           Jeff Duncan
}


const
    recognize  = $FF76;
    numvals    = 257;      { max tree size + 1 }
    speof      = 256;      { special end of file marker }
    dle: char  = #$90;
    buffersize = 128; (* 128 byte buffer *)

type
    tree       = array [0..255,0..1] of integer;
    hexstr     = string[4];

var
    debug : boolean;
    in_file, out_file: file;
    in_FN: string[30];
    dnode: tree;
    inchar, curin, filecksum, bpos, i, repct, numnodes: integer;
    c, lastchar: char;
    origfile: string[14];
    docfile, eofin, abort: boolean;
    abortM: string[50];
    infilebuffer : array[1..2048] of byte; (* allow for a 2k input buffer *)
    infilepointer : integer; (* pointer into buffer *)
    infilesize : integer;    (* input filesize for buffer loading *)
    inbuffersize : integer;  (* maximum count for buffer pointer *)
    outfilebuffer : array[1..128] of byte; (* output buffer will be minimum *)
    outfilepointer : integer; (* pointer into output buffer *)
    outbuffer_num : integer;  (* how many buffer-fulls used *)

{ iftext -- find out if output file is text and return true if so. EL }
function iftext : boolean;
  var answer: char;
  begin
    repeat
      write('Is the output file a text file?  ');
      read(kbd,answer);
      answer := upcase(answer);
    until (answer in ['Y','N']);
    writeln(answer);
    if answer='Y'
      then iftext:=true
      else iftext:=false;
  end;


function hex(num: integer): hexstr;
  var i, j: integer;
      h: string[16];
      str: hexstr;
  begin
    str := '0000';   h := '0123456789ABCDEF';   j := num;
    for i:=4 downto 1
      do begin
           str[i] := h[(j and 15)+1];
           j := j shr 4;
         end;
    hex := str;
  end;

function getc: integer;

  begin
    if (infilepointer > inbuffersize) and (not eof(in_file)) then
      begin  (* is input buffer empty and more data to follow *)
        if infilesize < 16 then (* less than 2048 bytes left? *)
          begin
            blockread(in_file, infilebuffer, infilesize); (* no get rest *)
            infilepointer := 1;
            inbuffersize := infilesize * 128;
          end
        else
          begin (* full 2048 left so get maximum *)
            blockread(in_file, infilebuffer, 16);
            inbuffersize := 2048;
            infilepointer := 1;
            infilesize := infilesize - 16;
          end;
      end;
    if not ((infilepointer > inbuffersize) and eof(in_file)) then
      begin  (* another character to read  available *)
        getc := infilebuffer[infilepointer];
        infilepointer := infilepointer + 1;
      end;
  end;

{ getw - get a word value from the input file }
function getw: integer;
    var in1,in2: byte;
  begin
    in1 := getc; (* use getc for these to simplify buffer manipulation *)
    in2 := getc;
    getw := ord(in1) + ord(in2) shl 8;
  end;


procedure initialize;
  var str: string[14];
  begin
    abort := false;     { no error conditions presently exist }
    repct:=0;
    bpos:=99;
    origfile:='';
    eofin:=false;
    clrscr;   gotoxy(1,5);   write('Enter the file to unsqueeze:');
    readln(in_FN);
    assign(in_file,in_FN);
    {$I-}
    reset(in_file);
    {$I+}
    if (IOresult = 0) then (* file is found *)
      begin
        inbuffersize := 0; (* dummy for first pass *)
        infilesize := filesize(in_file); (* filesize at initiate *)
        writeln('Input file ', in_fn,' is ',infilesize * 128,' bytes.');
        infilepointer := 1; (* point beyond buffer, so we get data on entry *)
        i := getw;
      end
    else
      i := 0;
    if (recognize <> i)
      then begin
             abort  := true;
             abortM := 'File is not a squeezed file'; (* could be not found also *)
             numnodes := -1;
           end
      else begin
             filecksum := getw;     { get checksum from chars 2 - 3 of file }
             repeat    { build original file name }
                 inchar:=getc;
                 if inchar <> 0
                   then origfile := origfile + chr(inchar);
               until inchar = 0;
             writeln('Original file name is ',origfile);
             write('Output to (return to default) ? ');
             readln(str);
             if length(str)=0 then
               str:=origfile;
             assign(out_file,str);
             rewrite(out_file);
             outfilepointer := 1; (* good idea to start at beginning of buffer *)
             outbuffer_num := 0; (* not neccessary *)
             numnodes:=ord(getw); { get the number of nodes in this files tree }
             if (numnodes<0) or (numnodes>=numvals)
               then begin
                      abort  := true;
                      abortM := 'File has invalid decode tree size';
                    end;
           end;
    if not(abort)
      then begin
             dnode[0,0]:= -(speof+1);
             dnode[0,1]:= -(speof+1);
             numnodes:=numnodes-1;
             for i:=0 to numnodes
               do begin
                    dnode[i,0]:=getw;
                    dnode[i,1]:=getw;
                  end;
             { following is for test }
             {for i:=0 to numnodes
               do writeln(lst,'#',i:3,' ',hex(dnode[i,0]),' ',hex(dnode[i,1]));}
           end;
  end;

procedure dochar(c: char;  text: boolean);
  begin
    if text then
      c:=chr(ord(c) and $7F); {strip off parity bit}
    outfilebuffer[outfilepointer] := ord(c); (* save data in output buffer *)
    outfilepointer := outfilepointer + 1; (* increment the pointer *)
    if outfilepointer > buffersize then
      begin (* we have a full buffer, lets write it out *)
        blockwrite(out_file,outfilebuffer,1);
        outfilepointer := 1; (* reset pinter *)
        outbuffer_num := outbuffer_num + 1;
      end;
  end;

function getuhuff: char;
var i: integer;
  begin
    i:=0;
    repeat
        bpos:=bpos+1;
        if bpos>7 then begin
                         curin := getc;
                         bpos:=0;
                       end
                  else curin := curin shr 1;
        i := ord(dnode[i,ord(curin and $0001)]);
      until (i<0);
    i := -(i+1);
    if i=speof
      then begin
             eofin:=true;
             getuhuff:=chr(26)
           end
      else getuhuff:=chr(i);
  end;

function getcr: char;
var c: char;
  begin
    if (repct>0)
      then begin
             repct:=repct-1;
             getcr:=lastchar;
           end
      else begin
             c:=getuhuff;
             if c<>dle
               then begin
                      getcr:=c;
                      lastchar:=c;
                    end
               else begin
                      repct:=ord(getuhuff);
                      if repct=0 then getcr:=dle
                                 else begin
                                        repct:=repct-2;
                                        getcr:=lastchar;
                                      end;
                    end;
           end;
  end; {getcr}

begin { main }
  debug := true;
  initialize;
  if not(abort)
    then begin
           docfile := iftext;
           writeln(output,'Tree loaded sucessfully. Un-squeezing begins...');
           while not(eof(in_file)) or not(eofin)
             do begin
                  c:=getcr;
                  dochar(c,docfile);
                end;
           if docfile then (* to close the file we have to write the last
                              buffer.  If it's a text file we need to append
                              a ^Z to the buffer before writing.
                           *)
             if outfilepointer <= buffersize then
               outfilebuffer[outfilepointer] := ord(^Z);
           blockwrite(out_file,outfilebuffer, 1);
           close(out_file); (* write last buffer of data *)
         end
    else writeln('Error -- ',AbortM);
  close(in_file);
end.
 then
  