{
 ßßßßßßßß  ßßßßßß    ßß   ßß
   ßß     ßß   ßß   ßß  ßß
  ßß     ßß   ßßß  ßßßßß  The DoorKit!
 ßß     ßß   ßß   ßß  ßß
ßß     ßßßßßß    ßß    ßß
The BBS Door Development Kit By The People - For The People!


   Feel free to modify or optimize this code at will. All I ask is that if
   find a better way to do things (and you will), please send me a copy of
   your modifications. Thanks in advance!....Larry L. Athey....

   This is the primary DoorKit unit with all the critical functions.}

{$A+,B-,D+,E+,F+,G+,I-,L+,N-,O+,P-,Q-,R-,S-,T-,V+,X+}
UNIT DOORKIT1;

INTERFACE

USES _EXIT, DOS, TDK_VARS;

{Ä--[Headers]-ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}

PROCEDURE InitDoorKit;
{^ This procedure is like a "DO-ALL" procedure for starting up a door. This
   eliminates the need for you to make a whole string of spaghetti at the
   beginning of your program. Just set what ever variables you want that to
   change from their default vaules (if needed) then call this procedure.}
PROCEDURE ReadCTL;
{^ This is used to read the settings for each node into the program and is
   called automatically by InitDoorKit.}
PROCEDURE StartUpLog;
{^ This procedure is automatically called in the InitDoorKit procedure.
   However, some people may choose to use node specific log files which
   requires you to define your log file and path AFTER The DoorKit has
   been initialized. You can then call this procedure AFTER The DoorKit
   has been initialized and AFTER you have defined your LogFile/LogPath.}
