/* OpenDoors Online Software Programming Toolkit
 * (C) Copyright 1991 - 1999 by Brian Pirie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *        File: ODEmu.c
 *
 * Description: Code for the TTY/ANSI/AVATAR terminal emulation routines,
 *              including .ASC/.ANS/.AVT/.RIP file display functions.
 *
 *   Revisions: Date          Ver   Who  Change
 *              ---------------------------------------------------------------
 *              Oct 13, 1994  6.00  BP   New file header format.
 *              Oct 21, 1994  6.00  BP   Further isolated com routines.
 *              Dec 09, 1994  6.00  BP   Standardized coding style.
 *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
 *              Jun 07, 1995  6.00  BP   Added od_emu_simulate_modem.
 *              Jul 18, 1995  6.00  BP   Fixed warning in call to _ulongdiv().
 *              Aug 19, 1995  6.00  BP   32-bit portability.
 *              Nov 11, 1995  6.00  BP   Removed register keyword.
 *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
 *              Nov 15, 1995  6.00  BP   Terminal emulation speed optimization.
 *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
 *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
 *              Dec 24, 1995  6.00  BP   Use od_connect_speed for modem sim.
 *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
 *              Jan 21, 1996  6.00  BP   Use ODScrnShowMessage().
 *              Jan 09, 1996  6.00  BP   ODComOutbound() returns actual size.
 *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
 *              Mar 03, 1996  6.10  BP   Begin version 6.10.
 *              Mar 06, 1996  6.10  BP   Prevent TC generated N_LXMUL@ call.
 *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
 *              Oct 18, 2001  6.11  MD   Added od_send_file_section()
 *              Aug 10, 2003  6.23  SH   *nix support
 */

#define BUILDING_OPENDOORS

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>

#include "OpenDoor.h"
#include "ODStr.h"
#include "ODCore.h"
#include "ODGen.h"
#include "ODCom.h"
#include "ODTypes.h"
#include "ODScrn.h"
#include "ODKrnl.h"
#include "ODUtil.h"


/* Manifest constants. */
#define MODEM_SIMULATOR_TICK 54L

#define LEVEL_NONE      0
#define LEVEL_ASCII     1
#define LEVEL_ANSI      2
#define LEVEL_AVATAR    3
#define LEVEL_RIP       4


/* Local helper function prototypes. */
static void ODEmulateFromBuffer(char *pszBuffer, BOOL bRemoteEcho);
static FILE *ODEmulateFindCompatFile(const char *pszBaseName, INT *pnLevel);
static void ODEmulateFillArea(BYTE btLeft, BYTE btTop, BYTE btRight,
   BYTE btBottom, char chToFillWith);


/* Current terminal emulator state variables. */
static BYTE btANSISeqLevel = 0;
static INT anANSIParams[10];
static char szCurrentParam[4] = "";
static BYTE btCurrentParamLength;
static BYTE btSavedColumn=1;
static BYTE btSavedRow = 1;
static char szToRepeat[129];
static BYTE btRepeatCount;
static BYTE btAvatarSeqLevel = 0;
static char chPrevParam;
static BYTE btNumParams;
static BYTE btDefaultAttrib = 7;
static BOOL bAvatarInsertMode = FALSE;
static INT8 btScrollLines;
static BYTE btScrollLeft, btScrollTop, btScrollRight, btScrollBottom;

/* Variables for tracking hotkeys while displaying a menu file. */
static char *pszCurrentHotkeys=NULL;
static char chHotkeyPressed;

/* Lookup table to map colors from ANSI values to PC color values. */
static BYTE abtANSIToPCColorTable[8] = {0, 4, 2, 6, 1, 5, 3, 7};


/* ----------------------------------------------------------------------------
 * od_hotkey_menu()
 *
 * Displays a .ASC/.ANS/.AVT/.RIP file, just as od_send_file() does. However,
 * unlike od_send_file(), od_hotkey_menu() also allows checks for keypresses
 * by the user. If any of the hotkeys listed in pszHotKeys are pressed while
 * the file is being displayed, or after the file has finished being displayed,
 * od_hotkey_menu() will return this value to the caller. This allows menu
 * screens to be easily displayed from on-disk files, while permitting the user
 * to choose a menu item at any time.
 *
 * Parameters: pszFileName  - Pointer to the filename to display.
 *
 *             pszHotKeys   - Pointer to a string listing the valid keys.
 *                            Keys are not case sensitive.
 *
 *             bWait        - If TRUE, od_hotkey_menu() will not return until
 *                            the user presses one of the valid keys. If FALSE,
 *                            then od_hotkey_menu() will return as soon as it
 *                            has finished displaying the file, even if the
 *                            user has not pressed any key;.
 *
 *     Return: The key pressed by the user, or '\0' if no key was pressed.
 */
ODAPIDEF char ODCALL od_hotkey_menu(char *pszFileName, char *pszHotKeys,
   BOOL bWait)
{
   char chPressed;

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_hotkey_menu()");

   /* Ensure that OpenDoors has been initialized. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   if(!pszHotKeys)
   {
      od_control.od_error = ERR_PARAMETER;
      OD_API_EXIT();
      return('\0');
   }

   /* Store pointer to string of hotkeys in global hotkey array for access */
   /* from od_send_file(). */
   pszCurrentHotkeys = (char *)pszHotKeys;

   /* Clear the hotkey status variable. */
   chHotkeyPressed = '\0';

   /* Display the menu file using od_send_file() primitive. */
   if(!od_send_file(pszFileName))
   {
      OD_API_EXIT();
      return('\0');
   }

   /* Clear the global hotkey array. */
   pszCurrentHotkeys = NULL;

   /* If the file display was interrupted by the pressing of one of the */
   /* hotkeys, return the pressed hotkey immediately.                   */
   if(chHotkeyPressed != '\0')
   {
      OD_API_EXIT();
      return(chHotkeyPressed);
   }

   /* If no hotkey has been pressed key, and the wait flag has been set, */
   /* wait for the user to press a valid hotkey.                         */
   if(bWait)
   {
      /* Wait for a valid hotkey using the od_get_answer() primitive. */
      chPressed = od_get_answer(pszHotKeys);

      /* If a remote user is connected on this node. */
      if(od_control.baud)
      {
         /* Clear the outbound buffer. */
         ODComClearOutbound(hSerialPort);
      }

      /* Return the hotkey pressed by the user. */
      OD_API_EXIT();
      return(chPressed);
   }

   /* No hotkey has been pressed, so return 0.                          */
   /* (Since 0 is used to terminate the string of valid hotkeys, it can */
   /* never be a valid hotkey itself, and is therefore a safe value to  */
   /* indicate the "no key press" state.)                               */
   OD_API_EXIT();
   return(0);
}


/* ----------------------------------------------------------------------------
 * od_send_file()
 *
 * Displays a .ASC/.ANS/.AVT/.RIP file to the local and remote screens. If
 * OpenDoors is unable to display the required file format locally, an
 * equivalent file that is locally displayable is selected. If no such
 * equivalent file can be found, then a message box is displayed, indicating
 * that the file is being transmitted to the remote system.
 *
 * Parameters: pszFileName - The name of the file to send. This parameter may
 *                           explicitly specify the full filename and
 *                           extension, or the extension may be omitted. In the
 *                           case that the extension is omitted, this function
 *                           automatically selects the appropriate file type
 *                           for the current display mode (RIP, ANSI, etc.).
 *
 *     Return: TRUE on success, or FALSE on failure.
 */
