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