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