ODAPIDEF BOOL ODCALL od_send_file(const char *pszFileName)
{
   FILE *pfRemoteFile;
   FILE *pfLocalFile = NULL;
   BOOL bAnythingLocal = TRUE;
   void *pWindow;
   INT nFileLevel;
   BYTE btCount;
   BOOL bPausing;
   char chKey;
   char *pchParsing;
   char szMessage[74];

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_send_file()");

   /* Initialize OpenDoors if it hasn't already been done. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   if(!pszFileName)
   {
      od_control.od_error = ERR_PARAMETER;
      OD_API_EXIT();
      return(FALSE);
   }

   /* Initialize local variables. */
   btCount = 2;

   /* Turn on page pausing, if available. */
   bPausing = od_control.od_page_pausing;

   /* If operating in auto-filename mode (no extension specified). */
   if(strchr(pszFileName, '.') == NULL)
   {                                
      /* Begin by searching for a .RIP file. */
      nFileLevel = LEVEL_RIP;

      /* If no .ASC/.ANS/.AVT/.RIP file. */      
      if((pfRemoteFile = ODEmulateFindCompatFile(pszFileName, &nFileLevel))
         == NULL)
      {
         /* Then return with an error. */
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      /* If the file found was a .RIP. */
      if(nFileLevel == LEVEL_RIP)
      {
         /* Search for file to display locally. */
         nFileLevel = LEVEL_AVATAR;

         /* No page pausing with .RIP display. */
         bPausing = FALSE;

         if((pfLocalFile = ODEmulateFindCompatFile(pszFileName, &nFileLevel))
            == NULL)
         {
            /* If there is no further .ASC/.ANS/.AVT/.RIP files, then no */
            /* local display.                                            */
            bAnythingLocal = FALSE;
         }
      }
      else if(nFileLevel == LEVEL_NONE)
      {
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      /* Get filename of remote file. */
      strcpy(szODWorkString, pszFileName);
      strcat(szODWorkString, ".rip");
      strupr(szODWorkString);
   }
   else
   {
      /* If the full filename was specified, then attempt to open that file. */
      if((pfRemoteFile = fopen(pszFileName,"rb")) == NULL)
      {
         /* If unable to open file, then return. */
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      strcpy(szODWorkString, pszFileName);
      strupr(szODWorkString);

      if(strstr(szODWorkString, ".rip"))
      {
         /* No page pausing during .RIP display. */
         bPausing = FALSE;

         /* Disable local display. */
         bAnythingLocal = FALSE;
      }
   }

   /* Set default colour to grey on black. */
   btDefaultAttrib = 0x07;

   /* Reset all terminal emulation. */
   btAvatarSeqLevel = 0;
   btANSISeqLevel = 0;

   /* Turn off AVATAR insert mode. */
   bAvatarInsertMode = FALSE;

   /* Reset [S]top/[P]ause control key status. */
   chLastControlKey = 0;

   if(!bAnythingLocal)
   {
      strcpy(szODWorkString, od_control.od_sending_rip);
      strcat(szODWorkString, pszFileName);
      ODStringCopy(szMessage, szODWorkString, sizeof(szMessage));

      pWindow = ODScrnShowMessage(szMessage, 0);
   }

   /* Loop to display each line in the file(s) with page pausing, etc. */
   for(;;)
   {
      /* Call the OpenDoors kernel routine. */
      CALL_KERNEL_IF_NEEDED();

      /* If hotkeys are active. */
      if(pszCurrentHotkeys != NULL)
      {
         /* If a key is waiting in buffer. */
         while((chKey = (char)tolower(od_get_key(FALSE))) != 0)
         {
            /* Check for key in hotkey string. */
            pchParsing = (char *)pszCurrentHotkeys;
            while(*pchParsing)
            {
               /* If key is found. */
               if(tolower(*pchParsing) == chKey)
               {
                  /* Return, indicating that hotkey was pressed. */
                  chHotkeyPressed = *pchParsing;
                  goto abort_send;
               }
               ++pchParsing;
            }
         }
      }

      /* If a control key has been pressed. */
      if(chLastControlKey)
      {
         switch(chLastControlKey)
         {
            /* If it was a stop control key. */
            case 's':
               if(od_control.od_list_stop)
               {
                  /* If enabled, clear keyboard buffer. */
abort_send:
                  /* If operating in remote mode. */
                  if(od_control.baud)
                  {
                     /* Clear the outbound FOSSIL buffer. */
                     ODComClearOutbound(hSerialPort);
                  }

                  /* Return from function. */
                  goto end_transmission;
               }
               break;

            /* If control key was "pause". */
            case 'p':
               /* If pause is enabled. */
               if(od_control.od_list_pause)
               {
                  /* Clear keyboard buffer. */
                  od_clear_keybuffer();

                  /* Wait for any keypress. */
                  od_get_key(TRUE);
               }
         }

         /* Clear control key status. */
         chLastControlKey = 0;
      }

      /* Get next line, if any. */
      if(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfRemoteFile) == NULL)
      {
         /* If different local file. */
         if(pfLocalFile)
         {
            /* Display rest of it. */
            while(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfLocalFile))
            {
                /* Pass each line to terminal emulator. */
               ODEmulateFromBuffer(szODWorkString, FALSE);
            }
         }

         /* Return from od_send_file(). */
         goto end_transmission;
      }

      /* Set parsepos = last char in globworkstr. */
      pchParsing = (char *)&szODWorkString;
      while(*++pchParsing) ;
      --pchParsing;

      /* Check for end of page state. */
      if((*pchParsing == '\r' || *pchParsing == '\n') &&
         ++btCount >= od_control.user_screen_length && bPausing)
      {
         /* Display page pause prompt. */
         if(ODPagePrompt(&bPausing))
         {
            /* If user chose to abort, then return from od_send_file(). */
            goto end_transmission;
         }

         /* Reset line count. */
         btCount = 2;
      }


      /* If the file is also to be displayed locally. */
      if(bAnythingLocal)
      {
         /* If the local file is different from the remote file, then obtain */
         /* the next line from the local file.                               */
         if(pfLocalFile)
         {
            od_disp(szODWorkString, strlen(szODWorkString), FALSE);

            if(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfLocalFile) == NULL)
            {
               while(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfRemoteFile))
               {
                  od_disp(szODWorkString, strlen(szODWorkString), FALSE);
               }

               /* Return from od_send_file(). */
               goto end_transmission;
            }

            ODEmulateFromBuffer(szODWorkString, FALSE);
         }
         else
         {
            /* Pass the string through the local terminal emulation */
            /* system, and send a copy to the remote system.        */
            if(od_control.od_no_ra_codes)
            {
               ODEmulateFromBuffer(szODWorkString, FALSE);
               od_disp(szODWorkString, strlen(szODWorkString), FALSE);
            }
            else
            {
               ODEmulateFromBuffer(szODWorkString,TRUE);
            }
         }
      }
      else
      {
         /* If the file is not being displayed locally, then just send the */
         /* entire line to the remote system (if any).                     */
         od_disp(szODWorkString,strlen(szODWorkString),FALSE);
      }
   }

end_transmission:

   /* Close remote file. */
   fclose(pfRemoteFile);

   /* If there is a different local file, then close it too. */
   if(pfLocalFile)
   {
      fclose(pfLocalFile);
   }

   /* If we are not displaying anything on the local system. */
   if(!bAnythingLocal)
   {
      /* Wait while file is being sent. */
      if(od_control.baud != 0)
      {
         int nOutboundSize;
         do
         {
            CALL_KERNEL_IF_NEEDED();
            ODComOutbound(hSerialPort, &nOutboundSize);
         } while(nOutboundSize != 0);
      }

      /* Get rid of the window. */
      ODScrnRemoveMessage(pWindow);
   }

   OD_API_EXIT();
   return(TRUE);
}



