home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TCONFIG.ZIP / CONFIGUR.PAS
Encoding:
Pascal/Delphi Source File  |  1987-09-07  |  13.5 KB  |  305 lines

  1. (******************************************************************************
  2.  
  3.                    CONFIG.PAS
  4.                   Version 1.0
  5.                   July 8, 1986
  6.                    by Randy Forgaard
  7.                   CompuServe 70307,521
  8.  
  9. Requires PC-DOS/MS-DOS Turbo Pascal 3.0 or higher.  This file provides routines
  10. for  "configuring"  a program, by changing certain typed constant values within
  11. the program after it has been compiled.  CONFIG.PAS  differs  from  some  other
  12. cloning  methods  in that the configuration program need not have been compiled
  13. with the same version of Turbo Pascal as the target program.  Also,  CONFIG.PAS
  14. allows one to configure Turbo EXTENDER ".EXE" type programs, Turbo chain files,
  15. and  Turbo  overlay  files.  The CONFIG.PAS method may be used to have programs
  16. configure themselves or  other  programs.   If  desired,  CONFIG.PAS  can  also
  17. configure  files  that are not programs at all (the latter use of CONFIG.PAS is
  18. not addressed here).
  19.  
  20. The  method used by CONFIG.PAS requires that the target program contain what we
  21. will call a Configuration Block.  A Configuration Block is  a  group  of  typed
  22. constants that have the following form:
  23.  
  24.   const
  25.     head: string[32] = 'A SAMPLE CONFIGURATION ID STRING';
  26.  
  27.     ...various typed constant declarations: the Configuration Parameters...
  28.  
  29.     tail: Byte = 0;  {Configuration Tail}
  30.  
  31. The  names  of  the  "head"  and  "tail"  variables  above are irrelevant.  The
  32. Configuration ID String may be any string that does  not  appear  elsewhere  in
  33. your program.  (For safety's sake, it is good to pick a fairly long string like
  34. the  one  above,  to reduce the possibility that you might accidentally include
  35. another identical string elsewhere in your program.)  The  declared  length  of
  36. the  Configuration ID String (32 in the above example) MUST be the exact length
  37. of the string  itself,  not  a  larger  value.   The  "various  typed  constant
  38. declarations"  above  are the Configuration Parameters that you would like your
  39. configuration program to be able to change.  The  Configuration  Tail  variable
  40. ("tail,"  in  the above example) can be any type, need not have the value zero,
  41. and in fact can be used for other purposes if desired, but it must be  a  typed
  42. constant, and it must appear immediately after the Configuration Parameters.
  43.  
  44. A  Configuration  Block may be among the "global" typed constants of a program,
  45. or it may be located within a procedure or function.   There  may  be  multiple
  46. Configuration  Blocks  in  a  single program, if one wishes to modify different
  47. Configuration Parameters under different circumstances.  For each Configuration
  48. Block in  the  target  program,  there  must  be  an  identical  copy  of  that
  49. Configuration  Block  in the configuration program.  (Although all of the typed
  50. constant types in the two Configuration  Blocks  must  be  identical,  and  the
  51. Configuration   ID  Strings  must  be  the  same,  the  actual  values  of  the
  52. Configuration Parameters typed constants need not  match.)   If  a  program  is
  53. going  to  be  configuring  itself,  then  the latter condition will already be
  54. automatically satisfied.
  55.  
  56. The  routine  CfgReplace,  below,  is the one that actually performs the act of
  57. replacing the Configuration Parameters in the target program.   CfgClone  is  a
  58. special routine (which uses CfgReplace) that allows a program to "clone" a copy
  59. of  itself.   CfgAbort  is  the procedure that gets invoked by CfgReplace if an
  60. error is encountered.  It is purposely simple-minded; you will probably want to
  61. replace it with a routine that performs cleanup and  error  reporting  that  is
  62. suitable  for  your  particular  program.  BlockPos is a general-purpose speedy
  63. Inline routine that searches a large buffer (up to 64K) to look for  a  string.
  64. It  is  used  by CfgReplace, but also feel free to use it for other purposes in
  65. your own programs if you wish.
  66.  
  67. At  the  bottom  of  this file is a sample program that demonstrates the use of
  68. CfgReplace and CfgClone.  Enjoy!
  69.  
  70. ******************************************************************************)
  71.  
  72.  
  73. type
  74.   FileName = string[66];
  75.   TextLine = string[80];
  76.   Str255 = string[255];
  77.  
  78.  
  79. {Simple-minded error-reporting routine, used by CfgReplace.  Should probably be
  80.  replaced with one that is tailored to your particular program.}
  81.  
  82. procedure CfgAbort (msg: TextLine);
  83. begin
  84.   writeln;
  85.   writeln('Abort -- ', msg);
  86.   Halt
  87. end {CfgAbort};
  88.  
  89.  
  90. {Returns the offset, within "buffer," of the first occurrence of the characters
  91.  comprising "s," where the first byte of "buffer" is considered to be offset 1.
  92.  The  "size" is the size, in bytes, of "buffer" (an unsigned value in the range
  93.  0..65535).  If there are no occurrences of "s" within "buffer," or if "size" <
  94.  Length(s), or if "s" is empty, returns zero.  This is similar to Turbo's "Pos"
  95.  routine, except that a buffer up to 64K bytes in size may be searched.   NOTE:
  96.  This  is  only  for use on the 8088/86 family, under DOS (probably works under
  97.  CP/M-86, too).}
  98.  
  99. function BlockPos (var buffer; size: Integer; s: Str255): Integer;
  100. begin
  101.   Inline(
  102.          {Load   "buffer"   address   into  ES:DI,  "buffer"  offset  into  BX,
  103.           Length(s)-1 into DX, contents of "s[1]" into  AL,  offset  of  "s[2]"
  104.           into  SI,  and "size"-Length(s)+1 into CX.  If "size" < Length(s), or
  105.           if Length(s) = 0, return zero.}
  106.          $1E/               {        PUSH    DS           }
  107.          $16/               {        PUSH    SS           }
  108.          $1F/               {        POP     DS           }
  109.          $C4/$BE/>buffer/   {        LES     DI,buffer[BP]}
  110.          $89/$FB/           {        MOV     BX,DI        }
  111.          $8B/$8E/>size/     {        MOV     CX,size[bp]  }
  112.          $8D/$76/<s+2/      {        LEA     SI,s+2[bp]   }
  113.          $8A/$46/<s+1/      {        MOV     AL,s+1[bp]   }
  114.          $8A/$56/<s/        {        MOV     DL,s[bp]     }
  115.          $84/$D2/           {        TEST    DL,DL        }
  116.          $74/$23/           {        JZ      ERROR        }
  117.          $FE/$CA/           {        DEC     DL           }
  118.          $30/$F6/           {        XOR     DH,DH        }
  119.          $29/$D1/           {        SUB     CX,DX        }
  120.          $76/$1B/           {        JBE     ERROR        }
  121.  
  122.          {Scan  the  ES:DI  buffer, looking for the first occurrence of "s[1]."
  123.           If not found prior to reaching Length(s) characters before the end of
  124.           the buffer, return zero.  If Length(s) = 1,  the  entire  string  has
  125.           been found, so report success.}
  126.          $FC/               {        CLD                  }
  127.          $F2/               {NEXT:   REPNE                }
  128.          $AE/               {        SCASB                }
  129.          $75/$16/           {        JNE     ERROR        }
  130.          $85/$D2/           {        TEST    DX,DX        }
  131.          $74/$0C/           {        JZ      FOUND        }
  132.  
  133.          {Compare  "s" (which is at SS:SI) with the ES:DI buffer, in both cases
  134.           starting with the first byte just past the length byte of the string.
  135.           If "s" does not match what is at the DI position of the buffer, reset
  136.           the registers to the values they had just prior  to  the  comparison,
  137.           and look again for the next occurrence of the length byte.}
  138.          $51/               {        PUSH    CX           }
  139.          $57/               {        PUSH    DI           }
  140.          $56/               {        PUSH    SI           }
  141.          $89/$D1/           {        MOV     CX,DX        }
  142.          $F3/               {        REPE                 }
  143.          $A6/               {        CMPSB                }
  144.          $5E/               {        POP     SI           }
  145.          $5F/               {        POP     DI           }
  146.          $59/               {        POP     CX           }
  147.          $75/$EC/           {        JNE     NEXT         }
  148.  
  149.          {String  found in buffer.  Set AX to the offset, within buffer, of the
  150.           first byte of the string (the length byte), assuming that  the  first
  151.           byte of the buffer is at offset 1.}
  152.          $89/$F8/           {FOUND:  MOV     AX,DI        }
  153.          $29/$D8/           {        SUB     AX,BX        }
  154.          $EB/$02/           {        JMP     SHORT RETURN }
  155.  
  156.          {An "error" condition.  Return zero.}
  157.          $31/$C0/           {ERROR:  XOR     AX,AX        }
  158.          $1F/               {RETURN: POP     DS           }
  159.          $8B/$E5/           {        MOV     SP,BP        }
  160.          $5D/               {        POP     BP           }
  161.          $C2/$08/$01)       {        RET     108H         }
  162. end {BlockPos};
  163.  
  164.  
  165. {Configures  the  file  "fn"  by  replacing  Configuration  Parameters  that it
  166.  contains.  The "head" parameter should be the Configuration  ID  String  typed
  167.  constant  at  the beginning of a Configuration Block, and "tail" should be the
  168.  Configuration Tail typed constant at the end of the Configuration Block.  This
  169.  routine looks for the corresponding Configuration Block in "fn," and  replaces
  170.  the  Configuration  Parameters  in  that file with the ones between "head" and
  171.  "tail" in this program.  Uses temporary heap space on the heap (which it  then
  172.  releases  before  exiting).   Aborts if there are fewer than 255 bytes of heap
  173.  space available, file "fn" cannot be opened, the Configuration Block  in  "fn"
  174.  cannot  be found, or "fn" cannot be modified due to I/O errors (e.g., the disk
  175.  is write protected).}
  176.  
  177. procedure CfgReplace (fn: FileName; var head, tail);
  178. type
  179.   buffer = array[1..$7FFF] of Byte;
  180. var
  181.   data: array[0..256] of Byte absolute head;
  182.   source: string[255] absolute head;
  183.   bufPtr: ^buffer;
  184.   len, searchLen: Byte;
  185.   bufLen, inBuf, i, actual, amount, result: Integer;
  186.   pos: Real;
  187.   f: File;
  188. begin
  189.   len := Length(source);
  190.   searchLen := Pred(len);
  191.   bufLen := MaxAvail;
  192.   if (bufLen < 0) or (bufLen > (SizeOf(buffer) shr 4)) then
  193.     bufLen := SizeOf(buffer)
  194.   else bufLen := bufLen shl 4;
  195.   if bufLen < len then
  196.     CfgAbort('CfgReplace: Not enough available heap space');
  197.   GetMem(bufPtr, bufLen);
  198.   Assign(f, fn);
  199.   {$I-} Reset(f, 1); {$I+}
  200.   if IOresult <> 0 then CfgAbort('CfgReplace: Unable to open "' + fn + '"');
  201.   BlockRead(f, bufPtr^, bufLen, actual);
  202.   pos := actual;
  203.   i := BlockPos(bufPtr^, actual, source);
  204.   while (i = 0) and (actual >= len) do
  205.     begin
  206.       Move(bufPtr^[Succ(actual - searchLen)], bufPtr^[1], searchLen);
  207.       BlockRead(f, bufPtr^[len], bufLen - searchLen, actual);
  208.       pos := pos + actual;
  209.       actual := actual + searchLen;
  210.       i := BlockPos(bufPtr^, actual, source)
  211.     end;
  212.   FreeMem(bufPtr, bufLen);
  213.   if i <> 0 then LongSeek(f, pos + i + searchLen - actual)
  214.   else CfgAbort('CfgReplace: Cannot find header string in file');
  215.   amount := Ofs(tail) - Ofs(data[Succ(len)]);
  216.   writeln('Writing ', amount, ' bytes');
  217.   BlockWrite(f, data[Succ(len)], amount, result);
  218.   if result <> amount then CfgAbort('CfgReplace: Unable to write data');
  219.   Close(f)
  220. end {CfgReplace};
  221.  
  222.  
  223. {Performs  the  same  operation  as  CfgReplace,  using  the  file  name of the
  224.  currently running program.  I.e., this routine allows a program to  "clone"  a
  225.  new  copy  of  itself  with  different Configuration Parameters.  This routine
  226.  finds the name of the currently-running program by looking at the end  of  the
  227.  environment  block  in  the  PSP  of this program.  Since this feature is only
  228.  available in DOS 3.0 and higher, this routine will only work with DOS 3.0  and
  229.  up.  Returns "true" iff DOS 3.0 or higher is running.  If "false" is returned,
  230.  the  cloning  was  unsuccessful because an earlier DOS is being used.  In this
  231.  case, use other means to obtain the name of the current program, and then  use
  232.  CfgReplace  instead  of  this  one,  passing  the  name  of  this  program  to
  233.  CfgReplace.}
  234.  
  235. function CfgClone (var head, tail): Boolean;
  236. type
  237.   environment = array[0..$7FFF] of Char;
  238. var
  239.   envPtr: ^environment;
  240.   regPack: record
  241.              case Integer of
  242.                1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
  243.                2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
  244.            end;
  245.   i, j: Integer;
  246.   fn: FileName;
  247. begin
  248.   CfgClone := false;
  249.   regPack.AH := $30; {Get DOS Version Number}
  250.   MsDos(regPack);
  251.   if regPack.AL < 3 then exit;
  252.   regPack.AH := $62; {Get PSP Address}
  253.   MsDos(regPack);
  254.   envPtr := Ptr(MemW[regPack.BX:$002C], 0);
  255.   i := 0;
  256.   while (envPtr^[i] <> #0) or (envPtr^[Succ(i)] <> #0) do i := Succ(i);
  257.   i := i + 4;
  258.   j := 0;
  259.   while envPtr^[i] <> #0 do
  260.     begin
  261.       j := Succ(j);
  262.       fn[j] := envPtr^[i];
  263.       i := Succ(i)
  264.     end;
  265.   fn[0] := Chr(j);
  266.   CfgReplace(fn, head, tail);
  267.   CfgClone := true
  268. end {CfgClone};
  269.  
  270.  
  271. { Example program -- remove next line to enable (a Bela Lubkin trick) }
  272. (*
  273.  
  274. {Sample Configuration Block}
  275. const
  276.   head: string[38] = 'SAMPLE CONFIGURATION PARAMETERS FOLLOW';
  277.  
  278.   {Configuration constants to be modified:}
  279.   cfgInt: Integer =     0;
  280.   cfgReal: Real =     0.0;
  281.   cfgStr: string[60] = '';
  282.  
  283.   tail: Byte = 0;
  284.  
  285.  
  286. var
  287.   fn: FileName;
  288. begin
  289.   writeln('To see the effects of running this program, try running it again.');
  290.   write('Integer:   old = ', cfgInt, ';       new = ');
  291.   readln(cfgInt);
  292.   write('Real:      old = ', cfgReal:0:5, '; new = ');
  293.   readln(cfgReal);
  294.   writeln('String:    old = "', cfgStr, '";');
  295.   write('           new =  ');
  296.   readln(cfgStr);
  297.   if not CfgClone(head, tail) then
  298.     begin
  299.       write('Please type the name of this executable program: ');
  300.       readln(fn);
  301.       CfgReplace(fn, head, tail)
  302.     end
  303. end.
  304. *)