home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 19
/
CD_ASCQ_19_010295.iso
/
vrac
/
addhlp.zip
/
ADDHELP.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1994-09-03
|
25KB
|
703 lines
program addhelp; {adds help screen invoked by /? to .COM utilities,
especially those created from DEBUG scripts}
{ version 1.0, August 1994.
written by John Nurick, 70162.2472@compuserve.com
written for Turbo Pascal v6.0 but should compile with v5.x
and later.
May be used, copied and distributed freely.
History: Versions 0.x first written in 1992 and in use since
with no problems but no formal testing.
For Version 1.0 I restructured the TP code and added
error-handling but did not change the tried if not formally
tested routines for actually modifying files, or the machine
code that is inserted into the .COM files.
Syntax: ADDHELP /A STANDARD.COM HELP.TXT MODIFIED.COM
ADDHELP /E MODIFIED.COM HELP.TXT STANDARD.COM
ADDHELP /?
STANDARD.COM : path to .COM file without help screen
MODIFIED.COM : path to modified .COM file with help screen added
HELP.TXT : path to ASCII file containing help text
/A switch : add help text display to .COM file or substitute a
new help screen in an already-modified file.
/E switch : extract original .COM and help text files from
a .COM file already modified by ADDHELP
/? switch : display help screens.
*** OPERATION ********************************
(1) ADDHELP checks command line parameters and disk and memory space.
If the first parameter is '/?' it displays two screens of help text.
If there is no parameter it displays syntax summary.
If an error is identified it displays error message and syntax summary.
(2) Progress messages and error messages are displayed via stdout, so can
be redirected to a log file if ADDHELP is used in batch files.
(4) ADDHELP returns DOS ERRORLEVEL 0 if successful and 1 if an error
was identified.
*** with /A switch ***************************
(1) ADDHELP checks that MODIFIED.COM will fit in the 64k limit for .COM
files.
(2) It reads STANDARD.COM into memory (comarray^) and checks for an
ADDHELP signature. If the first two bytes are the .EXE format
signature of 'MZ' it reports an error. If STANDARD.COM is shorter
than 32 bytes it is padded with NOPs.
(4) If signature was found, it substitutes the new help text and adjusts
addresses rather than adding a second envelope of help code.
(5) If adding to a plain .COM file, ADDHELP writes a 32-byte header:
Offset Bytes Item
0h 3h JUMP instruction to beginning of the ADDHELP code.
3h 1h NOP
4h 2h address (offset + 100h) of the displaced first byte
of the original .COM file
6h 2h address (offset + 100h) of the beginning of the help
text
8h 2h number of bytes of help text
Ah 2h spare
Ch 14h ADDHELP signature (20 bytes)
(6) Next, it writes bytes 33 to the end (offset 20h to end) of STANDARD.COM
to MODIFIED.COM, followed immediately by bytes 1 to 32.
(5) This is followed by the code to display the help message (see below),
and finally by the help text.
*** with /E switch ***************************
(1) ADDHELP reads MODIFIED.COM into memory.
(2) If the ADDHELP signature is found, it reconstructs the code of
the original program and writes it to STANDARD.COM.
(3) It writes the text of the help screen to HELP.TXT.
*** When you run MODIFIED.COM **************************************
DOS (as usual) loads MODIFIED.COM into the memory beginning at CS:0100,
and puts a copy of the command line tail beginning at CS:0081. It then
executes the instruction at CS:0100, which is the JUMP with which the
32-byte header in MODIFIED.COM begins. This takes us to the beginning of
the ADDHELP code, which does the following:
(1) Retrieve the DOS switch character (usually '/');
(2) Check that there is a DOS command tail. If not, go to (5).
(3) Read the DOS command tail (at $0081 onwards) looking for the
switch character. If found, go to (4); if not, go to (5).
(4) If character after switch character is '?', write the help text
to DOS standard output and return control to DOS. Otherwise:
(5) Copy the first 32 bytes of STANDARD.COM from from their present
location in memory to to $0100 .. $011F, thus producing a complete
a complete memory image of STANDARD.COM starting at $0100.
(6) Jump to $0100 to begin execution of STANDARD.COM exactly as if that
was what had been loaded in the first place.
*** Code to add to MODIFIED.COM ******************************
NB: this was assembled using DEBUG, starting at 0200h, but it will end
up somewhere else in CS: depending on the length of STANDARD.COM. The
jump commands (JMP, JZ, etc.) are not affected by this "relocation",
because they assemble into jumps relative to the starting point of the
jump, not jumps to a set address. Where it *is* necessary to goto a
particular address, this is done with an indirect jump.
The Turbo Pascal array constant asmcode (below) contains the actual
bytes of code which the compiler will incorporate into ADDHELP.EXE
ready for ADDHELP to write them into MODIFIED.COM.
Addr Machine code Mnemonics comments
0200 31DB XOR BX,BX ;clear BX
0202 B437 MOV AH,37 ;DOS function 37
0204 B000 MOV AL,00 ;sub-function 00
0206 CD21 INT 21 ;returns switch character in DOS v. >=2.0
0208 88D3 MOV BL,DL ;put switch character in BL
020A 31D2 XOR DX,DX ;zero DX
020C 8A168000 MOV DL,[0080];get length of command tail
0210 83FA00 CMP DX,+00 ;if zero
0213 7419 JZ 022E ;go prepare to run original program
0215 81C28000 ADD DX,0080 ;= address of end of command tail
0219 BF8000 MOV DI,0080 ;one before beginning of tail
021C 47 INC DI ;begin first loop: next char in tail
021D 3A1D CMP BL,[DI] ;is it switch character?
021F 7406 JZ 0227 ;yes: go check for '?'
0221 39D7 CMP DI,DX ;no: are we at end of tail?
0223 75F7 JNZ 021C ;no: loop back
0225 EB07 JMP 022E ;yes: go prepare to run original program
0227 B03F MOV AL,3F ;get a '?'
0229 47 INC DI ;next character in command tail
022A 3A05 CMP AL,[DI] ;is it a '?'
022C 7411 JZ 023F ;yes: go display help screen;
;no: prepare to run original
022E 8B360401 MOV SI,[0104];get address of first 32 bytes
;of STANDARD.COM
0232 BF0001 MOV DI,0100 ;where to put them
0235 B92000 MOV CX,0020 ;number of bytes to move
0238 F3 REPZ
0239 A4 MOVSB ;move them
023A BF0001 MOV DI,0100 ;address to begin execution
023D FFE7 JMP DI ;run the original
;display help text:
023F B440 MOV AH,40 ;DOS write function
0241 BB0100 MOV BX,0001 ;handle for STDOUT
0244 8B0E0801 MOV CX,[0108];get bytes in help text
0248 8B160601 MOV DX,[0106];get address of help text
024C CD21 INT 21 ;do it
024E B44C MOV AH,4C ;DOS exit function
0250 B040 MOV AL,40 ;errorlevel 64 in case it's useful
0252 CD21 INT 21
0254 90 NOP ;padding
0255 90 NOP
0256 90 NOP
0257 90 NOP
********************************************************}
uses dos, crt;
const asmarraycount = 11; {adjust this if the machine code changes}
comlimit = $FF00; {the biggest .COM file we will consider}
numhelpmessages = 12;
type asmarray = array[1..asmarraycount] of array[1..8] of byte;
bigarray = array[1..comlimit] of byte;
bigarrayptr = ^bigarray;
messagearray = array[1 .. numhelpmessages] of string[79];
file_of_byte = file of byte;
mode_type = (adding, extracting, helping, no_parameters);
const notice1 = 'ADDHELP is copyright (C) John Nurick 1994.';
notice2 = 'John Nurick asserts the moral right to be identified';
notice3 = 'as the author of this work. Subject to this, ADDHELP';
notice4 = 'may freely be used, copied and distributed.';
JMP : byte = $E9; {machine code}
NOP : byte = $90;
JMPsize = 3; {bytes in a JMP nnnn instruction}
headersize = $20; {bytes in the ADDHELP header}
signature = 'ADDHELP(C)J.NURICK94'; {this must be the right length
to bring the length of the header to headersize;
currently, this is headersize - 12, i.e. 20 bytes}
asmcode : asmarray = {this contains the machine code that ADDHELP
inserts into the new .COM file, padded with
$90 (NOP) instructions}
( ( $31 , $DB , $B4 , $37 , $B0 , $00 , $CD , $21 ),
( $88 , $D3 , $31 , $D2 , $8A , $16 , $80 , $00 ),
( $83 , $FA , $00 , $74 , $19 , $81 , $C2 , $80 ),
( $00 , $BF , $80 , $00 , $47 , $3A , $1D , $74 ),
( $06 , $39 , $D7 , $75 , $F7 , $EB , $07 , $B0 ),
( $3F , $47 , $3A , $05 , $74 , $11 , $8B , $36 ),
( $04 , $01 , $BF , $00 , $01 , $B9 , $20 , $00 ),
( $F3 , $A4 , $BF , $00 , $01 , $FF , $E7 , $B4 ),
( $40 , $BB , $01 , $00 , $8B , $0E , $08 , $01 ),
( $8B , $16 , $06 , $01 , $CD , $21 , $B4 , $4C ),
( $B0 , $40 , $CD , $21 , $90 , $90 , $90 , $90 ) );
err_insufficientmemory = $1;
err_numparameters = $2;
err_unrecognisedswitch = $3;
err_dupname = $4;
err_open_infile = $5;
err_open_helpfile = $6;
err_open_outfile = $7;
err_filenotrecognised = $8;
err_closingfile = $9;
err_nodiskspace = $A;
err_COMtoobig = $B;
err_EXEfile = $C;
errs : messagearray =
('Insufficient memory to load .COM file' ,
'Too few or too many command line parameters' ,
'Unrecognised command line switch or parameter' ,
'Two command line parameters point to the same file' ,
'Could not open input file ' ,
'Could not open help text file ' ,
'Could not open output file ' ,
'Cannot find ADDHELP code in input file ',
'Error closing file ' ,
'Insufficient disk space to write file(s)' ,
'Input .COM file is so big there is no room to add help screen',
'Input file appears to be in .EXE format' );
var comarray : bigarrayptr;
insize, helpsize, outsize, errorcode, gaugeunit: word;
inname, helpname, outname : PathStr;
infile, helpfile, outfile : file_of_byte;
already_modified: boolean;
sw: char; {DOS switch character '/' or '-' or whatever}
mode: mode_type;
exitsave: pointer;
{$F+}
function HeapFunc(Size: word) : integer;
{make new() return a nil pointer if insufficient memory}
begin
HeapFunc := 1;
end;
procedure MyExitProc; {sets DOS errorlevel to 0 if OK, 1 if any error}
begin
ExitProc := exitsave; {restore TP exit procedure}
if not ((ExitCode = 0) and (errorcode = 0))
then ExitCode := 1;
end;
function get_switch: char; {gets DOS switch character}
var R: registers;
begin
with R do
begin
AX := $3700;
MsDos(R);
get_switch:= char(lo(DX));
end;
end;
procedure sign_on;
begin
writeln; writeln;
writeln('ADDHELP.EXE : adds help facility to .COM files');
writeln(' Version 1.0 : John Nurick 1994');
writeln;
end;
procedure read_infile; {reads infile, looks for signature string
and sets or clears already_modified flag}
var n: word;
sig_test: string[20];
begin
write(' Reading ', inname, ' .');
read(infile, comarray^[1]);
read(infile, comarray^[2]);
if char(comarray^[1]) + char(comarray^[2]) = 'MZ' then
begin
errorcode := err_EXEfile;
exit;
end;
for n:= 3 to insize do
begin
read(infile, comarray^[n]);
if n mod gaugeunit = 0 then write('.');
end;
writeln('.');
if (mode = adding) and (insize < 32) then
begin
writeln(' Padding tiny file to 32 bytes ..');
while insize < 32 do
begin
inc(insize);
comarray^[insize] := NOP;
end;
end;
writeln(' Looking for ADDHELP signature in input file ...');
sig_test := '';
for n := 13 to 32 do sig_test := sig_test + char(comarray^[n]);
already_modified := (sig_test = signature);
end;
procedure initialise;
var s: String; dummy: integer;
begin
exitsave := ExitProc;
ExitProc := @MyExitProc;
HeapError := @HeapFunc;
errorcode := 0;
FileMode := 0;
mode := no_parameters;
if ParamCount = 0 then
exit {and display syntax message}
else
begin
s := ParamStr(1);
if s[1] = sw then
begin
case UpCase(s[2]) of
'E' : mode := extracting;
'A' : mode := adding;
'?' : begin
mode := helping;
exit; {to show help screens}
end;
else errorcode := err_unrecognisedswitch;
end {case}
end
else errorcode := err_unrecognisedswitch;
end;
if errorcode <> 0 then exit;
if ParamCount <> 4 then
errorcode := err_numparameters
else
begin
inname := FExpand(ParamStr(2));
helpname := FExpand(ParamStr(3));
outname := FExpand(ParamStr(4));
{check for duplicated names}
if inname = outname then errorcode := err_dupname;
if inname = helpname then errorcode := err_dupname;
if outname = helpname then errorcode := err_dupname;
end;
if errorcode <> 0 then exit;
{check files can be opened}
{$I-}
assign(infile, inname);
reset(infile);
if IOResult> 0 then
begin
errorcode := err_open_infile;
exit;
end;
insize := FileSize(infile);
if insize >= comlimit then
begin
errorcode := err_COMtoobig;
exit;
end;
case insize of
1 .. 256 : gaugeunit := 24;
256 .. 1023 : gaugeunit := 96;
1024 .. 4095: gaugeunit := 384;
4096 .. 16383: gaugeunit := 1536;
else gaugeunit := 5124;
end; {case}
new(comarray);
if comarray = nil then
begin
errorcode := err_insufficientmemory;
exit;
end;
read_infile;
if errorcode <> 0 then exit;
if (mode = extracting) and not already_modified then
begin
errorcode := err_filenotrecognised;
exit;
end;
assign(helpfile, helpname);
if mode = adding then reset(helpfile);
if mode = extracting then rewrite(helpfile);
if IOResult > 0 then
begin
errorcode := err_open_helpfile;
exit;
end;
assign(outfile, outname);
if errorcode = 0 then rewrite(outfile);
if IOResult > 0 then
begin
errorcode := err_open_outfile;
exit;
end;
{$I+}{check file sizes and disk space}
case mode of
adding :
begin
helpsize := FileSize(helpfile);
outsize := insize + helpsize + SizeOf(asmcode) + headersize;
if outsize >= comlimit then
begin
errorcode := err_COMtoobig;
exit;
end;
{add a bit to allow for cluster boundaries}
if outsize + $400 > DiskFree(ord(outname[1]) - 64)
then
begin
errorcode := err_nodiskspace;
exit;
end;
end;
extracting :
begin
outsize := insize + $400;
helpsize := $400;
if (outsize > DiskFree(ord(outname[1]) - 64)) or
(helpsize > DiskFree(ord(helpname[1]) - 64)) then
begin
errorcode := err_nodiskspace;
exit
end;
end;
end; {case}
end;
procedure write_word(w: word); {writes a word in byte-reversed order }
var h,l: byte; ww: word;
begin
h := hi(w);
l := lo(w);
write(outfile, l, h);
end;
procedure write_str(p: string); {writes string to file}
var n: integer;
begin
for n:= 1 to length(p) do
write(outfile, byte(p[n]));
end;
procedure write_header; {writes header to MODIFIED.COM}
begin
writeln(' Writing header for new .COM file ...');
write(outfile,JMP); {this and next line write the JMP instruction}
write_word(insize + headersize - JMPsize);
write(outfile, NOP); {unused byte to maintain word bounds}
write_word($100 + insize); {address of displaced first 32 bytes}
write_word($100 + headersize + insize + SizeOf(asmcode));
{address of beginning of help text}
write_word(helpsize); {length of help text}
write_word($100); {spare word}
write_str(signature);
end;
procedure write_old; {writes STANDARD.COM to MODIFIED.COM,
first 32 bytes displaced to end}
var n: word;
begin
write(' Writing original program to ', outname, '.');
for n:= headersize + 1 to insize do {write the bulk of the .COM file}
begin
write(outfile, comarray^[n]);
if n mod gaugeunit = 0 then write('.');
end;
for n:= 1 to headersize do {append the beginning of the .COM file}
write(outfile, comarray^[n]);
writeln('.');
end;
procedure write_code; {writes ADDHELP machine code to MODIFIED.COM }
var i,j: word;
begin
writeln(' Writing help code ...');
for i:= 1 to asmarraycount do
for j := 1 to 8 do
write(outfile,asmcode[i,j]);
end;
procedure modify_old; {this procedure used when ADDHELP finds its
signature in STANDARD.COM}
var l, h: byte;
oldhelpstart, n: word;
begin
l := comarray^[7]; h := comarray^[8];
oldhelpstart := (l + $100 * h) - $100;
{change helpsize in comarray^ header}
comarray^[9] := lo(helpsize);
comarray^[10]:= hi(helpsize);
write(' Writing original program to ', outname, '.');
for n:= 1 to oldhelpstart do {write the bulk of the .COM file}
begin
write(outfile, comarray^[n]);
if n mod gaugeunit = 0 then write('.');
end;
writeln('.');
end;
procedure write_help; {writes help text to MODIFIED.COM}
var b: byte;
begin
writeln(' Writing help text ...');
repeat
read(helpfile, b);
write(outfile, b);
until eof(helpfile);
end;
procedure extract_COM; {get STANDARD.COM out of comarray^}
var n, c_len: word;
begin
write(' Extracting ', outname, '..');
c_len := comarray^[5] + (256 * comarray^[6]) - $100;
for n := c_len + 1 to c_len + 32 do write(outfile, comarray^[n]);
for n := 33 to c_len do
begin
write(outfile, comarray^[n]);
if n mod gaugeunit = 0 then write('.');
end;
writeln('.');
end;
procedure extract_text; {get help text out of comarray^}
var n, h_loc, h_len: word;
begin
writeln(' Extracting ', helpname, ' ...');
h_loc := comarray^[7] + (256 * comarray^[8]) - $100 + 1;
{+1 to adjust for the difference between
base 0 addressing in the file and and
base 1 addressing in the array}
h_len := comarray^[9] + (256 * comarray^[10]);
for n := h_loc to (h_loc + h_len -1) do write(helpfile, comarray^[n]);
end;
procedure show_syntax;
begin
writeln(' Syntax: ADDHELP ', sw, 'A[dd] STANDARD.COM HELP.TXT MODIFIED.COM');
writeln(' ADDHELP ', sw, 'E[xtract] MODIFIED.COM HELP.TXT STANDARD.COM');
writeln(' ADDHELP ', sw, '? (display full help screens)');
end;
procedure show_help;
var dummy: char;
begin
AssignCRT(Output); Rewrite(Output); {enable fast writing and ReadKey}
ClrScr;
sign_on;
writeln;
writeln(' ADDHELP will take a .COM program file and add to it the ability to display');
writeln(' a help screen when it is run with the ', sw, '? switch, just like a standard');
writeln(' DOS internal or external command. ');
writeln;
show_syntax;
writeln;
writeln(' The three filespecs can include paths. You must specify the .COM or other');
writeln(' extensions. STANDARD.COM must be a .COM program (see next page of help).');
writeln(' HELP.TXT must be a plain ASCII file of not more than 23 lines of text. If');
writeln(' it looks right when displayed with the command TYPE helpfile, it will work.');
writeln;
writeln;
write(' Press any key to continue');
dummy := readkey;
if dummy = #0 then dummy := readkey; {this is to deal with two-byte key codes}
ClrScr;
writeln;
writeln(' ADDHELP seems safe to use on most .COM files including TSRs. It does not');
writeln(' increase the amount of RAM occupied by TSRs. It is not suitable for .COM');
writeln(' files that are merely part of a large program, such as WIN.COM in Windows.');
writeln(' ADDHELP is not recommended for .COM files that modify themselves, e.g.');
writeln(' to store configuration information.');
writeln(' Programs modified by ADDHELP may fail if they are subsequently "patched",');
writeln(' e.g. to change screen colours or hotkeys, either by a configuration program');
writeln(' or with a disk editor. Avoid this problem by configuring a copy of the ');
writeln(' original program and using ADDHELP to add the help facility to that copy.');
writeln(' You can use ADDHELP with the ', sw, 'E command line switch to reconstruct copies ');
writeln(' of the original program and helptext files.');
writeln;
writeln(' WARNING: The author gives no undertaking or warranty whatever about ADDHELP''s');
writeln(' reliability or performance or its suitability for any purpose whatever.');
writeln;
writeln(' USE IT AT YOUR OWN RISK!! KEEP BACKUPS OF ANYTHING IMPORTANT!!!!');
writeln;
writeln(' ADDHELP v. 1.0 was written by John Nurick, who can sometimes be reached at');
writeln(' 70162.2472@compuserve.com. It may be freely used, copied and distributed.');
writeln;
writeln;
write(' Press any key to continue');
dummy := readkey;
if dummy = #0 then dummy := readkey;
ClrScr;
writeln;
show_syntax;
assign(Output,''); rewrite(output); {back to StdOut}
end;
procedure handle_error;
var dummy: word; message: string[79];
begin
{$I-}
close(outfile);
close(infile); {not all these files are necessarily open, so trying }
close(helpfile); {to close them is likely to produce an IO error}
dummy := IOResult; {which this line ignores}
{$I+}
writeln(^G);
case errorcode of
5: writeln(errs[errorcode], inname);
6: writeln(errs[errorcode], helpname);
7: writeln(errs[errorcode], outname);
else writeln(errs[errorcode]);
end;
writeln;
show_syntax;
end;
procedure tidy_up;
var ok: boolean;
begin
{$I-}
case mode of
adding .. extracting :
begin
close(infile);
if IOResult > 0 then errorcode := err_closingfile;
close(helpfile);
if IOResult > 0 then errorcode := err_closingfile;
close(outfile);
if IOResult > 0 then errorcode := err_closingfile;
end;
end;
{$I+}
if errorcode = 0 then
begin
writeln;
case mode of
adding :
begin
writeln(' ADDHELP finished.');
writeln(' Modified program is ', outname, '.')
end;
extracting :
begin
writeln(' ADDHELP finished.');
writeln(' Extracted original program is ', outname, ';');
writeln(' Extracted help text is ', helpname, '.');
end;
end;
end
else handle_error;
end;
procedure addproc;
begin
if not already_modified then
begin
write_header;
write_old;
write_code;
write_help;
end
else {adding revised help to an already modified .COM file}
begin
writeln(' File has already been modified by ADDHELP');
writeln(' Substituting new help text ...');
modify_old;
write_help;
end;
end;
procedure extractproc;
begin
extract_COM;
extract_text;
end;
begin {main}
assign(output,''); rewrite(output); {allow DOS I/O redirection}
sw:= get_switch;
sign_on;
initialise;
if errorcode = 0 then
case mode of
adding: addproc;
extracting: extractproc;
helping: show_help;
no_parameters: show_syntax;
end; {case}
if errorcode = 0
then tidy_up
else handle_error;
end.