/* ----------------------------------------------------------------------------
 * od_send_file_section()
 *
 * Displays a .ASC/.ANS/.AVT/.RIP multi-section file to the local and remote
 * screens.  If OpenDoors is unable to display the required file format locally,
 * an equivalent file that is locally displayable is selected. If no such
 * equivalent file can be found, then a message box is displayed, incdicating
 * that the file is being transmitted to the remote system.
 *
 * Note: This function works virtually identical to od_send_file() except it
 *       checks for the section headers (if found).
 *
 * Parameters: pszFileName - The name of the file to send. This parameter may
 *                           explicitly specify the full filename and
 *                           extension, or the extension may be omitted. In the
 *                           case that the extension is omitted, this function
 *                           automatically selects the appropriate file type
 *                           for the current display mode (RIP, ANSI, etc.).
 *             pszSectionname - Name of the section in which to send. This 
 *                              parameter must include only the section name 
 *                              and not the @# delimiter.
 *
 *     Return: TRUE on success, or FALSE on failure.
 */
ODAPIDEF BOOL ODCALL od_send_file_section(char *pszFileName, char *pszSectionName)
{
   FILE *pfRemoteFile;
   FILE *pfLocalFile = NULL;
   BOOL bAnythingLocal = TRUE;
   void *pWindow;
   INT nFileLevel;
   BYTE btCount;
   BOOL bPausing;
   char chKey;
   char *pchParsing;
   char szMessage[74];
   char szFullSectionName[256];
   BOOL bSectionFound = FALSE;
   UINT uSectionNameLength;

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_send_file_section()");

   /* Initialize OpenDoors if it hasn't already been done. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   if(!pszFileName || !pszSectionName)
   {
      od_control.od_error = ERR_PARAMETER;
      OD_API_EXIT();
      return(FALSE);
   }

   /* Initialize local variables. */
   btCount = 2;

   /* Turn on page pausing, if available. */
   bPausing = od_control.od_page_pausing;

   /* If operating in auto-filename mode (no extension specified). */
   if(strchr(pszFileName, '.') == NULL)
   {                                
      /* Begin by searching for a .RIP file. */
      nFileLevel = LEVEL_RIP;

      /* If no .ASC/.ANS/.AVT/.RIP file. */      
      if((pfRemoteFile = ODEmulateFindCompatFile(pszFileName, &nFileLevel))
         == NULL)
      {
         /* Then return with an error. */
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      /* If the file found was a .RIP. */
      if(nFileLevel == LEVEL_RIP)
      {
         /* Search for file to display locally. */
         nFileLevel = LEVEL_AVATAR;

         /* No page pausing with .RIP display. */
         bPausing = FALSE;

         if((pfLocalFile = ODEmulateFindCompatFile(pszFileName, &nFileLevel))
            == NULL)
         {
            /* If there is no further .ASC/.ANS/.AVT/.RIP files, then no */
            /* local display.                                            */
            bAnythingLocal = FALSE;
         }
      }
      else if(nFileLevel == LEVEL_NONE)
      {
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      /* Get filename of remote file. */
      strcpy(szODWorkString, pszFileName);
      strcat(szODWorkString, ".rip");
      strupr(szODWorkString);
   }
   else
   {
      /* If the full filename was specified, then attempt to open that file. */
      if((pfRemoteFile = fopen(pszFileName,"rb")) == NULL)
      {
         /* If unable to open file, then return. */
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }

      strcpy(szODWorkString, pszFileName);
      strupr(szODWorkString);

      if(strstr(szODWorkString, ".rip"))
      {
         /* No page pausing during .RIP display. */
         bPausing = FALSE;

         /* Disable local display. */
         bAnythingLocal = FALSE;
      }
   }

   /* Set default colour to grey on black. */
   btDefaultAttrib = 0x07;

   /* Reset all terminal emulation. */
   btAvatarSeqLevel = 0;
   btANSISeqLevel = 0;

   /* Turn off AVATAR insert mode. */
   bAvatarInsertMode = FALSE;

   /* Reset [S]top/[P]ause control key status. */
   chLastControlKey = 0;

   if(!bAnythingLocal)
   {
      strcpy(szODWorkString, od_control.od_sending_rip);
      strcat(szODWorkString, pszFileName);
      ODStringCopy(szMessage, szODWorkString, sizeof(szMessage));

      pWindow = ODScrnShowMessage(szMessage, 0);
   }

   /* Create section name information */
   strcpy(szFullSectionName, "@#");
   strncat(szFullSectionName, pszSectionName, 254);

   /* Get the length of the section name in it's full form */
   uSectionNameLength = strlen(szFullSectionName); 

   /* Loop to display each line in the file(s) with page pausing, etc. */
   for(;;)
   {
      /* Call the OpenDoors kernel routine. */
      CALL_KERNEL_IF_NEEDED();

      /* If hotkeys are active. */
      if(pszCurrentHotkeys != NULL)
      {
         /* If a key is waiting in buffer. */
         while((chKey = (char)tolower(od_get_key(FALSE))) != 0)
         {
            /* Check for key in hotkey string. */
            pchParsing = (char *)pszCurrentHotkeys;
            while(*pchParsing)
            {
               /* If key is found. */
               if(tolower(*pchParsing) == chKey)
               {
                  /* Return, indicating that hotkey was pressed. */
                  chHotkeyPressed = *pchParsing;
                  goto abort_send;
               }
               ++pchParsing;
            }
         }
      }

      /* If a control key has been pressed. */
      if(chLastControlKey)
      {
         switch(chLastControlKey)
         {
            /* If it was a stop control key. */
            case 's':
               if(od_control.od_list_stop)
               {
                  /* If enabled, clear keyboard buffer. */
abort_send:
                  /* If operating in remote mode. */
                  if(od_control.baud)
                  {
                     /* Clear the outbound FOSSIL buffer. */
                     ODComClearOutbound(hSerialPort);
                  }

                  /* Return from function. */
                  goto end_transmission;
               }
               break;

            /* If control key was "pause". */
            case 'p':
               /* If pause is enabled. */
               if(od_control.od_list_pause)
               {
                  /* Clear keyboard buffer. */
                  od_clear_keybuffer();

                  /* Wait for any keypress. */
                  od_get_key(TRUE);
               }
         }

         /* Clear control key status. */
         chLastControlKey = 0;
      }

      /* Get next line, if any. */
      if(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfRemoteFile) == NULL)
      {
         /* If different local file. */
         if(pfLocalFile)
         {
            /* Display rest of it. */
            while(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfLocalFile))
            {
               if (!bSectionFound && strncmp(szFullSectionName, szODWorkString, uSectionNameLength) == 0) 
               {
                  /* Section Found, allow all lines up to EOF or new section to be displayed */
                  bSectionFound = TRUE;
                  /* Read next line, ignore the section line */
                  continue;
               } 
               else if (!bSectionFound) 
               {
                  /* Section not found yet, continue loop */
                  continue;
               } 
               else if (bSectionFound && strncmp(szODWorkString, "@#", 2) == 0) 
               {
                  /* New Section Intercepted */
                  goto end_transmission;
               }
                /* Pass each line to terminal emulator. */
               ODEmulateFromBuffer(szODWorkString, FALSE);
            }
         }

         /* Return from od_send_file(). */
         goto end_transmission;
      }

      /* Check for section header @# + pszSectionName */
      if (!bSectionFound && strncmp(szFullSectionName, szODWorkString, uSectionNameLength) == 0) 
      {
         /* Flag the section as found */
         bSectionFound = TRUE;
         /* Read next line, ignore section line */
         continue;
      } 
      else if (!bSectionFound) 
      {
         /* Section hasn't been found yet */
         continue;
      } 
      else if (bSectionFound && strncmp(szODWorkString, "@#", 2) == 0) 
      {
         /* New section found, terminate send */
         goto end_transmission;
      }

      /* Set parsepos = last char in globworkstr. */
      pchParsing = (char *)&szODWorkString;
      while(*++pchParsing) ;
      --pchParsing;

      /* Check for end of page state. */
      if((*pchParsing == '\r' || *pchParsing == '\n') &&
         ++btCount >= od_control.user_screen_length && bPausing)
      {
         /* Display page pause prompt. */
         if(ODPagePrompt(&bPausing))
         {
            /* If user chose to abort, then return from od_send_file(). */
            goto end_transmission;
         }

         /* Reset line count. */
         btCount = 2;
      }


      /* If the file is also to be displayed locally. */
      if(bAnythingLocal)
      {
         /* If the local file is different from the remote file, then obtain */
         /* the next line from the local file.                               */
         if(pfLocalFile)
         {
            od_disp(szODWorkString, strlen(szODWorkString), FALSE);

            if(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfLocalFile) == NULL)
            {
               while(fgets(szODWorkString, OD_GLOBAL_WORK_STRING_SIZE-1, pfRemoteFile))
               {
                  od_disp(szODWorkString, strlen(szODWorkString), FALSE);
               }

               /* Return from od_send_file(). */
               goto end_transmission;
            }

            ODEmulateFromBuffer(szODWorkString, FALSE);
         }
         else
         {
            /* Pass the string through the local terminal emulation */
            /* system, and send a copy to the remote system.        */
            if(od_control.od_no_ra_codes)
            {
               ODEmulateFromBuffer(szODWorkString, FALSE);
               od_disp(szODWorkString, strlen(szODWorkString), FALSE);
            }
            else
            {
               ODEmulateFromBuffer(szODWorkString,TRUE);
            }
         }
      }
      else
      {
         /* If the file is not being displayed locally, then just send the */
         /* entire line to the remote system (if any).                     */
         od_disp(szODWorkString,strlen(szODWorkString),FALSE);
      }
   }

