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