home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / CHNLOD.ZIP / CHNLOD.PAS < prev   
Encoding:
Pascal/Delphi Source File  |  1987-02-15  |  25.6 KB  |  729 lines

  1. {**************************************************************************
  2. *..............................  CHNLOD  .................................*
  3. *                                                                         *
  4. *  A memory resident program that automatically loads any Turbo Pascal    *
  5. *  CHN file into memory and clones a copy of the runtime library for      *
  6. *  the program to use. This can save you a lot of disk space (12-16K      *
  7. *  bytes per program) compared to storing COM files for small utilities,  *
  8. *  and will reduce loading time whenever one of these utilities is used.  *
  9. *                                                                         *
  10. *  You may also note that this program does much of the low level         *
  11. *  interfacing needed to rewrite CED in Turbo Pascal.                     *
  12. *                                                                         *
  13. ***************************************************************************
  14. *                                                                         *
  15. *  USAGE:                                                                 *
  16. *    Install CHNLOD simply by typing CHNLOD <Enter>. It should be         *
  17. *    installed AFTER CED in order to allow use of CED synonyms in         *
  18. *    loading CHN files.                                                   *
  19. *                                                                         *
  20. *    Precede the name of the chain file with the "chainchar" defined      *
  21. *    below. By default, the chainchar is !. Thus if you have a chain      *
  22. *    file named MYPROG.CHN, you can call it from DOS with the following   *
  23. *    command line:                                                        *
  24. *                                                                         *
  25. *         !MYPROG  <Enter>                                                *
  26. *                                                                         *
  27. *    Command line parameters will be passed on to the program as usual.   *
  28. *    If the CHN file is not found in the current directory, the PATH will *
  29. *    be searched to find it. The program name may be preceded by a drive  *
  30. *    and/or path name, for example as follows:                            *
  31. *                                                                         *
  32. *         !B:\BIN\MYPROG  CommandLineParameters  Redirection              *
  33. *                                                                         *
  34. ***************************************************************************
  35. *                                                                         *
  36. *  RESTRICTIONS:                                                          *
  37. *    1) The FCBs in the program segment prefix of the CHN program         *
  38. *       are not initialized.                                              *
  39. *    2) The PATH that is searched for the file is the PATH that was       *
  40. *       in effect when LODCHN was made resident. PATH changes after       *
  41. *       this will not be available. Similarly, the environment passed     *
  42. *       to the CHN program will be a copy of the resident program's       *
  43. *       environment.  The calling program name as provided by DOS 3.X     *
  44. *       will name LODCHN.COM and not the name of the chain file.          *
  45. *    3) The technique used will not work in a BATCH file, but only from   *
  46. *       the DOS command line (similar to CED).                            *
  47. *    4) If the chainchar ! is entered in DOS DEBUG or EDLIN or any other  *
  48. *       program which reads via int 21 function 0AH, LODCHN will stupidly *
  49. *       try to load the "named" CHN file.                                 *
  50. *    5) LODCHN will work in concert with CED synonyms as long as          *
  51. *       LODCHN is loaded into memory AFTER CED is loaded. Thus, you       *
  52. *       could set up synonyms including the chainchar.                    *
  53. *    6) LODCHN must be compiled with the same version of Turbo Pascal     *
  54. *       that your CHN programs were compiled with. Otherwise, the cloned  *
  55. *       runtime library is incompatible with the program, and who knows   *
  56. *       what will happen.                                                 *
  57. *    7) Input and Output redirection are supported only in a simple       *
  58. *       fashion. Only the < and > operators are handled. Not handled      *
  59. *       are | and >>. See OpenStdIO if you want to add more.              *
  60. *    8) The memory resident program eats about 22K bytes.                 *
  61. *                                                                         *
  62. ***************************************************************************
  63. *                                                                         *
  64. *  Copyright (C) 1986, Kim Kokkonen, TurboPower Software.                 *
  65. *  Released to the public domain for personal, non-commercial use only.   *
  66. *  Version 1.0 - 2/10/86                                                  *
  67. *                                                                         *
  68. ***************************************************************************
  69. *                                                                         *
  70. *  Requires Turbo Pascal Version 3 to compile.                            *
  71. *  Set Min/Max Heap to $140/$140 and compile to a COM file before running.*
  72. *  Should run on any PC/MSDOS system using DOS 2.X or later.              *
  73. *  Developed and tested on a Compaq Deskpro 286 running Compaq DOS 3.0.   *
  74. *                                                                         *
  75. **************************************************************************}
  76.  
  77. PROGRAM ChainLoader;
  78.   {-memory-resident loader for Turbo Pascal CHN files}
  79.  
  80. CONST
  81.   version = '1.00';
  82.  
  83.   {character which denotes that a chain file is to be loaded}
  84.   chainchar = '!';
  85.  
  86. TYPE
  87.   address =
  88.   RECORD
  89.     offset, segment : Integer;
  90.   END;
  91.  
  92.   registers =
  93.   RECORD
  94.     CASE Integer OF
  95.       1 : (AX, BX, CX, DX, BP, SI, DI, DS, ES, FLags : Integer);
  96.       2 : (AL, AH, BL, BH, CL, CH, DL, DH : Byte);
  97.   END;
  98.  
  99.   LongString = STRING[255];
  100.  
  101.   {mask for keyboard buffer returned by DOS function $0A}
  102.   KeyBuffer =
  103.   RECORD
  104.     maxlen : Byte;
  105.     keys : LongString;
  106.   END;
  107.   KeyBufferPtr = ^KeyBuffer;
  108.  
  109.   {mask for program segment prefix}
  110.   PSPblock =
  111.   RECORD
  112.     int20 : Integer;
  113.     topofmem : Integer;
  114.     reserved : Byte;
  115.     funcdispatch : ARRAY[1..5] OF Byte;
  116.     termofs : Integer;
  117.     termseg : Integer;
  118.     breakofs : Integer;
  119.     breakseg : Integer;
  120.     errorofs : Integer;
  121.     errorseg : Integer;
  122.     whoknows1 : ARRAY[$16..$2B] OF Byte;
  123.     envseg : Integer;
  124.     whoknows2 : ARRAY[$2E..$7F] OF Byte;
  125.     comtail : LongString;
  126.   END;
  127.   PSPblockPtr = ^PSPblock;
  128.  
  129. CONST
  130.   {code segment variables initialized by the program}
  131.   thisDS : Integer = 0;
  132.   thisSS : Integer = 0;
  133.   thisSP : Integer = 0;
  134.   applDS : Integer = 0;
  135.   applDX : Integer = 0;
  136.   applSS : Integer = 0;
  137.   applSP : Integer = 0;
  138.   tempSP : Integer = 0;
  139.   tempBP : Integer = 0;
  140.   newSEG : Integer = 0;
  141.  
  142.   {previous value of int 21 stored here}
  143.   OldVector : address = (offset : 0; segment : 0);
  144.  
  145.   {address of new program segment prefix stored here}
  146.   newPSP : address = (offset : 0; segment : 0);
  147.  
  148. VAR
  149.   {mask for system interrupt vectors}
  150.   Vector : ARRAY[0..$FF] OF address ABSOLUTE 0 : 0;
  151.   MemReq : Integer;
  152.   Reg : registers;
  153.   MCBstart : Integer;
  154.  
  155.   PROCEDURE SetTurboEnvironment;
  156.     {-save salient features of the program for the interrupt handler}
  157.   BEGIN
  158.     {save the current memory control block as starting point for later searches}
  159.     MCBstart := CSeg-1;
  160.     {get the amount of memory required by this TSR}
  161.     MemReq := MemW[MCBstart:3];
  162.     {save DS, SS and SP for use of the interrupt handler}
  163.     thisDS := DSeg;
  164.     thisSS := SSeg;
  165.     INLINE($2E/$89/$26/thisSP); {mov CS:thisSP,SP}
  166.   END;                        {SetTurboEnvironment}
  167.  
  168.   PROCEDURE ExitStayResident(MemReq : Integer);
  169.     {-allocate memreq paragraphs and exit leaving program in memory}
  170.   BEGIN
  171.     Reg.AX := $3100;          {exit with success code}
  172.     Reg.DX := MemReq;
  173.     MsDos(Reg);
  174.   END;                        {ExitStayResident}
  175.  
  176.   PROCEDURE ExecuteInterrupt;
  177.     {-high level handler}
  178.   VAR
  179.     kPtr : KeyBufferPtr;
  180.     command, tail : LongString;
  181.     F : FILE;
  182.     PSPptr : PSPblockPtr;
  183.     FirstRunJump : Integer ABSOLUTE CSeg : $101;
  184.     RuntimeSize : Integer;
  185.     fSize : Integer;
  186.     GotParas : Integer;
  187.  
  188.     PROCEDURE WriteString(s : LongString);
  189.       {-write a string via DOS standard output service}
  190.     BEGIN
  191.       s := s+#13#10#36;
  192.       Reg.AH := $9;
  193.       Reg.DS := Seg(s);
  194.       Reg.DX := Ofs(s[1]);
  195.       MsDos(Reg);
  196.     END;                      {writestring}
  197.  
  198.     PROCEDURE ChnInComing;
  199.       {-completed CHN application returns here}
  200.       {-restore and return to handler         }
  201.     BEGIN
  202.       INLINE(
  203.         {restore Turbo's stack pointers}
  204.         $FA/                  {CLI    }
  205.         $2E/$8E/$16/thisSS/   {MOV    SS,CS:thisSS}
  206.         $2E/$8B/$26/tempSP/   {MOV    SP,CS:tempSP}
  207.         $2E/$8B/$2E/tempBP/   {MOV    BP,CS:tempBP}
  208.         $FB/                  {STI    }
  209.  
  210.         {restore Turbo's data segment}
  211.         $2E/$A1/thisDS/       {MOV    AX,CS:thisDS}
  212.         $8E/$D8               {MOV    DS,AX}
  213.         );
  214.       {return info already on stack causes proper return}
  215.  
  216.     END;                      {incoming}
  217.  
  218.     PROCEDURE ChnOutGoing;
  219.       {-save needed stuff and jump to the loaded application}
  220.     BEGIN
  221.       INLINE(
  222.         {save Turbo's stack pointers}
  223.         $2E/$89/$2E/tempBP/   {MOV    CS:tempBP,BP}
  224.         $2E/$89/$26/tempSP/   {MOV    CS:tempSP,SP}
  225.         $83/$EC/$08/          {SUB    SP,8}
  226.         $2E/$89/$26/thisSP/   {MOV    CS:thisSP,SP}
  227.  
  228.         {set up the segment registers and stack for the new program}
  229.         $2E/$A1/newSEG/       {MOV    AX,CS:newSEG}
  230.         $8E/$C0/              {MOV    ES,AX}
  231.         $8E/$D0/              {MOV    SS,AX}
  232.         $8E/$D8/              {MOV    DS,AX}
  233.         $8B/$26/$06/$00/      {MOV    SP,[0006] - gets top of new segment}
  234.         $31/$C0/              {XOR    AX,AX}
  235.         $50/                  {PUSH    AX}
  236.         $2E/$FF/$2E/newPSP    {JMP    FAR CS:newPSP}
  237.         );
  238.     END;                      {outgoing}
  239.  
  240.     FUNCTION ChainLoad(keystr : LongString;
  241.                        chainchar : Char;
  242.                        VAR command, tail : LongString) : Boolean;
  243.       {-return true if chainchar is found in a legal position in key string   }
  244.       {legal positions are always within the first space or tab delimited word}
  245.       {the chain character may precede a drive or path name in the first word }
  246.       {also return the command name and command tail                          }
  247.     VAR
  248.       i : Byte;
  249.     BEGIN
  250.       i := 0;
  251.       REPEAT
  252.         i := Succ(i);
  253.       UNTIL (i > Length(keystr)) OR (keystr[i] IN [' ', ^I, ^M]);
  254.       command := Copy(keystr, 1, Pred(i));
  255.       tail := Copy(keystr, i, 255);
  256.       i := Pos(chainchar, command);
  257.       ChainLoad := (i <> 0);
  258.       IF i <> 0 THEN
  259.         {remove the chain character from the command}
  260.         Delete(command, i, 1);
  261.     END;                      {chainload}
  262.  
  263.     FUNCTION existfile(fname : LongString; VAR F : FILE) : Boolean;
  264.       {-search for the file, return true and an opened file var if found}
  265.     CONST
  266.       MaxPathEntries = 15;
  267.     TYPE
  268.       PathString = STRING[64];
  269.       PathArray = ARRAY[1..MaxPathEntries] OF PathString;
  270.     VAR
  271.       fullname : PathString;
  272.       slashpos : Byte;
  273.       pathnum : Integer;
  274.     CONST
  275.       pathes : PathArray =
  276.       ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
  277.  
  278.       PROCEDURE GetPath;
  279.         {-access the DOS environment and return a parsed array of path strings}
  280.         {only done once the first time existfile is called}
  281.       VAR
  282.         pathCnt, envseg, envOfs : Integer;
  283.         teststring : STRING[5];
  284.         endOfEnv : Boolean;
  285.         CH : Char;
  286.       BEGIN                   {GetPath}
  287.         {get the address of the environment}
  288.         envseg := MemW[CSeg:$2C];
  289.         envOfs := 0;
  290.         {scan the environment to find the PATH entry, if any}
  291.         teststring[0] := #5;
  292.         REPEAT
  293.           Move(Mem[envseg:envOfs], teststring[1], 5);
  294.           endOfEnv := (Copy(teststring, 1, 2) = #0#0);
  295.           envOfs := Succ(envOfs);
  296.         UNTIL endOfEnv OR (teststring = 'PATH=');
  297.         IF teststring = 'PATH=' THEN BEGIN
  298.           {parse the pathnames}
  299.           envOfs := envOfs+4;
  300.           pathCnt := 1;
  301.           REPEAT
  302.             CH := Chr(Mem[envseg:envOfs]);
  303.             IF (CH <> ';') AND (CH <> #0) THEN
  304.               pathes[pathCnt] := pathes[pathCnt]+CH
  305.             ELSE BEGIN
  306.               {assure that all pathes end with \}
  307.               IF pathes[pathCnt][Length(pathes[pathCnt])] <> '\' THEN
  308.                 pathes[pathCnt] := pathes[pathCnt]+'\';
  309.               pathCnt := Succ(pathCnt);
  310.             END;
  311.             envOfs := Succ(envOfs);
  312.           UNTIL (CH = #0) OR (pathCnt > MaxPathEntries);
  313.         END;
  314.       END;                    {GetPath}
  315.  
  316.     BEGIN                     {ExistFile}
  317.       {assume success}
  318.       existfile := True;
  319.       {see if the file exists as named}
  320.       Assign(F, fname);
  321.       {$I-} Reset(F, 1) {$I+} ;
  322.       IF IOResult = 0 THEN Exit;
  323.  
  324.       {see if fname is already a pathname or includes an explicit drive}
  325.       slashpos := Pos('\', fname)+Pos(':', fname);
  326.       IF slashpos <> 0 THEN BEGIN
  327.         {cannot prepend path names, we have failed}
  328.         existfile := False;
  329.         Exit;
  330.       END;
  331.  
  332.       {try the DOS environment PATH}
  333.       IF pathes[1] = '' THEN GetPath;
  334.  
  335.       {try all the pathes in order}
  336.       pathnum := 1;
  337.       WHILE pathes[pathnum] <> '' DO BEGIN
  338.         fullname := pathes[pathnum]+fname;
  339.         Assign(F, fullname);
  340.         {$I-} Reset(F, 1) {$I+} ;
  341.         IF IOResult = 0 THEN Exit;
  342.         pathnum := Succ(pathnum);
  343.       END;
  344.  
  345.       {we didn't find file on PATH either}
  346.       existfile := False;
  347.  
  348.     END;                      {ExistFile}
  349.  
  350.     FUNCTION getNewSeg : Integer;
  351.       {-return the next free segment of memory                    }
  352.       {apparently we can't use the standard DOS Allocate function }
  353.       {  when calling from within COMMAND.COM as occurs here.     }
  354.     VAR
  355.       mcb : Integer;
  356.       id : Byte;
  357.     BEGIN
  358.       {start at the block assigned to the TSR}
  359.       mcb := MCBstart;
  360.       id := Mem[mcb:0];
  361.       WHILE (id = $4D) OR (id = $5A) DO BEGIN
  362.         getNewSeg := Succ(mcb);
  363.         mcb := Succ(mcb+MemW[mcb:3]);
  364.         id := Mem[mcb:0];
  365.       END;
  366.     END;                      {getnewseg}
  367.  
  368.     PROCEDURE OpenStdIO(VAR tail : LongString);
  369.       {-determine whether command tail specifies redirection       }
  370.       {open the standard handles 0 and 1 as needed                 }
  371.       {They will be passed on to the application that is started up}
  372.     VAR
  373.       iname, oname : LongString;
  374.  
  375.       FUNCTION GetName(rchar : Char; VAR tail : LongString) : LongString;
  376.         {-parse the tail for a redirection file name}
  377.       VAR
  378.         ipos, spos, i : Byte;
  379.         tname : LongString;
  380.       BEGIN
  381.         tname := '';
  382.         ipos := Pos(rchar, tail);
  383.         IF ipos <> 0 THEN BEGIN
  384.  
  385.           {find next non-blank character}
  386.           i := ipos;
  387.           REPEAT
  388.             i := Succ(i);
  389.           UNTIL (i > Length(tail)) OR NOT(tail[i] IN [' ', ^I]);
  390.           IF i > Length(tail) THEN BEGIN
  391.             WriteString('illegal redirection');
  392.             Exit;
  393.           END;
  394.  
  395.           {find next blank character}
  396.           spos := i;
  397.           REPEAT
  398.             i := Succ(i);
  399.           UNTIL (i > Length(tail)) OR (tail[i] IN [' ', ^I]);
  400.  
  401.           {copy out the device name and delete it from the command tail}
  402.           tname := Copy(tail, spos, i-spos);
  403.           Delete(tail, ipos, i-ipos);
  404.  
  405.         END;
  406.         GetName := tname;
  407.       END;                    {getname}
  408.  
  409.       PROCEDURE OpenFile(handle : Integer; name : LongString);
  410.         {-open the redirected file and dup it onto the selected handle}
  411.       VAR
  412.         thandle : Integer;
  413.       BEGIN
  414.         {open the file in appropriate mode}
  415.         name := name+#00;
  416.         Reg.DS := Seg(name);
  417.         Reg.DX := Ofs(name[1]);
  418.         CASE handle OF
  419.           0 : Reg.AX := $3D00;
  420.           1 : BEGIN
  421.                 Reg.AH := $3C;
  422.                 Reg.CX := 0;
  423.               END;
  424.         END;
  425.         MsDos(Reg);
  426.         IF Odd(Reg.FLags) THEN BEGIN
  427.           WriteString('illegal redirection');
  428.           Exit;
  429.         END;
  430.         {now DUP its handle onto the appropriate device}
  431.         thandle := Reg.AX;
  432.         Reg.BX := thandle;
  433.         Reg.CX := handle;
  434.         Reg.AH := $46;
  435.         MsDos(Reg);
  436.         IF Odd(Reg.FLags) THEN BEGIN
  437.           WriteString('illegal redirection');
  438.           Exit;
  439.         END;
  440.         {now close the original handle}
  441.         Reg.AH := $3E;
  442.         Reg.BX := thandle;
  443.         MsDos(Reg);
  444.         IF Odd(Reg.FLags) THEN BEGIN
  445.           WriteString('illegal redirection');
  446.           Exit;
  447.         END;
  448.       END;                    {openfile}
  449.  
  450.     BEGIN
  451.       {WARNING: only simple < and > supported here}
  452.       {input}
  453.       iname := GetName('<', tail);
  454.       IF iname <> '' THEN OpenFile(0, iname);
  455.       {output}
  456.       oname := GetName('>', tail);
  457.       IF oname <> '' THEN OpenFile(1, oname);
  458.     END;                      {openstdio}
  459.  
  460.     PROCEDURE DOSgetMem(paras : Integer; VAR newSEG, GotParas : Integer);
  461.       {-allocate requested memory and return the segment and number of paras}
  462.     BEGIN
  463.       Reg.AH := $48;
  464.       Reg.BX := paras;
  465.       MsDos(Reg);
  466.       IF Odd(Reg.FLags) THEN BEGIN
  467.         {do it again with the available paragraphs}
  468.         Reg.AH := $48;
  469.         MsDos(Reg);
  470.         GotParas := Reg.BX;
  471.       END ELSE
  472.         GotParas := paras;
  473.       newSEG := Reg.AX;
  474.     END;                      {DOSgetmem}
  475.  
  476.     PROCEDURE DOSreleaseMem(newSEG : Integer);
  477.       {-release DOS memory}
  478.     BEGIN
  479.       Reg.AH := $49;
  480.       Reg.ES := newSEG;
  481.       MsDos(Reg);
  482.     END;                      {dosreleasemem}
  483.  
  484.     PROCEDURE DOSsetTermAddress(segment, offset : Integer);
  485.       {-set the new termination address}
  486.     BEGIN
  487.       Reg.AX := $2522;
  488.       Reg.DS := segment;
  489.       Reg.DX := offset;
  490.       MsDos(Reg);
  491.     END;                      {DOSsettermaddress}
  492.  
  493.     PROCEDURE DOSmakePSP(newSEG : Integer);
  494.       {-have DOS set up a new PSP}
  495.     BEGIN
  496.       Reg.AH := $26;
  497.       Reg.DX := newSEG;
  498.       MsDos(Reg);
  499.     END;                      {DOSmakePSP}
  500.  
  501.     FUNCTION cardinal(i : Integer) : Real;
  502.       {-return a real 0..65535}
  503.     BEGIN
  504.       cardinal := 256.0*Hi(i)+Lo(i);
  505.     END;                      {cardinal}
  506.  
  507.     PROCEDURE NullKeyBuffer(VAR keys : LongString);
  508.       {-return an empty key string to fool DOS}
  509.     BEGIN
  510.       keys[0] := Chr(0);
  511.       keys[1] := ^M;
  512.     END;                      {nullkeybuffer}
  513.  
  514.   BEGIN
  515.     {point a structure to the returned keyboard buffer}
  516.     kPtr := Ptr(applDS, applDX);
  517.  
  518.     {see if anything there}
  519.     IF (Length(kPtr^.keys) = 0) THEN Exit;
  520.  
  521.     {see if special chain loader character is present in command name}
  522.     IF NOT(ChainLoad(kPtr^.keys, chainchar, command, tail)) THEN Exit;
  523.  
  524.     {display the <CR> at end of line}
  525.     WriteString('');
  526.  
  527.     {we have a chain file to load - find the file}
  528.     IF NOT(existfile(command+'.CHN', F)) THEN BEGIN
  529.       WriteString('CHN file not found');
  530.       NullKeyBuffer(kPtr^.keys);
  531.       Exit;
  532.     END;
  533.     fSize := FileSize(F);
  534.  
  535.     {open I/O devices, if any}
  536.     OpenStdIO(tail);
  537.  
  538.     {find memory for the CHN program}
  539.     newSEG := getNewSeg;
  540.  
  541.     {set the new termination address}
  542.     DOSsetTermAddress(CSeg, Ofs(ChnInComing));
  543.  
  544.     {create a PSP for it}
  545.     DOSmakePSP(newSEG);
  546.  
  547.     {tell DOS about memory we want}
  548.     DOSreleaseMem(newSEG);
  549.     DOSgetMem($A000, newSEG, GotParas);
  550.  
  551.     {check for adequate memory to load}
  552.     RuntimeSize := FirstRunJump+3;
  553.     IF cardinal(fSize+RuntimeSize) > 16.0*cardinal(GotParas) THEN BEGIN
  554.       Close(F);
  555.       DOSreleaseMem(newSEG);
  556.       WriteString('insufficient memory - program aborted');
  557.       NullKeyBuffer(kPtr^.keys);
  558.       Exit;
  559.     END;
  560.  
  561.     {initialize the rest of the program segment prefix}
  562.     WITH newPSP DO BEGIN
  563.       segment := newSEG;
  564.       offset := $100;
  565.     END;
  566.     PSPptr := Ptr(newSEG, 0);
  567.     WITH PSPptr^ DO BEGIN
  568.       {pass in command tail}
  569.       comtail := tail+^M;
  570.       {set topofmem to above the allocated block}
  571.       topofmem := newSEG+GotParas;
  572.       {point environment to our copy - WARNING: may be out of date}
  573.       envseg := MemW[CSeg:$2C];
  574.     END;
  575.  
  576.     {copy the runtime library above the PSP}
  577.     Move(Mem[CSeg:$100], Mem[newSEG:$100], RuntimeSize);
  578.  
  579.     {read the file above the library}
  580.     BlockRead(F, Mem[newSEG:RuntimeSize+$100], fSize);
  581.  
  582.     Close(F);
  583.  
  584.     {jump to the new program via Outgoing procedure}
  585.     ChnOutGoing;
  586.     {get back to here via ChnIncoming procedure - STACK MAGIC}
  587.  
  588.     {release the memory}
  589.     DOSreleaseMem(newSEG);
  590.  
  591.     {null out the key buffer}
  592.     {so DOS command processor ignores what just happened}
  593.     NullKeyBuffer(kPtr^.keys);
  594.  
  595.   END;                        {executeinterrupt}
  596.  
  597.   PROCEDURE InterruptHandler;
  598.     {-low level handler                                    }
  599.     {save and restore machine state of interrupted program }
  600.     {transfer control to Pascal interrupt handler          }
  601.   CONST
  602.     IDstring : LongString = 'TURBO CHN FILE LOADER';
  603.   BEGIN
  604.  
  605.     INLINE(
  606.  
  607.       {compiler inserts the following}
  608.       {note pushes are onto DOS stack}
  609.       {PUSH BP}
  610.       {MOV BP,SP}
  611.       {PUSH BP}
  612.  
  613.       {see if we care about this call}
  614.       $9C/                    {PUSHF    }
  615.       $80/$FC/$0A/            {CMP    AH,0A}
  616.       $74/$09/                {JZ    YesWeCare}
  617.  
  618.       {pass control on over to DOS}
  619.       $9D/                    {POPF    }
  620.       $8B/$E5/                {MOV    SP,BP}
  621.       $5D/                    {POP    BP}
  622.       $2E/$FF/$2E/OldVector/  {JMP    FAR CS:oldvector}
  623.  
  624.       {YesWeCare:}
  625.       {let DOS (or CED) handle the keyboard function, but return control here}
  626.       $9C/                    {PUSHF    }
  627.       $2E/$FF/$1E/OldVector/  {CALL    FAR CS:OldVector}
  628.  
  629.       {switch stacks}
  630.       $FA/                    {CLI    }
  631.       $2E/$8C/$16/applSS/     {MOV    CS:applSS,SS}
  632.       $2E/$89/$26/applSP/     {MOV    CS:applSP,SP}
  633.       $2E/$8E/$16/thisSS/     {MOV    SS,CS:thisSS}
  634.       $2E/$8B/$26/thisSP/     {MOV    SP,CS:thisSP}
  635.  
  636.       {save all the registers}
  637.       $50/                    {PUSH    AX}
  638.       $53/                    {PUSH    BX}
  639.       $51/                    {PUSH    CX}
  640.       $52/                    {PUSH    DX}
  641.       $56/                    {PUSH    SI}
  642.       $57/                    {PUSH    DI}
  643.       $1E/                    {PUSH    DS}
  644.       $06/                    {PUSH    ES}
  645.  
  646.       {save the parameters to the function call in Turbo variables}
  647.       $8C/$D8/                {MOV    AX,DS}
  648.       $2E/$A3/applDS/         {MOV    CS:applDS,AX}
  649.       $2E/$89/$16/applDX/     {MOV    CS:applDX,DX}
  650.  
  651.       {switch to Turbo's data segment}
  652.       $2E/$A1/thisDS/         {MOV    AX,CS:thisDS}
  653.       $8E/$D8/                {MOV    DS,AX}
  654.       $FB                     {STI    }
  655.       );
  656.  
  657.     {call Turbo routines here}
  658.     ExecuteInterrupt;
  659.  
  660.     INLINE(
  661.       {restore the registers}
  662.       $07/                    {POP    ES}
  663.       $1F/                    {POP    DS}
  664.       $5F/                    {POP    DI}
  665.       $5E/                    {POP    SI}
  666.       $5A/                    {POP    DX}
  667.       $59/                    {POP    CX}
  668.       $5B/                    {POP    BX}
  669.       $58/                    {POP    AX}
  670.  
  671.       {switch stacks again}
  672.       $FA/                    {CLI    }
  673.       $2E/$89/$26/thisSP/     {MOV    CS:thisSP,SP}
  674.       $2E/$8E/$16/applSS/     {MOV    SS,CS:applSS}
  675.       $2E/$8B/$26/applSP/     {MOV    SP,CS:applSP}
  676.  
  677.       {restore application's stack from first pushes}
  678.       $9D/                    {POPF    }
  679.       $5D/                    {POP    BP}
  680.       $8B/$E5/                {MOV    SP,BP}
  681.       $5D/                    {POP    BP}
  682.  
  683.       {and return}
  684.       $FB/                    {STI    }
  685.       $CF                     {IRET    }
  686.       );
  687.  
  688.   END;                        {interrupthandler}
  689.  
  690.   PROCEDURE CheckForPreviousInstallation;
  691.     {-if previously installed then halt}
  692.   CONST
  693.     IDstring : LongString = 'TURBO CHN FILE LOADER';
  694.   VAR
  695.     tstring : LongString;
  696.   BEGIN
  697.     Move(Mem[Vector[$21].segment:(Vector[$21].offset+7)], tstring, Succ(Length(IDstring)));
  698.     IF tstring = IDstring THEN BEGIN
  699.       WriteLn('CHN Loader previously installed...');
  700.       Halt(1);
  701.     END;
  702.   END;                        {checkforpreviousinstallation}
  703.  
  704. BEGIN
  705.   {install resident CHN loader}
  706.   CheckForPreviousInstallation;
  707.  
  708.   {save information needed by the interrupt handler}
  709.   SetTurboEnvironment;
  710.  
  711.   {save old vector $21}
  712.   OldVector.offset := Vector[$21].offset;
  713.   OldVector.segment := Vector[$21].segment;
  714.  
  715.   WriteLn('Installing Turbo Resident CHN file loader');
  716.   WriteLn('....by TurboPower Software - Version ', version);
  717.   WriteLn('Precede CHN file name with ', chainchar, ' to invoke loader');
  718.  
  719.   {store the new interrupt handler}
  720.   INLINE($FA);
  721.   Vector[$21].offset := Ofs(InterruptHandler);
  722.   Vector[$21].segment := CSeg;
  723.   INLINE($FB);
  724.  
  725.   {terminate and stay resident}
  726.   ExitStayResident(MemReq);
  727.  
  728. END.
  729.