[comp.lang.pascal] Self Patching TP Code

qseclrb@prism.gatech.EDU (BOB BAGGERMAN) (06/12/90)

Several people asked about Turbo Pascal code which will go in and modify
its own .EXE file to install options and things of that sort.  Below is
an example program I got off of CompuServe.  Note that this procedure will
probably only work for TP 4.0.  I think in TP 5.0 and 5.5 they moved the 
location of constants.  If so, maybe someone could bone up on the later
versions and post an AutoInst which works for TP 5.0 and above.

Regards,   Bob Baggerman    rwb@csdvax.gatech.edu

p.s. I have some great toolboxes for Turbo Pascal available via anonymous
     ftp from csdvax.gatech.edu.  Help yourself!

-------------------------------------------------------------------------

program AutoInst3;

  { ==================================================================

                            Program: AutoInst v3.0
                             Author: David Dubois
                                     Zelkop Software
                                     Halifax, Nova Scotia
               CompuServe User I.D.: 71401,747
                  Date last revised: 1988.05.05

    ==================================================================

    I hereby dedicate this knowledge to the public domain. I would
    appreciate, though, if you mentioned my name.

    This program demonstrates how to write a program which will change
    the value of a typed constant in its own .EXE file. When the
    program is run again, the data will be initialized to the new
    value. No external files are necessary.

    Unlike version 1 of this program, this code will work in a unit.
    Thanks to those who suggested using PrefixSeg.

    Version 3 has corrected a bug in found by some freindly users of
    Version 2. The "seek" computation (see below) was changed to
    longint arithmetic. Thanks to those who notified me of the problem.

    The code shown here is designed for Turbo Pascal version 4.0.
    There is a simpler method available for Turbo 3.0.

    For this example, I have written a program that writes a certain
    string a certain number of times. It then prompts the user for a
    new string and a new number. The .EXE file is updated to reflect
    the values entered by the user. The next time the program is run,
    the new values are used.

    USES

    This can be used to write a program that can install itself.
    Examples of the usefulness of this technique would be:

    A program that allows the user to change default display colors.

    A program that keeps track of a password that the user can change.

    HOW TO USE THIS TECHNIQUE

    In your own program just copy the ReInstall procedure listed here.
    Replace InstallBlock with the name of the typed constant you wish
    to change, and replace the file name with the name of your
    program. It's that easy.

    HOW IT WORKS

    You don't have to understand all the details in order to use this
    technique, but here they are.

    The data to be changed must be stored in a TurboPascal typed
    constant. In all effect, a typed constant is actually a pre-
    initialized variable. It is always stored in the program's Data
    Segment. The data can be of any type.

    First, the procedure finds the .EXE file. In this example, the
    file must be in the current directory, and the user cannot have
    changed the name of the file.

    The untyped file is opened with a record size of 1. This allows us
    to read or write a string of bytes using BlockRead and BlockWrite.

    As documented in the DOS Technical Reference, the size of the .EXE
    header, in paragraphs (a paragraph is 16 bytes), is stored as a
    two-byte word at position 8 of the file. This is read into the
    variable HeaderSize.

    The next step is to find the position of the typed constant in the
    .EXE file. This requires an understanding of the Turbo Pascal 4.0
    memory map, documented on the first and second pages of the Inside
    Turbo Pascal chapter. (That's chapter 26, pages 335 and 336 in my
    manual.)

    First, find the address in memory where the typed constant is
    stored. This can be done in Turbo Pascal by using the Seg and Ofs
    functions. Next find the segment of the PSP (program segment
    prefix). This should always be the value returned by PrefixSeg.
    That will mark the beginning of the program in memory. The
    position of the typed constant in the .EXE image should be the
    number of bytes between these two places in memory. But ...

    But, two corrections must be made. First, the PSP is not stored in
    the .EXE file. As mentioned on page 335, the PSP is always 256
    bytes. We must subtract that out. Secondly, there is the .EXE file
    header. The size of this has already been read in and must be
    added in to our calculations.

    You may be wondering about the purpose of longint. One of the values
    had to be typecast into a longint so that the final result will be a
    longint. This is in case your typed constant appears more than 65535
    bytes into the file.

    Once the position has been determined, the data stored in the
    typed constant is written in one fell swoop using a BlockWrite.
    This replaces the original data, so that the next time the program
    is run, the new values will used.

    ERROR CHECKING

    For the sake of simplicity, error checking has been left out of
    this example.

    In a real-world application, I would imagine that you would want
    to check that the file has actually been found. If the user ran
    the program from a different directory, or if the program name had
    been changed, the program will fail. In this case, you may want to
    prompt the user for the path where the program can be found.
    (Perhaps you might want to store this information as well.) A
    clever program may be able to search for copies of itself on disk,
    but that's a little dangerous.

    Another check could be added. Before writing to disk, you could
    read the value of the data currently stored on the file to make
    sure it matches the original values. This would guarantee that the
    file had not been altered, or that the wrong version of your
    program hadn't been found.

    LIMITATIONS

    Somehow, the program must be able to find the .EXE file. The
    program cannot be run from the integrated enviroment with the
    Memory option, since no .EXE file is generated.

    You cannot use MicroSoft's EXEPACK on the .EXE file, or any other
    packing method I know of. This may change the position, or even
    the size of the typed constant in the file image.

    NOTES

    Since typed constants are always stored in the data segment, the
    function call to Seg ( InstallBlock ) can be replaced with DSeg. I
    prefer using Seg since it is more descriptive.

    One might think that Cseg can used as an alternative to using
    PrefixSeg and subtracting 256. This will work only if the code
    resides in the main program. If, on the other hand, the code is
    used in a unit, PrefixSeg must be used as described here. You
    might as well use PrefixSeg and save yourself some headaches.

    If you have any comments or questions I would be glad to hear
    them. If you're on CompuServe, you can EasyPlex a letter to
    71401,747. Or leave a message on the Borland Programmer's A
    Forum (GO BPROGA). Or, you can write to

                         Zelkop Software
                         P.O. Box 5177
                         Armdale, N.S.
                         Canada
                         B3L 4M7

    ==================================================================}