end_transmission:

   /* Close remote file. */
   fclose(pfRemoteFile);

   /* If there is a different local file, then close it too. */
   if(pfLocalFile)
   {
      fclose(pfLocalFile);
   }

   /* If we are not displaying anything on the local system. */
   if(!bAnythingLocal)
   {
      /* Wait while file is being sent. */
      if(od_control.baud != 0)
      {
         int nOutboundSize;
         do
         {
            CALL_KERNEL_IF_NEEDED();
            ODComOutbound(hSerialPort, &nOutboundSize);
         } while(nOutboundSize != 0);
      }

      /* Get rid of the window. */
      ODScrnRemoveMessage(pWindow);
   }
   
   /* If the section was not found, return FALSE for error */
   if (!bSectionFound)
   {
      OD_API_EXIT();
      return (FALSE);   
   }

   OD_API_EXIT();
   return(TRUE);
}



/* ----------------------------------------------------------------------------
 * ODEmulateFindCompatFile()                           *** PRIVATE FUNCTION ***
 *
 * Searches for an .ASC/.ANS/.AVT/.RIP file that is compatible with the
 * specified display capabilities level.
 *
 * Parameters: pszBaseName - Base filename to use.
 *
 *             pnLevel     - Highest file level to search for. This value is
 *                           updated to indicate the type of file found.
 *
 *     Return: A pointer to a now-open file of the required type, or NULL if
 *             no match was found.
 */
static FILE *ODEmulateFindCompatFile(const char *pszBaseName, INT *pnLevel)
{
   FILE *pfCompatibleFile;

   ASSERT(pszBaseName != NULL);
   ASSERT(pnLevel != NULL);
   ASSERT(*pnLevel >= 0 && *pnLevel <= 4);

   /* Loop though .RIP/.AVT/.ANS/.ASC extensions. */
   for(;*pnLevel > LEVEL_NONE; --*pnLevel)
   {
      /* Get base-filename passed in. */
      strcpy(szODWorkString, pszBaseName);

      /* Try current extension. */
      switch(*pnLevel)
      {
         case LEVEL_RIP:
            if(!od_control.user_rip) continue;
            strcat(szODWorkString, ".rip");
            break;

         case LEVEL_AVATAR:
            if(!od_control.user_avatar) continue;
            strcat(szODWorkString, ".avt");
            break;

         case LEVEL_ANSI:
            if(!od_control.user_ansi) continue;
            strcat(szODWorkString, ".ans");
            break;

         case LEVEL_ASCII:
            strcat(szODWorkString, ".asc");
            break;
      }

      /* If we are able to open this file, then return a pointer to */
      /* the file.                                                  */
      if((pfCompatibleFile = fopen(szODWorkString,"rb")) != NULL)
         return(pfCompatibleFile);
   }

   /* Return NULL if no file was found. */
   return(NULL);
}


/* ----------------------------------------------------------------------------
 * od_disp_emu()
 *
 * Sends an entire string to both local and remote systems. The characters
 * displayed locally are fed through the local terminal emulation sub-system,
 * allowing aribtrary ANSI/AVATAR control sequences to be displayed both
 * locally and remotely.
 *
 * Parameters: pszToDisplay - Pointer to string to display.
 *
 *             bRemoteEcho  - TRUE if characters should also be transmitted
 *                            to the remote system.
 *
 *     Return: void
 */
ODAPIDEF void ODCALL od_disp_emu(const char *pszToDisplay, BOOL bRemoteEcho)
{
   BOOL bTranslateRemote;

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_disp_emu()");

   /* Ensure that OpenDoors has been initialized. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   /* Send pszToDisplay to remote system. */
   if(bRemoteEcho)
   {
      if(od_control.od_no_ra_codes)
      {
         od_disp(pszToDisplay, strlen(pszToDisplay), FALSE);
         bTranslateRemote = FALSE;
      }
      else
      {
         bTranslateRemote = TRUE;
      }
   }
   else
   {
      bTranslateRemote = FALSE;
   }

   /* Pass string to be emulated to local terminal emulation function. */
   ODEmulateFromBuffer(pszToDisplay, bTranslateRemote);

   OD_API_EXIT();
}


/* ----------------------------------------------------------------------------
 * od_emulate()
 *
 * Sends a single character to both local and remote systems. The characters
 * displayed locally are fed through the local terminal emulation sub-system,
 * allowing aribtrary ANSII/AVATAR control sequences to be displayed both
 * locally and remotely.
 *
 * Parameters: chToEmulate - Character to feed through the terminal emulator.
 *
 *     Return: void
 */
ODAPIDEF void ODCALL od_emulate(char chToEmulate)
{
   static char szBuffer[2];

   *szBuffer = chToEmulate;

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_emulate()");

   /* Ensure that OpenDoors has been initialized. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   /* Pass character to be emulated to local terminal emulation function. */
   ODEmulateFromBuffer(szBuffer, TRUE);

   OD_API_EXIT();
}


