{$R-} {$U-} {$C-} {$K-} program dearc512; { REVISION - Now supports ARC 5.12 and earlier files - 6-10-86 by DWC } { DEARC.PAS - Program to extract all files from an archive created by version 5.12 or earlier of the ARC utility. ARC is COPYRIGHT 1985 by System Enhancement Associates. This program requires Turbo Pascal Version 3.01A. It should work in all supported environments (PCDOS, CPM, etc.) but I have only tested it on an IBM PC running PC DOS version 3.10. Usage: DEARC arcname arcname is the path/file name of the archive file. All files contained in the archive will be extracted into the current directory. *** ORIGINAL AUTHOR UNKNOWN *** Version 1.01 - 10/19/85. Changed end-of-file processing to, hopefully, be more compatible with CPM (whatever that is). Version 1.01A - 12/19/85 By Roy Collins Mail: TechMail BBS @ 703-430-2535 - or - P.O.Box 1192, Leesburg, Va 22075 Modified V1.01 to work with Turbo Pascal Version 2 Added functions ARGC (argument count) and ARGV (argument value) Modified all references to "EXIT" command to be GOTO EXIT, with EXIT defined as a LABEL, at the end of the function/procedure involved. Will not accept path names - archives must be in the current directory. Version 2.00 - 6/11/86 By David W. Carroll Mail: High Sierra RBBS-PC @ 209/296-3534 Now supports ARC version 5.12 files, compression types 7 and 8. } (************************* ARGC & ARGV functions **************************) type arglist_string = string[100]; const arglist_max = 20; arglist_number : integer = -1; var argvlist : array[1..arglist_max] of ^arglist_string; function argv(num : integer) : arglist_string; var argument : arglist_string absolute cseg:$80; newparm, parmline : arglist_string; i, j : integer; state : (leading_ws, non_quote, quoted, end_quote); inchar : char; procedure saveparm; begin if arglist_number < arglist_max then begin arglist_number := arglist_number+1; new(argvlist[arglist_number]); argvlist[arglist_number]^ := newparm; newparm := ''; end; end; (* proc saveparm *) begin if arglist_number = -1 then begin arglist_number := 0; parmline := argument+' '; state := leading_ws; newparm := ''; for i := 1 to length(parmline) do begin inchar := parmline[i]; case state of leading_ws: begin if inchar = '''' then state := quoted else if inchar <> ' ' then begin newparm := newparm+inchar; state := non_quote; end; end; (* leading_ws *) non_quote: begin if inchar = ' ' then begin saveparm; state := leading_ws; end else newparm := newparm+inchar; end; (* non_quote *) quoted: begin if inchar = '''' then state := end_quote else newparm := newparm+inchar; end; (* quoted *) end_quote: begin if inchar = '''' then begin newparm := newparm+inchar; state := quoted; end else if inchar <> ' ' then begin newparm := newparm+inchar; state := non_quote; end else begin saveparm; state := leading_ws; end; end; (* end_quote *) end; (* case state *) end; (* for *) end; (* if arglist_number = -1 *) if (num > 0) and (num <= arglist_number) then argv := argvlist[num]^ else argv := ''; end; (* func argv *) function argc : integer; var dummy : arglist_string; begin if arglist_number = -1 then dummy := argv(1); {force evaluation} argc := arglist_number; end; (* func argc *) (****************** end of ARGC & ARGV functions **************************) const BLOCKSIZE = 128; arcmarc = 26; { special archive marker } arcver = 8; { max archive header version code } strlen = 100; { standard string length } fnlen = 12; { file name length - 1 } const crctab : array [0..255] of integer = ( $0000, $C0C1, $C181, $0140, $C301, $03C0, $0280, $C241, $C601, $06C0, $0780, $C741, $0500, $C5C1, $C481, $0440, $CC01, $0CC0, $0D80, $CD41, $0F00, $CFC1, $CE81, $0E40, $0A00, $CAC1, $CB81, $0B40, $C901, $09C0, $0880, $C841, $D801, $18C0, $1980, $D941, $1B00, $DBC1, $DA81, $1A40, $1E00, $DEC1, $DF81, $1F40, $DD01, $1DC0, $1C80, $DC41, $1400, $D4C1, $D581, $1540, $D701, $17C0, $1680, $D641, $D201, $12C0, $1380, $D341, $1100, $D1C1, $D081, $1040, $F001, $30C0, $3180, $F141, $3300, $F3C1, $F281, $3240, $3600, $F6C1, $F781, $3740, $F501, $35C0, $3480, $F441, $3C00, $FCC1, $FD81, $3D40, $FF01, $3FC0, $3E80, $FE41, $FA01, $3AC0, $3B80, $FB41, $3900, $F9C1, $F881, $3840, $2800, $E8C1, $E981, $2940, $EB01, $2BC0, $2A80, $EA41, $EE01, $2EC0, $2F80, $EF41, $2D00, $EDC1, $EC81, $2C40, $E401, $24C0, $2580, $E541, $2700, $E7C1, $E681, $2640, $2200, $E2C1, $E381, $2340, $E101, $21C0, $2080, $E041, $A001, $60C0, $6180, $A141, $6300, $A3C1, $A281, $6240, $6600, $A6C1, $A781, $6740, $A501, $65C0, $6480, $A441, $6C00, $ACC1, $AD81, $6D40, $AF01, $6FC0, $6E80, $AE41, $AA01, $6AC0, $6B80, $AB41, $6900, $A9C1, $A881, $6840, $7800, $B8C1, $B981, $7940, $BB01, $7BC0, $7A80, $BA41, $BE01, $7EC0, $7F80, $BF41, $7D00, $BDC1, $BC81, $7C40, $B401, $74C0, $7580, $B541, $7700, $B7C1, $B681, $7640, $7200, $B2C1, $B381, $7340, $B101, $71C0, $7080, $B041, $5000, $90C1, $9181, $5140, $9301, $53C0, $5280, $9241, $9601, $56C0, $5780, $9741, $5500, $95C1, $9481, $5440, $9C01, $5CC0, $5D80, $9D41, $5F00, $9FC1, $9E81, $5E40, $5A00, $9AC1, $9B81, $5B40, $9901, $59C0, $5880, $9841, $8801, $48C0, $4980, $8941, $4B00, $8BC1, $8A81, $4A40, $4E00, $8EC1, $8F81, $4F40, $8D01, $4DC0, $4C80, $8C41, $4400, $84C1, $8581, $4540, $8701, $47C0, $4680, $8641, $8201, $42C0, $4380, $8341, $4100, $81C1, $8081, $4040 ); type long = record { used to simulate long (4 byte) integers } l, h : integer end; type strtype = string[strlen]; fntype = array [0..fnlen] of char; buftype = array [1..BLOCKSIZE] of byte; heads = record name : fntype; size : long; date : integer; time : integer; crc : integer; length : long end; var hdrver : byte; arcfile : file; arcbuf : buftype; arcptr : integer; arcname : strtype; endfile : boolean; extfile : file; extbuf : buftype; extptr : integer; extname : strtype; { definitions for unpack } const DLE = $90; var state : (NOHIST, INREP); crcval : integer; size : real; lastc : integer; { definitions for unsqueeze } const ERROR = -1; SPEOF = 256; NUMVALS = 256; { 1 less than the number of values } type nd = record child : array [0..1] of integer end; var node : array [0..NUMVALS] of nd; bpos : integer; curin : integer; numnodes : integer; { definitions for uncrunch } const TABSIZE = 4096; TABSIZEM1 = 4095; NO_PRED = $FFFF; EMPTY = $FFFF; type entry = record used : boolean; next : integer; predecessor : integer; follower : byte end; var stack : array [0..TABSIZEM1] of byte; sp : integer; string_tab : array [0..TABSIZEM1] of entry; var code_count : integer; code : integer; firstc : boolean; oldcode : integer; finchar : integer; inbuf : integer; outbuf : integer; newhash : boolean; { definitions for dynamic uncrunch } const BITS = 12; HSIZE = 5003; INIT_BITS = 9; FIRST = 257; CLEAR = 256; HSIZEM1 = 5002; BITSM1 = 11; RMASK : array[0..8] of byte = ($00, $01, $03, $07, $0f, $1f, $3f, $7f, $ff); var n_bits, maxcode : integer; prefix : array[0..HSIZEM1] of integer; suffix : array[0..TABSIZEM1] of byte; buf : array[0..BITSM1] of byte; clear_flg : integer; stack1 : array[0..HSIZEM1] of byte; free_ent : integer; maxcodemax : integer; offset, sizex : integer; firstch : boolean; procedure abort(s : strtype); { terminate the program with an error message } begin writeln('ABORT: ', s); halt; end; (* proc abort *) function fn_to_str(var fn : fntype) : strtype; { convert strings from C format (trailing 0) to Turbo Pascal format (leading length byte). } var s : strtype; i : integer; begin s := ''; i := 0; while fn[i] <> #0 do begin s := s + fn[i]; i := i + 1 end; fn_to_str := s end; (* func fn_to_str *) function unsigned_to_real(u : integer) : real; { convert unsigned integer to real } { note: INT is a function that returns a REAL!!!} begin if u >= 0 then unsigned_to_real := Int(u) else if u = $8000 then unsigned_to_real := 32768.0 else unsigned_to_real := 65536.0 + u end; (* func unsigned_to_real *) function long_to_real(l : long) : real; { convert long integer to a real } { note: INT is a function that returns a REAL!!! } var r : real; s : (POS, NEG); const rcon = 65536.0; begin if l.h >= 0 then begin r := Int(l.h) * rcon; s := POS end else begin s := NEG; if l.h = $8000 then r := rcon * rcon else r := Int(-l.h) * rcon end; r := r + unsigned_to_real(l.l); if s = NEG then long_to_real := -r else long_to_real := r end; (* func long_to_real *) procedure Read_Block; { read a block from the archive file } begin if EOF(arcfile) then endfile := TRUE else BlockRead(arcfile, arcbuf, 1); arcptr := 1 end; (* proc read_block *) procedure Write_Block; { write a block to the extracted file } begin BlockWrite(extfile, extbuf, 1); extptr := 1 end; (* proc write_block *) procedure open_arc; { open the archive file for input processing } begin {$I-} assign(arcfile, arcname); {$I+} if ioresult <> 0 then abort('Cannot open archive file.'); {$I-} reset(arcfile); {$I+} if ioresult <> 0 then abort('Cannot open archive file.'); endfile := FALSE; Read_Block end; (* proc open_arc *) procedure open_ext; { open the extracted file for writing } begin {$I-} assign(extfile, extname); {$I+} if ioresult <> 0 then abort('Cannot open extract file.'); {$I-} rewrite(extfile); {$I+} if ioresult <> 0 then abort('Cannot open extract file.'); extptr := 1; end; (* proc open_ext *) function get_arc : byte; { read 1 character from the archive file } begin if endfile then get_arc := 0 else begin get_arc := arcbuf[arcptr]; if arcptr = BLOCKSIZE then Read_Block else arcptr := arcptr + 1 end end; (* func get_arc *) procedure put_ext(c : byte); { write 1 character to the extracted file } begin extbuf[extptr] := c; if extptr = BLOCKSIZE then Write_Block else extptr := extptr + 1 end; (* proc put_ext *) procedure close_arc; { close the archive file } begin close(arcfile) end; (* proc close_arc *) procedure close_ext; { close the extracted file } begin while extptr <> 1 do put_ext(Ord(^Z)); { pad last block w/ Ctrl-Z (EOF) } close(extfile) end; (* proc close_ext *) procedure fseek(offset : real; base : integer); { re-position the current pointer in the archive file } var b : real; i, ofs, rec : integer; c : byte; begin case base of 0 : b := offset; 1 : b := offset + (unsigned_to_real(FilePos(arcfile)) - 1.0) * BLOCKSIZE + arcptr - 1.0; 2 : b := offset + unsigned_to_real(FileSize(arcfile)) * BLOCKSIZE - 1.0 else abort('Invalid parameters to fseek') end; rec := Trunc(b / BLOCKSIZE); ofs := Trunc(b - (Int(rec) * BLOCKSIZE)); { Int converts to Real } seek(arcfile, rec); Read_Block; for i := 1 to ofs do c := get_arc end; (* proc fseek *) procedure fread(var buf; reclen : integer); { read a record from the archive file } var i : integer; b : array [1..MaxInt] of byte absolute buf; begin for i := 1 to reclen do b[i] := get_arc end; (* proc fread *) procedure GetArcName; { get the name of the archive file } var i : integer; begin (***************************************** if ParamCount > 1 then abort('Too many parameters'); if ParamCount = 1 then arcname := ParamStr(1) *****************************************) if argc > 1 then abort('Too many parameters'); if argc = 1 then arcname := argv(1) else begin write('Enter archive filename: '); readln(arcname); if arcname = '' then abort('No file name entered'); writeln; writeln; end; for i := 1 to length(arcname) do arcname[i] := UpCase(arcname[i]); if pos('.', arcname) = 0 then arcname := arcname + '.ARC' end; (* proc GetArcName *) function readhdr(var hdr : heads) : boolean; { read a file header from the archive file } { FALSE = eof found; TRUE = header found } label exit; var name : fntype; try : integer; begin try := 10; if endfile then begin readhdr := FALSE; goto exit (******** was "exit" ************) end; while get_arc <> arcmarc do begin if try = 0 then abort(arcname + ' is not an archive'); try := try - 1; writeln(arcname, ' is not an archive, or is out of sync'); if endfile then abort('Archive length error') end; (* while *) hdrver := get_arc; if hdrver < 0 then abort('Invalid header in archive ' + arcname); if hdrver = 0 then begin { special end of file marker } readhdr := FALSE; goto exit (******** was "exit" ************) end; if hdrver > arcver then begin fread(name, fnlen); writeln('I dont know how to handle file ', fn_to_str(name), ' in archive ', arcname); writeln('I think you need a newer version of DEARC.'); halt; end; if hdrver = 1 then begin fread(hdr, sizeof(heads) - sizeof(long)); hdrver := 2; hdr.length := hdr.size end else fread(hdr, sizeof(heads)); readhdr := TRUE; exit: end; (* func readhdr *) procedure putc_unp(c : integer); begin crcval := ((crcval shr 8) and $00FF) xor crctab[(crcval xor c) and $00FF]; put_ext(c) end; (* proc putc_unp *) procedure putc_ncr(c : integer); begin case state of NOHIST : if c = DLE then state := INREP else begin lastc := c; putc_unp(c) end; INREP : begin if c = 0 then putc_unp(DLE) else begin c := c - 1; while (c <> 0) do begin putc_unp(lastc); c := c - 1 end end; state := NOHIST end; end; (* case *) end; (* proc putc_ncr *) function getc_unp : integer; begin if size = 0.0 then getc_unp := -1 else begin size := size - 1.0; getc_unp := get_arc end; end; (* func getc_unp *) procedure init_usq; { initialize for unsqueeze } var i : integer; begin bpos := 99; fread(numnodes, sizeof(numnodes)); if (numnodes < 0) or (numnodes > NUMVALS) then abort('File has an invalid decode tree'); node[0].child[0] := -(SPEOF + 1); node[0].child[1] := -(SPEOF + 1); for i := 0 to numnodes-1 do begin fread(node[i].child[0], sizeof(integer)); fread(node[i].child[1], sizeof(integer)) end; end; (* proc init_usq; *) function getc_usq : integer; { unsqueeze } label exit; var i : integer; begin i := 0; while i >= 0 do begin bpos := bpos + 1; if bpos > 7 then begin curin := getc_unp; if curin = ERROR then begin getc_usq := ERROR; goto exit (******** was "exit" ************) end; bpos := 0; i := node[i].child[1 and curin] end else begin curin := curin shr 1; i := node[i].child[1 and curin] end end; (* while *) i := - (i + 1); if i = SPEOF then getc_usq := -1 else getc_usq := i; exit: end; (* func getc_usq *) function h(pred, foll : integer) : integer; { calculate hash value } { thanks to Bela Lubkin } var Local : Real; S : String[20]; I, V : integer; C : char; begin if not newhash then begin Local := (pred + foll) or $0800; if Local < 0.0 then Local := Local + 65536.0; Local := (Local * Local) / 64.0; { convert Local to an integer, truncating high order bits. } { there ***MUST*** be a better way to do this!!! } Str(Local:15:5, S); V := 0; I := 1; C := S[1]; while C <> '.' do begin if (C >= '0') and (C <= '9') then V := V * 10 + (Ord(C) - Ord('0')); I := I + 1; C := S[I] end; h := V and $0FFF end (* func h *) else begin Local := (pred + foll) * 15073; { convert Local to an integer, truncating high order bits. } { there ***MUST*** be a better way to do this!!! } Str(Local:15:5, S); V := 0; I := 1; C := S[1]; while C <> '.' do begin if (C >= '0') and (C <= '9') then V := V * 10 + (Ord(C) - Ord('0')); I := I + 1; C := S[I] end; h := V and $0FFF end; end; function eolist(index : integer) : integer; var temp : integer; begin temp := string_tab[index].next; while temp <> 0 do begin index := temp; temp := string_tab[index].next end; eolist := index end; (* func eolist *) function hash(pred, foll : integer) : integer; var local : integer; tempnext : integer; begin local := h(pred, foll); if not string_tab[local].used then hash := local else begin local := eolist(local); tempnext := (local + 101) and $0FFF; while string_tab[tempnext].used do begin tempnext := tempnext + 1; if tempnext = TABSIZE then tempnext := 0 end; string_tab[local].next := tempnext; hash := tempnext end; end; (* func hash *) procedure upd_tab(pred, foll : integer); begin with string_tab[hash(pred, foll)] do begin used := TRUE; next := 0; predecessor := pred; follower := foll end end; (* proc upd_tab *) function gocode : integer; label exit; var localbuf : integer; returnval : integer; begin if inbuf = EMPTY then begin localbuf := getc_unp; if localbuf = -1 then begin gocode := -1; goto exit (******** was "exit" ************) end; localbuf := localbuf and $00FF; inbuf := getc_unp; if inbuf = -1 then begin gocode := -1; goto exit (******** was "exit" ************) end; inbuf := inbuf and $00FF; returnval := ((localbuf shl 4) and $0FF0) + ((inbuf shr 4) and $000F); inbuf := inbuf and $000F end else begin localbuf := getc_unp; if localbuf = -1 then begin gocode := -1; goto exit (******** was "exit" ************) end; localbuf := localbuf and $00FF; returnval := localbuf + ((inbuf shl 8) and $0F00); inbuf := EMPTY end; gocode := returnval; exit: end; (* func gocode *) procedure push(c : integer); begin stack[sp] := c; sp := sp + 1; if sp >= TABSIZE then abort('Stack overflow') end; (* proc push *) function pop : integer; begin if sp > 0 then begin sp := sp - 1; pop := stack[sp] end else pop := EMPTY end; (* func pop *) procedure init_tab; var i : integer; begin FillChar(string_tab, sizeof(string_tab), 0); for i := 0 to 255 do upd_tab(NO_PRED, i); inbuf := EMPTY; { outbuf := EMPTY } end; (* proc init_tab *) procedure init_ucr(i:integer); begin newhash := i = 1; sp := 0; init_tab; code_count := TABSIZE - 256; firstc := TRUE end; (* proc init_ucr *) function getc_ucr : integer; label exit; var c : integer; code : integer; newcode : integer; begin if firstc then begin firstc := FALSE; oldcode := gocode; finchar := string_tab[oldcode].follower; getc_ucr := finchar; goto exit (******** was "exit" ************) end; if sp = 0 then begin newcode := gocode; code := newcode; if code = -1 then begin getc_ucr := -1; goto exit (******** was "exit" ************) end; if not string_tab[code].used then begin code := oldcode; push(finchar) end; while string_tab[code].predecessor <> NO_PRED do with string_tab[code] do begin push(follower); code := predecessor end; finchar := string_tab[code].follower; push(finchar); if code_count <> 0 then begin upd_tab(oldcode, finchar); code_count := code_count - 1 end; oldcode := newcode end; getc_ucr := pop; exit: end; (* func getc_ucr *) function getcode : integer; label next, exit; var code, r_off, bitsx : integer; bp : byte; begin if firstch then begin offset := 0; sizex := 0; firstch := false; end; bp := 0; if (clear_flg > 0) or (offset >= sizex) or (free_ent > maxcode) then begin if free_ent > maxcode then begin n_bits := n_bits + 1; if n_bits = BITS then maxcode := maxcodemax else maxcode := (1 shl n_bits) - 1; end; if clear_flg > 0 then begin n_bits := INIT_BITS; maxcode := (1 shl n_bits) - 1; clear_flg := 0; end; for sizex := 0 to n_bits-1 do begin code := getc_unp; if code = -1 then goto next else buf[sizex] := code; end; sizex := sizex + 1; next: if sizex <= 0 then begin getcode := -1; goto exit; end; offset := 0; sizex := (sizex shl 3) - (n_bits - 1); end; r_off := offset; bitsx := n_bits; { get first byte } bp := bp + (r_off shr 3); r_off := r_off and 7; { get first parft (low order bits) } code := buf[bp] shr r_off; bp := bp + 1; bitsx := bitsx - (8 - r_off); r_off := 8 - r_off; if bitsx >= 8 then begin code := code or (buf[bp] shl r_off); bp := bp + 1; r_off := r_off + 8; bitsx := bitsx - 8; end; code := code or ((buf[bp] and rmask[bitsx]) shl r_off); offset := offset + n_bits; getcode := code; exit: end; procedure decomp; label next,exit; var stackp, finchar :integer; code, oldcode, incode : integer; begin { INIT var } if firstch then maxcodemax := 1 shl bits; code := getc_unp; if code <> BITS then begin writeln('File packed with ',code,' bits, I can only handle ',BITS); halt; end; clear_flg := 0; n_bits := INIT_BITS; maxcode := (1 shl n_bits ) - 1; for code := 255 downto 0 do begin prefix[code] := 0; suffix[code] := code; end; free_ent := FIRST; oldcode := getcode; finchar := oldcode; if oldcode = -1 then goto exit; putc_ncr(finchar); stackp := 0; code := getcode; while code > -1 do begin if code = CLEAR then begin for code := 255 downto 0 do prefix[code] := 0; clear_flg := 1; free_ent := FIRST - 1; code := getcode; if code = -1 then goto next; end; next: incode := code; if code >= free_ent then begin stack1[stackp] := finchar; stackp := stackp + 1; code := oldcode; end; while code >= 256 do begin stack1[stackp] := suffix[code]; stackp := stackp + 1; code := prefix[code]; end; finchar := suffix[code]; stack1[stackp] := finchar; stackp := stackp + 1; repeat stackp := stackp - 1; putc_ncr(stack1[stackp]); until stackp <= 0; code := free_ent; if code < maxcodemax then begin prefix[code] := oldcode; suffix[code] := finchar; free_ent := code + 1; end; oldcode := incode; code := getcode; end; exit: end; procedure unpack(var hdr : heads); label exit; var c : integer; begin crcval := 0; size := long_to_real(hdr.size); state := NOHIST; case hdrver of 1, 2 : begin c := getc_unp; while c <> -1 do begin putc_unp(c); c := getc_unp end end; 3 : begin c := getc_unp; while c <> -1 do begin putc_ncr(c); c := getc_unp end end; 4 : begin init_usq; c := getc_usq; while c <> -1 do begin putc_ncr(c); c := getc_usq end end; 5 : begin init_ucr(0); c := getc_ucr; while c <> -1 do begin putc_unp(c); c := getc_ucr end end; 6 : begin init_ucr(0); c := getc_ucr; while c <> -1 do begin putc_ncr(c); c := getc_ucr end end; 7 : begin init_ucr(1); c := getc_ucr; while c <> -1 do begin putc_ncr(c); c := getc_ucr end end; 8 : begin decomp; end; else writeln('I dont know how to unpack file ', fn_to_str(hdr.name)); writeln('I think you need a newer version of DEARC'); fseek(long_to_real(hdr.size), 1); goto exit (******** was "exit" ************) end; (* case *) if crcval <> hdr.crc then writeln('WARNING: File ', fn_to_str(hdr.name), ' fails CRC check'); exit: end; (* proc unpack *) procedure extract_file(var hdr : heads); begin extname := fn_to_str(hdr.name); writeln('Extracting file : ', extname); open_ext; unpack(hdr); close_ext end; (* proc extract *) procedure extarc; var hdr : heads; begin open_arc; while readhdr(hdr) do extract_file(hdr); close_arc end; (* proc extarc *) procedure PrintHeading; begin writeln; writeln('Turbo Pascal DEARC Utility'); writeln('Version 2.0, 6/11/86'); writeln('Supports ARC version 5.12 files'); writeln; end; (* proc PrintHeading *) begin firstch := true; PrintHeading; { print a heading } GetArcName; { get the archive file name } extarc { extract all files from the archive } end.