Contributor: MARUIS ELLEN             

{
From: MARIUS ELLEN
Subj: DOS Environment
}

Program Environment;
{$M $1000,32776,32776 }
{    1K stack, 32k+8 bytes heap }
{$T- No @ Typed checking}
{$X+ Extended function syntax}
{$Q- No overflow checking}
{$A+ Word align data}
{$S+ Stack checking}

uses

    dos,
    strings;

type

    PJFTRec = ^TJFTRec;
    TJFTRec = record
      JFTtable : array[1..20] of byte;
    end;


    PMCBrec = ^TMCBrec;
    TMCBrec = record
      Next     : char;      {4d "M", of 5a "Z"}
      PSPOwner : word;
      Length   : word;
      Filler   : array[0..10] of byte;
    end;


    PPSPrec = ^TPSPrec;
    TPSPrec = record       {ofs, length }
      INT20   :word;       {00h  2 BYTEs   INT 20 instruction for CP/M CALL 0
                                           program termination the CDh 20h
                                           here is often used as a signature
                                           for a valid PSP }
      FreeSeg :word;       {02h    WORD    segment of first byte beyond
                                           memory allocated to program}
      UnUsed04:byte;       {04h    BYTE    unused filler }
      CMPCall :byte;       {05h    BYTE    CP/M CALL 5 service request
                                           (FAR JMP to 000C0h) BUG: (DOS 2+)
                                           PSPs created by INT 21/AH=4Bh
                                           point at 000BEh}
      CPMSize :word;       {06h    WORD    CP/M compatibility--size of
                                           first segment for .COM files}
      CPMrem  :word;       {08h  2 BYTEs   remainder of FAR JMP at 05h}
      INT22   :pointer;    {0Ah    DWORD   stored INT 22 termination address}
      INT23   :pointer;    {0Eh    DWORD   stored INT 23 control-Break addr.}
      INT24   :pointer;    {12h    DWORD   DOS 1.1+ stored INT 24 address}
      ParPSP  :word;       {16h    WORD    segment of parent PSP}
      JFT     :TJFTRec;    {18h 20 BYTEs   DOS 2+ Job File Table, one byte
                                           per file handle, FFh = closed}
      SEGEnv  :word;       {2Ch    WORD    DOS 2+ segment of environment
                                           for process}
      SSSP    :pointer;    {2Eh    DWORD   DOS 2+ process's SS:SP on entry
                                           to last INT 21 call}
      JFTCount:word;       {32h    WORD    DOS 3+ number of entries in JFT
                                           (default is 20)}
      JFTPtr  :pointer;    {34h    DWORD   DOS 3+ pointer to JFT
                                           (default PSP:0018h)}
      PrevPSP :pointer;    {38h    DWORD   DOS 3+ pointer to previous PSP
                                           (default FFFFFFFFh in 3.x)
                                           used by SHARE in DOS 3.3}
      UnUsed3c:byte;       {3Ch    BYTE    apparently unused by DOS
                                           versions <= 6.00}
      UnUsed3d:byte;       {3Dh    BYTE    apparently used by some versions
                                           of APPEND}
      NovFlag :byte;       {3Eh    BYTE    (Novell NetWare) flag: next byte
                                           initialized if CEh}
      NovTask :byte;       {3Fh    BYTE    (Novell Netware) Novell task
                                           number if previous byte is CEh}
      DosVers :word;       {40h  2 BYTEs   DOS 5+ version to return on
                                           INT 21/AH=30h}
      NextPSP :word;       {42h    WORD    (MSWin3) selector of next PSP
                                           (PDB) in linked list. Windows
                                           keeps a linked list of Windows
                                           programs only}
      UnUsed44:pointer;    {44h  4 BYTEs   unused by DOS versions <= 6.00}
      WinFlag :byte;       {48h    BYTE    (MSWindows3) bit 0 set if non-
                                           Windows application (WINOLDAP)}
      UnUsed49:string[6];  {49h  7 BYTEs   unused by DOS versions <= 6.00}
      RETF21  :string[2];  {50h  3 BYTEs   DOS 2+ service request (INT
                                           21/RETF instructions)}
      UnUsed53:word;       {53h  2 BYTEs   unused in DOS versions <= 6.00}
      UnUsed55:string[6];  {55h  7 BYTEs   unused in DOS versions <= 6.00;
                                           can be used to make first FCB
                                           into an extended FCB }
      FCB1    :string[15]; {5Ch 16 BYTEs   first default FCB, filled in
                                           from first commandline argument
                                           overwrites second FCB if opened}
      FCB2    :string[15]; {6Ch 16 BYTEs   second default FCB, filled in
                                           from second commandline
                                           argument, overwrites beginning
                                           of commandline if opened}
      UnUsed7c:pointer;    {7Ch  4 BYTEs   unused}
      DTAArea :string[127];{80h 128 BYTEs  commandline / default DTA
                                           command tail is BYTE for length
                                           of tail, N BYTEs for the tail,
                                           followed by a BYTE containing
                                           0Dh}
    end;


    PMCBPSPrec = ^TMCBPSPrec;
    TMCBPSPrec = record
      MCB :TMCBRec;
      PSP :TPSPRec;
    end;