/* ----------------------------------------------------------------------------
 * ODEmulateFromBuffer()                               *** PRIVATE FUNCTION ***
 *
 * Displays a string on the local screen, interpreting any terminal emulation
 * control sequences. The string may also be sent to the remote system at the
 * same time.
 *
 * Parameters: pszBuffer   - Pointer to the string to transmit.
 *
 *             bRemoteEcho - TRUE if string should also be sent to the remote
 *                           system, FALSE if it should not be.
 *
 *     Return: void
 */
static void ODEmulateFromBuffer(char *pszBuffer, BOOL bRemoteEcho)
{
   char chCurrent;
   static tODScrnTextInfo TextInfo;
   INT nTemp;
   BOOL bEchoThisChar;
   INT nCharsPerTick;
   INT nCharsThisTick;
   tODTimer ModemSimTimer;

   ASSERT(pszBuffer != NULL);

   /* If we should simulate modem transmission speed. */
   if(od_control.od_emu_simulate_modem)
   {
      DWORD lCharsPerSecond;

      /* Determine character per second rate to simulate. */
      if(od_control.baud == 0)
      {
         lCharsPerSecond = 960L;
      }
      else
      {
         ODDWordDivide(&lCharsPerSecond, (DWORD *)NULL,
            od_control.od_connect_speed, 10L);
      }

      /* Determine number of characters to send per timer tick. */
      lCharsPerSecond = ODDWordMultiply(lCharsPerSecond, MODEM_SIMULATOR_TICK);
      ODDWordDivide(&lCharsPerSecond, (DWORD *)NULL, lCharsPerSecond, 1000L);
      nCharsPerTick = (INT)lCharsPerSecond;

      /* Start tick timer. */
      ODTimerStart(&ModemSimTimer, MODEM_SIMULATOR_TICK);

      /* Reset number of characters that we have sent during this tick. */
      nCharsThisTick = 0;
   }

   while(*pszBuffer)
   {
      /* Read the next character from the buffer into local variable for */
      /* access speed efficiency.                                        */
      chCurrent = *pszBuffer;

      /* If we should simulate modem transmission speed. */
      if(od_control.od_emu_simulate_modem)
      {
         /* If we have now sent all of the characters that should be sent */
         /* during this tick.                                             */
         if(nCharsThisTick++ >= nCharsPerTick)
         {
            /* Wait for timer to elapse. */
            ODTimerWaitForElapse(&ModemSimTimer);

            /* Restart tick timer. */
            ODTimerStart(&ModemSimTimer, MODEM_SIMULATOR_TICK);

            /* Reset characters sent in this tick. */
            nCharsThisTick = 0;
         }
      }

      bEchoThisChar = bRemoteEcho;

      /* Switch according to ANSI emulator state. */
      switch(btANSISeqLevel)
      {
         /* If we are not in the middle of an ANSI command string. */
         case 0:
            /* Switch according to current character. */
            switch(chCurrent)
            {
               /* If this is the escape character. */
               case 27:
                  /* If we are not in the middle of an AVATAR sequence. */
                  if(btAvatarSeqLevel == 0)
                  {
                     /* Then treat this as the start an ANSI sequence. */
                     btANSISeqLevel = 1;
                     break;
                  }

                  /* Deliberate fallthrough to default case. */

               /* If not start of an ANSI sequence. */
               default:
                  /* Check our position in AVATAR sequence. */
                  switch(btAvatarSeqLevel)
                  {
                     /* If not in middle of an AVATAR command. */
                     case 0:
                        /* Check the character we've been sent. */
                        switch(chCurrent)
                        {
                           /* Check for QBBS/RA pause for keypress code. */
                           case 0x01:
                              if(od_control.od_no_ra_codes)
                              {
                                 goto output_next_char;
                              }

                              /* Wait for user to press [ENTER] key. */
                              od_get_answer("\n\r");
                              bEchoThisChar = FALSE;
                              break;

                           /* QBBS/RA ^F user parameters. */
                           case 0x06:
                              if(od_control.od_no_ra_codes)
                              {
                                 goto output_next_char;
                              }
                              btAvatarSeqLevel = 21;
                              bEchoThisChar = FALSE;
                              break;

                           /* QBBS/RA ^K user parameters. */
                           case 0x0b:
                              if(od_control.od_no_ra_codes)
                              {
                                 goto output_next_char;
                              }
                              btAvatarSeqLevel = 22;
                              bEchoThisChar = FALSE;
                              break;

                           case 0x0c:
                              bAvatarInsertMode = FALSE;
                              ODScrnSetAttribute((BYTE)(
                                 od_control.od_cur_attrib = btDefaultAttrib));
                              ODScrnClear();
                              break;

                           case 0x19:
                              bAvatarInsertMode = FALSE;
                              btAvatarSeqLevel = 1;
                              break;

                           case 0x16:   /* ^V */
                              btAvatarSeqLevel = 3;
                              break;

                           default:
   output_next_char:
                              /* Output next character. */
                              if(bAvatarInsertMode)
                              {
                                 ODScrnGetTextInfo(&TextInfo);
                                 if(TextInfo.curx < 80)
                                 {
                                    ODScrnCopyText(TextInfo.curx,
                                       TextInfo.cury, 79, TextInfo.cury,
                                       (BYTE)(TextInfo.curx + 1),
                                       TextInfo.cury);
                                 }
                                 ODScrnDisplayChar(chCurrent);
                              }

                              else
                              {
                                 ODScrnDisplayChar(chCurrent);
                              }
                        }
                        break;

                     case 1:
                        bAvatarInsertMode = FALSE;
                        chPrevParam = chCurrent;
                        btAvatarSeqLevel = 2;
                        break;

                     case 2:
                        for(nTemp = 0; nTemp < (INT)chCurrent;
                           ++nTemp)
                        {
                           ODScrnDisplayChar(chPrevParam);
                        }
                        btAvatarSeqLevel = 0;
                        break;

                     case 3:
                        switch(chCurrent)
                        {
                           case 0x01:
                              bAvatarInsertMode = FALSE;
                              btAvatarSeqLevel = 4;
                              break;

                           case 0x02:
                              bAvatarInsertMode = FALSE;
                              ODScrnGetTextInfo(&TextInfo);
                              ODScrnSetAttribute((BYTE)
                                 (od_control.od_cur_attrib =
                                 TextInfo.attribute | 0x80));
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x03:
                              bAvatarInsertMode = FALSE;
                              ODScrnGetTextInfo(&TextInfo);
                              if(TextInfo.cury > 1)
                              {
                                 ODScrnSetCursorPos(TextInfo.curx,
                                    (BYTE)(TextInfo.cury - 1));
                              }
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x04:
                              bAvatarInsertMode = FALSE;
                              ODScrnGetTextInfo(&TextInfo);
                              if(TextInfo.cury < 25)
                              {
                                 ODScrnSetCursorPos(TextInfo.curx,
                                    (BYTE)(TextInfo.cury + 1));
                              }
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x05:
                              bAvatarInsertMode = FALSE;
                              ODScrnGetTextInfo(&TextInfo);
                              if(TextInfo.curx > 1)
                              {
                                 ODScrnSetCursorPos((BYTE)(TextInfo.curx - 1),
                                    TextInfo.cury);
                              }
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x06:
                              bAvatarInsertMode = FALSE;
                              ODScrnGetTextInfo(&TextInfo);
                              if(TextInfo.curx < 80)
                              {
                                 ODScrnSetCursorPos((BYTE)(TextInfo.curx + 1),
                                    TextInfo.cury);
                              }
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x07:
                              bAvatarInsertMode = FALSE;
                              ODScrnClearToEndOfLine();
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x08:
                              bAvatarInsertMode = FALSE;
                              btAvatarSeqLevel = 5;
                              break;

                           case 0x09:   /* ^I */
                              bAvatarInsertMode = TRUE;
                              btAvatarSeqLevel = 0;
                              break;

                           case 0x0a:   /* ^J */
                              btScrollLines = -1;
                              btAvatarSeqLevel = 7;
                              break;

                           case 0x0b:   /* ^K */
                              btScrollLines = 1;
                              btAvatarSeqLevel = 7;
                              break;

                           case 0x0c:   /* ^L */
                              btAvatarSeqLevel = 14;
                              break;

                           case 0x0d:   /* ^M */
                              btAvatarSeqLevel = 15;
                              break;

                           case 0x0e:   /* ^N */
                              ODScrnGetTextInfo(&TextInfo);
                              if(TextInfo.curx < 80)
                              {
                                 ODScrnCopyText((BYTE)(TextInfo.curx + 1),
                                    TextInfo.cury, 80, TextInfo.cury,
                                    TextInfo.curx, TextInfo.cury);
                              }

                              ODScrnEnableScrolling(FALSE);
                              ODScrnSetCursorPos(80, TextInfo.cury);
                              ODScrnDisplayChar(' ');
                              ODScrnEnableScrolling(TRUE);
                              ODScrnSetCursorPos(TextInfo.curx, TextInfo.cury);

                              btAvatarSeqLevel = 0;
                              break;

                           case 0x19:   /* ^Y */
                              btAvatarSeqLevel = 19;
                              break;

                           default:
                              btAvatarSeqLevel = 0;
                        }
                        break;

                     case 4:
                        ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                           = chCurrent));
                        btAvatarSeqLevel = 0;
                        break;

                     case 5:
                        chPrevParam = chCurrent;
                        btAvatarSeqLevel = 6;
                        break;

                     case 6:
                        ODScrnSetCursorPos(chCurrent, chPrevParam);
                        btAvatarSeqLevel = 0;
                        break;

                     case 7:
                        if(btScrollLines < 1)
                        {
                           btScrollLines = chCurrent;
                        }
                        else
                        {
                           btScrollLines = -chCurrent;
                        }
                        btAvatarSeqLevel = 8;
                        break;

                     case 8:
                        btScrollTop = chCurrent;
                        btAvatarSeqLevel = 9;
                        break;

                     case 9:
                        btScrollLeft = chCurrent;
                        btAvatarSeqLevel = 10;
                        break;

                     case 10:
                        btScrollBottom = chCurrent;
                        btAvatarSeqLevel = 11;
                        break;

                     case 11:
                        btScrollRight = chCurrent;
                        btAvatarSeqLevel = 12;
                        break;

                     case 12:
                        if(btScrollLines == 0
                           || abs(btScrollLines) > 
                           (btScrollBottom - btScrollTop))
                        {
                           ODEmulateFillArea(btScrollLeft, btScrollTop,
                              btScrollRight, btScrollBottom, ' ');
                        }

                        else if(btScrollLines < 0)
                        {
                           ODScrnCopyText(btScrollLeft, btScrollTop,
                              btScrollRight,
                              (BYTE)(btScrollBottom + btScrollLines),
                              btScrollLeft,
                              (BYTE)(btScrollTop - btScrollLines));
                           ODEmulateFillArea(btScrollLeft, btScrollTop,
                              btScrollRight,
                              (BYTE)(btScrollTop - (btScrollLines - 1)), ' ');
                        }

                        else
                        {
                           ODScrnCopyText(btScrollLeft,
                              (BYTE)(btScrollTop + btScrollLines),
                              btScrollRight, btScrollBottom,
                              btScrollLeft, btScrollTop);
                           ODEmulateFillArea(btScrollLeft,
                              (BYTE)(btScrollBottom - (btScrollLines - 1)),
                              btScrollRight, btScrollBottom, ' ');
                        }
                        btAvatarSeqLevel = 0;
                        break;

                     case 14:
                        btScrollLines = (chCurrent & 0x7f);
                        btScrollRight = ' ';
                        btAvatarSeqLevel = 17;
                        break;

                     case 15:
                        btScrollLines = (chCurrent & 0x7f);
                        btAvatarSeqLevel = 16;
                        break;

                     case 16:
                        btScrollRight = chCurrent;
                        btAvatarSeqLevel = 17;
                        break;

                     case 17:
                        btScrollTop = chCurrent;
                        btAvatarSeqLevel = 18;
                        break;

                     case 18:
                        ODScrnGetTextInfo(&TextInfo);
                        ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                           = btScrollLines));
                        ODEmulateFillArea(TextInfo.curx, TextInfo.cury,
                           (BYTE)(TextInfo.curx + chCurrent),
                           (BYTE)(TextInfo.cury + btScrollTop), btScrollRight);
                        btAvatarSeqLevel = 0;
                        break;

                     case 19:
                        btScrollLines = (chCurrent & 0x7f);
                        szToRepeat[btRepeatCount = 0] = '\0';
                        btAvatarSeqLevel = 20;
                        break;

                     case 20:
                        if(btRepeatCount < btScrollLines)
                        {
                           szToRepeat[btRepeatCount] = chCurrent;
                           szToRepeat[++btRepeatCount] = '\0';
                        }
                        else
                        {
                           for(btRepeatCount = 0; btRepeatCount <
                              (BYTE)chCurrent;++btRepeatCount)
                           {
                              ODScrnDisplayString(szToRepeat);
                           }
                           btAvatarSeqLevel = 0;
                        }
                        break;

                     /* RA/QBBS ^F control codes. */
                     case 21:
                        bEchoThisChar = FALSE;
                        switch(chCurrent)
                        {
                           case 'A':
                              od_disp_str(od_control.user_name);
                              break;
                           case 'B':
                              od_disp_str(od_control.user_location);
                              break;
                           case 'C':
                              od_disp_str(od_control.user_password);
                              break;
                           case 'D':
                              od_disp_str(od_control.user_dataphone);
                              break;
                           case 'E':
                              od_disp_str(od_control.user_homephone);
                              break;
                           case 'F':
                              od_disp_str(od_control.user_lastdate);
                              break;
                           case 'G':
                              od_disp_str(od_control.user_lasttime);
                              break;
                           case 'H':
                              btScrollLines = 0;
                              goto show_flags;
                           case 'I':
                              btScrollLines = 1;
                              goto show_flags;
                           case 'J':
                              btScrollLines = 2;
                              goto show_flags;
                           case 'K':
                              btScrollLines = 3;
   show_flags:                for(btRepeatCount = 0; btRepeatCount < 8;
                                 ++btRepeatCount)
                              {
                                 if((od_control.user_flags[btScrollLines] >>
                                    btRepeatCount) & 0x01)
                                 {
                                    szToRepeat[btRepeatCount] = 'X';
                                 }
                                 else
                                 {
                                    szToRepeat[btRepeatCount] = '-';
                                 }
                              }
                              szToRepeat[btRepeatCount] = '\0';
                              od_disp_str(szToRepeat);
                              break;
                           case 'L':
                              od_printf("%lu", od_control.user_net_credit);
                              break;
                           case 'M':
                              od_printf("%u", od_control.user_messages);
                              break;
                           case 'N':
                              od_printf("%u", od_control.user_lastread);
                              break;
                           case 'O':
                              od_printf("%u", od_control.user_security);
                              break;
                           case 'P':
                              od_printf("%u", od_control.user_numcalls);
                              break;
                           case 'Q':
                              od_printf("%ul", od_control.user_uploads);
                              break;
                           case 'R':
                              od_printf("%ul", od_control.user_upk);
                              break;
                           case 'S':
                              od_printf("%ul", od_control.user_downloads);
                              break;
                           case 'T':
                              od_printf("%ul", od_control.user_downk);
                              break;
                           case 'U':
                              od_printf("%d", od_control.user_time_used);
                              break;
                           case 'V':
                              od_printf("%d", od_control.user_screen_length);
                              break;
                           case 'W':
                              btRepeatCount = 0;
                              while(od_control.user_name[btRepeatCount])
                              {
                                 if((szToRepeat[btRepeatCount]
                                    = od_control.user_name[btRepeatCount])
                                    == ' ')
                                 {
                                    szToRepeat[btRepeatCount] = '\0';
                                    break;
                                 }
                                 ++btRepeatCount;
                              }
                              od_disp_str(szToRepeat);
                              break;
                           case 'X':
                              if(od_control.user_ansi)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case 'Y':
                              if(od_control.user_attribute & 0x04)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case 'Z':
                              if(od_control.user_attribute & 0x02)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case '0':
                              if(od_control.user_attribute & 0x40)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case '1':
                              if(od_control.user_attribute & 0x80)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case '2':
                              if(od_control.user_attrib2 & 0x01)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case '3':
                              od_disp_str(od_control.user_handle);
                              break;
                           case '4':
                              od_disp_str(od_control.user_firstcall);
                              break;
                           case '5':
                              od_disp_str(od_control.user_birthday);
                              break;
                           case '6':
                              od_disp_str(od_control.user_subdate);
                              break;
                           case '7':
                              /* days until subscrption expiry */
                              break;
                           case '8':
                              if(od_control.user_attrib2 & 0x02)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                              break;
                           case '9':
                              od_printf("%lu:%lu",
                                 od_control.user_uploads,
                                 od_control.user_downloads);
                              break;
                           case ':':
                              od_printf("%lu:%lu",
                                 od_control.user_upk,
                                 od_control.user_downk);
                              break;
                           case ';':
                              if(od_control.user_attrib2 & 0x04)
                              {
                                 od_disp_str("ON");
                              }
                              else
                              {
                                 od_disp_str("OFF");
                              }
                        }
                        btAvatarSeqLevel=0;
                        break;

                     /* QBBS/RA ^K control codes. */
                     case 22:
                        bEchoThisChar = FALSE;
                        switch(chCurrent)
                        {
                           case 'A':
                              od_printf("%lu", od_control.system_calls);
                              break;
                           case 'B':
                              od_disp_str(od_control.system_last_caller);
                              break;
                           case 'C':
                              /* number of active messages */
                              break;
                           case 'D':
                              /* system starting message number */
                              break;
                           case 'E':
                              /* system ending message number */
                              break;
                           case 'F':
                              /* number of times user has paged sysop */
                              break;
                           case 'G':
                              /* day of the week (Monday, Tuesday, etc.) */
                              break;
                           case 'H':
                              /* number of users in user file */
                              break;
                           case 'I':
                              /* Time in 24 hour format */
                              break;
                           case 'J':
                              /* today's date */
                              break;
                           case 'K':
                              /* minutes connected this call */
                              break;
                           case 'L':
                              /* Seconds connected (0) */
                              break;
                           case 'M':
                              od_printf("%d", od_control.user_time_used);
                              break;
                           case 'N':
                              od_disp_str("00");
                              break;
                           case 'O':
                              /* Minutes remaining today */
                              od_printf("%d", od_control.user_timelimit);
                              break;
                           case 'P':
                              /* seconds remaining today (0) */
                              break;
                           case 'Q':
                              od_printf("0", od_control.user_timelimit);
                              break;
                           case 'R':      /* current baud rate */
                              od_printf("0", od_control.baud);
                              break;
                           case 'S':
                              /* day of the week (MON, TUE) */
                              break;
                           case 'T':
                              /* Daily download limit (in K) */
                              break;
                           case 'U':
                              /* Minutes until next system event */
                              break;
                           case 'V':
                              ODScrnDisplayString(od_control.event_starttime);
                              break;
                           case 'W':
                              /* line number (from command line) */
                              break;
                           case 'X':
                              od_exit(2, TRUE);
                              break;
                           case 'Y':
                              /* Name of current msg area */
                              break;
                           case 'Z':
                              /* name of current file area */
                              break;
                           case '0':
                              /* # of messages in area */
                              break;
                           case '1':
                              /* # of message area */
                              break;
                           case '2':
                              /* # of file area */
                              break;
                        }
                        btAvatarSeqLevel = 0;
                  }
            }
            break;

         case 1:
            switch(chCurrent)
            {
               case '[':
                  btANSISeqLevel = 2;
                  btCurrentParamLength = 0;
                  btNumParams = 0;
                  break;

               default:
                  btANSISeqLevel = 0;
                  ODScrnDisplayChar(27);
                  ODScrnDisplayChar(chCurrent);
            }
            break;

         default:
            if((chCurrent >= '0' && chCurrent <= '9') || chCurrent == '?')
            {
               if(btCurrentParamLength < 3)
               {
                  szCurrentParam[btCurrentParamLength] = chCurrent;
                  szCurrentParam[++btCurrentParamLength] = '\0';
               }
               else
               {
                  btANSISeqLevel = 0;
               }
            }

            else if(chCurrent == ';')
            {
               if(btNumParams < 10)
               {
                  if(btCurrentParamLength != 0)
                  {
                     if(strcmp(szCurrentParam, "?9") == 0)
                     {
                        anANSIParams[btNumParams] = -2;
                     }
                     else
                     {
                        anANSIParams[btNumParams] = atoi(szCurrentParam);
                     }
                     szCurrentParam[0] = '\0';
                     btCurrentParamLength = 0;
                     ++btNumParams;
                  }
                  else
                  {
                     anANSIParams[btNumParams++] = -1;
                  }
               }
               else
               {
                  btANSISeqLevel = 0;
               }
            }

            else
            {
               btANSISeqLevel = 0;

               if(btCurrentParamLength != 0 && btNumParams < 10)
               {
                  if(strcmp(szCurrentParam,"?9") == 0)
                  {
                     anANSIParams[btNumParams] = -2;
                  }
                  else
                  {
                     anANSIParams[btNumParams] = atoi(szCurrentParam);
                  }
                  szCurrentParam[0] = '\0';
                  btCurrentParamLength = 0;
                  ++btNumParams;
               }

               ODScrnGetTextInfo(&TextInfo);

               switch(chCurrent)
               {
                  case 'A':
                     if(btNumParams == 0) anANSIParams[0] = 1;
                     if((nTemp = TextInfo.cury - anANSIParams[0]) < 1)
                     {
                        nTemp = 1;
                     }
                     if(nTemp > 25) nTemp=25;
                     ODScrnSetCursorPos(TextInfo.curx, (BYTE)nTemp);
                     break;

                  case 'B':
                     if(btNumParams == 0) anANSIParams[0] = 1;
                     if((nTemp = TextInfo.cury + anANSIParams[0]) > 25)
                     {
                        nTemp = 25;
                     }
                     if(nTemp < 1) nTemp = 1;
                     ODScrnSetCursorPos(TextInfo.curx, (BYTE)nTemp);
                     break;

                  case 'C':
                     if(btNumParams == 0) anANSIParams[0] = 1;
                     if((nTemp=TextInfo.curx + anANSIParams[0]) > 80)
                     {
                        nTemp = 80;
                     }
                     if(nTemp < 1) nTemp = 1;
                     ODScrnSetCursorPos((BYTE)nTemp, TextInfo.cury);
                     break;

                  case 'D':
                     if(btNumParams == 0) anANSIParams[0] = 1;
                     if((nTemp = TextInfo.curx - anANSIParams[0]) < 1)
                     {
                        nTemp = 1;
                     }
                     if(nTemp > 80) nTemp = 80;
                     ODScrnSetCursorPos((BYTE)nTemp, TextInfo.cury);
                     break;

                  case 'H':
                  case 'f':
                     if(btNumParams >= 2)
                     {
                        if(anANSIParams[0] == -1)
                        {
                           ODScrnSetCursorPos((BYTE)anANSIParams[1], 1);
                        }
                        else
                        {
                           ODScrnSetCursorPos((BYTE)anANSIParams[1],
                              (BYTE)anANSIParams[0]);
                        }
                     }
                     else if(btNumParams == 1)
                     {
                        if(anANSIParams[0] <= 0)
                        {
                           ODScrnSetCursorPos(1, TextInfo.cury);
                        }
                        else
                        {
                           ODScrnSetCursorPos(1, (BYTE)anANSIParams[0]);
                        }
                     }
                     else /* if(num_params==0) */
                     {
                        ODScrnSetCursorPos(1, 1);
                     }
                     break;

                  case 'J':
                     if(btNumParams >= 1 && anANSIParams[0] == 2)
                     {
                        /* Clear entire screen. */
                        ODScrnClear();
                     }
                     else if(btNumParams == 0 || anANSIParams[0] == 0)
                     {
                        /* Not supported - Clears from cursor to end of */
                        /* screen.                                      */
                     }
                     else if(btNumParams>=1 && anANSIParams[0]==1)
                     {
                        /* Not supported - Clears from beginning of screen to */
                        /* cursor.                                            */
                     }
                     break;

                  case 'K':
                     if(btNumParams == 0 || anANSIParams[0] == 0)
                     {
                        /* Clear to end of line. */
                        ODScrnClearToEndOfLine();
                     }
                     else if(btNumParams >= 1 && anANSIParams[0] == 1)
                     {
                        /* Not supported - should clear to beginning of line. */
                     }
                     else if(btNumParams >= 1 && anANSIParams[0] == 2)
                     {
                        /* Not supported - should clear entire line. */
                     }
                     break;

                  case 'm':
                     for(nTemp = 0; nTemp < btNumParams; ++nTemp)
                     {
                        if(anANSIParams[nTemp] == 0)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute = 0x07));
                        }
                        else if(anANSIParams[nTemp] == 1)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = TextInfo.attribute | 0x08));
                        }
                        else if(anANSIParams[nTemp] == 2)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = TextInfo.attribute & (~0x08)));
                        }
                        else if(anANSIParams[nTemp] == 4)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = (TextInfo.attribute & 0xf8) | (1)));
                        }
                        else if(anANSIParams[nTemp] == 5)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = TextInfo.attribute | 0x80));
                        }
                        else if(anANSIParams[nTemp] == 7)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = (TextInfo.attribute << 4)
                              | (TextInfo.attribute >> 4)));
                        }
                        else if(anANSIParams[nTemp] == 8)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = (TextInfo.attribute & 0xf0)
                              | ((TextInfo.attribute >> 4) & 0x07)));
                        }
                        else if(anANSIParams[nTemp] >= 30
                           && anANSIParams[nTemp] <= 37)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                              = TextInfo.attribute
                              = (TextInfo.attribute & 0xf8)
                              + abtANSIToPCColorTable[
                              (anANSIParams[nTemp] - 30)]));
                        }
                        else if(anANSIParams[nTemp] >= 40
                           && anANSIParams[nTemp]<=47)
                        {
                           ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib
                            = TextInfo.attribute
                            = (TextInfo.attribute & 0x8f)
                            + (abtANSIToPCColorTable[
                            anANSIParams[nTemp] - 40] << 4)));
                        }
                     }
                     break;

                  case 's':
                     btSavedColumn = TextInfo.curx;
                     btSavedRow = TextInfo.cury;
                     break;

                  case 'u':
                     ODScrnSetCursorPos(btSavedColumn, btSavedRow);
                     break;

                  case '@':
                     /* Not supported - inserts spaces at cursor. */
                     break;

                  case 'P':
                     /* Not supported - deletes characters at cursor. */
                     break;

                  case 'L':
                     /* Not supported - inserts lines at cursor. */
                     break;

                  case 'M':
                     /* Not supported - deletes lines at cursor. */
                     break;

                  case 'r':
                     /* Not supported - sets scrolling zone - 1st param is */
                     /* top row, 2nd param is bottom row. Cursor may go    */
                     /* outside zone, but no scrolling occurs there.       */
                     /* Also resets cursor to row 1, column 1.             */
                     /* If only one param, bottom row is bottom of screen. */
                     break;

                  case 'h':
                     if(btNumParams >= 1 && anANSIParams[0] == 4)
                     {
                        /* Not suppored - turn insert mode on. */
                     }
                     else if(btNumParams >= 1 && anANSIParams[0] == -2)
                     {
                        /* Home cursor. */
                        ODScrnSetCursorPos(1, 1);
                     }
                     break;

                  case 'l':
                     if(btNumParams >= 1 && anANSIParams[0] == 4)
                     {
                        /* Not suppored - turn insert mode off. */
                     }
                     break;

                  case 'E':
                     /* Not supported - repeat CRLF specified # of times. */
                     break;

                  case 'F':
                     /* Not supported - repeat reverse CRLF specified # */
                     /* of times. Also not suppored ESC M - reverse     */
                     /* linefeed, ESC D - LF, ESC E - CRLF              */
                     break;
               }
            }
      }

      if(bEchoThisChar && od_control.baud != 0)
      {
         ODComSendByte(hSerialPort, chCurrent);
      }

      ++pszBuffer;
   }
}


