home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
High Voltage Shareware
/
high1.zip
/
high1
/
DIR31
/
A2Z16D.ZIP
/
A2Z16D.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1989-10-15
|
49KB
|
1,535 lines
{$A+} {word alignment}
{$B-} {shortcircuit boolean eval}
{$D-} {Debug information off (was +)}
{$E-} {8087 emulation off}
{$F-} {Force FAR calls as needed (was +)}
{$I-} {I/O checking off}
{$L-} {Local symbols off (was +)}
{$N-} {Numeric coprocessing=software}
{$O-} {No Overlays allowed (was +)}
{$R-} {Range checking off}
{$S-} {Stack overflow checking off (was on)}
{$V-} {Var-string checking off}
{$M 32767,32767,32767}
PROGRAM A2Z;
{
Version 1.6
This version represents minor changes in code structure. It will also be
the last version released, unless some fatal flaw is uncovered. Enjoy.
Ian McLean 404 428 7829 (voice)
3365 Timber Lake Road
Kennesaw, GA
30144
v1.6d Toad Hall, 12 Oct 89
- Removing the -ex, -eb4, etc. for the newer PK v1.01 ZIP.
v1.6c Toad Hall, 15 Jun 89
- Tweaking a little
v1.6b Toad Hall, 9 Apr 89
- WHY are we messing with the horrendous error-trapping
(during unarcing procedures), processing strings for error msgs?
Just look for the ERRORLEVEL returned by the executing program!
If it's not 0, there was a problem! THEN Die!
- Why are we processing strings for file sizes, number of archive
files, etc? All that is right there in the .ARC or .PAK file
entry! Snarf it from the raw file.
(Of course that means we must know header structures, how to
reposition file pointers, snarf long integers from the headers,
etc. .. but we can do that, ne?
v1.6a Toad Hall Tweak, 7 Apr 89
- Changed some switches (above)
- Added Ascii file "-ex" switch
- Tightened up the Convert code by consolidating
common (shared) code.
- Vastly tightened up the Log, StatFile and other reports
by consolidating WriteLn, etc.
- Enabled the F- (no FAR calls) switch
with F+ switches only when absolutely required.
- To Do:
Figure how to read .PAK comments
David Kirschbaum
Toad Hall
kirsch@braggvax.ARPA
}
Uses DOS, CRT;
CONST
MAXDIRENTRIES = 20; { Maximum number of directories that can be specified
to search. This doesn't include those searched
"below" ones specified. }
DataNext: STRING[10]= 'CONFIGNEXT';
PKZIP: PathStr= 'U:\PKZIP.EXE'; { 80 bytes }
PKUNZIP: PathStr= 'U:\PKUNZIP.EXE'; { 80 bytes }
PKUNPAK: PathStr= 'U:\PKXARC.EXE'; { 80 bytes }
PAK: PathStr= 'U:\PAK.EXE'; { 80 bytes }
(* v1.6d
EASwitch : STRING[5] = ' -ex '; { 6 bytes v1.6a}
EBSwitch : STRING[5] = ' -eb4'; { 6 bytes v1.6a}
{ --------- }
{ 334 bytes }
*)
{ --------- }
{ 320 bytes }
HDRLEN = 319; {v1.6d length of directory names - 1}
TYPE
FullNameStr = STRING[12]; { Type for storing name+dot+extension }
DirSearchEntry = RECORD { This data type is used to store
all the paths that will be searched }
Dir : DirStr; { <-- Path to search }
Name : FullNameStr; { <-- File spec to search }
Below: BOOLEAN; { <-- TRUE=search directories
below the specified one }
END;
VAR
Dir : ARRAY[1..MAXDIRENTRIES] OF DirSearchEntry; { This holds all the
directories specified
to convert }
numdirs: Byte; { The number of directories used in
above array }
SearchZips: BOOLEAN; { Search ZIP files for inclosed ARCs
or PAKs }
AppendLog: BOOLEAN; { TRUE=Append to log
FALSE=rewrite log file }
BatchMode: BOOLEAN; { TRUE=Don't wait for a keypress
at beginning }
SuppressLog: BOOLEAN; { TRUE=Don't make a log file }
LogFile: TEXT; { Log file handle, A2Z.LOG }
OldExitProc1: pointer; { Pointer to previous exit procedure
routine. }
oldSeg,oldOfs: word; { Old segment and offset
for interrupt 29h handler }
OldExitProc2: pointer; { Holder for old exit proc }
Reg: Registers; { Register storage for DOS calls }
cmdY: Byte; { Line the cursor's on
in the bottom window }
bufData: longint; { Pointer to the text buffer }
bufferSeg: word; { Segment of the text buffer }
bufferOfs: word; { Offset " " " " }
BufferPtr: pointer; { Pointer to the text buffer,
in pointer format }
bufferlen: word; { Current length of text buffer }
numfiles: word; { Number of files to convert }
numBytes: longint; { Number of bytes to convert }
filenum: word; { Current file number }
ConvertingInside: BOOLEAN; { TRUE=Converting internal arc files }
saved: longint; { Total bytes saved so far }
TrickleUpError: BOOLEAN; { Error converting an internal file }
internalcount: Byte;
InterruptRequested: BOOLEAN;
WorkDir: String;
StatusFile: TEXT;
StatusFileName: String;
TStr: String; {v1.6a}
execerror : Byte; {v1.6b}
ExecErrStr : STRING[5]; {v1.6b}
function File_Found(F: ComStr): BOOLEAN;
{
This returns TRUE if the file F exists, FALSE otherwise.
F can contain wildcard characters.
}
VAR
SRec : SearchRec;
BEGIN
SRec.Name := '*';
FindFirst(F,0,SRec);
File_Found := NOT (SRec.Name='*'); {v1.6a}
END; {of File_Found}
function Valid_Dir(D: String): BOOLEAN;
VAR
T: FILE;
BEGIN
Assign(T, D+'VALID!!!.A2Z');
{$I-} REWRITE(T); {$I+}
IF IOResult<>0 THEN Valid_Dir := FALSE
ELSE BEGIN
CLOSE(T);
Erase(T);
Valid_Dir := TRUE;
END;
END; {of Valid_Dir}
PROCEDURE HaltWithMsg(M: String);
{
Displays the message in M to the user and halts program execution.
Used with critical errors.
}
BEGIN
WRITELN(M);
HALT;
END; {of HaltWithMsg}
PROCEDURE DisplayProgramHeader;
{
Display program version number and credits.
}
BEGIN
WRITELN;
WRITELN('A2Z - ARC/PAK to ZIP converter');
WRITELN('version 1.6d by Ian McLean (with a Toad Hall Tweak!)');
WRITELN;
END; {of DisplayProgramHeader}
PROCEDURE InvokeConfiguration;
{
Configure A2Z by entering the paths for PKZIP, PKUNZIP, PKUNPAK, and PAK,
as well as a compression level for ASCII and binary files. This information
is then stored in the executable for A2Z for future use.
}
VAR
A: FILE OF Byte; { Temp variable for referencing A2Z.EXE }
l: longint; { Location of search }
matchup: Byte; { Number of bytes currently matched }
C: CHAR; { Character to match to }
PROCEDURE Get_Name(VAR Name : PathStr; Pk : BOOLEAN);
{v1.6c a common procedure.
v1.6d Bug fixed.
}
VAR S,N : PathStr;
BEGIN
IF Pk THEN S := 'PKWare''s ' + Name + '.'
ELSE S := 'NoGate Consulting''s ' + Name + '.';
N := Name; {v1.6d}
REPEAT
WRITELN;
WRITELN('Enter the name and path for ', S);
WRITELN('Please be sure to enter a path, filename, and extension:');
READLN(N);
UNTIL File_Found(N); {v1.6d}
Name := N; {post global v1.6d}
END; {of Get_Name}
TYPE
Str4 = STRING[4];
FUNCTION Get_Char(Query : String; CharSet : Str4) : CHAR;
VAR Ch : CHAR;
BEGIN
WRITE(Query);
REPEAT
REPEAT UNTIL KeyPressed;
Ch := UpCase(ReadKey);
UNTIL POS(Ch,CharSet) <> 0;
Get_Char := Ch;
END; {of Get_Char}
BEGIN {InvokeConfiguration}
DisplayProgramHeader;
IF NOT File_Found('A2Z.EXE')
THEN HaltWithMsg(
'A2Z.EXE must be in the current directory when invoking configuration.');
Get_Name(PKZIP,TRUE); {v1.6c get PKZIP.EXE's path and name}
Get_Name(PKUNZIP,TRUE); {v1.6c get PKUNZIP.EXE's path and name}
Get_Name(PKUNPAK,TRUE); {v1.6c get PKUNPAK.EXE's path and name}
C := Get_Char('Do you have .PAK files to convert? [Y/N] ', 'YN');
WRITELN(C); {display, new line}
IF C = 'Y'
THEN Get_Name(PAK,FALSE)
ELSE PAK := '';
WRITELN;
(* v1.6d
C := Get_Char('Compression level for binary files [1..4]: ', '1234');
EBSwitch[5] := C; {v1.6a move into ' -eb%' string}
WRITELN(EBSwitch); {v1.6a}
{ v1.6a Add 'ex' for ASCII files }
C := Get_Char('Compression level for ASCII files [1..4,X]: ', '1234X');
IF C = 'X' {v1.6a}
THEN EASwitch := ' -ex' {v1.6a}
ELSE EASwitch := ' -ea' + C; {v1.6a}
WRITELN(EASwitch); {v1.6a}
WRITELN;
*)
Assign(A, 'A2Z.EXE'); { Configuration information is written to A2Z.EXE, }
RESET(A); { overlaying what was in the CONST block previously}
l := FileSize(A)-1; { Search starting at EOF, as constants are usually
found there }
matchup := 10; { First character to match is the fifth of the
string CONFIGNEXT }
REPEAT
Seek(A, l); { Read character from file }
READ(A, Byte(C));
Dec(l); { Decrement counter (search backwards) }
CASE matchup OF
10: IF C=DataNext[matchup] { If the char matches,}
THEN Dec(matchup); { we need to match the next one,
otherwise we}
ELSE IF C=DataNext[matchup] { need to match the tenth next }
THEN Dec(matchup) ELSE matchup := 10; { (string wasn't correct)}
END;
UNTIL (matchup=0) OR (l=0); { Repeat this until string found (matchup=0)
or we're at start of file }
IF matchup <> 0
THEN HaltWithMsg(
'Unable to find configuration data area. Corrupted A2Z.EXE!');
Seek(A, l+12); { Seek the configuration information block }
(* v1.6d
for l := 0 TO 321 DO {Write the Directory/filenames}
WRITE(A, Mem[Seg(PKZIP):Ofs(PKZIP)+l]); { and compression levels }
*)
FOR l := 0 TO HDRLEN DO {Write the Directory/filenames} {v1.6d}
WRITE(A,Mem[Seg(PKZIP):Ofs(PKZIP)+l]);
CLOSE(A);
HaltWithMsg('A2Z is now configured for use.');
END; { of InvokeConfiguration }
PROCEDURE ShowInvokation;
{
Display program information and the invokation parameters for A2Z,
then halt the program.
}
BEGIN
DisplayProgramHeader;
WRITELN(
'A2Z [/C] [/Z] [/A] [/B] [/S=device] [/W=dir] [filespec] [!filespec]');
WRITELN;
WRITELN(
'/C Invoke configuration');
WRITELN(
'/Z Search ZIP files for imbedded ARC/PAK files and process');
WRITELN(
'/A Append to log file, if it exists, instead of overwriting');
WRITELN(
'/B Batch mode. Don''t pause for a keypress at beginning');
WRITELN(
'/N Create no log file.');
WRITELN(
'/S=[device] Set the status display device (eg: /S=COM1). Default is NUL');
WRITELN(
'/W=[dir] Set the work directory to [dir]. Default is current directory');
WRITELN(
' or value set by the environment variable A2ZWORK.');
WRITELN;
WRITELN(
'[filespec] Directory name or search specification of files to convert. If');
WRITELN(
' there''s an ! before the name, subdirectories of the one specified');
WRITELN(
' are searched. Up to twenty path names may be entered.');
WRITELN;
WRITELN('Examples:');
WRITELN(
'A2Z !C:\ !D:\ /Z Convert all dirs on drives C: and D:, search ZIPs');
WRITELN(
' for imbedded ARC/PAKs');
WRITELN(
'A2Z FOOBAR.ARC Convert the file FOOBAR.ARC to a ZIP');
WRITELN(
'A2Z C:\*.PAK Convert all PAK files in dir C:\ to ZIPs');
HALT;
END; {of ShowInvocation}
PROCEDURE ReadCommandLine;
{
Read the parameters entered at the command line and build the list of
directories to convert. Check for configuration and show invokation if
necessary.
}
PROCEDURE ParseParameter(S: String);
{
Parse the parameter in S.
}
VAR
D : DirStr; { Temp holders for path name, etc }
N : NameStr;
E : ExtStr;
BEGIN
IF S[1]='/' THEN
CASE UpCase(S[2]) OF
'C': InvokeConfiguration;
'Z': SearchZips := TRUE;
'A': AppendLog := TRUE;
'B': BatchMode := TRUE;
'N': SuppressLog := TRUE;
'W': BEGIN
IF (LENGTH(S)<5) OR (S[3]<>'=') THEN ShowInvokation;
WorkDir := COPY(S,4,255);
END;
'S': BEGIN
IF (LENGTH(S)<4) OR (S[3]<>'=') THEN ShowInvokation;
StatusFileName := COPY(S,4,255);
END;
ELSE ShowInvokation;
END {case}
ELSE BEGIN
Inc(numdirs);
WITH Dir[numdirs] DO BEGIN
IF S[1]='!' THEN BEGIN
S := COPY(S,2,255);
Below := TRUE;
END
ELSE Below := FALSE;
IF S[LENGTH(S)]<>'\' THEN
IF (NOT File_Found(S)) AND (File_Found(S+'\*.*')) THEN S := S+'\';
FSplit(FExpand(S), D,N,E);
IF N='' THEN N := '*';
IF (E='') OR (E='.') THEN E := '.*';
Dir := D;
Name := N+E;
END;
END;
END; {of ParseParameter}
VAR l : Byte; { Loop variable }
BEGIN {ReadCommandLine}
SearchZips := FALSE;
AppendLog := FALSE;
BatchMode := FALSE;
SuppressLog := FALSE;
WorkDir := GetEnv('A2ZWORK');
StatusFileName := 'NUL';
numdirs := 0;
IF ParamCount=0 THEN ShowInvokation; {and die}
for l := 1 TO ParamCount DO ParseParameter(ParamStr(l));
IF numdirs=0 THEN ShowInvokation;
IF WorkDir='' THEN GetDir(0,WorkDir);
WorkDir := FExpand(WorkDir);
IF WorkDir[LENGTH(WorkDir)]<>'\' THEN WorkDir := WorkDir+'\';
IF NOT Valid_Dir(WorkDir)
THEN HaltWithMsg('Invalid work directory specified.');
Assign(StatusFile, StatusFileName);
{$I-} REWRITE(StatusFile); {$I+}
IF IOResult<>0 THEN BEGIN
WRITELN('Unable to open specified status file.');
Assign(StatusFile, 'NUL');
REWRITE(StatusFile);
END
ELSE WRITELN(StatusFile, 'A2Z v1.6b by Ian McLean');
END; { of ReadCommandLine }
{$F+} {v1.6a Use FAR calls}
PROCEDURE NewExitProc1;
{
This exit procedure closes the log file.
}
BEGIN
IF NOT SuppressLog THEN CLOSE(LogFile);
CLOSE(StatusFile);
ExitProc := OldExitProc1;
END; {of NewExitProc1}
{$F-} {v1.6a No FAR calls}
PROCEDURE CheckSubPrograms;
BEGIN
IF PKZIP='UNCONFIGURED' THEN InvokeConfiguration;
IF NOT (File_Found(PKZIP) AND File_Found(PKUNZIP)
AND File_Found(PKUNPAK)
AND (File_Found(PAK)
OR (PAK='')))
THEN BEGIN
WRITELN;
WRITELN('** Invalid program paths in configuration **');
InvokeConfiguration;
END;
END; {of CheckSubPrograms}
PROCEDURE OpenLogFile;
{
Open the file A2Z.LOG in the current directory. If it exists, append to it.
Place a date/time stamp on it, too, just for the heck of it. Also sets up
an exit procedure to close the file. If AppendLog is true, we'll append
to the log, otherwise we'll rewrite it.
}
function Date_String: String;
{
Returns the current date in a string of the form: MON ## YEAR.
E.g, 21 Feb 1989 or 02 Jan 1988.
}
CONST
Month: ARRAY[1..12] OF STRING[3]=
('Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec');
VAR
y,m,d,junk : word;
DS,YS: STRING[5];
BEGIN
GetDate(y,m,d,junk);
STR(y,YS);
STR(d,DS);
IF LENGTH(DS)<2 THEN DS := '0'+DS;
Date_String := DS+' '+Month[m]+' '+YS;
END;
function Time_String: String;
{
Returns the current time in the form: HH:MM am/pm
E.g, 12:00 am or 09:12 pm.
}
VAR
h,m,junk: word;
HS,MS: STRING[5];
Am: BOOLEAN;
BEGIN
GetTime(h,m,junk,junk);
CASE h OF
0: BEGIN
Am := TRUE;
h := 12;
END;
1..11: Am := TRUE;
12: Am := FALSE;
ELSE BEGIN
Am := FALSE;
h := h-12;
END;
END;
STR(h,HS);
STR(m,MS);
IF LENGTH(HS)<2 THEN HS := '0'+HS;
IF LENGTH(MS)<2 THEN MS := '0'+MS;
IF Am THEN Time_String := HS+':'+MS+' am'
ELSE Time_String := HS+':'+MS+' pm';
END; {of Time_String}
BEGIN {OpenLogFile}
IF NOT SuppressLog THEN BEGIN
Assign(LogFile, 'A2Z.LOG');
{$I-}
IF AppendLog THEN Append(LogFile) ELSE REWRITE(LogFile);
{$I+}
IF IOResult<>0 THEN REWRITE(LogFile);
WRITELN(LogFile);
WRITELN(LogFile, Date_String+' '+Time_String);
WRITELN(LogFile, '--------------------');
END;
{$F+} {v1.6a Use FAR calls}
OldExitProc1 := ExitProc;
ExitProc := @NewExitProc1;
{$F-} {v1.6a No FAR calls}
END; { of OpenLogFile }
function Indent_Spaces: String;
{v1.6a moved this to be a global function,
tightened up a bit.
}
VAR
S: String;
i : INTEGER; {v1.6b}
BEGIN
i := internalcount ShL 1; {v1.6b}
S[0] := CHAR(i); {v1.6b}
IF internalcount <> 0
THEN FillChar(S[1], i,' '); {v1.6b}
Indent_Spaces := S;
END; {of Indent_Spaces}
PROCEDURE LogError(E: String; Stat : BOOLEAN);
{
Write the message in string E to the screen and to the log file.
v1.6a If Stat is TRUE, write it to the status file also.
}
BEGIN
WRITELN(E);
IF NOT SuppressLog THEN WRITELN(LogFile, E);
IF Stat THEN WRITELN(StatusFile,Indent_Spaces + E); {v1.6a}
END; {of LogError}
PROCEDURE WriteStatus(M: String);
{v1.6a We ALWAYS Write(M),
and we ALWAYS indent .. so consolidating here.
}
BEGIN
M := Indent_Spaces + M; {v1.6a}
WRITE(M); {v1.6a}
WRITE(StatusFile,M);
END; {of WriteStatus}
PROCEDURE WritelnStatFil(M: String; W : BOOLEAN);
{
Write the message in M to the status device, with linefeed.
v1.6a IF W is TRUE, Writeln(M) also.
}
BEGIN
IF W THEN WRITELN(M); {v1.6a}
WRITELN(StatusFile,M);
END; {of WritelnStatFil}
{********* The following search engine routines are sneakly swiped *********
********* from Turbo Technix v1n6. See there for further details *********}
{$F+} {v1.6a we need far calls for this ProcType business}
TYPE
ProcType= PROCEDURE(VAR S: SearchRec; P: PathStr);
VAR
EngineMask: FullNameStr;
engineattr: Byte;
EngineProc: ProcType;
enginecode: Byte;
{$F-}
function Valid_Extension(VAR S: SearchRec): BOOLEAN;
VAR
Junk : String;
E : ExtStr;
BEGIN
IF S.Attr AND Directory=Directory THEN BEGIN
Valid_Extension := TRUE;
Exit;
END;
FSplit(S.Name,Junk,Junk,E);
Valid_Extension := {v1.6a}
(E='.ARC') OR (E='.PAK')
OR (SearchZips AND (E='.ZIP') );
END; {of Valid_Extension}
{$F+} {v1.6a Use FAR calls}
PROCEDURE SearchEngine(Mask: PathStr; attr: Byte; Proc: ProcType;
VAR errorcode: Byte);
VAR
S : SearchRec;
P : PathStr;
Ext : ExtStr;
BEGIN
FSplit(Mask, P, Mask, Ext);
Mask := Mask+Ext;
FindFirst(P+Mask,attr,S);
IF DosError<>0 THEN BEGIN
errorcode := DosError;
Exit;
END;
WHILE DosError=0 DO BEGIN
IF Valid_Extension(S) THEN Proc(S, P);
FindNext(S);
END;
IF DosError=18 THEN errorcode := 0
ELSE errorcode := DosError;
END; {of SearchEngine}
{$F-} {v1.6a No FAR calls}
function Good_Directory(S: SearchRec): BOOLEAN;
{1 call}
BEGIN
Good_Directory := (S.name<>'.') AND (S.Name<>'..')
AND (S.attr AND Directory=Directory);
END; {of Good_Directory}
{$F+} {v1.6a Use FAR calls}
PROCEDURE SearchOneDir(VAR S: SearchRec; P: PathStr);
BEGIN
IF Good_Directory(S) THEN BEGIN
P := P+S.Name;
SearchEngine(P+'\'+EngineMask,engineattr,EngineProc,enginecode);
SearchEngine(P+'\*.*',Directory OR Archive, SearchOneDir,enginecode);
END;
END; {of SearchOneDir}
PROCEDURE SearchEngineAll(Path: PathStr; Mask: FullNameStr; attr: Byte;
Proc: ProcType; VAR errorcode: Byte);
BEGIN
EngineMask := Mask;
EngineProc := Proc;
engineattr := attr;
SearchEngine(Path+Mask,attr,Proc,errorcode);
SearchEngine(Path+'*.*',Directory OR Archive,SearchOneDir,errorcode);
errorcode := enginecode;
END; {of SearchEngineAll}
{************** Thus ends the sneakly swiped code *************
**** We now return you to our regularly scheduled program ****}
PROCEDURE AddToEstimate(VAR S: SearchRec; P: PathStr);
{$F-} {v1.6a and back to no Far calls}
{
Called by the search engine, adds the information in S to the file estimates
numfiles and numBytes. Displays the filename temporarily, too.
}
VAR
x : Byte;
BEGIN
Inc(numfiles); {bump nr of files processed}
Inc(numBytes,S.size); {bump total bytes in all files
by .ARC/.PAK file size}
x := WhereX;
ClrEol;
WRITE(S.Name);
GotoXY(x,WhereY);
END; {of AddToEstimate}
PROCEDURE GetFileEstimates;
{
Estimate the number of bytes and number of files to convert.
}
VAR
l, errorcode : Byte;
BEGIN
DisplayProgramHeader;
WRITELN('Searching directories...');
WRITELN;
numfiles := 0; {clear nr ARC/PAK files processed}
numBytes := 0; {and total nr file bytes}
for l := 1 TO numdirs DO
WITH Dir[l] DO BEGIN
WRITE(Dir);
{$F+} {v1.6a Use Far calls}
IF Below THEN SearchEngineAll(Dir,Name,Archive,AddToEstimate,errorcode)
ELSE SearchEngine(Dir+Name,Archive,AddToEstimate,errorcode);
{$F-} {v1.6a No Far calls}
ClrEol;
WRITELN;
END;
WRITELN;
WRITE(numBytes,' bytes in ',numfiles,' file(s) to ');
IF SearchZips THEN WRITELN('convert/examine.')
ELSE WRITELN('convert.');
WRITELN;
IF numfiles=0 THEN HaltWithMsg('No files to convert!');
IF NOT BatchMode THEN BEGIN
WRITELN('Press any key...');
REPEAT UNTIL KeyPressed;
END;
WHILE KeyPressed DO CHAR(l) := ReadKey;
END; { of GetFileEstimates }
PROCEDURE IPP;
{ Interrupt pre-processor. This is a new handler for interrupt 29h which
provides special functions. See comments in IHAND.ASM}
interrupt;
BEGIN
InLine(
$06/ { push es }
$1E/ { push ds }
$53/ { push bx }
$57/ { push di }
$BB/$3F/$3F/ { mov bx, 3f3fh }
$8E/$C3/ { mov es, bx }
$BB/$3F/$3F/ { mov bx, 3f3fh }
$26/$8B/$3F/ { mov di, word ptr [es:bx] }
$26/$8E/$5F/$02/ { mov ds, word ptr [es:bx+2] }
$88/$05/ { mov byte ptr [di], al }
$26/$FF/$07/ { inc word ptr [es:bx] }
$5F/ { pop di }
$5B/ { pop bx }
$1F/ { pop ds }
$07/ { pop es }
$3C/$0A/ { cmp al, 10 }
$75/$28/ { jne looper }
$50/ { push ax }
$52/ { push dx }
$51/ { push cx }
$53/ { push bx }
$B4/$03/ { mov ah, 3 }
$B7/$00/ { mov bh, 0 }
$CD/$10/ { int 10h }
$80/$FE/$18/ { cmp dh, 24 }
$75/$15/ { jne popper }
$FE/$CE/ { dec dh }
$B7/$00/ { mov bh, 0 }
$B4/$02/ { mov ah, 2 }
$CD/$10/ { int 10h }
$B8/$01/$06/ { mov ax, 0601h }
$B7/$07/ { mov bh, 7 }
$B9/$00/$11/ { mov cx, 1100h }
$BA/$4F/$18/ { mov dx, 184fh }
$CD/$10/ { int 10h }
{popper: }
$5B/ { pop bx }
$59/ { pop cx }
$5A/ { pop dx }
$58/ { pop ax }
{looper: }
$9C/ { pushf }
$9A/$00/$00/$00/$00/ { call far [0:0] }
$CF); { iret }
END; {of IPP}
{$F+} {v1.6a Use FAR calls}
PROCEDURE NewExitProc2;
{ This exit procedure removes the interrupt 29h handler from memory
and places the cursor at the bottom of the screen. }
BEGIN
Reg.AX := $2529; {v1.6a}
Reg.DS := oldSeg;
Reg.DX := oldOfs;
MsDos(Reg);
Window(1,1,80,25);
GotoXY(1,24);
TextAttr := $07;
ClrEol;
WRITELN('Thank you for using A2Z!');
ExitProc := OldExitProc2;
END; {of NewExitProc2}
{$F-} {v1.6a No FAR calls}
PROCEDURE ResetBuffer;
{ Reset pointers to the text buffer, effectively deleting any text in it }
BEGIN
MemW[Seg(bufData):Ofs(bufData)] := bufferOfs; { Set first 2 bytes
of bufData to point to buffer offset }
MemW[Seg(bufData):Ofs(bufData)+2] := bufferSeg; { And next two bytes
to point to buffer segment }
MemW[Seg(IPP):Ofs(IPP)+21] := Seg(bufData); { Now point the interrupt
routine to bufData for pointer }
MemW[Seg(IPP):Ofs(IPP)+26] := Ofs(bufData); { to the text buffer }
END; {of ResetBuffer}
function in_Buffer(S: String): INTEGER;
{ This searched the text buffer for the string S,
and if it's found, returns the offset in the buffer.
If it's not found, a -1 is returned.
}
VAR
l,m : word;
x : Byte;
BEGIN
x := 1;
l := bufferOfs;
m := MemW[Seg(bufData):Ofs(bufData)]-bufferOfs; {v1.6a}
WHILE (x<=LENGTH(S)) AND (l<=m) DO BEGIN
IF Mem[bufferSeg:l] = Byte(S[x]) THEN Inc(x)
ELSE x := 1;
Inc(l);
END;
IF x > LENGTH(S) THEN in_Buffer := l-LENGTH(S)
ELSE in_Buffer := -1;
END; {of in_Buffer}
PROCEDURE InstallInterruptHandler;
{ Installs the int 29h handler }
BEGIN
bufferlen := $4000; { Set up a 16k buffer }
GetMem(BufferPtr,bufferlen); { Allocate memory pointed at
by BufferPtr }
bufferSeg := Seg(BufferPtr^); { Read segment and offset
of buffer for easy access }
bufferOfs := Ofs(BufferPtr^);
ResetBuffer; { Place these values in the IPP
routine, resetting buffer }
Reg.AX := $3529; {v1.6a DOS svc 35H,
get Int 29H vector}
MsDos(Reg);
oldSeg := Reg.ES; { Store the segment and offset
of the old vector for later use }
oldOfs := Reg.BX;
MemW[Seg(IPP):Ofs(IPP)+90] := Reg.BX; { And store them }
MemW[Seg(IPP):Ofs(IPP)+92] := Reg.ES; { so IPP can call the routine }
Reg.AX := $2529; {v1.6a DOS svc 25H,
set Int 29H vector}
Reg.DS := Seg(IPP); { Store segment and offset for IPP.
The +16 is to skip TP stack }
Reg.DX := Ofs(IPP)+16; { maintanence routines }
MsDos(Reg);
{$F+} {v1.6a use FAR calls}
OldExitProc2 := ExitProc; { Set up new exit procedure to remove
routine at program termination }
ExitProc := @NewExitProc2;
{$F-} {v1.6a No FAR calls}
TextAttr := $07; { Clear the screen to white on black}
ClrScr;
GotoXY(1,15); { Go to line 15 and 16 and draw an
inverse bar which will show the }
TextAttr := $70; { current command being executed. }
WRITE('DOS COMMAND:');
ClrEol; WRITELN; ClrEol;
TextAttr := $07; { Set text color back
to white on black }
Window(1,1,80,13); { Make active window at top of screen
and home cursor }
GotoXY(1,1);
cmdY := 18; { Assume the cursor in the lower
window's at the top of window }
END; {of InstallInterruptHandler}
PROCEDURE ExecCommand(Cmd,Parm: String);
{ Executes the command in Cmd with command line parameters in Parm.
This is executed in the lower window }
VAR
ox,oy : Byte; { Upper window X and Y }
BEGIN
ResetBuffer; { Clear text buffer }
ox := WhereX; { Store upper window X and Y }
oy := WhereY;
Window(1,1,80,25); { Make entire screen active window }
GotoXY(14,15); { Go to line 14 (COMMAND bar) }
TextAttr := $70;
WRITE(Cmd,' ',Parm); { Write the command and parameters
in inverse }
GotoXY(1,cmdY); { Go to location in bottom window }
TextAttr := $07; { Normal text color }
Exec(Cmd,Parm); { Execute command }
execerror := DosExitCode; {v1.6b remember child process
exit codes}
IF execerror <> 0 {v1.6b there was one!}
THEN STR(execerror,ExecErrStr); {v1.6b so make the string now}
cmdY := WhereY; { Store new Y location }
GotoXY(14,15);
TextAttr := $70; { Erase the COMMAND bar }
ClrEol; WRITELN; ClrEol;
TextAttr := $07;
Window(1,1,80,13); { Reset the upper window }
GotoXY(ox,oy); { Re-position cursor }
END; {of ExecCommand}
function Internal_In_Zip: BOOLEAN;
{1 call}
BEGIN
IF (in_Buffer('.ARC')<>-1)
OR (in_Buffer('.PAK')<>-1)
OR (in_Buffer('.ZIP'#13#10' ')<>-1)
OR (in_Buffer('.ZIP'#13#10'-')<>-1)
THEN Internal_In_Zip := TRUE
ELSE Internal_In_Zip := FALSE;
END; {of Internal_In_Zip}
VAR
x: INTEGER;
L: STRING[60];
C: STRING[10];
code: INTEGER;
Okay: BOOLEAN;
T: TEXT;
SRec: SearchRec;
Z: ComStr;
ec: Byte;
RC: CHAR;
CurWork: String;
{$F+}
PROCEDURE Convert(VAR S: SearchRec; P: PathStr);
{$F-}
PROCEDURE ArchiveError(N: String);
{
Report an archive error if we're working with the top file, otherwise
set an error flag.
v1.6a Common code: We ALWAYS call WriteLnStatus right after
ArchiveError (with an Indent_Str added to the front).
Adding this common function right here.
}
BEGIN
IF ConvertingInside THEN BEGIN
WRITELN(Indent_Spaces + N); {v1.6a}
TrickleUpError := TRUE;
END
ELSE LogError(N,TRUE); {v1.6 WriteLn to StatFile also}
END; {of ArchiveError}
PROCEDURE DeleteDir(P: String);
{
Delete all files in the directory named and remove it.
}
VAR
SRec: SearchRec;
errorcode: Byte;
BEGIN
FindFirst(P+'\*.*',0,SRec);
WHILE DosError=0 DO BEGIN
Assign(T, P+'\'+SRec.Name);
{$I-} Erase(T); {$I+}
errorcode := IOResult;
FindNext(SRec);
END;
{$I-} RmDir(P); {$I+}
errorcode := IOResult;
END; {of DeleteDir}
PROCEDURE CopyFile(SourceName,DestName: ComStr);
{v1.6a Replacing length DIV and * instructions with shifts}
VAR
Source,Dest: FILE;
recsread: word;
buffer: pointer;
bufsize: word;
t: longint;
BEGIN
IF MaxAvail>65535 THEN bufsize := 65535 ELSE bufsize := MaxAvail;
bufsize := bufsize ShR 10; {v1.6a}
GetMem(buffer, bufsize ShL 10); {v1.6a}
Assign(Source, SourceName);
RESET(Source,1024);
Assign(Dest,DestName);
REWRITE(Dest,1024);
for t := 1 TO FileSize(Source) DO BEGIN
BlockRead(Source,buffer^,bufsize,recsread);
BlockWrite(Dest,buffer^,recsread);
END;
t := FileSize(Source) ShL 10; {v1.6a}
RESET(Source,1);
RESET(Dest,1);
Seek(Source,t);
Seek(Dest,t);
bufsize := bufsize ShL 10; {v1.6a}
REPEAT
BlockRead(Source, buffer^, bufsize, recsread); {v1.6a}
BlockWrite(Dest,buffer^,recsread); {v1.6a}
UNTIL recsread = 0; {v1.6a}
GetFTime(Source, t);
SetFTime(Dest, t);
CLOSE(Source);
CLOSE(Dest);
FreeMem(buffer, bufsize); {v1.6a}
Erase(Source);
END; {of CopyFile}
VAR {for Convert}
N: NameStr;
E: ExtStr;
ArcComment: STRING[50];
filesinarc: word;
unarcedSize: longint;
arcedSize: longint;
OCI: BOOLEAN;
PROCEDURE Comment_Common;
{v1.6a Common code .. we do this twice}
BEGIN
ArcComment := '';
REPEAT
Inc(x);
UNTIL CHAR(Mem[bufferSeg:x]) IN [' ',#13,#10];
IF CHAR(Mem[bufferSeg:x])=' ' THEN BEGIN
REPEAT
Inc(x);
UNTIL CHAR(Mem[bufferSeg:x])=' ';
Inc(x);
REPEAT
ArcComment := ArcComment+CHAR(Mem[bufferSeg:x]);
Inc(x);
UNTIL CHAR(Mem[bufferSeg:x]) IN [#10,#13];
END; {had a comment}
WHILE ArcComment[LENGTH(ArcComment)]=' ' DO Dec(ArcComment[0]);
END; {of Comment_Common}
PROCEDURE Snarf_Digits;
{v1.6a more common code}
BEGIN
C := '';
REPEAT
C := C+L[1];
L := COPY(L,2,255);
UNTIL L[1]=' ';
WHILE L[1]=' ' DO L := COPY(L,2,255);
END; {of Snarf_Digits}
PROCEDURE Build_L;
{v1.6a common code}
BEGIN
REPEAT
L := L+CHAR(Mem[bufferSeg:x]);
Inc(x);
UNTIL CHAR(Mem[bufferSeg:x]) IN [#10,#13];
END; {of Build_L}
PROCEDURE Strip_Zeros;
{v1.6a strips leading 0's from L (a filesinarc string)}
BEGIN
WHILE (LENGTH(L) <> 0)
AND (L[1] = '0') DO
DELETE(L,1,1);
END; {of Strip_Zeros}
BEGIN {Convert}
IF TrickleUpError THEN Exit;
IF KeyPressed THEN BEGIN
RC := ReadKey;
IF RC=#27 THEN BEGIN
IF ConvertingInside THEN BEGIN
TrickleUpError := TRUE;
InterruptRequested := TRUE;
Exit;
END
ELSE BEGIN
TStr := '*** Conversion interrupted ***'; {v1.6a}
LogError(TStr,TRUE); {v1.6a Writeln to StatFile also}
HALT;
END;
END;
END;
FSplit(P+S.Name,P,N,E);
WritelnStatFil(
Indent_Spaces + 'Converting '+S.Name,
TRUE); {v1.6a Writeln also}
IF NOT ConvertingInside THEN BEGIN
Inc(filenum); {bump file count}
WritelnStatFil('', TRUE); {v1.6a Writeln also}
TextAttr := $0F;
WRITELN('Converting ',P+S.Name,
' Saved: ',saved,' bytes File: ',filenum,' of ',numfiles);
TextAttr := $07;
END
ELSE BEGIN
WRITE(Indent_Spaces); {v1.6a}
TStr := 'Converting internal file ' + N + E; {v1.6a}
TextAttr := $0F;
WRITELN(TStr); {v1.6a}
TextAttr := $07;
WritelnStatFil(Indent_Spaces + TStr,FALSE); {v1.6a No Writeln}
END;
IF E='.ZIP' THEN BEGIN
WriteStatus('Checking ' + S.Name
+ ' for internal files...'); {v1.6a Write also}
ExecCommand(PKZIP,'/V '+P+N);
IF execerror <> 0 THEN BEGIN {v1.6b Exec had child process error}
WRITELN;
ArchiveError('Error ' + ExecErrStr + ' in ZIPfile ' + P+N+E
+ '; file skipped.'); {v1.6b}
Exit;
END;
arcedSize := S.size;
IF Internal_In_Zip THEN BEGIN
WritelnStatFil(' found.',TRUE); {v1.6a Writeln also}
{v1.6a Common error string for two possible errors }
TStr := 'Error in Zipfile ' + P+N+E+'; file skipped.'; {v1.6a}
x := in_Buffer('Searching');
IF x=-1 THEN BEGIN
ArchiveError(TStr); {v1.6a}
Exit;
END;
Inc(x,15);
Comment_Common; {v1.6a do common code}
L := '';
x := in_Buffer('--------'#13#10); {v1.6a}
IF x=-1 THEN BEGIN
ArchiveError(TStr); {v1.6a unchanged from last error}
Exit;
END;
REPEAT
Inc(x);
UNTIL CHAR(Mem[bufferSeg:x]) IN ['0'..'9'];
Build_L; {v1.6a common code}
Snarf_Digits; {v1.6a builds C}
Z := C; {v1.6a save unarcedSize a sec}
Snarf_Digits; {v1.6a builds C}
WHILE L[1]<>' ' DO L := COPY(L,2,255);
WHILE L[1]=' ' DO L := COPY(L,2,255);
{ v1.6a reminder:
Z = unarcedSize string
L = filesinarc string
C = arcedSize string
S.size := arcedSize word
The only variable we'll use later is arcedSize,
so no need to convert the other strings to numbers.
The filesinarc string may be 0-padded, so we'll strip
those leading 0's.
}
arcedSize := S.size; {v1.6a}
Strip_Zeros; {v1.6a strip L's leading 0's}
WritelnStatFil( {v1.6a}
Indent_Spaces + L + 'FILE(s), ' {v1.6a}
+ C + ' bytes zipped, ' {v1.6a}
+ Z + ' bytes unzipped', TRUE); {v1.6a Writeln also}
if ArcComment<>''
THEN WritelnStatFil( {v1.6a}
Indent_Spaces + 'Zipfile comment: :'
+ ArcComment + '"', TRUE); {v1.6a Writeln also}
WriteStatus('Unzipping ' + N + E + '...'); {v1.6a Write also}
CurWork := CurWork+'\A2Z.$$$';
MkDir(CurWork);
ExecCommand(PKUNZIP,P+N+' '+CurWork);
IF execerror <> 0 THEN BEGIN {v1.6b}
DeleteDir(CurWork);
Dec(CurWork[0],8);
ArchiveError(#$0D#$0A'Error ' + ExecErrStr {v1.6b}
+ ' unzipping ' + P + N + E
+ '; FILE skipped.'); {v1.6a}
(* v1.6a didn't DO this quite right .. leaving it for now...
WriteLnStatus(#$0D#$0A + Indent_Spaces + TStr); {v1.6a}
*)
Exit;
END;
WritelnStatFil(' done.', TRUE); {v1.6a Writeln also}
END
ELSE BEGIN
WritelnStatFil(' none found.', TRUE); {v1.6a Writeln also}
ArchiveError(N+E+' did not need to be modified.'); {v1.6a}
TrickleUpError := FALSE;
Exit;
END;
END
ELSE BEGIN {PKPak file}
WriteStatus('Analyzing ' + N + E + '...'); {v1.6a Write also}
ExecCommand(PKUNPAK,'-V '+P+N+E);
WritelnStatFil(' done.', TRUE); {v1.6a Writeln also}
TStr := 'Error ' + ExecErrStr + ' in archive ' + P + N + E {v1.6b}
+ '; file skipped.'; {v1.6a}
IF execerror <> 0 THEN BEGIN {v1.6b}
ArchiveError(TStr); {v1.6a}
Exit;
END;
x := in_Buffer('Searching');
IF x=-1 THEN BEGIN
ArchiveError(TStr); {v1.6a Same error msg}
Exit;
END;
Inc(x,11); {v1.6c}
Comment_Common; {v1.6a common code}
L := '';
x := in_Buffer(#13#10'---- '); {v1.6a}
IF x=-1 THEN BEGIN
ArchiveError(TStr); {v1.6a same error msg}
Exit;
END;
Inc(x,52); {v1.6c}
Build_L; {v1.6a read until CR/LF}
Snarf_Digits; {v1.6a builds C}
TStr := C; {v1.6a save filesinarc string}
Snarf_Digits; {v1.6a builds C}
Z := C; {v1.6a save unarcedSize string}
C := '';
REPEAT
C := C+L[1];
L := COPY(L,2,255);
UNTIL L[1] IN [#13,#10,#32];
L := TStr; {v1.6a restore filesinarc string}
{ v1.6a reminder:
Z = unarcedSize string
L = filesinarc string
C = arcedSize string
The only variable we'll need later is arcedSize,
so that's the only one we'll convert.
The filesinarc string may be 0-padded,
so we'll strip those leading 0's.
}
Val(C,arcedSize,code); {v1.6a We'll need this later}
Strip_Zeros; {v1.6a Strip L's leading 0's}
WritelnStatFil( {v1.6a}
Indent_Spaces + L + ' file(s), '{v1.6a}
+ C + ' bytes arced, '
+ Z + ' bytes unarced', TRUE); {v1.6a Writeln also}
IF ArcComment<>''
THEN WritelnStatFil(
Indent_Spaces
+ 'Archive comment: "'
+ ArcComment + '"', TRUE); {v1.6a WriteLn also}
WriteStatus('Extracting files...'); {v1.6a Write also}
CurWork := CurWork+'\A2Z.$$$';
MkDir(CurWork);
IF E='.ARC' THEN BEGIN
ExecCommand(PKUNPAK,P+N+' '+CurWork);
Okay := (execerror = 0); {v1.6b}
END;
IF E='.PAK' THEN BEGIN
ExecCommand(PAK,'e '+P+N+' '+CurWork);
Okay := (execerror = 0); {v1.6b}
END;
WRITELN(' done.');
IF NOT Okay THEN BEGIN
ArchiveError('Error ' + ExecErrStr + ' extracting ' {v1.6b}
+ P + N + E
+ '; skipping.'); {v1.6a}
DeleteDir(CurWork);
Dec(CurWork[0],8);
Exit;
END;
END;
WritelnStatFil(
Indent_Spaces
+ 'Checking internal files...', TRUE); {v1.6a Writeln also}
OCI := ConvertingInside;
ConvertingInside := TRUE;
Inc(internalcount);
{$F+} {v1.6a need Far calls for this}
SearchEngine(CurWork+'\*.*',Archive,Convert,ec);
{$F-}
Dec(internalcount);
ConvertingInside := OCI;
IF TrickleUpError THEN BEGIN
IF InterruptRequested THEN BEGIN
DeleteDir(CurWork);
Dec(CurWork[0],8);
IF ConvertingInside THEN Exit;
LogError('*** Conversion interrupted ***',
TRUE); {v1.6a Status file also}
HALT;
END;
IF NOT ConvertingInside THEN BEGIN
TrickleUpError := FALSE;
LogError('Unable to convert '
+ P + N + E
+ ' due to an internal file error.',
TRUE); {v1.6a StatFile output also}
END;
DeleteDir(CurWork);
Dec(CurWork[0],8);
Exit;
END;
CopyFile(P+N+E,P+N+'.A2B');
WriteStatus('Creating ZIP file '
+ N + '.ZIP...'); {v1.6a Write also}
Z := P+N+'.ZIP';
Assign(T, Z);
{$I-} Erase(T); {$I+}
code := IOResult;
IF ArcComment='' THEN
(* v1.6d
ExecCommand(PKZIP,Z + EASwitch + EBSwitch + ' ' {v1.6a}
+ CurWork+'\*.*')
*)
ExecCommand(PKZIP,Z + ' ' + CurWork+'\*.*') {v1.6d}
ELSE BEGIN
Assign(T, WorkDir+'ZCOMMENT.A2Z');
REWRITE(T);
WRITELN(T, ArcComment);
CLOSE(T);
Reg.BX := 0;
Reg.AH := $45;
MsDos(Reg);
code := Reg.AX;
RESET(T);
Reg.BX := TextRec(T).Handle;
Reg.CX := 0;
Reg.AH := $46;
MsDos(Reg);
(* v1.6d
ExecCommand(PKZIP,Z + EASwitch + EBSwitch + ' -a ' {v1.6a}
+ CurWork+'\*.* -z');
*)
ExecCommand(PKZIP,Z + ' -a ' + CurWork+'\*.* -z'); {v1.6d}
Reg.BX := code;
Reg.CX := 0;
Reg.AH := $46;
MsDos(Reg);
Reg.BX := code;
Reg.AH := $3E;
MsDos(Reg);
CLOSE(T);
Erase(T);
END;
WritelnStatFil(' done.', TRUE); {v1.6a Writeln also}
IF execerror <> 0 THEN BEGIN {v1.6b}
ArchiveError('Error ' + ExecErrStr {v1.6b}
+ ' Unable to create zip file: ' {v1.6b}
+ Z + '; file skipped.'); {v1.6a}
DeleteDir(CurWork);
Dec(CurWork[0],8);
CopyFile(P+N+'.A2B',P+N+E);
Exit;
END;
FindFirst(Z,0,SRec);
Assign(T, P+N+'.A2B');
{$I-} Erase(T); {$I+}
code := IOResult;
IF NOT ConvertingInside THEN saved := saved+(arcedSize-SRec.size);
Assign(T, Z);
RESET(T);
SetFTime(T, S.Time);
CLOSE(T);
{ v1.6a no need to do this yet .. it may NOT be needed:
STR(arcedSize-SRec.size,C);
}
IF ConvertingInside
THEN WritelnStatFil(
Indent_Spaces
+ 'Internal file '
+ N + E
+ ' converted.',
TRUE) {v1.6a Writeln also}
ELSE BEGIN {v1.6a}
STR(arcedSize-SRec.size,C); {v1.6a NOW do this}
TStr := 'File ' + P + N + E; {v1.6a build first part}
IF E = '.ZIP'
THEN TStr := TStr + ' internally'; {v1.6a build first part}
TStr := TStr + ' converted to ZIP, '
+ C + ' bytes saved.'; {v1.6a}
LogError(TStr,TRUE); {v1.6a StatFile output also}
END;
DeleteDir(CurWork);
Dec(CurWork[0],8);
END; {of Convert}
PROCEDURE ConvertFiles;
{
This is the main conversion loop of the program. It will call the convert
arc routine from the search engine.
}
VAR
l, errorcode : Byte;
BEGIN
filenum := 0;
ConvertingInside := FALSE;
internalcount := 0;
InterruptRequested := FALSE;
saved := 0;
TrickleUpError := FALSE;
CurWork := COPY(WorkDir,1,PRED(LENGTH(WorkDir)) ); {v1.6a}
for l := 1 TO numdirs DO
WITH Dir[l] DO
{$F+} {v1.6a need FAR calls}
IF Below THEN SearchEngineAll(Dir,Name,Archive,Convert,errorcode)
ELSE SearchEngine(Dir+Name,Archive,Convert,errorcode);
{$F-} {v1.6a No FAR calls}
END; {of ConvertFiles}
PROCEDURE SummarizeLog;
VAR S : STRING[30];
BEGIN
STR(saved, S);
LogError(S+' bytes saved total.', FALSE); {v1.6a no Stat File}
END; {of SummarizeLog}
BEGIN {main}
ReadCommandLine;
CheckSubPrograms;
OpenLogFile;
GetFileEstimates;
CheckBreak := FALSE;
InstallInterruptHandler;
ConvertFiles;
SummarizeLog;
END.