var

    MainEnvSeg:word;
    MainEnvSize:word;


{$ifndef TryAssembler}
    {Find DOS master environment, command/4dos etc...}
    procedure GetMainEnvironment(var envseg,envsize:word);
    var R:PMCBPSPrec;
      Rrec:array[0..1] of word absolute R;
    begin
      asm
        mov     ah,52h            {Get First MCB, }
        int     $21               {DOS Memory Control Block (MCB)}
        mov     ax,es:[bx-2]      {Bevind zich 2 terug}
        mov     R.word[0],0       {Offset is altijd 0}
        mov     R.word[2],ax      {MCB:=first DOS mcb}
      end;

      while true do begin
        if pos(R^.mcb.next,'MZ')=0
        then halt(7);             {Memory control block destroyed}

        if R^.mcb.PSPOwner=R^.PSP.ParPSP then begin {found}
          EnvSeg :=R^.PSP.SegEnv;
          R:=Ptr(EnvSeg-1,0);
          EnvSize:=R^.mcb.length shl 4;
          if EnvSize>32767
          then halt(10);          {Environment invalid (usually >32K)}
          exit;
        end;
        if R^.mcb.next='Z'
        then halt(9);             {Memory block address invalid}
                                  {Er moet een environment zijn!}
        R:=ptr((Rrec[1]+(R^.mcb.length)+1),0);
      end;
    end;