PROCEDURE UpdateTime;
{^ If your door is busy (ie: Not at an sReadkey/sKeyPressed prompt) you will
   want to occasionally call this procedure to be sure to update the user's
   time. It would be best to call this procedure like every 5 or 10 seconds
   so your program doesn't have to slow down for this procedure. Even if you
   only call it every 10 seconds, 10 seconds will still be deducted from the
   user's time. Be sure to see the variable DoorSys.IdleCount as well.}
FUNCTION InitComport : BOOLEAN;
{^ Initializes the comport for IO. This is normally the 2nd thing that is
   called when your door starts (the 1st thing would either be reading a
   DropFile, or INI file, or both). This must be called before any of the
   other comport IO routines are called! (any procs. that use the modem).
   No params are needed because all the values needed are taken from the
   DoorSys record (Port,Baud,Parity), Set up all DoorSys variables first!
   This isn't needed if you are using the InitDoorKit procedure.}
PROCEDURE DeInitComport;
{^ DeInitializes the comport. You can call this at the end of your door, but
   you don't have to. It will be called automatically on its own at the end
   of the program, its better if you don't call it. (see also: AddToExitChain)}
FUNCTION Carrier : BOOLEAN;
{^ Returns True if User is Connected, false if not. If DoorSys.Comport = 0
   (local mode) then this will always return true.}
FUNCTION GetChar : CHAR;
{^ This is an unconditional version of sReadKey and should be used with
   extreme caution. No time slicing, carrier watching, etc is done with
   this function. USE THIS AT YOUR OWN RISK!}
PROCEDURE Lower_DTR;
{^ Lowers the DTR (Drops Carrier) on the user.}
FUNCTION DataAvailable : BOOLEAN;
{^ Returns true if there is data available in the modem buffer.}
PROCEDURE SendStr(S : STRING);
{^ This is used to send a string of data directly to the comport thus
   bypassing any output to the local screen.}
PROCEDURE sCursorUp(N : BYTE);
PROCEDURE sCursorDown(N : BYTE);
PROCEDURE sCursorLeft(N : BYTE);
PROCEDURE sCursorRight(N : BYTE);
{^ Move the cursor n times in a any direction. If the cursor is already at
   the maximum or minimum position in the direction its moving, it will not
   move any further. This will only work if ANSI is enabled, otherwise any
   calls to these procedures will be ignored.}
PROCEDURE sClrscr;
{^ Clears the screen with the current text attribute.}
PROCEDURE sClrEol;
{^ Clears the current line starting from the current cursor position, to the
   end of the line, w/o moving the cursor.....This happens on the remote and
   local screen (according to UpdateLocal)....This will only work if ANSI is
   enabled, otherwise any calls to this procedure will be ignored.}
PROCEDURE sGotoXY(X,Y : BYTE);
{^ Moves the cursor to the values in X,Y on the remote, and local screen
   (according to UpdateLocal). The valid ranges are: X=1..80; Y=1..LengthScr,
   if either value is over the max range nothing will happen. This will only
   work if ANSI is enabled.}
PROCEDURE FlushOutput;
{^ Flushes the output buffer. This procedure does not return until all the
   output in the buffer has been sent to the remote.}
PROCEDURE PurgeOutPut;
{^ Purges all output in the Output buffer. Anything in the buffer is not
   displayed (or sent to remote).}
PROCEDURE PurgeInput;
{^ Clears all input in the input buffer. Anything in the buffer will not be
   read by the input routines.}
PROCEDURE sWriteC(C : CHAR);
PROCEDURE sWritelnC(C : CHAR); {append CRLF after the character}
{^ Writes a character to the comport, and the local screen (if UpdateLocal is
   true).}
PROCEDURE sWriteN(N : LONGINT);
PROCEDURE sWritelnN(N : LONGINT); {append CRLF after the number}
{^ Writes any whole number to the comport, and the local screen (according to
   UpdateLocal). You can use shortint,byte,integer,word & longints with this.
   (You can also use: write(IO,'The Number is: ',mynumber)}
PROCEDURE sWrite(S : STRING);
PROCEDURE sWriteln(S : STRING); {append CRLF after the string}
{^ Writes a string to the comport, and the local screen (according to
   UpdateLocal).}
FUNCTION sKeyPressed : BOOLEAN;
{^ Returns True if a local key has been pressed (if LocalInputON), or if a
   key is waiting in the Input buffer (from remote).}
PROCEDURE sWaitInput(Ms : INTEGER);
{^ Waits for a keypress, or Ms milliseconds. Accurate to 10 milliseconds.}
FUNCTION sReadKey : CHAR;
{^ Reads either the first key in the INPUT buffer from the comport, or if a
   local key was pressed. The scan code returned is just like TP's readkey.
   (except Function keys (F1-F12, AltF1-AltF12))}
FUNCTION AnsiColor : STRING;
PROCEDURE SetFore(Fore : BYTE);
PROCEDURE SetBack(Back : BYTE);
PROCEDURE Set_Color(Fore,Back : BYTE);
{^ Sets the foreground and background colors to the values given. These
   Procedures WILL actually send the ansi codes needed to change the color
   to the remote screen, and change TextAttr locally. Repetetive color
   changes of the same foreground and background are filtered from being
   sent to the comport automatically (Makes for faster drawing).}
PROCEDURE ShowStatusBar;
{^ This redraws the Local Status Bar.}
PROCEDURE HideStatusBar;
{^ This hides the Local Status Bar.}
FUNCTION InitVirtScr : BOOLEAN;
{^ Sets up the virtual screen to be used and sets the DoorSys variable for
   it also. This virtual screen is maintained by all the output routines in
   this library. When ever something is displayed to the player it is also
   written to the virtual screen. When initializing the virtual screen,
   After you call this function you should clear the screen (with a call to
   sClrScr) to be sure that the screens get synchronized. This feature can
   be useful for a couple of reasons: 1) This allows the user to "Refresh"
   his/her screen anywhere in the door, if their screen gets garbled from
   line noise or something. 2) If the door is running in Sysop Blockout Mode
   (ie: nothing is being drawn to the local screen) and at some point the
   door comes out of that mode, allowing the sysop to see what's going on
   again...The door can update the local screen immediately. The only real
   drawback to these routines is that they slow down the normal output
   routines in this library, But it probably won't be noticable to anyone.}
PROCEDURE FreeVirtScr;
{^ Frees the Virtual Screen, and sets the DoorSys variable to reflect it.
   After a call to this the Virtual Screen is not used, and will be a NIL
   pointer if you try to access it. This is called automatically when the
   program exits, so you don't have to call it.}
PROCEDURE DrawScr(Scr : POINTER; X1,Y1,X2,Y2 : BYTE);
{^ Refreshes the remote and local screens using the Virtual Screen. So that
   must of been initialized first. Scr is a pointer to a buffer that holds
   the screen. It must point to an array of tScreen, X1,Y1,X2,Y2 is the
   rectangle to draw. The very last char in the bottom corner (80,25) will
   NEVER be drawn. Otherwise the screen will scroll up.}
PROCEDURE AutoDetect;
{^ This proceudre is used to detect the remote graphics capabilities and
   screen length. Note that the screen length may not be 100% accurate
   due to the variations in terminal program design. This procedure also
   detects the local operating system as well.}
PROCEDURE FakeVirus;
{^ Displays a bogus Telix file download screen that appears to transfer the
   file VIRUS.COM to the user's C:\ directory and then drops carrier on them.
   This is mainly for getting rid of problem users from the system. <G>}
PROCEDURE ReadDorInfo(DoorFn : PathStr; VAR DropInfo : tDoor);
{^ Reads a DORINFO#.DEF drop file into the DoorSys record.}
PROCEDURE ReadDoorSys(DoorFn : PathStr; VAR DropInfo : tDoor);
{^ Reads a DOOR.SYS drop file into the DoorSys record.}
PROCEDURE ReadMxUserDat(DoorFn : PathStr; VAR DropInfo : tDoor);
{^ Reads a MX_USER.DAT drop file into the DoorSys record.}
PROCEDURE CommandLineHelp;
{^ You will find very few uses for this procedure since it is called
   automatically by the command line processing routine. However, if
   you ever need to show the user what the acceptable command line
   parameters are, this is what you need.}
PROCEDURE ClockOn;
PROCEDURE ClockOff;
{^ These are internally used procedures, you really should have no need to
   call these since they are already called from within other procedures.}

IMPLEMENTATION

USES CRT, _SIO, ASYNC, COMM, SVGAANSI, DOORKIT2, DOORKIT3;

CONST
  F1 = #59; F2 = #60; F3 = #61; F4 = #62; F5 = #63;
  F6 = #64; F7 = #65; F8 = #66; F9 = #67; F10 = #68;
  AltF1 = #104; AltF2 = #105; AltF3 = #106; AltF4 = #107; AltF5 = #108;
  AltF6 = #109; AltF7 = #110; AltF8 = #111; AltF9 = #112; AltF10 = #113;
  {^ These are the constants for your sysop function keys. There is no
     way for the remote to send these keys to your door.}

{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ClockOn;
VAR
  S100 : WORD;
BEGIN
  WITH CurTime DO BEGIN
    GETTIME(Hour,Min,Sec,S100);
    StartClock := (Hour * 3600) + (Min * 60) + Sec;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ClockOff;
VAR
  S100 : WORD;
BEGIN
  WITH CurTime DO BEGIN
    GETTIME(Hour,Min,Sec,S100);
    StopClock := (Hour * 3600) + (Min * 60) + Sec;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE UpdateTime;
BEGIN
  ClockOff;
  ClockResult := (StopClock - StartClock);
  WITH DoorSys DO BEGIN
    IF NOT Carrier THEN BEGIN
      ErrLevel := 3;
      HALT(ErrLevel);
    END;
    IF UpdateIdle THEN BEGIN
      INC(IdleCount,ClockResult);
      IF IdleCount = 300 THEN BEGIN {Halt for 5 minutes of user inactivity}
        ErrLevel := 5;
        HALT(ErrLevel);
      END ELSE IF (IdleCount = 60) AND (Graphics <> MAX) THEN BEGIN {Wake-Up the caller after 60 secs}
        IF NOT Local THEN SendStr(^G) ELSE WRITE(^G);
      END;
    END ELSE IdleCount := 0;
    IF UpdateSecs THEN BEGIN
      DEC(SecondsLeft,ClockResult);
      IF SecondsLeft <= 0 THEN BEGIN
        ErrLevel := 4;
        HALT(ErrLevel);
      END;
      MinutesLeft := SecondsLeft DIV 60;
    END;
  END;
  ClockOn;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION InitComport;
BEGIN
  InitComport := FALSE;
  IF DoorSys.IOinstalled THEN EXIT;
  DoorSys.IOinstalled := TRUE;
  IF DoorSys.Comport > 0 THEN BEGIN
    InternalInSize  := DoorSys.InBufSize;
    InternalOutSize := DoorSys.OutBufSize;
    SelectPort(DoorSys.Comport);
    IF InitOK THEN SetBaud(DoorSys.BaudRate);
  END ELSE InitOK := TRUE;
  IF NOT InitOK THEN BEGIN
    ErrLevel := 1;
    ErrorLog('Cannot Initialize Comport!',ErrLevel,TRUE);
  END;
  DoorSys.IOinstalled := InitOK;
  IF (Not Carrier) THEN InitOK := FALSE ELSE BEGIN
    PurgeOutput;
    PurgeInput;
  END;
  InitComport    := InitOK;
  DoorSys.Online := InitOK;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE DeInitComport;
BEGIN
  IF NOT DoorSys.IOinstalled THEN EXIT;
  IF (DoorSys.Comport > 0) THEN CloseUp;
  DoorSys.IOinstalled := FALSE;
  DoorSys.Online      := FALSE;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION Carrier;
BEGIN
  IF DoorSys.Comport > 0 THEN BEGIN
    Carrier := CarrierPresent;
  END ELSE BEGIN
    Carrier := TRUE;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION DataAvailable : BOOLEAN;
BEGIN
  DataAvailable := False;
  IF (DoorSys.IOInstalled) AND (DoorSys.Comport > 0) THEN DataAvailable := Data_Available;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION GetChar : CHAR;
VAR
  Ch : CHAR;
BEGIN
  Ch := #0;
  ReceiveChar(Ch);
  GetChar := Ch;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE Lower_DTR;
VAR
  Count : BYTE;
  Ch    : CHAR;
BEGIN
  Count := 0;
  IF NOT Local THEN BEGIN
    ErrLevel := 3;
    sWriteln('');
    sWriteln('');
    OutTxtL(15,4,' CLICK! ');
    OutTxtL(15,0,' ');
    sWriteln('');
    PurgeInput;
    REPEAT
      INC(Count);
      Set_DTR(FALSE);
      Wait(1);
      Set_DTR(TRUE);
      IF Carrier THEN BEGIN
        PurgeInput;
        SendStr('+++');
        Wait(2);
        IF DataAvailable THEN BEGIN
          WHILE DataAvailable DO Ch := GetChar;
          PurgeInput;
          SendStr('ATH0'+#13);
          Wait(1);
        END;
      END;
    UNTIL (NOT Carrier) OR (Count = 3);
    CLRSCR;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE SendStr(S : STRING);
VAR
  Loop : BYTE;
BEGIN
  IF S = #12#13#10 THEN SvgaAnsiOn := FALSE;
  IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(S);
  IF (DoorSys.Comport > 0) AND (Carrier) THEN BEGIN
    FOR Loop := 1 TO LENGTH(S) DO SendChar(S[Loop]);
  END ELSE BEGIN
    DoorSys.Online := FALSE
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sCursorUp;
VAR
  S : STRING[3];
BEGIN
  WITH DoorSys DO IF (Graphics <> TTY) AND (VirtY > 1) THEN BEGIN
    IF N > 1 THEN STR(N,S) ELSE S := '';
    SendStr(#27'['+S+'A');
    DEC(VirtY,N);
    IF VirtY < 1 THEN VirtY := 1;
    IF (UpdateLocal) AND (NOT SvgaActive) THEN BEGIN
      GOTOXY(VirtX,WhereY-N);
      IF (VirtY <= DoorSys.LocalMaxY) THEN ShowCursor;
    END;
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'['+S+'A');
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sCursorDown;
VAR
  S : STRING[3];
BEGIN
  WITH DoorSys DO IF (Graphics <> TTY) AND (VirtY < 25) THEN BEGIN
    IF N > 1 THEN STR(N,S) ELSE S := '';
    SendStr(#27'['+S+'B');
    INC(VirtY,N);
    IF VirtY > 25 THEN VirtY := 25;
    IF (UpdateLocal) AND (NOT SvgaActive) THEN BEGIN
      GOTOXY(VirtX,WhereY+N);
      IF (VirtY > DoorSys.LocalMaxY) THEN HideCursor;
    END;
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'['+S+'B');
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sCursorRight;
VAR
  S : STRING[3];
BEGIN
  WITH DoorSys DO IF (Graphics <> TTY) AND (VirtX < 80) THEN BEGIN
    IF N > 1 THEN STR(N,S) ELSE S := '';
    SendStr(#27'['+S+'C');
    INC(VirtX,N);
    IF VirtX > 80 THEN VirtX := 80;
    IF (UpdateLocal) AND (NOT SvgaActive) THEN GOTOXY(VirtX,VirtY);
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'['+S+'C');
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sCursorLeft;
VAR
  S : STRING[3];
  I : BYTE;
BEGIN
  WITH DoorSys DO IF (Graphics <> TTY) AND (VirtX > 1) THEN BEGIN
    IF N > 80 THEN N := 80;
    IF N > 1 THEN STR(N,S) ELSE S := '';
    SendStr(#27'['+S+'D');
    DEC(VirtX,N);
    IF VirtX < 1 THEN VirtX := 1;
    IF (UpdateLocal) AND (NOT SvgaActive) THEN GOTOXY(VirtX,VirtY);
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'['+S+'D');
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sClrScr;
BEGIN
  WITH DoorSys DO BEGIN
    IF Graphics = TTY THEN SendStr(#12) ELSE SendStr(#27'[2J');
    IF (UpdateLocal) AND (NOT SvgaActive) THEN CLRSCR;
  END;
  VirtX := 1;
  VirtY := 1;
  IF DoorSys.UseVirtScr THEN FillWord(VirtScr^,SIZEOF(VirtScr^),TextAttr,' ');
  IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'[2J');
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sClrEol;
BEGIN
  WITH DoorSys DO IF Graphics <> TTY THEN BEGIN
    SendStr(#27'[K');
    IF (UpdateLocal) AND (NOT SvgaActive) THEN CLREOL;
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'[K');
  END;
  IF DoorSys.UseVirtScr THEN FillWord(VirtScr^[VirtY-1,(VirtX-1)*2],(80-VirtX+1)*2,TextAttr,' ');
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sGotoXY;
BEGIN
  WITH DoorSys DO IF Graphics <> TTY THEN BEGIN
    SendStr(#27'['+IStr(Y,0)+';'+IStr(X,0)+'H');
    IF (UpdateLocal) AND (NOT SvgaActive) THEN BEGIN
      GOTOXY(X,Y);
      IF (Y > DoorSys.LocalMaxY) THEN HideCursor ELSE ShowCursor;
    END;
    IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(#27'['+IStr(Y,0)+';'+IStr(X,0)+'H');
  END;
  IF (X > 0) AND (X < 81) THEN VirtX := X;
  IF (Y > 0) AND (Y < 26) THEN VirtY := Y;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION sKeyPressed;
VAR
  B : BOOLEAN;
BEGIN
  B := FALSE;
  IF Carrier THEN BEGIN
    B := Data_Available;
    IF DoorSys.LocalInputON AND (NOT B) THEN B := KEYPRESSED;
  END ELSE BEGIN
    DoorSys.Online := FALSE;
  END;
  sKeyPressed := B;
  IF NOT B THEN TimeSlice;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE FlushOutput;
BEGIN
  IF (DoorSys.IOInstalled) AND (Carrier) THEN Flush_Output;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE PurgeOutput;
BEGIN
  IF (DoorSys.IOInstalled) AND (DoorSys.Comport > 0) THEN Purge_Output;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE PurgeInput;
BEGIN
  IF (DoorSys.IOInstalled) AND (DoorSys.Comport > 0) THEN Purge_Input;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE DoVirt(C : CHAR; Yinc : BYTE); {Yinc should be ONLY 0 or 1!}
VAR
  I : BYTE;
BEGIN
  IF NOT (C IN [#07,#08,#10,#13]) THEN BEGIN
    IF DoorSys.UseVirtScr THEN BEGIN
      VirtScr^[VirtY-1,(VirtX-1)*2]   := BYTE(C);
      VirtScr^[VirtY-1,(VirtX-1)*2+1] := TextAttr;
    END;
    INC(VirtX);
  END ELSE CASE C OF
   {#07 : Cursor does not move.}
    #08 : IF VirtX > 1 THEN DEC(VirtX);
    #10 : INC(VirtY);
    #13 : VirtX := 1;
  END;
  IF (VirtX > 80) OR (VirtY > 25) OR (Yinc > 0) THEN BEGIN
    IF (VirtX > 80) THEN VirtX := 1;
    IF VirtY < 25 THEN INC(VirtY) ELSE BEGIN
      VirtY := 25;
      IF DoorSys.UseVirtScr THEN BEGIN
        IF NOT SvgaActive THEN MOVE(VirtScr^[1],VirtScr^[0],4000-160);
        FillWord(VirtScr^[24],160,TextAttr,' ');
      END;
    END;
  END;
  IF (DoorSys.UpdateLocal) THEN IF (VirtY <= DoorSys.LocalMaxY) THEN ShowCursor ELSE HideCursor;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWriteC;
BEGIN
  IF Carrier THEN BEGIN
    IF DoorSys.Comport > 0 THEN SendChar(C);
  END ELSE DoorSys.Online := FALSE;
  IF (DoorSys.UpdateLocal) AND (NOT SvgaActive) THEN WRITE(C);
  IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSI(C);
  DoVirt(C,0);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWritelnC;
BEGIN
  IF Carrier THEN BEGIN
    IF DoorSys.Comport > 0 THEN BEGIN
      SendChar(C);
      SendChar(#13);
      SendChar(#10);
    END;
  END ELSE DoorSys.Online := FALSE;
  IF (DoorSys.UpdateLocal) AND NOT ((VirtY >= DoorSys.LocalMaxY) AND (C = #10)) AND (VirtY = WhereY) THEN BEGIN
    IF NOT SvgaActive THEN WRITELN(C);
  END;
  IF (SvgaActive) AND (SvgaAnsiOn) THEN Display_ANSIstr(C+#13);
  DoVirt(C,1);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWriteN;
VAR
  I : INTEGER;
  S : STRING[12];
BEGIN
  STR(N,S);
  FOR I := 1 TO LENGTH(S) DO sWriteC(S[I]);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWritelnN;
VAR
  I : INTEGER;
  S : STRING[12];
BEGIN
  STR(N,S);
  FOR I := 1 TO LENGTH(S)-1 DO sWriteC(S[I]);
  sWritelnC(S[I+1]);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWrite;
VAR
  I : INTEGER;
BEGIN
  IF LENGTH(S) = 1 THEN sWriteC(S[1]) ELSE FOR I := 1 TO LENGTH(S) DO sWriteC(S[I]);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWriteln;
VAR
  I : INTEGER;
BEGIN
  IF S = '' THEN BEGIN
    WRITELN(SIO,'');
    EXIT;
  END;
  IF LENGTH(S) = 1 THEN BEGIN
    WRITELN(SIO,S[1]);
    EXIT;
  END;
  FOR I := 1 TO LENGTH(S)-1 DO sWriteC(S[I]);
  WRITELN(SIO,S[I+1]);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE sWaitInput(Ms : INTEGER);
VAR
  I : INTEGER;
BEGIN
  I := Ms DIV 10;
  WHILE NOT ((I = 0) OR sKeyPressed) DO BEGIN
    DELAY(10);
    DEC(I);
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE CheckFKeys(Ch : CHAR);
CONST
  StatusType = 1 SHL 4 + 15;
  Info       = 1 SHL 4 + 11;
BEGIN
  CASE Ch OF
    F1 : IF NOT SvgaActive THEN BEGIN
           WITH DoorSys DO IF UpdateStatusBar THEN BEGIN
             Log('SysOp Toggling Status Bar (Help)');
             UpdateStatusBar := FALSE;
             DVWrite(1,StatusBarY,StatusType,
             '                                                                                ');
             DVWrite(2,StatusBarY,StatusType,'F1:');   DVWrite(5,StatusBarY,Info,'Toggle');
             DVWrite(12,StatusBarY,StatusType,'F2:');  DVWrite(15,StatusBarY,Info,'Shell');
             DVWrite(21,StatusBarY,StatusType,'F3:');  DVWrite(24,StatusBarY,Info,'Chat');
             DVWrite(29,StatusBarY,StatusType,'F4:');  DVWrite(32,StatusBarY,Info,'FakeVirus');
             DVWrite(42,StatusBarY,StatusType,'F5:');  DVWrite(45,StatusBarY,Info,'-5 Min');
             DVWrite(52,StatusBarY,StatusType,'F6:');  DVWrite(55,StatusBarY,Info,'+5 Min');
             DVWrite(62,StatusBarY,StatusType,'F9:');  DVWrite(65,StatusBarY,Info,'Eject');
             DVWrite(71,StatusBarY,StatusType,'F10:'); DVWrite(75,StatusBarY,Info,'Drop');
             DVWrite(61,24,8,'Free Memory: '+IntToStr(MEMAVAIL));
           END ELSE BEGIN
             Log('SysOp Toggling Status Bar (Normal)');
             UpdateStatusBar := TRUE;
             ShowStatusBar;
             DVWrite(61,24,8,'Free Memory: '+IntToStr(MEMAVAIL));
           END;
         END;
    F2 : IF NOT SvgaActive THEN BEGIN
           DoorSys.UpdateSecs := FALSE;
           DoorSys.UpdateIdle := FALSE;
           Log('SysOp Shelling To DOS');
           ShellToDos;
           Log('SysOp Returned From DOS');
           DoorSys.UpdateSecs := TRUE;
           DoorSys.UpdateIdle := TRUE;
         END;
    F3 : IF NOT InChat THEN BEGIN
           Log('SysOp Chatting With Caller');
           ChatSelect;
         END;
    F4 : BEGIN
           Log('SysOp Sending Caller A Fake Virus - PSYCHE!');
           RipToText;
           FakeVirus;
         END;
    F5 : WITH DoorSys DO BEGIN
           Log('SysOp Removing 5 Minutes Online Time');
           DEC(SecondsLeft,5 * 60);
           IF SecondsLeft < 0 THEN SecondsLeft := 1;
           MinutesLeft := SecondsLeft DIV 60;
           IF NOT SvgaActive THEN BEGIN
             DVWrite(2,StatusBarY-1,11,'Minutes Left:');
             DVWrite(16,StatusBarY-1,15,PadRight(IntToStr(MinutesLeft),' ',62));
           END;
         END;
    F6 : WITH DoorSys DO BEGIN
           Log('SysOp Adding 5 Minutes Online Time');
           INC(SecondsLeft,5 * 60);
           IF SecondsLeft > (24 * 60 * 60) THEN SecondsLeft := (24 * 60 * 60);
           MinutesLeft := SecondsLeft DIV 60;
           IF NOT SvgaActive THEN BEGIN
             DVWrite(2,StatusBarY-1,11,'Minutes Left:');
             DVWrite(16,StatusBarY-1,15,PadRight(IntToStr(MinutesLeft),' ',62));
           END;
         END;
    F7 : IF NOT SvgaActive THEN BEGIN
           WITH DoorSys DO IF UpdateStatusBar THEN BEGIN
             Log('SysOp Hiding Status Bar');
             UpdateStatusBar := FALSE;
             HideStatusBar;
           END ELSE BEGIN
             Log('SysOp Showing Status Bar');
             UpdateStatusBar := TRUE;
             ShowStatusBar;
           END;
         END;
    F9 : BEGIN
           Log('SysOp Ejected Caller From The Program');
           HALT;
         END;
    F10: BEGIN
           Log('SysOp Dropping Carrier On Caller');
           IF NOT Local THEN _HangUp := TRUE;
           HALT;
         END;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION sReadkey;
VAR
  Ch        : CHAR;
  Found     : BOOLEAN;
  Cnt       : BYTE;
  LastCheck : BYTE;
  S100      : WORD;
BEGIN
  WITH CurTime DO GETTIME(Hour,Min,Sec,S100);
  LastCheck := CurTime.Sec;
  WITH DoorSys DO BEGIN
    IdleCount := 0;
    Cnt       := 0;
    Ch        := #0;
    Found     := FALSE;
    LocalKey  := FALSE;
    REPEAT
      WITH CurTime DO GETTIME(Hour,Min,Sec,S100);
      IF LastCheck <> CurTime.Sec THEN BEGIN
        UpdateTime;
        LastCheck := CurTime.Sec;
      END;
      IF (LocalInputON) AND (NOT Found) THEN BEGIN
        IF KEYPRESSED THEN BEGIN
          s_ReadKey := '';
          Ch := READKEY;
          s_ReadKey := s_ReadKey + Ch;
          Found := (Ch <> #0);
          IF Ch = #0 THEN BEGIN
            Ch := READKEY;
            s_ReadKey := s_ReadKey + Ch;
            IF Ch IN [F1..F10,AltF1..AltF10] THEN CheckFKeys(Ch)
            ELSE BEGIN
              Ch       := #0;
              Found    := TRUE;
              LocalKey := TRUE;
            END;
          END ELSE LocalKey := TRUE;
        END;
      END;
      IF (NOT Found) AND (Comport > 0) THEN BEGIN
        Found := DataAvailable;
        IF Found THEN ReceiveChar(Ch);
      END;
      IF Cnt >= 100 THEN BEGIN
        Cnt := 25;
        TimeSlice;
      END ELSE INC(Cnt);
    UNTIL Found;
    sReadKey := Ch;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE SetFore;
BEGIN
  Set_Color(Fore,TextAttr SHR 4);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE SetBack;
BEGIN
  Set_Color(TextAttr MOD 16, Back);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE AddANSIcode(VAR St : STRING; StAdd : STRING);
BEGIN
  IF St[LENGTH(St)] <> '[' THEN St := St + ';' + StAdd ELSE St := St + StAdd;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION AnsiColor : STRING;
VAR
  Temp : STRING;
CONST
  AnsiColors : ARRAY[0..7] OF CHAR = '04261537';
BEGIN
  Temp := '';
  IF CurColor <> TextAttr THEN BEGIN
    IF Graphics IN [Ansi,Rip,Max] THEN BEGIN
      Temp := #27'[';
      IF TextAttr = LightGray THEN AddAnsiCode(Temp,'0') ELSE BEGIN
        IF (TextAttr AND $8) <> (CurColor AND $8) THEN
          IF (TextAttr AND $8 = $8) THEN AddAnsiCode(Temp,'1') ELSE BEGIN
            CurColor := Lightgray;
            Temp := #27'[0';
          END;
          IF (TextAttr AND $80) <> (CurColor AND $80) THEN
          IF (TextAttr AND $80 = $80) THEN AddAnsiCode(Temp,'5') ELSE BEGIN
            CurColor := LightGray;
            Temp := #27'[0';
            IF (TextAttr AND $8 = $8) THEN AddAnsiCode(Temp,'1')
          END;
        IF (TextAttr AND $7) <> (CurColor AND $7) THEN
            AddAnsiCode(Temp,'3' + AnsiColors[TextAttr AND $7]);
        IF (TextAttr SHR $4) <> (CurColor SHR $4) THEN
            AddAnsiCode(Temp,'4' + AnsiColors[(TextAttr SHR $4) AND $7]);
      END;
      Temp := Temp + 'm';
    END ELSE IF Graphics = Avatar THEN BEGIN
      Temp := #22#1 + CHAR(TextAttr AND $7F);
      IF (TextAttr AND $80 = $80) THEN Temp := Temp + #22#2;
    END;
  END;
  AnsiColor := Temp;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE Set_Color(Fore,Back : BYTE);
BEGIN
  TextColor(Fore);
  TextBackground(Back);
  IF (CurColor <> TextAttr) Then SendStr(AnsiColor);
  CurColor := TextAttr;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ShowStatusBar;
CONST
  StatusType = 1 SHL 4 + 15;
  Info       = 1 SHL 4 + 11;
  SysopInfo  = 1 SHL 4 + 14;
VAR
  X,Y : BYTE;
BEGIN
  IF SvgaActive THEN EXIT;
  DoorSys.UpdateStatusBar := TRUE;
  DoorSys.LocalMaxY       := DoorSys.StatusBarY-1;
  X                       := VirtX;
  Y                       := VirtY;
  WINDOW(1,1,80,DoorSys.StatusBarY-1);
  GOTOXY(X,Y);
  IF (Y > DoorSys.LocalMaxY) THEN HideCursor;
  WITH DoorSys DO BEGIN
    DVWrite(1,StatusBarY,StatusType,'                                                                                ');
    DVWrite(2,StatusBarY,StatusType,'User:');  DVWrite(8,StatusBarY,Info,UserName);
    DVWrite(28,StatusBarY,StatusType,'BPS:');  DVWrite(33,StatusBarY,Info,IntToStr(BaudRate));
    DVWrite(39,StatusBarY,StatusType,'Node:'); DVWrite(45,StatusBarY,Info,IntToStr(Node));
    DVWrite(49,StatusBarY,StatusType,'Sec:');  DVWrite(54,StatusBarY,Info,IntToStr(Access));
    DVWrite(60,StatusBarY,StatusType,'Port:'); DVWrite(66,StatusBarY,Info,IntToStr(ComPort));
    DVWrite(69,StatusBarY,SysopInfo,'(F1 : Help)');
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE HideStatusBar;
VAR
  X,Y,A : BYTE;
BEGIN
  IF SvgaActive THEN EXIT;
  DoorSys.UpdateStatusBar := FALSE;
  DoorSys.LocalMaxY       := DoorSys.StatusBarY;
  X                       := VirtX;
  Y                       := VirtY;
  WINDOW(1,1,80,DoorSys.StatusBarY);
  GOTOXY(1,DoorSys.StatusBarY);
  A        := TextAttr;
  TextAttr := 7;
  CLREOL;
  TextAttr := A;
  GOTOXY(X,Y);
  ShowCursor;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
FUNCTION InitVirtScr : BOOLEAN;
BEGIN
  InitVirtScr := FALSE;
  IF NOT DoorSys.UseVirtScr THEN BEGIN
    IF (VirtScr = NIL) AND (MAXAVAIL > SIZEOF(VirtScr^)) THEN BEGIN
      GETMEM(VirtScr,SIZEOF(VirtScr^));
      FillWord(VirtScr^,SIZEOF(VirtScr^),7,' ');
      VirtX              := 1;
      VirtY              := 1;
      DoorSys.UseVirtScr := TRUE;
    END ELSE DoorSys.UseVirtScr := FALSE;
    InitVirtScr := DoorSys.UseVirtScr;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE FreeVirtScr;
BEGIN
  IF VirtScr <> NIL THEN FREEMEM(VirtScr,SIZEOF(VirtScr^));
  VirtScr            := NIL;
  DoorSys.UseVirtScr := FALSE;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE DrawScr(Scr : POINTER; X1,Y1,X2,Y2 : BYTE);
VAR
  X,Y,VX,VY : BYTE;
BEGIN
  VX := VirtX;
  VY := VirtY;
  IF scr = NIL THEN BEGIN
    sClrScr;
  END ELSE BEGIN
    FOR Y := Y1-1 TO Y2-1 DO BEGIN
      sGotoXY(X1,Y+1);
      FOR X := X1-1 TO X2-1 DO IF (Y <> 25-1) OR (X <> 80-1) THEN BEGIN
        TextAttr := tScreen(Scr^)[Y,(X*2)+1];
        sWriteC(CHAR(tScreen(Scr^)[Y,(X*2)]));
      END;
    END;
    sGotoXY(VX,VY);
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE Autodetect;
VAR
  RipG,
  MaxG,
  AnsiG,
  AvatarG : BOOLEAN;
  Loop    : BYTE;
  Ch      : CHAR;
  Temp    : STRING;
  Code    : INTEGER;
BEGIN
  RipG     := FALSE;
  MaxG     := FALSE;
  AvatarG  := FALSE;
  AnsiG    := FALSE;
  Graphics := Ansi;
  MaxID    := '';
  sClrScr;
  {Detect RIP Graphics}
  IceText('ž Detecting RIP Graphics.......',FALSE);
  IF NOT Local THEN BEGIN
    PurgeInput;
    OutTxt(0,0,#27'[!');
    Loop := 1;
    REPEAT
      DELAY(10);
      INC(Loop);
    UNTIL (Loop = 50) OR DataAvailable;
    IF DataAvailable THEN BEGIN
      RipG := TRUE;
      WHILE DataAvailable DO BEGIN
        Ch := GetChar;
        DELAY(10);
      END;
      PurgeInput;
      PurgeOutput;
    END;
  END;
  {Detect MAX Graphics}
  IF RipG THEN BEGIN
    sWriteln('');
    IceText('ž Detecting MAX Graphics.......',FALSE);
    PurgeInput;
    OutTxt(0,0,#255'~'#255);
    Loop := 1;
    REPEAT
      DELAY(10);
      INC(Loop);
    UNTIL (Loop = 150) OR DataAvailable;
    IF DataAvailable THEN BEGIN
      MaxG := TRUE;
      RipG := FALSE;
      WHILE DataAvailable DO BEGIN
        Ch    := GetChar;
        MaxID := MaxID + Ch;
        DELAY(10);
      END;
      PurgeInput;
      PurgeOutput;
    END;
  END;
  sWriteln('');
  {Detect Ansi/Avatar Graphics}
  IF (NOT MaxG) AND (NOT RipG) THEN IceText('ž Detecting ANSI Graphics......',FALSE);
  IF NOT Local THEN BEGIN
    PurgeInput;
    OutTxt(0,0,#22#8#2#70#27'[6n');
    Loop     := 1;
    REPEAT
      DELAY(10);
      INC(Loop);
    UNTIL (Loop = 150) OR DataAvailable;
    DELAY(50);
    WHILE DataAvailable DO BEGIN
      Ch := GetChar;
      IF Ch = '7' THEN AvatarG := TRUE;
      IF Ch = '[' THEN AnsiG   := TRUE;
      DELAY(10);
    END;
    PurgeInput;
    PurgeOutput;
  END;
  sWriteln('');
  IF RipG THEN Graphics := Rip ELSE
  IF MaxG THEN Graphics := Max ELSE
  IF AvatarG THEN Graphics := Avatar ELSE
  IF AnsiG THEN Graphics := Ansi ELSE BEGIN
    Graphics := Tty;
    IF NOT Local THEN IceText('ž No Graphics Detected.........',TRUE) ELSE Graphics := Ansi;
  END;
  {Check for Shotgun SGT terminal program - Use this at your own risk!!!}
  {IF (NOT Local) AND (Graphics <> RIP) AND (Graphics <> MAX) THEN BEGIN
    PurgeInput;
    SendStr(#255#0#13);
    Loop := 1;
    Temp := '';
    REPEAT
      DELAY(10);
      INC(Loop);
    UNTIL (Loop = 150) OR DataAvailable;
    DELAY(50);
    WHILE DataAvailable DO BEGIN
      Ch   := GetChar;
      Temp := Temp + Ch;
      DELAY(10);
    END;
    IF POS(#255#255#13,Temp) > 0 THEN BEGIN
      SGT := TRUE;
      IceText('ž SGT/YAPP Protocol Detected...',TRUE);
    END;
    PurgeInput;
    PurgeOutput;
  END;}
  {Detect remote screen length}
  IF (Graphics <> TTY) AND (Graphics <> MAX) THEN BEGIN
    IceText('ž Detecting Screen Length......',FALSE);
    sCursorDown(25);
    IF NOT Local THEN BEGIN
      PurgeInput;
      Temp := '';
      OutTxt(0,0,#27'[6n');
      Loop := 1;
      REPEAT
        DELAY(10);
        INC(Loop);
      UNTIL (Loop = 150) OR DataAvailable;
      DELAY(50);
      WHILE DataAvailable DO BEGIN
        Ch   := GetChar;
        Temp := Temp + Ch;
        DELAY(10);
      END;
      PurgeInput;
      PurgeOutput;
      VAL(COPY(Temp,3,POS(';',Temp) - 3),LengthScr,Code);
      IF Code <> 0 THEN LengthScr := 24;
    END ELSE LengthScr := 24;
    sGotoXY(1,4);
    IceText('ž Screen Length Set To ' + IntToStr(LengthScr) + ' Rows.',TRUE);
  END ELSE LengthScr := 25;
  IF Local THEN IceText('ž Local Mode Now Active........',TRUE) ELSE CASE Graphics OF
    Rip    : IceText('ž RIP Graphics Detected........',TRUE);
    Max    : IceText('ž MAX Graphics Detected........',TRUE);
    Avatar : IceText('ž AVATAR Graphics Detected.....',TRUE);
    Ansi   : IceText('ž ANSI Graphics Detected.......',TRUE);
  END;
  sWriteln('');
  {Detect Local Operating System}
  IceText('ž ' + OsStr + ' Detected',TRUE);
  IF NOT Local THEN CASE DoorSys.WhichIO OF
    InternalIO : IceText('ž Using UART Comm Routines',FALSE);
    FossilIO   : IceText('ž Using Fossil: '+StripBoth(FossilName,' '),FALSE);
    DigiIO     : IceText('ž Using DigiBoard: '+StripBoth(FossilName,' '),FALSE);
  END;
  Wait(1);
  TextAttr := 7;
  IF LocalSVGA THEN Graphics := MAX;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ShutDownDoor; Far;
VAR
  Loop : BYTE;
  E    : STRING[40];
BEGIN
  IF Graphics = MAX THEN BEGIN
    SendStr(#13#10);
    SendStr(#12#13#10);
    SendStr(#255#126#169#255#13#10);
  END;
  IF Graphics = RIP THEN RipToText;
  HideStatusBar;
  NORMVIDEO;
  FreeVirtScr;
  TEXTBACKGROUND(0); CLRSCR;
  IF KillDrop THEN BEGIN
    FErase(DropFilePath+'DOOR.SYS');
    FErase(DropFilePath+'DORINFO'+IntToStr(DoorSys.Node)+'.DEF');
  END;
  WITH DoorSys DO BEGIN
    FErase('FIELDS.'+IntToStr(Node));
    FErase('EDITOR.'+IntToStr(Node));
    FErase('RES.'+IntToStr(Node));
    FErase('USER.'+IntToStr(Node));
    FErase('REMOTE.'+IntToStr(Node));
    FErase('PICKINFO.'+IntToStr(Node));
  END;
  IF (Local) AND (ErrLevel = 3) THEN ErrLevel := 0;
  CASE ErrLevel OF
    0 : E := 'Normal Exit';
    1 : E := 'Comm Port Error';
    2 : E := 'Unable To Open Drop File';
    3 : E := 'Carrier Lost';
    4 : E := 'User Time Limit Expired';
    5 : E := 'User Fell Asleep At The Keyboard';
    6 : E := 'CRITICAL ERROR! - System File Missing';
    7 : E := 'CRITICAL ERROR! - Low Conventional Memory';
    8 : E := 'CRITICAL ERROR! - Low EMS/XMS Memory';
    9 : E := 'CRITICAL ERROR! - No VESA BIOS Detected';
  END;
  IF ErrLevel > 9 THEN E := 'SysOp Defined Exit';
  Log(E);
  Log('Exiting At ErrorLevel ' + IntToStr(ErrLevel));
  Log('END');
  IF (ErrLevel = 4) OR (ErrLevel = 5) OR (_HangUp) THEN Lower_DTR;
  DeInitComport;
  Local           := TRUE;
  Graphics        := ANSI;
  SvgaAnsiOn      := FALSE;
  SvgaActive      := FALSE;
  DoorSys.Comport := 0;
  IF NOT HideParams THEN BEGIN
    LineBar(1,0,79);
    IceText('Program Name.....: ' + ProgramName,TRUE);
    IceText('Parameters Used..: ',FALSE); FOR Loop := 1 TO PARAMCOUNT DO IceText(AllCaps(PARAMSTR(Loop))+' ',FALSE); WRITELN;
    IceText('Operating System.: ' + OsStr,TRUE);
    IceText('Available Heap...: ' + IntToStr(MEMAVAIL),TRUE);
    IceText('Error Level......: ' + IntToStr(ErrLevel),TRUE);
    IceText('Exit Type........: ' + E,TRUE);
    LineBar(1,0,79);
    TEXTCOLOR(7);
    WRITELN; WRITELN;
  END;
  Halt(ErrLevel);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE FakeVirus;
VAR
  ProgCh : CHAR;
  Col,
  CPS,B,
  Loop,L : WORD;
BEGIN
  OutTxtXYL(11,5,15,1,'ÕĶµ Zmodem Download ĘĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶø');
  OutTxtXYL(11,6,15,1,'³                                                        ³');
  OutTxtXYL(11,7,15,1,'³ File name :                                            ³');
  OutTxtXYL(11,8,15,1,'³ File path :                                            ³');
  OutTxtXY(11,9,15,1,'³');OutTxt(14,1,' ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ');OutTxtL(15,1,' ³');
  OutTxtXY(11,10,15,1,'³');OutTxt(14,1,' Baud Rate      :           Approx CPS rate :          ');OutTxtL(15,1,' ³');
  OutTxtXY(11,11,15,1,'³');OutTxt(14,1,' Transfer time  :           Bytes to send   :          ');OutTxtL(15,1,' ³');
  OutTxtXY(11,12,15,1,'³');OutTxt(14,1,' Time remaining :           Bytes sent      :          ');OutTxtL(15,1,' ³');
  OutTxtXY(11,13,15,1,'³');OutTxt(14,1,' ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ');OutTxtL(15,1,' ³');
  OutTxtXYL(11,14,15,1,'³ °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°° Progress Ind. ³');
  OutTxtXY(11,15,15,1,'³');OutTxt(14,1,' ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ');OutTxtL(15,1,' ³');
  OutTxtXYL(11,16,15,1,'³ Last status/error : None                               ³');
  OutTxtXYL(11,17,15,1,'ŌĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶĶ¾');
  ProgCh := 'Ū'; L := 0; B := 0; Col := 13;
  IF Local THEN DoorSys.BaudRate := 14400;
  CPS := DoorSys.BaudRate DIV 9;
  OutTxtXY(30,10,14,1,IntToStr(DoorSys.BaudRate));
  OutTxtXY(25,7,15,1,'VIRUS.COM');
  OutTxtXY(25,8,15,1,'C:\');
  OutTxtXY(30,11,14,1,'00:15');
  OutTxtXY(30,12,14,1,'00:14');
  OutTxtXY(58,10,14,1,IntToStr(CPS));
  OutTxtXY(58,11,14,1,'4000');
  FOR Loop := 1 TO 4000 DO BEGIN
    INC(L); INC(B);
    IF L = 100 THEN BEGIN
      L := 0;
      OutTxtXY(Col,14,15,1,ProgCh);
      OutTxtXY(58,12,14,1,IntToStr(B));
      DELAY(30);
      INC(Col);
    END;
  END;
  OutTxtXY(30,12,14,1,'00:00');
  OutTxtXY(33,16,10,1,'Download Complete!');
  AlertTones;
  DELAY(2000);
  Set_Color(7,0);
  IF NOT Local THEN _HangUp := True;
  HALT;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ReadDorInfo(DoorFn : PathStr; VAR DropInfo : tDoor);
VAR
  DoorDrop : Text;
  Temp     : STRING;
  Code     : INTEGER;
  Temp2    : STRING;
  Loop     : BYTE;
BEGIN
  IF (DoorFn <> '') THEN BEGIN
    ASSIGN(DoorDrop,DoorFn);
    RESET(DoorDrop);
  END;
  IF (IORESULT <> 0) OR (DoorFn = '') THEN BEGIN
    TextAttr := 12;
    WRITELN('Unable To Open '+DoorFn+'!');
    ErrLevel := 2;
    ErrorLog('Unable To Open '+DoorFn+'!',ErrLevel,TRUE);
  END;
  WITH DropInfo DO BEGIN
    Node := BYTE(DoorFn[BYTE(DoorFn[0]) - LENGTH('.DEF')]) - 48;
    READLN(DoorDrop,BBSname);
    READLN(DoorDrop,Temp);
    READLN(DoorDrop,Temp2);
    READLN(DoorDrop,Temp);
    VAL(COPY(Temp,4,255),Comport,Code);
    Local := Comport = 0;
    READLN(DoorDrop,Temp);
    Loop := 1;
    WHILE Temp[Loop] <> #32 DO INC(Loop);
    Temp[0] := CHAR(loop - 1);
    VAL(Temp,BaudRate,Code);
    READLN(DoorDrop,Temp);
    READLN(DoorDrop,Temp);
    READLN(DoorDrop,Temp2);
    IF Temp2 <> 'NLN' THEN UserName := Temp+' '+Temp2 ELSE UserName := Temp;
    READLN(DoorDrop,UserCity);
    READLN(DoorDrop,Graphics);
    READLN(DoorDrop,Access);
    READLN(DoorDrop,MinutesLeft);
    SecondsLeft := MinutesLeft * LONGINT(60);
    CLOSE(DoorDrop);
    Phone     := '123-456-7890';
    WorkPhone := Phone;
    Password  := 'PASSWORD';
    Alias     := UserName;
  END;
  SplitUserName;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ReadDoorSys(DoorFn : PathStr; VAR DropInfo : tDoor);
VAR
  DoorDrop  : Text;
  Temp      : STRING;
  Loop,Code : INTEGER;
BEGIN
  IF (DoorFn <> '') THEN BEGIN
    ASSIGN(DoorDrop,DoorFn);
    RESET(DoorDrop);
  END;
  IF (IORESULT <> 0) OR (DoorFn = '') THEN BEGIN
    TextAttr := 12;
    WRITELN('Unable To Open '+DoorFn+'!');
    ErrLevel := 2;
    ErrorLog('Unable To Open '+DoorFn+'!',ErrLevel,TRUE);
  END;
  WITH DropInfo DO BEGIN
    BBSname := 'The BBS';
    READLN(DoorDrop,Temp);
    VAL(COPY(Temp,4,LENGTH(Temp) - 4),Comport,Code);
    Local := Comport = 0;
    READLN(DoorDrop,Temp);
    VAL(Temp,BaudRate,Loop);
    READLN(DoorDrop,Temp);
    READLN(DoorDrop,Node);
    READLN(DoorDrop,Temp);
    IF UPCASE(Temp[1]) <> 'N' THEN VAL(Temp,BaudRate,Loop);
    FOR Loop := 1 TO 4 DO READLN(DoorDrop,Temp);
    READLN(DoorDrop,UserName);
    READLN(DoorDrop,UserCity);
    READLN(DoorDrop,Phone);
    READLN(DoorDrop,WorkPhone);
    READLN(DoorDrop,Password);
    READLN(DoorDrop,Access);
    FOR Loop := 1 TO 3 DO READLN(DoorDrop,Temp);
    READLN(DoorDrop,MinutesLeft);
    SecondsLeft := Minutesleft * LONGINT(60);
    Loop := 0;
    CLOSE(DoorDrop);
    RESET(DoorDrop);
    WHILE NOT EOF(DoorDrop) DO BEGIN
      INC(Loop);
      READLN(DoorDrop,Temp);
      IF Loop = 36 THEN Alias := Temp;
    END;
    IF Loop < 36 THEN Alias := UserName;
  END;
  CLOSE(DoorDrop);
  SplitUserName;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ReadMxUserDat(DoorFn : PathStr; VAR DropInfo : tDoor);
TYPE User_Record      = RECORD
     User_Number      : LONGINT;    {User's record number.                   }
     Locked_Out       : BOOLEAN;    {Is the user locked out? [Y/N]           }
     Security         : WORD;       {User's security level.                  }
     Flags : ARRAY[1..26] OF BYTE;  {User's access flags.                    }
     Name             : STRING[30]; {User's real name.                       }
     Alias            : STRING[30]; {User's alias name.                      }
     Password         : STRING[20]; {User's password.                        }
     Street           : STRING[30]; {User's street address.                  }
     City             : STRING[30]; {User's city.                            }
     State            : STRING[30]; {User's state.                           }
     Country          : STRING[20]; {User's country.                         }
     Zip              : STRING[20]; {User's zip code.                        }
     Voice            : STRING[20]; {User's voice phone number.              }
     Data             : STRING[20]; {User's data phone number.               }
     BirthDate        : STRING[6];  {User's birthdate in MMDDYY.             }
     Gender           : BYTE;       {User's gender 1-Male, 2-Female.         }
     BBS_Name         : STRING[30]; {Name of the user's BBS.                 }
     BBS_Phone        : STRING[20]; {Phone number of the user's BBS.         }
     BBS_NetAddress   : STRING[20]; {Network address of the user's BBS.      }
     Log_Offs         : LONGINT;    {Number of proper log offs.              }
     Carrier_Drops    : LONGINT;    {Number of carrier drops.                }
     Time_Outs        : LONGINT;    {Number of inactivity time outs.         }
     Ansi_Calls       : LONGINT;    {Number of ANSI calls.                   }
     Ascii_Calls      : LONGINT;    {Number of ASCII calls.                  }
     Rip_Calls        : LONGINT;    {Number of RIP calls.                    }
     Max_Calls        : LONGINT;    {Number of MAX calls.                    }
     Wall_Done        : BOOLEAN;    {Did user write on the wall today? [Y/N] }
     Messages         : LONGINT;    {Number of messages posted by the user.  }
     MessagesToday    : WORD;       {Number of messages posted today.        }
     Files_Up         : LONGINT;    {Number of files uploaded by the user.   }
     Bytes_Up         : LONGINT;    {Number of bytes uploaded by the user.   }
     Files_Down       : LONGINT;    {Number of files downloaded by the user. }
     Bytes_Down       : LONGINT;    {Number of bytes downloaded by the user. }
     Files_Today      : WORD;       {Number of files downloaded today.       }
     Bytes_Today      : LONGINT;    {Number of bytes downloaded today.       }
     TimeInBank       : LONGINT;    {Time stored in the bank.                }
     BytesInBank      : LONGINT;    {Bytes stored in the bank.               }
     CreditInBank     : LONGINT;    {Credit stored in the bank.              }
     TimeBankWD       : LONGINT;    {Time withdrawn from the bank today.     }
     ByteBankWD       : LONGINT;    {Bytes withdrawn from the bank today.    }
     CreditBankWD     : LONGINT;    {Credit withdrawn from the bank today.   }
     Def_Archiver     : WORD;       {Default Archiver.                       }
     Def_Protocol     : WORD;       {Default Protocol.                       }
     QWK_Pkt_Limit    : WORD;       {QWK packet total message limit.         }
     QWK_Conf_Limit   : WORD;       {QWK packet conference limit.            }
     Expire_Date      : LONGINT;    {Expiration date.                        }
     Expire_Sec       : WORD;       {User's security after expiration.       }
     Demerits         : WORD;       {Number of demerits against the user.    }
     Suspended        : BOOLEAN;    {Is the user under temporary suspension? }
     Days_Left        : BYTE;       {Number of days of suspension left.      }
     Old_Security     : WORD;       {User's security level before suspension.}
     First_Call       : LONGINT;    {User's first call.                      }
     Last_Call        : LONGINT;    {User's last call.                       }
     Time_Left        : LONGINT;    {Time remaining today in seconds.        }
     InUserList       : BOOLEAN;    {Is user shown in the userlist? [Y/N]    }
     Chat_Page        : BOOLEAN;    {Is user available for chat? [Y/N]       }
     SysOp_Page       : BOOLEAN;    {Can the user page the SysOp? [Y/N]      }
     Verified         : BOOLEAN;    {Was user verified through the CBV? [Y/N]}
     Comment          : STRING[50]; {SysOp comment about the user.           }
     Spare : ARRAY[1..100] OF BYTE; {Just in case - for future development.  }
     END;
VAR
  User  : User_Record;
  FUser : FILE OF User_Record;
BEGIN
  IF (DoorFn <> '') THEN BEGIN
    ASSIGN(FUser,DoorFn);
    RESET(FUser);
  END;
  IF (IORESULT <> 0) OR (DoorFn = '') THEN BEGIN
    TextAttr := 12;
    WRITELN('Unable To Open '+DoorFn+'!');
    ErrLevel := 2;
    ErrorLog('Unable To Open '+DoorFn+'!',ErrLevel,TRUE);
  END;
  READ(FUser,User);
  CLOSE(FUser);
  WITH DropInfo DO BEGIN
    UserName    := User.Name;
    Alias       := User.Alias;
    PassWord    := User.Password;
    UserCity    := User.City + ', ' + User.State;
    Phone       := User.Voice;
    WorkPhone   := User.Data;
    Access      := User.Security;
    UserNumber  := User.User_Number;
    SecondsLeft := User.Time_Left;
    MinutesLeft := User.Time_Left DIV 60;
  END;
  SplitUserName;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE CommandLineHelp;
BEGIN
  ShowProgramAd;
  WRITELN;
  IceText('Command Line Parameters',TRUE);
  LineBar(1,0,23);
  TextAttr := 3;
  IF UseDoorSys THEN WRITELN(PARAMSTR(0) + ' /D={Node Work Path}\DOOR.SYS');
  IF UseDorInfo THEN WRITELN(PARAMSTR(0) + ' /R={Node Work Path}\DORINFO#.DEF');
  IF UseMxUser  THEN WRITELN(PARAMSTR(0) + ' /M={Node Work Path}\MX_USER.DAT');
  WRITELN;
  WRITELN('Add /N### To Force Node Number');
  WRITELN('Add /S##### To Force Baud Rate');
  WRITELN;
  IF IntConfig THEN WRITELN('Use /CONFIG To Configure Program');
  IF UseSVGA THEN WRITELN('Use /V To Activate Local SVGA Graphics');
  IF UseLocal THEN WRITELN('Use /L To Force Local Mode');
  TextAttr := 7;
  sGOTOXY(1,24);
  AnyKey;
  Halt(ErrLevel);
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE DoCommandLine;
VAR
  ExtParams : BOOLEAN;
  TempParam : STRING;
  NewBaud   : LONGINT;
  NewNode,
  Code      : INTEGER;
  Loop      : BYTE;
BEGIN
  NewNode := - 1;
  NewBaud := 0;
  Local   := False;
  IF PARAMCOUNT = 0 THEN CommandLineHelp;
  FOR Loop := 1 TO PARAMCOUNT DO BEGIN
    TempParam := AllCaps(PARAMSTR(Loop));
    IF TempParam[1] = '/' THEN BEGIN
      CASE TempParam[2] OF
        'D' : BEGIN
                ReadDoorSys(COPY(TempParam,4,255),DoorSys);
                DropFilePath := GetFilePath(COPY(TempParam,4,255));
              END;
        'R' : BEGIN
                ReadDorinfo(COPY(TempParam,4,255),DoorSys);
                DropFilePath := GetFilePath(COPY(TempParam,4,255));
              END;
        'M' : BEGIN
                ReadMxUserDat(COPY(TempParam,4,255),DoorSys);
                DropFilePath := GetFilePath(COPY(TempParam,4,255));
              END;
        'N' : BEGIN
                VAL(COPY(TempParam,3,255),NewNode,Code);
                IF Code <> 0 THEN BEGIN
                  WRITELN('Error In Forced Node Number: '+TempParam);
                  AlertTones;
                  ErrLevel := 1;
                  ErrorLog('Error In Forced Node Number: '+TempParam,ErrLevel,TRUE);
                END;
              END;
        'S' : BEGIN
                VAL(COPY(TempParam,3,255),NewBaud,Code);
                IF Code <> 0 THEN BEGIN
                  WRITELN('Error In Forced Baud Rate: '+TempParam);
                  AlertTones;
                  ErrLevel := 1;
                  ErrorLog('Error In Forced Baud Rate: '+TempParam,ErrLevel,TRUE);
                END;
              END;
        'L' : IF UseLocal Then Local := TRUE ELSE HALT;
        '?' : CommandLineHelp;
        'H' : CommandLineHelp;
        'V' : IF UseSVGA THEN LocalSVGA := TRUE;
        'A',
        'B',
        'C',
        'X',
        'Y',
        'Z' : ; {Scrap Parameters...No real reason for them to be here...}
        ELSE BEGIN
          CommandLineHelp;
        END;
      END;
    END ELSE BEGIN
      CommandLineHelp;
    END;
  END;
  IF NewNode <> - 1 THEN DoorSys.Node := NewNode;
  ReadCTL;
  IF NewBaud <> 0 THEN DoorSys.BaudRate := NewBaud;
  IF (NOT Local) AND (Ctl.NSP) AND (DoorSys.WhichIO = InternalIO) THEN BEGIN
    C_PortAddr[Ctl.Port] := HexToInt(Ctl.HexAddr);
    C_PortInt[Ctl.Port]  := Ctl.IRQ;
    DoorSys.Comport      := Ctl.Port;
  END;
  IF NOT Local THEN InitComport;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE ReadCTL;
VAR
  NodeStr : STRING[3];
  CtlFile : File Of ControlFile;
BEGIN
  STR(DoorSys.Node,NodeStr);
  IF NOT FExist('NODE'+NodeStr+'.CTL') THEN BEGIN
    sClrScr;
    OutTxtL(15,4,'!!! FATAL ERROR !!!');
    sWriteln('');
    OutTxtL(12,0,'NODE'+NodeStr+'.CTL Not Found!');
    sWriteln('');
    OutTxtL(4,0,'Please Run The Configuration Program');
    OutTxtL(4,0,'To Create A Control File For Node #'+NodeStr+'!');
    AlertTones;
    DELAY(2000);
    ErrorLog('FATAL ERROR - NODE'+NodeStr+'.CTL NOT FOUND!',6,TRUE);
  END;
  ASSIGN(CtlFile,'NODE'+NodeStr+'.CTL');
  RESET(CtlFile);
  SEEK(CtlFile,0);
  READ(CtlFile,Ctl);
  CLOSE(CtlFile);
  IF Ctl.HomePath = '\' THEN BEGIN
    sClrScr;
    OutTxtL(15,4,'!!! FATAL ERROR !!!');
    sWriteln('');
    OutTxtL(12,0,'No BBS Home Directory Defined In NODE'+NodeStr+'.CTL!');
    sWriteln('');
    OutTxtL(4,0,'Please Run The Configuration Program');
    OutTxtL(4,0,'To Correct This Error...............');
    AlertTones;
    DELAY(2000);
    ErrorLog('FATAL ERROR - No BBS Home Directory Defined In NODE'+NodeStr+'.CTL!',6,TRUE);
  END;
  IF POS('\SHOTGUN\',Ctl.HomePath) > 0 THEN Shotgun := TRUE;
  WITH DoorSys DO IF NOT Local THEN BEGIN
    SetUpPorts;
    IF Ctl.UseFossil THEN WhichIO := FossilIO ELSE WhichIO := InternalIO;
    IF Ctl.UseDigi THEN WhichIO := DigiIO;
    CASE WhichIO OF
      InternalIO : SelectInternal;
      FossilIO   : SelectFossil(FossilName);
      DigiIO     : SelectDigiBoard(FossilName);
    END;
    BaudRate   := Ctl.PortSpeed;
    WordSize   := Ctl.WordSize;
    Parity     := Ctl.Parity;
    StopBits   := Ctl.StopBits;
    InBufSize  := Ctl.InBuffer;
    OutBufSize := Ctl.OutBuffer;
  END;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE StartUpLog;
BEGIN
  Log('BEGIN');
  Log('Incoming Caller Connected At '+IntToStr(DoorSys.BaudRate)+' Baud');
  Log('Free Memory: '+IntToStr(MEMAVAIL)+' Bytes');
  CASE SystemEnv OF
    NoTasker : Log('Running Under DOS / No Multi-Tasker');
    DDOS     : Log('Running Under Double-Dos');
    DV       : Log('Running Under DesqView');
    WIN      : Log('Running Under MS Windows');
    OS2      : Log('Running Under IBM OS/2');
    Network  : Log('Running Under Network OS');
  END;
  IF Ctl.UseDigi THEN Log('Using DigiBoard Comm Routines') ELSE BEGIN
    IF Ctl.UseFossil THEN Log('Using Fossil Comm Routines')
                     ELSE Log('Using Internal Comm Routines');
  END;
  CASE Graphics OF
    Rip    : Log('RIP Graphics Detected');
    Max    : Log('MAX Graphics Detected');
    Avatar : Log('AVATAR Graphics Detected');
    Ansi   : Log('ANSI Graphics Detected');
    Tty    : Log('No Graphics Detected');
  END;
  IF SGT THEN Log('SGT/YAPP Terminal Program Detected');
  IF (Graphics <> TTY) AND (Graphics <> MAX) THEN Log('Screen Length Set To '+IntToStr(LengthScr)+' Rows');
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE InitDoorKit;
VAR
  Ch : CHAR;
BEGIN
  FileMode := 66;
  ErrLevel := 0;
  DoCommandLine;
  IF DropFilePath = '' THEN DoorSys.Access := Ctl.SysSec;
  IF Local THEN BEGIN
    IF DoorSys.UserName = 'Joe User' THEN DoorSys.UserName := Ctl.SFirst+' '+Ctl.SLast;
    IF DoorSys.Alias    = 'Joe User' THEN DoorSys.Alias    := DoorSys.UserName;
    IF DoorSys.Access   = 0 THEN DoorSys.Access := Ctl.SysSec;
    DoorSys.BBSname := Ctl.BBSname;
    UFirst := Ctl.SFirst;
    ULast  := Ctl.SLast;
  END;
  PurgeInput;
  PurgeOutput;
  AutoDetect;
  StartUpLog;
  IF Graphics = TTY THEN BEGIN
    REPEAT;
      IF DataAvailable THEN WHILE DataAvailable DO Ch := GetChar;
      RipToText;
      PurgeInput;
      PurgeOutput;
      Set_Color(7,0);
      sClrScr;
      sWriteln('');
      sWriteln('ANSI Graphics Capabilities could not be detected in your terminal');
      sWriteln('program. This program has features that use special cursor control');
      sWriteln('routines that requires the user to have ANSI Graphics Capabilities.');
      sWriteln('');
      sWrite('Do you wish to attempt to force ANSI graphics on? [Y/N] ');
      Ch := UPCASE(sReadKey);
      IF NOT (Ch IN ['Y','N']) THEN BEGIN
        Log('Restarting graphics autodetection procedure');
        RipToText;
        IF DataAvailable THEN WHILE DataAvailable DO Ch := GetChar;
        PurgeInput;
        PurgeOutput;
        Wait(5);
        AutoDetect;
      END;
    UNTIL (Ch IN ['Y','N']) OR (Graphics <> TTY);
    IF Ch = 'Y' THEN Graphics := ANSI;
    IF (Ch = 'N') AND (NOT UseTTY) THEN BEGIN
      sWriteln('');
      sWriteln(ProgramName+' Now Exiting!');
      Wait(3);
      ErrLevel := 0;
      HALT(ErrLevel);
    END;
  END;
  DVWrite(61,24,8,'Free Memory: '+IntToStr(MEMAVAIL));
  DELAY(500);
  HideStatusBar;
  ShowStatusBar;
  TextAttr := 7;
  sClrScr;
  InitVirtScr;
  PurgeInput;
  PurgeOutput;
  IF UseAd THEN ShowProgramAd;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
VAR
  U : WORD;
BEGIN
  GETTIME(StartTime.Hour,StartTime.Min,StartTime.Sec,U);
  GETDATE(StartTime.Year,StartTime.Month,StartTime.Day,U);
  AddtoExitChain(ShutDownDoor);
  SystemEnv := NoTasker;
  DetectOS;
  WITH CTL DO BEGIN
    Month       := StartTime.Month;
    Day         := StartTime.Day;
    Year        := StartTime.Year;
    SFirst      := 'The';
    SLast       := 'SysOp';
    SysSec      := 500;
    BBSname     := 'The BBS';
    SerialNumber:= '';
    HomePath    := '';
    UseFossil   := FALSE;
    PortSpeed   := 38400;
    UseDigi     := FALSE;
    WordSize    := 8;
    Parity      := 'N';
    StopBits    := 1;
    InBuffer    := 4096;
    OutBuffer   := 4126;
    NSP         := FALSE;
    Port        := 0;
    IRQ         := 4;
    HexAddr     := '03F8';
  END;
  WITH CS DO BEGIN
    Hfg         := 15;
    Hbg         := 3;
    Wbg         := 1;
    Wh          := 9;
    Wl          := 0;
    Sfg         := 8;
    Sbg         := 0;
    Ffg         := 15;
    Fbg         := 1;
    Bfg         := 14;
    TxFG        := 3;
    TxBG        := 0;
    CPBfg       := 9;
    CPBbg       := 0;
    CPKfg       := 14;
    CPKbg       := 0;
    CPTfg       := 13;
    CPTbg       := 0;
  END;
  WITH DoorSys DO BEGIN
    UserName    := 'Joe User';
    Alias       := UserName;
    UserCity    := 'AnyTown, AnyState';
    PassWord    := 'PASSWORD';
    Phone       := '123-456-7890';
    WorkPhone   := Phone;
    BBSname     := 'The BBS';
    Access      := 500;
    UserNumber  := 0;
    Event       := 1400;
    ComPort     := 0;
    Baudrate    := 0;
    WhichIO     := InternalIO;
    IOinstalled := FALSE;
    InBufSize   := 4096;
    OutBufSize  := 4126;
    IRQ         := 4;
    WordSize    := 8;
    Parity      := 'N';
    StopBits    := 1;
    Node        := 1;
    LocalInputON:= TRUE;
    UpdateLocal := TRUE;
    UpdateStatusBar := TRUE;
    UseVirtScr  := FALSE;
    StatusBarY  := 25;
    LocalMaxY   := 25;
    MinutesLeft := 1440;
    SecondsLeft := MinutesLeft * LONGINT(60);
    IdleCount   := 0;
    UpdateSecs  := TRUE;
    UpdateIdle  := TRUE;
    LocalKey    := TRUE;
    OnLine      := TRUE;
  END;
  CurColor      := TextAttr;
  BackSpaceChar := ' ';
  ErrLevel      := 0;
  GetDir(0,LogPath);
  LogPath       := FixPath(LogPath);
  LogFile       := '';
  DropFilePath  := '';
  ProgramName   := 'No Program Name';
  ProgramDesc   := 'No Program Description';
  ClockOn;
END.
