home *** CD-ROM | disk | FTP | other *** search
/ Media Share 9 / MEDIASHARE_09.ISO / tsr / tsrsrc33.zip / MEMU.PAS < prev    next >
Pascal/Delphi Source File  |  1992-01-08  |  26KB  |  925 lines

  1. {**************************************************************************
  2. *   MEMU - utility unit for TSR Utilities.                                *
  3. *   Copyright (c) 1991 Kim Kokkonen, TurboPower Software.                 *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. *                                                                         *
  6. *   Version 3.0 9/24/91                                                   *
  7. *     first release                                                       *
  8. *   Version 3.1 11/4/91                                                   *
  9. *     update for new WATCH identification behavior                        *
  10. *     update HasEnvironment for programs that shrink env size to 0        *
  11. *   Version 3.2 11/22/91                                                  *
  12. *     add FindHiMemStart function to generalize high memory access        *
  13. *     modify FindTheBlocks for new high memory approach                   *
  14. *     add MergeHiMemBlocks procedure to merge memory blocks in hi mem     *
  15. *     add ValidPsp function to determine whether a Psp still exists       *
  16. *   Version 3.3 1/8/92                                                    *
  17. *     add NextArg function to parse command lines more flexibly           *
  18. ***************************************************************************}
  19.  
  20. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  21.  
  22. unit MemU;
  23.   {-Miscellaneous memory functions needed for TSR Utilities}
  24.  
  25. interface
  26.  
  27. const
  28.   {!!!!!! Following may change when WATCH reassembled. Check WATCH.MAP !!!!!}
  29.   ChangeVectors = $320;
  30.   OrigVectors = $720;
  31.  
  32.   {Offsets into resident copy of WATCH.COM for data storage}
  33.   WatchOfs = $80;             {Location of length of command line}
  34.   WatchOffset = $81;          {Location of start of command line}
  35.   NextChange = $104;          {Data structures within WATCH}
  36.   WatchId = 'TSR WATCHER';    {ID placed in WATCH command line}
  37.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  38.  
  39.   Version = '3.3';            {TSR Utilities version number}
  40.   MarkID  = 'MM3.3 TSR';      {Marking string for TSR MARK}
  41.   FmarkID = 'FM3.3 TSR';      {Marking string for TSR file mark}
  42.   NmarkID = 'MN3.3 TSR';      {Marking string for TSR file mark}
  43.   NetMarkID = 'MN33';         {ID at start of net mark file}
  44.  
  45.   {Offsets into resident mark copies for id strings}
  46.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  47.   FmarkOffset = $60;          {Where FmarkID is found in FMARK TSR}
  48.   NmarkOffset = $60;          {Where NmarkID is found in FMARK TSR}
  49.  
  50.   {Offsets into resident copy of MARK for data storage}
  51.   VectorOffset = $120;        {Where vector table is stored}
  52.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  53.   IntComOffset = $528;        {Where the interapps comm area is stored}
  54.   ParentOffset = $538;        {(TER) Where parent's PSP segment is stored}
  55.   ParLenOffset = $53A;        {Where parent's PSP mcb length is stored}
  56.   EMScntOffset = $53C;        {Where count of EMS active pages is stored}
  57.   EMSmapOffset = $53E;        {Where the page map is stored}
  58.  
  59. const
  60.   MaxBlocks = 256;            {Max number of DOS allocation blocks supported}
  61.  
  62.   ProtectChar = '!';          {Marks whose name begins with this will be
  63.                                released ONLY if an exact name match occurs}
  64.  
  65. const
  66.   RBR = 0; {Receiver buffer register offset}
  67.   THR = 0; {Transmitter buffer register offset}
  68.   BRL = 0; {Baud rate low}
  69.   BRH = 1; {Baud rate high}
  70.   IER = 1; {Interrupt enable register}
  71.   IIR = 2; {Interrupt identification register}
  72.   LCR = 3; {Line control register}
  73.   MCR = 4; {Modem control register}
  74.   LSR = 5; {Line status register}
  75.   MSR = 6; {Modem status register}
  76.  
  77. type
  78.   OS =
  79.     record
  80.       O, S : Word;
  81.     end;
  82.  
  83.   StringPtr = ^String;
  84.  
  85.   NameArray = array[1..8] of Char;
  86.  
  87.   McbPtr = ^Mcb;
  88.   Mcb =
  89.     record
  90.       Id : Char;
  91.       Psp : Word;
  92.       Len : Word;
  93.       Unused : array[1..3] of Byte;
  94.       Name : NameArray;
  95.     end;
  96.  
  97.   Block =
  98.   record                      {Store info about each memory block}
  99.     mcb : Word;
  100.     psp : Word;
  101.     releaseIt : Boolean;
  102.   end;
  103.  
  104.   BlockType = 0..MaxBlocks;
  105.   BlockArray = array[1..MaxBlocks] of Block;
  106.  
  107.   McbGroup =
  108.   record
  109.     Count : Word;
  110.     Mcbs : array[1..MaxBlocks] of
  111.            record
  112.              mcb : Word;
  113.              psp : Word;
  114.            end;
  115.   end;
  116.  
  117.   ChangeBlock =
  118.   record                      {Store info about each vector takeover}
  119.     VecNum : byte;
  120.     case ID : byte of
  121.       0, 1 : (VecOfs, VecSeg : Word);
  122.       2    : (SaveCode : array[1..6] of byte);
  123.       $FF  : (PspAdd : Word);
  124.   end;
  125.   {
  126.   ID is interpreted as follows:
  127.     00 = ChangeBlock holds the new pointer for vector vecnum
  128.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  129.     02 = ChangeBlock holds the code underneath the vector patch
  130.     FF = ChangeBlock holds the segment of a new PSP
  131.   }
  132.   ChangeArray = array[0..MaxChanges] of ChangeBlock;
  133.  
  134.   {Structure of a device driver header}
  135.   DeviceHeader =
  136.     record
  137.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  138.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  139.       Attributes : Word;          {Device attributes}
  140.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  141.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  142.       DeviceName : array[1..8] of Char; {Name of the device}
  143.     end;
  144.   DeviceHeaderPtr = ^DeviceHeader;
  145.   DeviceArray = array[1..256] of DeviceHeaderPtr;
  146.  
  147.   FileRec =
  148.     record
  149.       OpenCnt : Word;
  150.       OpenMode : Word;
  151.       Attribute : Byte;
  152.       Unknown1 : Word;
  153.       DCB : Pointer;
  154.       InitCluster : Word;
  155.       Time : Word;
  156.       Date : Word;
  157.       Size : LongInt;
  158.       Pos : LongInt;
  159.       BeginCluster : Word;
  160.       CurCluster : Word;
  161.       Block : Word;
  162.       Unknown2 : Byte;            {Varies with DOS version beyond here}
  163.       Name : array[0..7] of Char;
  164.       Ext : array[0..2] of Char;
  165.       Unknown3 : array[0..5] of Byte;
  166.       Owner : Word;
  167.       Unknown4 : Word;
  168.     end;
  169.  
  170.   SftRecPtr = ^SftRec;
  171.   SftRec =
  172.     record
  173.       Next : SftRecPtr;
  174.       Count : Word;
  175.       Files : array[1..255] of FileRec;
  176.     end;
  177.  
  178.   DosRec =
  179.     record
  180.       McbSeg : Word;
  181.       FirstDPB : Pointer;
  182.       FirstSFT : SftRecPtr;
  183.       ClockDriver : Pointer;
  184.       ConDriver : Pointer;
  185.       MaxBlockBytes : Word;
  186.       CachePtr : Pointer;
  187.       DriveTable : Pointer;
  188.       FcbTable : Pointer;
  189.       ProtectedFcbCount : Word;
  190.       BlockDevices : Byte;
  191.       LastDrive : Byte;
  192.       NullDevice : DeviceHeader;
  193.       JoinedDrives : Byte;           {Following valid DOS 4.0 or later}
  194.       SpecialProgOfs : Word;
  195.       IFSPtr : Pointer;
  196.       IFSList : Pointer;
  197.       BuffersX : Word;
  198.       BuffersY : Word;
  199.       BootDrive : Byte;
  200.       Unknown1 : Byte;
  201.       ExtMemSize : Word;
  202.     end;
  203.   DosRecPtr = ^DosRec;
  204.  
  205.   ComRec =  {State of the communications system}
  206.     record
  207.       Base : Word;
  208.       IERReg : Byte;
  209.       LCRReg : Byte;
  210.       MCRReg : Byte;
  211.       BRLReg : Byte;
  212.       BRHReg : Byte;
  213.     end;
  214.   ComArray = array[1..2] of ComRec;
  215.  
  216. const
  217.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  218.   DosDelimSet : set of Char = ['\', ':', #0];
  219.  
  220. var
  221.   DosVM : Byte;      {Minor DOS version number}
  222.   DosV : Byte;       {Major DOS version number}
  223.   DosVT : Word absolute DosVM; {Combined version number}
  224.   DosList : Pointer; {Pointer to DOS list of lists}
  225.   Mcb1 : McbPtr;     {First MCB in system}
  226.  
  227. function GetDosListPtr : Pointer;
  228.   {-Return address of DOS list of lists}
  229.  
  230. function GetUmbLinkStatus : Boolean;
  231.   {-Return status of DOS 5 upper memory block link}
  232.  
  233. function SetUmbLinkStatus(On : Boolean) : Word;
  234.   {-Change state of DOS 5 upper memory block link}
  235.  
  236. function DosVersion : Word;
  237.   {-Return DOS version number with high byte = major version number}
  238.  
  239. function TopOfMemSeg : Word;
  240.   {-Return segment of top of normal memory}
  241.  
  242. function FindHiMemStart : word;
  243.   {-Return segment of first mcb in high memory, 0 if none}
  244.  
  245. procedure MergeHiMemBlocks(HiMemSeg : Word);
  246.   {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  247.  
  248. function HexB(B : Byte) : String;
  249.   {-Return hex string for byte}
  250.  
  251. function HexW(W : Word) : String;
  252.   {-Return hex string for word}
  253.  
  254. function HexPtr(P : Pointer) : string;
  255.   {-Return hex string for pointer}
  256.  
  257. function StUpcase(s : String) : String;
  258.   {-Return the uppercase string}
  259.  
  260. function JustFilename(PathName : String) : String;
  261.   {-Return just the filename of a pathname}
  262.  
  263. function JustName(PathName : String) : String;
  264.   {-Return just the name (no extension, no path) of a pathname}
  265.  
  266. function Extend(S : String; Len : Byte) : String;
  267.   {-Truncate or pad S to length Len}
  268.  
  269. function SmartExtend(S : String; Len : Byte) : String;
  270.   {-Truncate or pad S to length Len; end with '...' if truncated}
  271.  
  272. function Asc2Str(Name : NameArray) : String;
  273.   {-Convert array[1..8] of char to string}
  274.  
  275. procedure StripNonAscii(var S : String);
  276.   {-Return an empty string if input contains non-ASCII characters}
  277.  
  278. function CommaIze(L : LongInt; Width : Byte) : String;
  279.   {-Convert L to a string and add commas for thousands}
  280.  
  281. function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  282.   {-Return True if M has an associated environment block}
  283.  
  284. function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  285.   {-Return True if PspSeg is a valid, existing Psp}
  286.  
  287. function NameFromEnv(M : McbPtr) : String;
  288.   {-Return M's name from its environment (already known to exist)}
  289.  
  290. function NameFromMcb(M : McbPtr) : String;
  291.   {-Return name from the Mcb (DOS 4+ only)}
  292.  
  293. function MasterCommandSeg : Word;
  294.   {-Return PSP segment of master COMMAND.COM}
  295.  
  296. function WatchPspSeg : Word;
  297.   {-Find copy of WATCH.COM in memory, returning its PSP segment or 0}
  298.  
  299. procedure FindTheBlocks(HiMemSeg : Word;
  300.                         var Blocks : BlockArray;
  301.                         var BlockMax : BlockType;
  302.                         var StartMcb : Word;
  303.                         var CommandSeg : Word);
  304.   {-Scan memory for the allocated memory blocks}
  305.  
  306. procedure StuffKey(W : Word);
  307.   {-Stuff one key into the keyboard buffer}
  308.  
  309. procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  310.   {-Stuff up to 16 keys into keyboard buffer}
  311.  
  312. function ExistFile(path : String) : Boolean;
  313.   {-Return true if file exists}
  314.  
  315. function NextArg(S : String; var SPos : Word) : String;
  316.   {-Return next argument beginning at SPos in S.
  317.     Increment SPos to point past the argument.
  318.     Arguments are delimited by white space, '-', and '/'}
  319.  
  320. procedure IntsOff;
  321.   {-Turn off CPU interrupts}
  322. inline($FA);
  323.  
  324. procedure IntsOn;
  325.   {-Turn on CPU interrupts}
  326. inline($FB);
  327.  
  328. procedure NullJump;
  329.   {-Slight delay}
  330. inline($EB/$00);
  331.  
  332.   {=======================================================================}
  333.  
  334. implementation
  335.  
  336. uses
  337.   xms;
  338.  
  339.   function GetDosListPtr : Pointer; Assembler;
  340.     {-Return address of DOS list of lists}
  341.   asm
  342.     mov     ah,$52
  343.     int     $21
  344.     mov     dx,es
  345.     mov     ax,bx
  346.   end;
  347.  
  348.   function GetUmbLinkStatus : Boolean; Assembler;
  349.     {-Return status of DOS 5 upper memory block link}
  350.   asm
  351.     mov     ax,$5802
  352.     int     $21
  353.   end;
  354.  
  355.   function SetUmbLinkStatus(On : Boolean) : Word; Assembler;
  356.     {-Change state of DOS 5 upper memory block link}
  357.   asm
  358.     mov     ax,$5803
  359.     mov     bl,On
  360.     xor     bh,bh
  361.     int     $21
  362.     jc      @1
  363.     xor     ax,ax
  364. @1:
  365.   end;
  366.  
  367.   function DosVersion : Word; Assembler;
  368.     {-Return major DOS version number}
  369.   asm
  370.     mov     ah,$30
  371.     int     $21
  372.     xchg    ah,al
  373.   end;
  374.  
  375.   function TopOfMemSeg : Word;
  376.     {-Return segment of top of memory}
  377.   var
  378.     KBRAM : Word;
  379.   begin
  380.     asm
  381.       int $12
  382.       mov KBRAM,ax
  383.     end;
  384.     TopOfMemSeg := KBRAM shl 6;
  385.   end;
  386.  
  387.   function FindHiMemStart : word;
  388.     {-Return segment of first mcb in high memory}
  389.   var
  390.     Segment : word;
  391.     Size : word;
  392.     Mseg : word;
  393.     M : mcbptr;
  394.     N : mcbptr;
  395.     Status : byte;
  396.     Done : boolean;
  397.     Invalid : boolean;
  398.   begin
  399.     {assume failure}
  400.     FindHiMemStart := 0;
  401.  
  402.     {assure XMS driver installed}
  403.     if not XmsInstalled then
  404.       Exit;
  405.  
  406.     {look for umbs}
  407.     Status := AllocateUmbMem($FFFF, Segment, Size);
  408.     case status of
  409.       $B0, $B1 : ; {UMBs are possible, but not to allocate $FFFF paragraphs}
  410.     else
  411.       Exit;        {UMBs are not possible}
  412.     end;
  413.  
  414.     {find the starting umb}
  415.     Mseg := TopOfMemSeg;
  416.     Done := False;
  417.     repeat
  418.       M := Ptr(Mseg, 0);
  419.       case M^.Id of
  420.         'M' {, 'Z'} : {There must be at least 2 mcbs in high memory}
  421.           begin
  422.             {determine whether this is a valid chain of mcbs}
  423.             N := M;
  424.             Invalid := False;
  425.             repeat
  426.               case N^.Id of
  427.                 'M' :
  428.                   if $FFFE-N^.Len >= OS(N).S then
  429.                     {next mcb won't land beyond $FFFF}
  430.                     N := Ptr(OS(N).S+N^.Len+1, 0)
  431.                   else
  432.                     Invalid := true;
  433.                 'Z' :
  434.                   begin
  435.                     {found end of chain starting at M}
  436.                     FindHiMemStart := Mseg;
  437.                     Done := True;
  438.                   end;
  439.               else
  440.                 Invalid := True;
  441.               end;
  442.             until Done or Invalid;
  443.           end;
  444.       end;
  445.       if Mseg < $FFFF then
  446.         inc(Mseg)
  447.       else
  448.         Done := True;
  449.     until Done;
  450.   end;
  451.  
  452.   procedure MergeHiMemBlocks(HiMemSeg : Word);
  453.     {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  454.   var
  455.     M : McbPtr;
  456.     N : McbPtr;
  457.     Done : Boolean;
  458.   begin
  459.     if HiMemSeg = 0 then
  460.       Exit;
  461.     M := Ptr(HiMemSeg, 0);
  462.     Done := False;
  463.     repeat
  464.       Done := (M^.Id = 'Z');
  465.       if not Done then begin
  466.         N := Ptr(OS(M).S+M^.Len+1, 0);
  467.         if (M^.Psp = 0) and (N^.Psp = 0) then begin
  468.           {This block and the next are both free}
  469.           inc(M^.Len, N^.Len+1);
  470.           M^.Id := N^.Id;
  471.         end else
  472.           M := N;
  473.       end;
  474.     until Done;
  475.   end;
  476.  
  477.   function HexB(B : Byte) : String;
  478.     {-Return hex string for byte}
  479.   begin
  480.     HexB[0] := #2;
  481.     HexB[1] := Digits[B shr 4];
  482.     HexB[2] := Digits[B and $F];
  483.   end;
  484.  
  485.   function HexW(W : Word) : String;
  486.     {-Return hex string for word}
  487.   begin
  488.     HexW[0] := #4;
  489.     HexW[1] := Digits[Hi(W) shr 4];
  490.     HexW[2] := Digits[Hi(W) and $F];
  491.     HexW[3] := Digits[Lo(W) shr 4];
  492.     HexW[4] := Digits[Lo(W) and $F];
  493.   end;
  494.  
  495.   function HexPtr(P : Pointer) : string;
  496.     {-Return hex string for pointer}
  497.   begin
  498.     HexPtr := HexW(OS(P).S)+':'+HexW(OS(P).O);
  499.   end;
  500.  
  501.   function StUpcase(s : String) : String;
  502.     {-Return the uppercase string}
  503.   var
  504.     i : Byte;
  505.   begin
  506.     for i := 1 to Length(s) do
  507.       s[i] := UpCase(s[i]);
  508.     StUpcase := s;
  509.   end;
  510.  
  511.   function JustFilename(PathName : String) : String;
  512.     {-Return just the filename of a pathname}
  513.   var
  514.     I : Word;
  515.   begin
  516.     I := Word(Length(PathName))+1;
  517.     repeat
  518.       Dec(I);
  519.     until (PathName[I] in DosDelimSet) or (I = 0);
  520.     JustFilename := Copy(PathName, I+1, 64);
  521.   end;
  522.  
  523.   function JustName(PathName : String) : String;
  524.     {-Return just the name (no extension, no path) of a pathname}
  525.   var
  526.     DotPos : Byte;
  527.   begin
  528.     PathName := JustFilename(PathName);
  529.     DotPos := Pos('.', PathName);
  530.     if DotPos > 0 then
  531.       PathName := Copy(PathName, 1, DotPos-1);
  532.     JustName := PathName;
  533.   end;
  534.  
  535.   function Extend(S : String; Len : Byte) : String;
  536.     {-Truncate or pad S to length Len}
  537.   begin
  538.     if Length(S) < Len then
  539.       FillChar(S[Length(S)+1], Len-Length(S), ' ');
  540.     S[0] := Char(Len);
  541.     Extend := S;
  542.   end;
  543.  
  544.   function SmartExtend(S : String; Len : Byte) : String;
  545.     {-Truncate or pad S to length Len; end with '...' if truncated}
  546.   begin
  547.     if Length(S) > Len then
  548.       SmartExtend := copy(S, 1, Len-3)+'...'
  549.     else
  550.       SmartExtend := Extend(S, Len);
  551.   end;
  552.  
  553.   function Asc2Str(Name : NameArray) : String;
  554.     {-Convert array[1..8] of char to string}
  555.   var
  556.     I : Integer;
  557.   begin
  558.     I := 1;
  559.     while (I <= 8) and (Name[I] <> #0) and (Name[I] <> ' ') do begin
  560.       Asc2Str[I] := Name[I];
  561.       Inc(I);
  562.     end;
  563.     Asc2Str[0] := Char(I-1);
  564.   end;
  565.  
  566.   procedure StripNonAscii(var S : String);
  567.     {-Return an empty string if input contains non-ASCII characters}
  568.   var
  569.     I : Integer;
  570.     Ok : Boolean;
  571.   begin
  572.     Ok := True;
  573.     I := 1;
  574.     while Ok and (I <= Length(S)) do begin
  575.       case S[I] of
  576.         #0..#31, #126..#255 : Ok := False;
  577.       end;
  578.       Inc(I);
  579.     end;
  580.     if not Ok then
  581.       S := '';
  582.   end;
  583.  
  584.   function CommaIze(L : LongInt; Width : Byte) : String;
  585.     {-Convert L to a string and add commas for thousands}
  586.   var
  587.     I : Word;
  588.     Len : Word;
  589.     S : String[19];
  590.   begin
  591.     Str(L, S);
  592.     Len := Length(S);
  593.     I := Len;
  594.     while I > 1 do begin
  595.       if (Len+1-I) mod 3 = 0 then
  596.         insert(',', S, I);
  597.       dec(I);
  598.     end;
  599.     while Length(S) < Width do
  600.       insert(' ', S, 1);
  601.     CommaIze := S;
  602.   end;
  603.  
  604.   function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  605.     {-Return True if M has an associated environment block}
  606.   var
  607.     EnvSeg : Word;
  608.  
  609.     function HasEnv(Start : McbPtr) : Boolean;
  610.     var
  611.       N : McbPtr;
  612.       Done : Boolean;
  613.     begin
  614.       N := Start;
  615.       repeat
  616.         if (N^.Psp = M^.Psp) and (N^.Len > 0) and (EnvSeg = OS(N).S+1) then begin
  617.           HasEnv := True;
  618.           Exit;
  619.         end;
  620.         Done := (N^.Id = 'Z');
  621.         N := Ptr(OS(N).S+N^.Len+1, 0);
  622.       until Done;
  623.       HasEnv := False;
  624.     end;
  625.  
  626.   begin
  627.     EnvSeg := MemW[M^.Psp:$2C];
  628.     if HasEnv(Mcb1) then
  629.       HasEnvironment := True
  630.     else if (HiMemSeg <> 0) and HasEnv(Ptr(HiMemSeg, 0)) then
  631.       HasEnvironment := True
  632.     else
  633.       HasEnvironment := False;
  634.   end;
  635.  
  636.   function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  637.     {-Return True if PspSeg is a valid, existing Psp}
  638.  
  639.     function ValidP(Start : McbPtr) : Boolean;
  640.     var
  641.       N : McbPtr;
  642.       Done : Boolean;
  643.     begin
  644.       N := Start;
  645.       repeat
  646.         if (N^.Psp = PspSeg) and (N^.Len = PspLen) then begin
  647.           ValidP := True;
  648.           Exit;
  649.         end;
  650.         Done := (N^.Id = 'Z');
  651.         N := Ptr(OS(N).S+N^.Len+1, 0);
  652.       until Done;
  653.       ValidP := False;
  654.     end;
  655.  
  656.   begin
  657.     if ValidP(Mcb1) then
  658.       ValidPsp := True
  659.     else if (HiMemSeg <> 0) and ValidP(Ptr(HiMemSeg, 0)) then
  660.       ValidPsp := True
  661.     else
  662.       ValidPsp := False;
  663.   end;
  664.  
  665.   function NameFromEnv(M : McbPtr) : String;
  666.     {-Return M's name from its environment (already known to exist)}
  667.   type
  668.     CharArray = array[0..32767] of Char;
  669.     CharArrayPtr = ^CharArray;
  670.   var
  671.     E : Word;
  672.     Eptr : CharArrayPtr;
  673.     Name : String[79];
  674.     Nlen : Byte absolute Name;
  675.   begin
  676.     Eptr := Ptr(MemW[M^.Psp:$2C], 0);
  677.     E := 0;
  678.     repeat
  679.       if Eptr^[E] = #0 then begin
  680.         Inc(E);
  681.         if Eptr^[E] = #0 then begin
  682.           {found end of environment}
  683.           Inc(E, 3);
  684.           Nlen := 0;
  685.           while (Nlen < 63) and (Eptr^[E] <> #0) do begin
  686.             Inc(Nlen);
  687.             Name[Nlen] := Eptr^[E];
  688.             Inc(E);
  689.           end;
  690.           StripNonAscii(Name);
  691.           NameFromEnv := JustName(Name);
  692.           Exit;
  693.         end;
  694.       end;
  695.       Inc(E);
  696.     until (E > 32767);
  697.     NameFromEnv := '';
  698.   end;
  699.  
  700.   function NameFromMcb(M : McbPtr) : String;
  701.     {-Return name from the Mcb (DOS 4+ only)}
  702.   var
  703.     Name : String[79];
  704.   begin
  705.     Name := Asc2Str(M^.Name);
  706.     StripNonAscii(Name);
  707.     NameFromMcb := Name;
  708.   end;
  709.  
  710.   function MasterCommandSeg : Word;
  711.     {-Return PSP segment of master COMMAND.COM}
  712.   var
  713.     curmcb : mcbptr;
  714.     mseg : word;
  715.     par : word;
  716.   begin
  717.     {First block}
  718.     curmcb := mcb1;
  719.     repeat
  720.       curmcb := ptr(OS(curmcb).s+curmcb^.len+1, 0);
  721.       par := memw[curmcb^.psp:$16];
  722.       mseg := OS(curmcb).s;
  723.       if (par = curmcb^.psp) and (mseg+1 = curmcb^.psp) then begin
  724.         MasterCommandSeg := curmcb^.psp;
  725.         exit;
  726.       end;
  727.     until curmcb^.id = 'Z';
  728.     MasterCommandSeg := 0;
  729.   end;
  730.  
  731.   function WatchPspSeg : Word; assembler;
  732.     {-Find copy of WATCH.COM in memory, returning its PSP segment or zero}
  733.   asm
  734.     mov ax,$7761     {id call to WATCH}
  735.     int $21
  736.     jc @1
  737.     cmp ax,$6177     {WATCH flips ah and al if installed}
  738.     jne @1
  739.     mov ax,bx        {WATCH returns its own CS in BX}
  740.     jmp @2
  741. @1: xor ax,ax        {not installed}
  742. @2:
  743.   end;
  744.  
  745.   procedure FindTheBlocks(HiMemSeg : Word;
  746.                           var Blocks : BlockArray;
  747.                           var BlockMax : BlockType;
  748.                           var StartMcb : Word;
  749.                           var CommandSeg : Word);
  750.     {-Scan memory for the allocated memory blocks}
  751.   const
  752.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  753.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  754.   var
  755.     mcbSeg : Word;            {Segment address of current MCB}
  756.     nextSeg : Word;           {Computed segment address for the next MCB}
  757.     gotFirst : Boolean;       {True after first MCB is found}
  758.     gotLast : Boolean;        {True after last MCB is found}
  759.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  760.  
  761.     procedure StoreTheBlock(var mcbSeg, nextSeg : Word;
  762.                             var gotFirst, gotLast : Boolean);
  763.       {-Store information regarding the memory block}
  764.     var
  765.       nextID : Byte;
  766.       PspAdd : Word;       {Segment address of the current PSP}
  767.       mcbLen : Word;       {Size of the current memory block in paragraphs}
  768.  
  769.     begin
  770.  
  771.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  772.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  773.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  774.       nextID := Mem[nextSeg:0];
  775.  
  776.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  777.         if BlockMax < MaxBlocks then begin
  778.           inc(BlockMax);
  779.           gotFirst := True;
  780.           with Blocks[BlockMax] do begin
  781.             mcb := mcbSeg;
  782.             psp := PspAdd;
  783.           end;
  784.         end;
  785.         {Store master COMMAND.COM segment}
  786.         if CommandSeg = 0 then
  787.           if (McbSeg+1 = PspAdd) and (MemW[PspAdd:$16] = PspAdd) then
  788.             CommandSeg := PspAdd;
  789.       end;
  790.     end;
  791.  
  792.     procedure ScanBlocks;
  793.       {-Scan memory until ending block is found}
  794.     begin
  795.       repeat
  796.         idbyte := Mem[mcbSeg:0];
  797.         if idbyte = MidBlockID then begin
  798.           StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  799.           if gotFirst then
  800.             mcbSeg := nextSeg
  801.           else
  802.             inc(mcbSeg);
  803.         end else if gotFirst and (idbyte = EndBlockID) then begin
  804.           gotLast := True;
  805.           StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  806.         end else
  807.           {Start block was invalid}
  808.           gotLast := True;
  809.       until gotLast;
  810.     end;
  811.  
  812.   begin
  813.     BlockMax := 0;
  814.     CommandSeg := 0;
  815.     StartMCB := OS(MCB1).S;
  816.  
  817.     mcbSeg := StartMCB;
  818.     gotFirst := False;
  819.     gotLast := False;
  820.     ScanBlocks;
  821.  
  822.     if HiMemSeg <> 0 then begin
  823.       mcbSeg := HiMemSeg;
  824.       gotFirst := False;
  825.       gotLast := False;
  826.       ScanBlocks;
  827.     end;
  828.   end;
  829.  
  830.   const
  831.     KbdStart = $1E;
  832.     KbdEnd = $3C;
  833.   var
  834.     KbdHead : Word absolute $40 : $1A;
  835.     KbdTail : Word absolute $40 : $1C;
  836.  
  837.   procedure StuffKey(W : Word);
  838.     {-Stuff one key into the keyboard buffer}
  839.   var
  840.     SaveKbdTail : Word;
  841.   begin
  842.     SaveKbdTail := KbdTail;
  843.     if KbdTail = KbdEnd then
  844.       KbdTail := KbdStart
  845.     else
  846.       Inc(KbdTail, 2);
  847.     if KbdTail = KbdHead then
  848.       KbdTail := SaveKbdTail
  849.     else
  850.       MemW[$40:SaveKbdTail] := W;
  851.   end;
  852.  
  853.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  854.     {-Stuff up to 16 keys into keyboard buffer}
  855.   var
  856.     Len : Byte;
  857.     I : Byte;
  858.   begin
  859.     if ClearFirst then
  860.       KbdTail := KbdHead;
  861.     Len := Length(Keys);
  862.     if Len > 16 then
  863.       Len := 16;
  864.     for I := 1 to Length(Keys) do
  865.       StuffKey(Ord(Keys[I]));
  866.   end;
  867.  
  868.   function ExistFile(path : String) : Boolean;
  869.     {-Return true if file exists}
  870.   var
  871.     F : file;
  872.   begin
  873.     Assign(F, path);
  874.     Reset(F);
  875.     if IoResult = 0 then begin
  876.       ExistFile := True;
  877.       Close(F);
  878.     end else
  879.       ExistFile := False;
  880.   end;
  881.  
  882.   function NextArg(S : String; var SPos : Word) : String;
  883.     {-Return next argument beginning at SPos in S.
  884.       Increment SPos to point past the argument.
  885.       Arguments are delimited by white space, '-', and '/'}
  886.   var
  887.     Start : Word;
  888.  
  889.     function Delimiter(Ch : Char) : Boolean;
  890.     begin
  891.       case Ch of
  892.         #0..#32, '-', '/' : Delimiter := True;
  893.       else
  894.         Delimiter := False;
  895.       end;
  896.     end;
  897.  
  898.   begin
  899.     {Skip leading white space}
  900.     while (SPos <= Length(S)) and (S[SPos] <= ' ') do
  901.       inc(SPos);
  902.  
  903.     {Exit if beyond end of string}
  904.     if SPos > Length(S) then begin
  905.       NextArg := '';
  906.       Exit;
  907.     end;
  908.  
  909.     {Find end of this argument}
  910.     Start := SPos;
  911.     inc(SPos);
  912.     while (SPos <= Length(S)) and not Delimiter(S[Spos]) do
  913.       inc(SPos);
  914.  
  915.     {Return the string}
  916.     NextArg := Copy(S, Start, SPos-Start);
  917.   end;
  918.  
  919. begin
  920.   DosVT := DosVersion;
  921.   DosList := GetDosListPtr;     {pointer to dos list of lists}
  922.   Mcb1 := Ptr(MemW[OS(DosList).S:OS(DosList).O-2], 0); {first Mcb}
  923. end.
  924.  
  925.