{$else}
    procedure HaltIndirect(error:word);
    begin
      halt(error);
    end;


    {Find DOS master environment, command/4dos etc...}
    procedure GetMainEnvironment(var envsegP,envsizeP:word);
    assembler;
    var mcb:pointer;
    asm
        mov     ah,52h            {Get First MCB, }
        int     $21               {DOS Memory Control Block (MCB)}
        sub     bx,2
        xor     dx,dx             {offset altijd 0000}
        mov     ax,es:[bx]
        mov     mcb.word[0],dx
        mov     mcb.word[2],ax    {MCB:=first DOS mcb}

    @repeat:
        les     di,mcb
        mov     bl,es:[di]
        cmp     bl,4dH
        je      @MCBOk
        cmp     bl,5aH            {was het de laatste MCB}
        jne     @MCBError         {zo ja dan halt(9)}
    @MCBOk:
        mov     ax,es:[01h]       {is segment v/h prg bij deze MCB}
        cmp     ax,es:[26h]       {gelijk aan EnvSegment van het prg}
        je      @found            {zo ja dan is ie gevonden}

        cmp     bl,5ah            {is dit de laatste mcb ?}
        je      @MCBMissing       {!?!? MCB main env weg!?!?}
        les     di,mcb            {volgende MCB zit op}
        mov     ax,es             {oude MCB+next}
        add     ax,es:[3]         {+volgende}
        inc     ax                {+1}
        mov     mcb.word[2],ax
        jmp     @repeat           {herhaal tot gevonden}

    @MCBError:
        mov     al,7              {Memory control block destroyed}
        db      0a9h              {skip next mov al,xx=opcode test ax,w}
    @MCBMissing:
        mov     al,9              {Memory block address invalid}
        db      0a9h              {kan ook environment not found zijn!}
    @SizeErr:
        mov     al,10             {Environment invalid (usually >32K)}
        push    ax
        call    HaltIndirect

    @found:
        mov     ax,es:[3ch]       {Get segment environment}
        mov     dx,es             {save es}
        les     di,EnvSegP        {ptr van VAR parameter}
        mov     es:[di],ax        {Store environment segment}
        mov     es,dx             {rest es}

        dec     ax                {MCB van env. is 1 paragraaf terug}
        mov     es,ax             {Get Size van env. uit MCB}
        mov     ax,es:[3]         {deze is in paragrafen}
        mov     cl,4              {en wordt geconverteerd}
        shl     ax,cl             {naar bytes..}

        les     di,EnvSizeP       {ptr van VAR parameter}
        mov     es:[di],ax        {Store environment size}
        cmp     ax,32768          {size moet <32k}
        jae     @SizeErr          {anders een foutmelding}
    end;
{$endif}

    {Seperate Variable and return parameters}
    function StripEnvVariable(Variable:pchar):pchar;
    const stop='='#32#0;
    begin
      While pos(Variable^,stop)=0 do inc(Variable);
      StripEnvVariable:=Variable+1;
      Variable^:=#0;
    end;


    {like bp's getenv, this time removing spaces}
    function GetMainEnv(variable:string):string;
    var MainPtr,Params:pchar;
      data:array[0..512] of char;
    begin
      MainPtr:=ptr(MainEnvSeg,0);
      StrPCopy(@variable,variable);
      StrUpper(@variable);
      StripEnvVariable(@variable);

      if variable[0]<>#0 then begin
        while (MainPtr^<>#0) do begin
          StrCopy(Data,MainPtr);
          Params:=StripEnvVariable(data);
          if StrComp(Data,@Variable)=0 then begin
            GetMainEnv:=StrPas(Params);
            exit;
          end;
          MainPtr:=StrEnd(MainPtr)+1;
        end;
      end;
      GetMainEnv:='';
    end;


    {like bp's EnvCount}
    function MainEnvCount:integer;
    var MainPtr:pchar;
      index:integer;
    begin
      index:=0;
      MainPtr:=ptr(MainEnvSeg,0);
      while (MainPtr^<>#0) do begin
        MainPtr:=StrEnd(MainPtr)+1;
        inc(index);
      end;
      MainEnvCount:=index;
    end;


    {like bp's EnvStr}
    function MainEnvStr(index:integer):string;
    var MainPtr:pchar;
    begin
      MainPtr:=ptr(MainEnvSeg,0);
      while (MainPtr^<>#0) do begin
        dec(index);
        if index=0 then begin
          MainEnvStr:=StrPas(MainPtr);
          exit;
        end;
        MainPtr:=StrEnd(MainPtr)+1;
      end;
      MainEnvStr:='';
    end;


    {change environment "variable", returning succes}
    function MainEnvChange(variable:string; param:string):boolean;
    var data:array[0..512] of char;
      Mem,MainPtr,EnvPtr:pchar;
      NewSize:word absolute EnvPtr;
      EnvPtrLong:^Longint absolute EnvPtr;


      procedure EnvStrCopy(src:pchar);
      begin
        if NewSize+StrLen(src)<=MainEnvSize-4
        then begin
          StrCopy(EnvPtr,Src);
          EnvPtr:=StrEnd(EnvPtr)+1;
        end
        else MainEnvChange:=false;
      end;

      procedure PutVariable;
      begin
        if (Variable[0]<>#0) and (param[0]<>#0) then begin
          StrCopy(Data,@variable);
          StrCat(Data,'=');
          StrCat(Data,@param);
          EnvStrCopy(Data);
          variable[0]:=#0;
        end;
      end;

    begin
      getmem(Mem,MainEnvSize);
      MainPtr:=ptr(MainEnvSeg,0);
      EnvPtr:=Mem;

      StrPCopy(@variable,variable);
      StrUpper(@variable);
      StripEnvVariable(@variable);
      StrPCopy(@param,param);
      MainEnvChange:=variable[0]<>#0;

      while MainPtr^<>#0 do begin
        StrCopy(Data,MainPtr);
        StripEnvVariable(data);
        if StrComp(Data,@Variable)=0
        then PutVariable
        else EnvStrCopy(MainPtr);
        MainPtr:=StrEnd(MainPtr)+1;
      end;

      if variable[0]<>#0
      then PutVariable;

      EnvPtrLong^:=0; {4 terminating zero's}
      {1 byte terminating environment}
      {2 word counting trailing strings}
      {1 byte terminating the strings}
      {. last three disables paramstr(0)}
      move(Mem^,Ptr(MainEnvSeg,0)^,NewSize+4);
      freeMem(Mem,MainEnvSize);
    end;


var oldprmp:string;
begin
  GetMainEnvironment(MainEnvSeg,MainEnvSize);
  memw[prefixseg:$2c]:=MainEnvSeg;

  oldprmp:=GetMainEnv('fprompt');
  MainEnvChange('prompt','Please type EXIT!'#13#10+'$p$g');

  swapvectors;
  exec(GetMainEnv('comspec'),'');
  swapvectors;

  MainEnvChange('prompt',oldprmp);
end.