type
  InstallBlockType = record
                       TheNumber : integer;
                       TheString : string;
                     end;

const
  InstallBlock : InstallBlockType

               = ( TheNumber : 3;
                   TheString : 'Greetings from Zelkop Software' );

  procedure ReInstall;
  const
    FileName = 'AutoInst.Exe';
  var
    ExeFile    : file;
    HeaderSize : word;
  begin
    assign    ( ExeFile, FileName );
    reset     ( ExeFile, 1 );

    seek      ( ExeFile, 8 );
    blockread ( ExeFile, HeaderSize, sizeof ( HeaderSize ) );

    seek      ( ExeFile,   16 * (   longint ( seg ( InstallBlock ) )
                                  - PrefixSeg
                                  + HeaderSize          )
                         + ofs ( InstallBlock )
                         - 256                           );

    blockwrite ( ExeFile, InstallBlock, sizeof ( InstallBlock ) );

    close      ( ExeFile );
  end;

var
  I : integer;
begin
  with InstallBlock do
    begin

      { write out old data }

      for I := 1 to TheNumber do
        writeln ( TheString );

      { read in new data }

      write  ( 'Enter TheNumber: ' );
      readln ( TheNumber );
      write  ( 'Enter TheString: ' );
      readln ( TheString );
    end;

  { update .EXE file }

  ReInstall;

  writeln ( 'Now run this program again.' );
end.

-- 
Bob Baggerman                         !  rwb@csdvax.gatech.edu
Communications Systems Division       !  qseclrb@hydra.gatech.edu
Georgia Tech Research Institute       !  bbaggerm@gtri01.gatech.edu
Atlanta, GA  30332                    !  404-894-3525