/* ----------------------------------------------------------------------------
 * ODEmulateFillArea()                                 *** PRIVATE FUNCTION ***
 *
 * Fills an area of the local screen with the specified character, in the
 * current display color.
 *
 * Parameters: btLeft       - The left column of the area to fill.
 *
 *             btTop        - The top row of the area to fill.
 *
 *             btRight      - The right column of the area to fill.
 *
 *             btBottom     - The bottom row of the area to fill.
 *
 *             chToFillWith - Character to fill in the specified area with.
 *
 *     Return: void
 */
static void ODEmulateFillArea(BYTE btLeft, BYTE btTop, BYTE btRight,
   BYTE btBottom, char chToFillWith)
{
   BYTE btCount;
   BYTE btLast;
   static char szTemp[81];
   static tODScrnTextInfo TextInfo;

   ODScrnGetTextInfo(&TextInfo);

   btLast = btRight - btLeft;

   for(btCount=0; btCount <= btLast; ++btCount)
   {
      szTemp[btCount] = chToFillWith;
   }
   szTemp[btCount] = 0;

   ODScrnEnableScrolling(FALSE);

   for(btCount = btTop; btCount <= btBottom; ++btCount)
   {
      ODScrnSetCursorPos(btLeft, btCount);
      ODScrnDisplayString(szTemp);
   }

   ODScrnSetCursorPos(TextInfo.curx, TextInfo.cury);

   ODScrnEnableScrolling(TRUE);
}