Prechádzať zdrojové kódy

Now using Odoors!

  Goodbye MagiDoor, Hello OpenDoors.

  We now have -local as odoors local mode... seems to not like ansis
though for some reason... my guess is it can't tell screen size that
well.
david 4 rokov pred
rodič
commit
f8f1490b68
100 zmenil súbory, kde vykonal 62382 pridanie a 190 odobranie
  1. 2 2
      CMakeLists.txt
  2. 177 188
      main.c
  3. 3 0
      odoors/.vscode/settings.json
  4. 20 0
      odoors/CMakeLists.txt
  5. 341 0
      odoors/DOOR.CFG
  6. 12 0
      odoors/DORINFO1.DEF
  7. 422 0
      odoors/DOS.mak
  8. 196 0
      odoors/GNUmakefile
  9. BIN
      odoors/ODApp.ico
  10. 214 0
      odoors/ODAuto.c
  11. 832 0
      odoors/ODBlock.c
  12. 933 0
      odoors/ODCFile.c
  13. 664 0
      odoors/ODCmdLn.c
  14. 3705 0
      odoors/ODCom.c
  15. 137 0
      odoors/ODCom.h
  16. 1620 0
      odoors/ODCore.c
  17. 109 0
      odoors/ODCore.h
  18. 187 0
      odoors/ODDrBox.c
  19. 1239 0
      odoors/ODEdStr.c
  20. 2650 0
      odoors/ODEdit.c
  21. 2212 0
      odoors/ODEmu.c
  22. 1759 0
      odoors/ODFrame.c
  23. 58 0
      odoors/ODFrame.h
  24. 199 0
      odoors/ODGen.h
  25. 511 0
      odoors/ODGetIn.c
  26. 247 0
      odoors/ODGraph.c
  27. 450 0
      odoors/ODInEx.h
  28. 2536 0
      odoors/ODInEx1.c
  29. 1434 0
      odoors/ODInEx2.c
  30. 443 0
      odoors/ODInQue.c
  31. 57 0
      odoors/ODInQue.h
  32. BIN
      odoors/ODInfo.ico
  33. 1675 0
      odoors/ODKrnl.c
  34. 88 0
      odoors/ODKrnl.h
  35. 565 0
      odoors/ODList.c
  36. 258 0
      odoors/ODLog.c
  37. 255 0
      odoors/ODMulti.c
  38. 104 0
      odoors/ODOORS62.TXT
  39. 207 0
      odoors/ODPCB.c
  40. 1530 0
      odoors/ODPlat.c
  41. 203 0
      odoors/ODPlat.h
  42. 716 0
      odoors/ODPopup.c
  43. 197 0
      odoors/ODPrntf.c
  44. 619 0
      odoors/ODRA.c
  45. BIN
      odoors/ODRes.aps
  46. 78 0
      odoors/ODRes.h
  47. 197 0
      odoors/ODRes.rc
  48. 2554 0
      odoors/ODScrn.c
  49. 110 0
      odoors/ODScrn.h
  50. 1102 0
      odoors/ODSpawn.c
  51. 226 0
      odoors/ODStand.c
  52. 197 0
      odoors/ODStat.c
  53. 49 0
      odoors/ODStat.h
  54. 55 0
      odoors/ODStr.c
  55. 42 0
      odoors/ODStr.h
  56. 1453 0
      odoors/ODSwap.asm
  57. 68 0
      odoors/ODSwap.h
  58. 70 0
      odoors/ODTypes.h
  59. 461 0
      odoors/ODUtil.c
  60. 66 0
      odoors/ODUtil.h
  61. 270 0
      odoors/ODWCat.c
  62. 319 0
      odoors/ODWin.c
  63. BIN
      odoors/ODoorW.lib
  64. 14805 0
      odoors/OPENDOOR.TXT
  65. 99 0
      odoors/OpenDoor.def
  66. 1123 0
      odoors/OpenDoor.h
  67. 0 0
      odoors/README.NIX
  68. 36 0
      odoors/TODO-v7.txt
  69. BIN
      odoors/Toolbar.bmp
  70. 7 0
      odoors/buildall.bat
  71. 5 0
      odoors/builddos.bat
  72. 346 0
      odoors/ex_chat.c
  73. 578 0
      odoors/ex_diag.c
  74. 42 0
      odoors/ex_hello.c
  75. 153 0
      odoors/ex_music.c
  76. 602 0
      odoors/ex_ski.c
  77. 1294 0
      odoors/ex_vote.c
  78. 1 0
      odoors/hbuild.bat
  79. 809 0
      odoors/historic/ODHIST.TXT
  80. 70 0
      odoors/historic/ODN.FRM
  81. 170 0
      odoors/historic/ODN.NFO
  82. 447 0
      odoors/historic/ODTJ9304.TXT
  83. 724 0
      odoors/historic/ODTJ9305.TXT
  84. 2244 0
      odoors/historic/ODTJ9402.TXT
  85. 173 0
      odoors/historic/ROLLCALL.TXT
  86. 25 0
      odoors/historic/odtips3/BPFIND.H
  87. 342 0
      odoors/historic/odtips3/CMDLINE.C
  88. 5 0
      odoors/historic/odtips3/CMDLINE.H
  89. 352 0
      odoors/historic/odtips3/FILEVIEW.C
  90. 177 0
      odoors/historic/odtips3/PAGEVIEW.C
  91. 19 0
      odoors/historic/odtips3/PAGEVIEW.H
  92. 46 0
      odoors/historic/odtips3/SUMMARY.TXT
  93. 88 0
      odoors/historic/odtips3/TIP1.C
  94. 55 0
      odoors/historic/odtips3/TIP1.TXT
  95. 245 0
      odoors/historic/odtips3/TIP2.C
  96. 45 0
      odoors/historic/odtips3/TIP2.TXT
  97. 82 0
      odoors/historic/odtips3/TIP3.TXT
  98. 19 0
      odoors/historic/odtips3/TIP4.C
  99. 50 0
      odoors/historic/odtips3/TIP4.TXT
  100. 1 0
      odoors/lbuild.bat

+ 2 - 2
CMakeLists.txt

@@ -5,9 +5,9 @@ project(testdoors)
 set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
 find_package(SQLite3 REQUIRED)
 
-add_subdirectory(MagiDoor)
+add_subdirectory(odoors)
 include_directories(${SQLITE3_INCLUDE_DIRS})
 
 add_executable(main main.c)
-target_link_libraries(main mdoor ${SQLITE3_LIBRARIES})
+target_link_libraries(main odoors ${SQLITE3_LIBRARIES})
 

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 177 - 188
main.c


+ 3 - 0
odoors/.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
+}

+ 20 - 0
odoors/CMakeLists.txt

@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.0)
+
+project(odoors 
+ VERSION 6.24
+  LANGUAGES C)
+
+# file(GLOB ODOORS_SRC *.c)
+
+set(HEADERS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(HEADERS OpenDoor.h)
+
+set(SOURCE_FILES ODAuto.c ODBlock.c ODCFile.c ODCmdLn.c ODCom.c ODCore.c 
+ODDrBox.c ODEdit.c ODEdStr.c ODEmu.c ODGetIn.c ODGraph.c ODInEx1.c
+ODInEx2.c ODInQue.c ODKrnl.c ODList.c ODLog.c ODMulti.c ODPlat.c 
+ODPCB.c ODPopup.c ODPrntf.c ODRA.c ODScrn.c ODSpawn.c ODStand.c 
+ODStat.c ODUtil.c ODWCat.c ODWin.c ODStr.c )
+
+add_library(odoors STATIC ${SOURCE_FILES} )
+
+target_include_directories(odoors PUBLIC $<BUILD_INTERFACE:${HEADERS_DIR}>)

+ 341 - 0
odoors/DOOR.CFG

@@ -0,0 +1,341 @@
+; DOOR.CFG - Sample OpenDoors door configuration file
+;
+; This configuration file can be used by the sysop to customize an OpenDoors
+; door for use on their own system. The information in this file is NOT usually
+; needed, and the file can be left as is, or even erased, without effecting the
+; door's performance. OpenDoors is designed to run on almost any BBS system
+; automatically, without requiring anything but to the door's .EXE file.
+; However, there are many cases where the sysop may wish to customize a door's
+; operation using this configuration file. The configuration file system is
+; provided to allow the customization of options such as paging hours, maximum
+; time permitted within the door, etc., and to allow OpenDoors doors to be run
+; under even the most non-typical BBS setups.
+;
+; Any text following a semi-colon (;), and blank lines, are ignored.
+;
+;------------------------------------------------------------------------------
+;
+;  BBS system directory. Indicates where the door information file (drop file)
+;  can be found. Remove the semi-colon (;) to activate this option.
+;
+;BBSDir    C:\BBS
+;
+;------------------------------------------------------------------------------
+;
+;  The door's working directory. This is where the door's system files are
+;  located. Remove the semi-colon (;) to activate this option.
+;
+;DoorDir    C:\BBS\MYDOOR
+;
+;------------------------------------------------------------------------------
+;
+;  Local mode override. Forces door to always operate in local test mode.
+;  Remove the semi-colon (;) to activate this option. When this mode is
+;  activated, no door information file is required and default settings are
+;  used for the user's name, location, etc.
+;
+;LocalMode
+;
+;------------------------------------------------------------------------------
+;
+;  Door personality setting. This setting selects one of a number of sysop
+;  interface personalities. Each personality setting emulates the status line
+;  format and sysop function keys of a particular BBS package. Valid
+;  personality settings are:
+;
+;                     Standard            (OpenDoors style, simplified from RA)
+;                     PCBoard
+;                     RemoteAccess
+;                     Wildcat
+;
+Personality    Standard
+;
+;------------------------------------------------------------------------------
+;
+;  Log File options. "LogFileName" specifies filename (path optional) where the
+;  door should record log information. To disable the log file altogether,
+;  remove the semi-colon (;) from the "DisableLogging" line.
+;
+;LogFileName    DOOR.LOG
+;DisableLogging
+;
+;------------------------------------------------------------------------------
+;
+;  BBS node number that door is running on. Only used if OpenDoors is unable
+;  to determine the node number by some other means.
+;
+Node    1
+;
+;------------------------------------------------------------------------------
+;
+;  Sysop paging hours. Sysop paging will be permitted beginning at the start
+;  time, up until, but not including, the end time. Times should be in 24-hour
+;  format. To disable paging on a particular day, set the paging start and end
+;  times to the same time. To make paging always available, set the start time
+;  to 0:00 and the end time to 23:59.
+;
+;                      Start Time    End Time
+SundayPagingHours         9:00        22:00
+MondayPagingHours         8:30        22:00
+TuesdayPagingHours        8:30        22:00
+WednesdayPagingHours      8:30        22:00
+ThursdayPagingHours       8:30        22:00
+FridayPagingHours         8:30        22:00
+SaturdayPagingHours       9:00        22:00
+;
+;------------------------------------------------------------------------------
+;
+;  Duration of sysop page. Value indicates the number of beeps that compose the
+;  sysop page alarm, with one beep sounded per second.
+;
+PageDuration    10
+;
+;------------------------------------------------------------------------------
+;
+;  Maximum length of time a user is permitted to access the door. If the user's
+;  total remaining time on the BBS is less than this value, the user will only
+;  be permitted to access the door for this shorter length of time. This option
+;  may be disabled by placing a semi-colon (;) at the beginning of the line.
+;  When this option is disabled, the user will be permitted to use the full
+;  of their remaining time on the BBS within the door.
+;
+;MaximumDoorTime    15
+;
+;------------------------------------------------------------------------------
+;
+;  Inactivity timeout. Specifies the maximum number of seconds that may elapse
+;  without the user pressing any key, before the user will be automatically
+;  disconnected. A value of 0 disables inactivity timeouts.
+;
+InactivityTimeout    200
+;
+;------------------------------------------------------------------------------
+;
+;  Name of the sysop. OpenDoors can usually determine the sysop's name from the
+;  information passed to the door by the BBS. However, some BBS software does
+;  not supply this information to doors. In such cases, if the sysop's name is
+;  required by the door, it may be supplied here. Remove the semi-colon (;) to
+;  activate this option.
+;
+;SysopName    The Sysop
+;
+;------------------------------------------------------------------------------
+;
+;  Name of the BBS. OpenDoors can usually determine the name of the BBS from
+;  the information passed to the door by the BBS. However, some BBS software
+;  does not supply this information to door programs. In such cases, if the
+;  name of the BBS is needed by the door, it may be supplied here. Remove the
+;  semi-colon (;) to activate this option.
+;
+;SystemName    Unnamed BBS
+;
+;------------------------------------------------------------------------------
+;
+;  Door colour options. These options specify the various text colours that
+;  will be used by the door if ANSI or AVATAR graphics modes are available.
+;  Colours are specified in the format:
+;
+;         {Bright} {Flashing} [Foreground Colour] on [Background Colour]
+;
+;  Where foreground and background colours are one of:
+;
+;         Black
+;         Blue
+;         Green
+;         Cyan
+;         Red
+;         Magenta
+;         Yellow / Brown
+;         White / Grey
+;
+;  Note that some of these options (such as those that pertain to files
+;  listings) are not used for all doors.
+;
+ChatUserColour               Bright white on black
+ChatSysopColour              Bright red on black
+FileListTitleColour          Bright yellow on black
+FileListNameColour           Bright yellow on black
+FileListSizeColour           Bright magenta on black
+FileListDescriptionColour    Cyan on black
+FileListOfflineColour        Bright red on black
+PagePromptColour             Bright white on black
+PopupMenuTitleColour         Bright white on grey
+PopupMenuBorderColour        Black on grey
+PopupMenuTextColour          Black on grey
+PopupMenuKeyColour           Red on grey
+PopupMenuHighlightColour     Grey on black
+PopupMenuHighKeyColour       Red on black
+;
+;------------------------------------------------------------------------------
+;
+;  Memory swapping options. These options are generally not needed, but can be
+;  used to customize OpenDoor's swapping behaviour. "SwappingDir" can be used
+;  to specify which directory or directories should be used for swapping.
+;  Multiple directory paths can be seperated using a semi-colon.
+;  "SwappingNoEMS" can be used to prevent any swapping from being done to EMS
+;  memory, and "SwappingDisable" can be used to disable memory swapping
+;  altogether. Remove the semi-colon (;) to activate any of these options.
+;
+;SwappingDir    C:\
+;SwappingNoEMS
+;SwappingDisable
+;
+;------------------------------------------------------------------------------
+;
+;  Serial port options. These options are generally not needed, as these
+;  settings can usually be determined from the BBS door information file.
+;  "LockedBPS" specifies the the BPS rate at which the door should communicate
+;  with the modem. "SerialPort" specifies the port port number that the modem
+;  is connected to. Unless you have reassigned the port numbers through your
+;  FOSSIL drive, port 0 corresponds to COM1, port 1 corresponds to COM2, and
+;  so on. Remove the semi-colon (;) to activate either of these options.
+;
+;LockedBPS    38400
+;SerialPort    0
+;
+;
+;  Under DOS, a FOSSIL driver is normally used for serial I/O if one is
+;  available. If a FOSSIL driver has not been loaded, the door communicates
+;  directly with the modem. Removing the semi-colon (;) from the "NoFossil"
+;  option causes the door to always communicate directly with the modem,
+;  bypassing any FOSSIL driver.
+;
+;NoFossil
+;
+;------------------------------------------------------------------------------
+;
+;  The following options only apply for the MS-DOS version of this program,
+;  and only if a FOSSIL driver is NOT being used for serial communications.
+;  If a FOSSIL driver IS being used, these options are normally set on the
+;  FOSSIL driver command line. Under Windows, these options are set in the
+;  control panel.
+;
+;  Hexidecimal address of the serial port. This address can usually be
+;  determined automatically for ports COM1, COM2, COM3, and COM4, and is
+;  normally only required for ports COM5 and higher. Remove the semi-colon
+;  (;) to activate this option.
+;
+;PortAddress 2F8
+;
+;
+;  Interrupt request line that the serial port is using. May be any IRQ line
+;  from 1 to 15. By default, IRQ line 4 is used for ports COM1: and COM3:,
+;  while IRQ line 3 is used for all other ports. Remove the semi-colon (;)
+;  to activate this option.
+;
+;PortIRQ 4
+;
+;
+;  Serial I/O buffer sizes. "ReceiveBuffer" specifies the number of bytes in
+;  the serial I/O receive buffer. You may want to increase this buffer size
+;  if you find that characters being sent from the user's modem are being lost.
+;  "TransmitBuffer" specifies the number of bytes in the serial I/O transmit
+;  buffer. You may want to increase this buffer size for improved performance
+;  in some multitasking environments. A transmit buffer size smaller than 3072
+;  bytes is not recommended.
+;
+ReceiveBuffer 256
+TransmitBuffer 3072
+;
+;
+;  UART FIFO buffers. Normally, OpenDoors will use 16550A UART FIFO buffers
+;  if they are available. You can prevent OpenDoors from using the FIFO
+;  buffers, even if they are available, by removing the semi-colon before
+;  the "NoFIFO" keyword. The "FIFOTriggerSize" specifies how many characters
+;  may be placed in the FIFO buffers before an serial I/O interrupt is
+;  envoked. Valid values are 1, 4, 8 and 14 bytes. The default value is 4
+;  bytes.
+;
+;NoFIFO
+FIFOTriggerSize 4
+;
+;------------------------------------------------------------------------------
+;
+;  The following options control if and how the Windows version of this program
+;  disables DTR response by the modem prior to exiting. Normally, the sequence
+;  of modem commands specified by DisableDTR is sent before exiting, to prevent
+;  the modem from hanging up. To disable this feature, remove the semi-colon
+;  (;) at the beginning of the NoDTRDisable line.
+;
+;  DisableDTR specifies a series of commands to be sent to the modem, and
+;  responses to be received by the modem. Each command and response is
+;  separated by a space. A tilde (~) character denotes a one second pause, and
+;  a pipe (|) denotes a CR.
+;
+;NoDTRDisable
+DisableDTR ~+++ OK AT&D0| OK ATO|
+;
+;------------------------------------------------------------------------------
+;
+;  Custom door information file support. OpenDoors automatically recognizes
+;  most door information file (drop file) formats, including DORINFO?.DEF,
+;  EXITINFO.BBS, DOOR.SYS, SFDOORS.DAT, CALLINFO.BBS and CHAIN.TXT. However,
+;  to permit OpenDoors doors to operate on BBS systems that produce a different
+;  format file, you may define a custom door information file format. A custom
+;  door information file format is defined using the "CustomFileName" command,
+;  followed by one or more lines beginning with the "CustomFileLine" command.
+;
+;  The "CustomFileName" option specifies the filename used to distinguish this
+;  file format from other file formats. This filename should not include a
+;  path. To specify the path where the door information file is located, use
+;  the BBSDir setting, near the beginning of this file. If the filename of the
+;  custom format is the same as that of one of the built-in formats, the custom
+;  format will override the built-in format.
+;
+;  The actual format of the custom file is specified using a number of lines
+;  that begin with the keyword "CustomFileLine". Each of these lines will
+;  correspond to a single line in the door information file, with the option
+;  following the "CustomFileLine" keyword specifying the information that can
+;  be found on that line. This can be one of the following keywords:
+;
+;                    Ignore - Causes the next line in the door information
+;                             file to be ignored. Use on lines for which none
+;                             of the options below apply.
+;                   ComPort - COM? port the modem is connected to
+;                             (0 indicates local mode)
+;                FossilPort - Fossil port number the modem is connected to
+;                  ModemBPS - BPS rate at which to communicate with modem
+;                             (0 or non-numerical value indicates local mode)
+;                 LocalMode - 1, T or Y if door is operating in local mode
+;                  UserName - Full name of the user
+;             UserFirstName - First name(s) of the user
+;              UserLastName - Last name of the user
+;                     Alias - The user's psuedonym / handle
+;                 HoursLeft - Hours user has left online
+;               MinutesLeft - Minutes user has left online, or time left online
+;                             in format hh:mm
+;               SecondsLeft - Seconds user has left online, or time left online
+;                             in format hh:mm:ss or format mm:ss
+;                             (If more than one of the above time options are
+;                             used, the user time left is taken to be the total
+;                             of all of these values.)
+;                      ANSI - 1, T, Y or G for ANSI graphics mode
+;                    AVATAR - 1, T or Y for AVATAR graphics mode
+;                       RIP - 1, T or Y for RIP graphics mode
+;               PagePausing - 1, T or Y if user wishes a pause at end of screen
+;              ScreenLength - Number of lines on user's screen
+;            ScreenClearing - 1, T or Y if screen clearing mode is on
+;                  Security - The user's security level / access level
+;                      City - City the user is calling from
+;                      Node - Node number user is connected to
+;                 SysopName - Full name of the sysop
+;            SysopFirstName - The sysop's first name(s)
+;             SysopLastName - The sysop's last name
+;                SystemName - Name of the BBS
+;
+;
+CustomFileName    EXAMPLE.DEF                     ; Same format as DORINFO?.DEF
+CustomFileLine    SystemName
+CustomFileLine    SysopFirstName
+CustomFileLine    SysopLastName
+CustomFileLine    ComPort
+CustomFileLine    ModemBPS
+CustomFileLine    Ignore
+CustomFileLine    UserFirstName
+CustomFileLine    UserLastName
+CustomFileLine    City
+CustomFileLine    ANSI
+CustomFileLine    Security
+CustomFileLine    MinutesLeft
+;
+;------------------------------------------------------------------------------

+ 12 - 0
odoors/DORINFO1.DEF

@@ -0,0 +1,12 @@
+TEST BBS
+THE
+SYSOP
+COM0
+0 BAUD,N,8,1
+0
+TEST
+USER
+UNKNOWN LOCATION
+1
+10
+60

+ 422 - 0
odoors/DOS.mak

@@ -0,0 +1,422 @@
++#  OpenDoors 6.10
+#  (C) Copyright 1991 - 1997 by Brian Pirie. All Rights Reserved.
+#
+#
+#         File: DOS.mak
+#
+#  Description: Makefile used to build the MS-DOS OpenDoors libraries from
+#               the sources. Usage is described below.
+#
+#    Revisions: Date          Ver   Who  Change
+#               ---------------------------------------------------------------
+#               Oct 13, 1994  6.00  BP   New file header format.
+#               Oct 13, 1994  6.00  BP   Made directories configurable.
+#               Oct 13, 1994  6.00  BP   Erase tlib-created backup file.
+#               Oct 14, 1994  6.00  BP   Added ODGen.h dependencies.
+#               Oct 14, 1994  6.00  BP   Added ODPlat.c module.
+#               Oct 31, 1994  6.00  BP   Added headers dependency constant.
+#               Nov 01, 1994  6.00  BP   Added ODUtil.c module.
+#               Dec 31, 1994  6.00  BP   Added -B option for Borland Cs.
+#               Jan 01, 1995  6.00  BP   Added ODKrnl.c, ODKrnl.h.
+#               Jan 29, 1995  6.00  BP   Added ODCmdLn.c.
+#               Nov 16, 1995  6.00  BP   Added ODInQue.c, and new headers.
+#               Nov 21, 1995  6.00  BP   Created ODInit1.c, ODInit2.c.
+#               Dec 02, 1995  6.00  BP   Added ODRes.h
+#               Dec 02, 1995  6.00  BP   Added ODFrame.c, ODFrame.h.
+#               Dec 02, 1995  6.00  BP   Added ODStat.h, ODSwap.h.
+#               Dec 04, 1995  6.00  BP   Changes for building Win32 version.
+#               Dec 05, 1995  6.00  BP   Split into makefiles for each platform
+#               Dec 07, 1995  6.00  BP   Added ODEdit.c.
+#               Jan 04, 1996  6.00  BP   Added ODGetIn.c.
+#               Feb 09, 1996  6.00  BP   Renamed ODInit?.* to ODInEx?.*
+#               Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+#               Mar 03, 1996  6.10  BP   Begin version 6.10.
+#
+###############################################################################
+#
+# USAGE INFORMATION
+#
+###############################################################################
+#
+# Command Line:   make -fDOS.mak -DTARGET=?
+#                     or
+#                 nmake -fDOS.mak "TARGET=?"
+#
+# Where:  "TARGET=?"   - Specifies which version of the library should be
+#                        built. TARGET can be set to:
+#
+#                             t - MS-DOS version, tiny memory model
+#                             s - MS-DOS version, small memory model
+#                             c - MS-DOS version, compact memory model
+#                             m - MS-DOS version, medium memory model
+#                             l - MS-DOS version, large memory model
+#                             h - MS-DOS version, huge memory model
+#
+#                        Setting must be in lower case for Borland compilers,
+#                        uppercase for Microsoft compilers.
+#
+###############################################################################
+#
+# CONFIGURATION
+#
+# Customize this section of the makefile to provide the relevant information
+# for your compiler, assembler (if any) and build environment.
+#
+###############################################################################
+# Compiler executable file name. Use:
+#
+#                  tcc - For Borland Turbo C and Turbo C++
+#                  bcc - For Borland C++
+#                   cl - For Microsoft compilers
+#
+CC=tcc
+#
+#------------------------------------------------------------------------------
+#
+# Assembler executable file name. Use:
+#
+#                 tasm - For Turbo Assembler
+#                 masm - For Microsoft Macro Assembler
+#
+AS=tasm
+#
+#------------------------------------------------------------------------------
+#
+# Library managment utility. Use:
+#
+#                 tlib - For Borland compilers
+#                  lib - For Microsoft compilers
+#
+LIB=tlib
+#
+#------------------------------------------------------------------------------
+#
+# MS-DOS compiler command-line flags. Use:
+#
+#   -m$(TARGET) -c -O -G -Z -d -B  - For Borland compilers including Turbo C
+#          /a$(TARGET) /c /nologo  - For Microsoft compilers
+#
+CFLAGS=-m$(TARGET) -c -O -G -Z -d -Ic:\progra~1\tc\include
+#
+#------------------------------------------------------------------------------
+#
+# Assembler command-line flags.
+#
+AFLAGS=/mx
+ADEFLCODE=/dLCODE
+ADEFLDATA=/dLDATA
+#
+#------------------------------------------------------------------------------
+#
+# Output directories. customize for your own preferences. Note that trailing
+# backslash (\) characters are required.
+#
+SOURCEDIR=.\                                               # Comments required
+ODHEADERDIR=.\                                             # in order to
+OBJDIR=..\obj\                                             # avoid line
+LIBDIR=..\lib\                                             # concatentation
+#
+###############################################################################
+#
+# DEPENDENCIES
+#
+# You won't normally have to change anything after this point in this makefile.
+#
+###############################################################################
+#
+# Define primary target.
+#
+all: $(LIBDIR)ODoor$(TARGET).lib
+#
+#------------------------------------------------------------------------------
+#
+# Name of all headers.
+#
+HEADERS= $(HEADERDIR)ODCom.h\
+         $(HEADERDIR)ODCore.h\
+         $(HEADERDIR)ODFrame.h\
+         $(HEADERDIR)ODGen.h\
+         $(HEADERDIR)ODInEx.h\
+         $(HEADERDIR)ODInQue.h\
+         $(HEADERDIR)ODKrnl.h\
+         $(HEADERDIR)ODPlat.h\
+         $(HEADERDIR)ODRes.h\
+         $(HEADERDIR)ODScrn.h\
+         $(HEADERDIR)ODStat.h\
+         $(HEADERDIR)ODSwap.h\
+         $(HEADERDIR)ODTypes.h\
+         $(HEADERDIR)ODUtil.h\
+         $(HEADERDIR)OpenDoor.h
+#
+#------------------------------------------------------------------------------
+#
+# Build from C sources.
+#
+$(OBJDIR)odauto$(TARGET).obj : $(SOURCEDIR)odauto.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odauto.c
+   command /c erase $(OBJDIR)odauto$(TARGET).obj
+   move odauto.obj $(OBJDIR)odauto$(TARGET).obj
+
+$(OBJDIR)odblock$(TARGET).obj : $(SOURCEDIR)odblock.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odblock.c
+   command /c erase $(OBJDIR)odblock$(TARGET).obj
+   move odblock.obj $(OBJDIR)odblock$(TARGET).obj
+
+$(OBJDIR)odcfile$(TARGET).obj : $(SOURCEDIR)odcfile.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odcfile.c
+   command /c erase $(OBJDIR)odcfile$(TARGET).obj
+   move odcfile.obj $(OBJDIR)odcfile$(TARGET).obj
+
+$(OBJDIR)odcmdln$(TARGET).obj : $(SOURCEDIR)odcmdln.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odcmdln.c
+   command /c erase $(OBJDIR)odcmdln$(TARGET).obj
+   move odcmdln.obj $(OBJDIR)odcmdln$(TARGET).obj
+
+$(OBJDIR)odcom$(TARGET).obj : $(SOURCEDIR)odcom.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odcom.c
+   command /c erase $(OBJDIR)odcom$(TARGET).obj
+   move odcom.obj $(OBJDIR)odcom$(TARGET).obj
+
+$(OBJDIR)odcore$(TARGET).obj : $(SOURCEDIR)odcore.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odcore.c
+   command /c erase $(OBJDIR)odcore$(TARGET).obj
+   move odcore.obj $(OBJDIR)odcore$(TARGET).obj
+
+$(OBJDIR)oddrbox$(TARGET).obj : $(SOURCEDIR)oddrbox.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)oddrbox.c
+   command /c erase $(OBJDIR)oddrbox$(TARGET).obj
+   move oddrbox.obj $(OBJDIR)oddrbox$(TARGET).obj
+
+$(OBJDIR)odedit$(TARGET).obj : $(SOURCEDIR)odedit.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odedit.c
+   command /c erase $(OBJDIR)odedit$(TARGET).obj
+   move odedit.obj $(OBJDIR)odedit$(TARGET).obj
+
+$(OBJDIR)odedstr$(TARGET).obj : $(SOURCEDIR)odedstr.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odedstr.c
+   command /c erase $(OBJDIR)odedstr$(TARGET).obj
+   move odedstr.obj $(OBJDIR)odedstr$(TARGET).obj
+
+$(OBJDIR)odemu$(TARGET).obj : $(SOURCEDIR)odemu.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odemu.c
+   command /c erase $(OBJDIR)odemu$(TARGET).obj
+   move odemu.obj $(OBJDIR)odemu$(TARGET).obj
+
+$(OBJDIR)odgetin$(TARGET).obj : $(SOURCEDIR)odgetin.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odgetin.c
+   command /c erase $(OBJDIR)odgetin$(TARGET).obj
+   move odgetin.obj $(OBJDIR)odgetin$(TARGET).obj
+
+$(OBJDIR)odgraph$(TARGET).obj : $(SOURCEDIR)odgraph.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odgraph.c
+   command /c erase $(OBJDIR)odgraph$(TARGET).obj
+   move odgraph.obj $(OBJDIR)odgraph$(TARGET).obj
+
+$(OBJDIR)odinex1$(TARGET).obj : $(SOURCEDIR)odinex1.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odinex1.c
+   command /c erase $(OBJDIR)odinex1$(TARGET).obj
+   move odinex1.obj $(OBJDIR)odinex1$(TARGET).obj
+
+$(OBJDIR)odinex2$(TARGET).obj : $(SOURCEDIR)odinex2.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odinex2.c
+   command /c erase $(OBJDIR)odinex2$(TARGET).obj
+   move odinex2.obj $(OBJDIR)odinex2$(TARGET).obj
+
+$(OBJDIR)odinque$(TARGET).obj : $(SOURCEDIR)odinque.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odinque.c
+   command /c erase $(OBJDIR)odinque$(TARGET).obj
+   move odinque.obj $(OBJDIR)odinque$(TARGET).obj
+
+$(OBJDIR)odkrnl$(TARGET).obj : $(SOURCEDIR)odkrnl.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odkrnl.c
+   command /c erase $(OBJDIR)odkrnl$(TARGET).obj
+   move odkrnl.obj $(OBJDIR)odkrnl$(TARGET).obj
+
+$(OBJDIR)odlist$(TARGET).obj : $(SOURCEDIR)odlist.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odlist.c
+   command /c erase $(OBJDIR)odlist$(TARGET).obj
+   move odlist.obj $(OBJDIR)odlist$(TARGET).obj
+
+$(OBJDIR)odlog$(TARGET).obj : $(SOURCEDIR)odlog.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odlog.c
+   command /c erase $(OBJDIR)odlog$(TARGET).obj
+   move odlog.obj $(OBJDIR)odlog$(TARGET).obj
+
+$(OBJDIR)odmulti$(TARGET).obj : $(SOURCEDIR)odmulti.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odmulti.c
+   command /c erase $(OBJDIR)odmulti$(TARGET).obj
+   move odmulti.obj $(OBJDIR)odmulti$(TARGET).obj
+
+$(OBJDIR)odplat$(TARGET).obj : $(SOURCEDIR)odplat.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odplat.c
+   command /c erase $(OBJDIR)odplat$(TARGET).obj
+   move odplat.obj $(OBJDIR)odplat$(TARGET).obj
+
+$(OBJDIR)odpcb$(TARGET).obj : $(SOURCEDIR)odpcb.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odpcb.c
+   command /c erase $(OBJDIR)odpcb$(TARGET).obj
+   move odpcb.obj $(OBJDIR)odpcb$(TARGET).obj
+
+$(OBJDIR)odpopup$(TARGET).obj : $(SOURCEDIR)odpopup.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odpopup.c
+   command /c erase $(OBJDIR)odpopup$(TARGET).obj
+   move odpopup.obj $(OBJDIR)odpopup$(TARGET).obj
+
+$(OBJDIR)odprntf$(TARGET).obj : $(SOURCEDIR)odprntf.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odprntf.c
+   command /c erase $(OBJDIR)odprntf$(TARGET).obj
+   move odprntf.obj $(OBJDIR)odprntf$(TARGET).obj
+
+$(OBJDIR)odra$(TARGET).obj : $(SOURCEDIR)odra.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odra.c
+   command /c erase $(OBJDIR)odra$(TARGET).obj
+   move odra.obj $(OBJDIR)odra$(TARGET).obj
+
+$(OBJDIR)odscrn$(TARGET).obj : $(SOURCEDIR)odscrn.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odscrn.c
+   command /c erase $(OBJDIR)odscrn$(TARGET).obj
+   move odscrn.obj $(OBJDIR)odscrn$(TARGET).obj
+
+$(OBJDIR)odspawn$(TARGET).obj : $(SOURCEDIR)odspawn.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odspawn.c
+   command /c erase $(OBJDIR)odspawn$(TARGET).obj
+   move odspawn.obj $(OBJDIR)odspawn$(TARGET).obj
+
+$(OBJDIR)odstand$(TARGET).obj : $(SOURCEDIR)odstand.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odstand.c
+   command /c erase $(OBJDIR)odstand$(TARGET).obj
+   move odstand.obj $(OBJDIR)odstand$(TARGET).obj
+
+$(OBJDIR)odstat$(TARGET).obj : $(SOURCEDIR)odstat.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odstat.c
+   command /c erase $(OBJDIR)odstat$(TARGET).obj
+   move odstat.obj $(OBJDIR)odstat$(TARGET).obj
+
+$(OBJDIR)odsys$(TARGET).obj : $(SOURCEDIR)odsys.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odsys.c
+   command /c erase $(OBJDIR)odsys$(TARGET).obj
+   move odsys.obj $(OBJDIR)odsys$(TARGET).obj
+
+$(OBJDIR)odutil$(TARGET).obj : $(SOURCEDIR)odutil.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odutil.c
+   command /c erase $(OBJDIR)odutil$(TARGET).obj
+   move odutil.obj $(OBJDIR)odutil$(TARGET).obj
+
+$(OBJDIR)odwcat$(TARGET).obj : $(SOURCEDIR)odwcat.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odwcat.c
+   command /c erase $(OBJDIR)odwcat$(TARGET).obj
+   move odwcat.obj $(OBJDIR)odwcat$(TARGET).obj
+
+$(OBJDIR)odwin$(TARGET).obj : $(SOURCEDIR)odwin.c $(HEADERS)
+   $(CC) $(CFLAGS) $(SOURCEDIR)odwin.c
+   command /c erase $(OBJDIR)odwin$(TARGET).obj
+   move odwin.obj $(OBJDIR)odwin$(TARGET).obj
+#
+#------------------------------------------------------------------------------
+#
+# Build from assembly sources.
+#
+$(OBJDIR)odswapt.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswapt.obj
+   move odswap.obj $(OBJDIR)odswapt.obj
+
+$(OBJDIR)odswaps.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswaps.obj
+   move odswap.obj $(OBJDIR)odswaps.obj
+
+$(OBJDIR)odswapc.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(ADEFLDATA) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswapc.obj
+   move odswap.obj $(OBJDIR)odswapc.obj
+
+$(OBJDIR)odswapm.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(ADEFLCODE) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswapm.obj
+   move odswap.obj $(OBJDIR)odswapm.obj
+
+$(OBJDIR)odswapl.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(ADEFLDATA) $(ADEFLCODE) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswapl.obj
+   move odswap.obj $(OBJDIR)odswapl.obj
+
+$(OBJDIR)odswaph.obj : $(SOURCEDIR)odswap.asm
+   $(AS) $(AFLAGS) $(ADEFLDATA) $(ADEFLCODE) $(SOURCEDIR)odswap.asm
+   command /c erase $(OBJDIR)odswaph.obj
+   move odswap.obj $(OBJDIR)odswaph.obj
+#
+#------------------------------------------------------------------------------
+#
+# Build library from objects.
+#
+OBJECTS= $(OBJDIR)odauto$(TARGET).obj\
+         $(OBJDIR)odblock$(TARGET).obj\
+         $(OBJDIR)odcfile$(TARGET).obj\
+         $(OBJDIR)odcmdln$(TARGET).obj\
+         $(OBJDIR)odcom$(TARGET).obj\
+         $(OBJDIR)odcore$(TARGET).obj\
+         $(OBJDIR)oddrbox$(TARGET).obj\
+         $(OBJDIR)odedit$(TARGET).obj\
+         $(OBJDIR)odedstr$(TARGET).obj\
+         $(OBJDIR)odemu$(TARGET).obj\
+         $(OBJDIR)odgetin$(TARGET).obj\
+         $(OBJDIR)odgraph$(TARGET).obj\
+         $(OBJDIR)odinex1$(TARGET).obj\
+         $(OBJDIR)odinex2$(TARGET).obj\
+         $(OBJDIR)odinque$(TARGET).obj\
+         $(OBJDIR)odkrnl$(TARGET).obj\
+         $(OBJDIR)odlist$(TARGET).obj\
+         $(OBJDIR)odlog$(TARGET).obj\
+         $(OBJDIR)odmulti$(TARGET).obj\
+         $(OBJDIR)odplat$(TARGET).obj\
+         $(OBJDIR)odpcb$(TARGET).obj\
+         $(OBJDIR)odpopup$(TARGET).obj\
+         $(OBJDIR)odprntf$(TARGET).obj\
+         $(OBJDIR)odra$(TARGET).obj\
+         $(OBJDIR)odscrn$(TARGET).obj\
+         $(OBJDIR)odspawn$(TARGET).obj\
+         $(OBJDIR)odstand$(TARGET).obj\
+         $(OBJDIR)odstat$(TARGET).obj\
+         $(OBJDIR)odswap$(TARGET).obj\
+         $(OBJDIR)odsys$(TARGET).obj\
+         $(OBJDIR)odutil$(TARGET).obj\
+         $(OBJDIR)odwcat$(TARGET).obj\
+         $(OBJDIR)odwin$(TARGET).obj
+$(LIBDIR)odoor$(TARGET).lib : $(OBJECTS)
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odauto$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odblock$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odcfile$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odcmdln$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odcom$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odcore$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)oddrbox$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odedit$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odedstr$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odemu$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odgetin$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odgraph$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odinex1$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odinex2$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odinque$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odkrnl$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odlist$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odlog$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odmulti$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odplat$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odpcb$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odpopup$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odprntf$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odra$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odscrn$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odspawn$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odstand$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odstat$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odswap$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odsys$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odutil$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odwcat$(TARGET).obj
+   $(LIB) $(LIBDIR)odoor$(TARGET).lib -+$(OBJDIR)odwin$(TARGET).obj
+   erase $(LIBDIR)odoor$(TARGET).bak
+#
+#------------------------------------------------------------------------------

+ 196 - 0
odoors/GNUmakefile

@@ -0,0 +1,196 @@
+#  OpenDoors 6.23
+#  (C} Copyright 1991 - 1997 by Brian Pirie. All Rights Reserved.
+#
+#  Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net}
+#
+#
+#         File: Win32.mak
+#
+#  Description: Makefile used to build the Win32 OpenDoors libraries from
+#               the sources. Usage is described below.
+#
+#    Revisions: Date          Ver   Who  Change
+#               ---------------------------------------------------------------
+#               Aug 09, 2003  6.23  SH   *nix port
+#
+###############################################################################
+#
+# USAGE INFORMATION
+#
+###############################################################################
+#
+# Command Line:   make -fWin32.mak
+#                     or
+#                 nmake /f Win32.mak
+#
+###############################################################################
+#
+# CONFIGURATION
+#
+# Customize this section of the makefile to provide the relevant information
+# for your compiler, assembler (if any} and build environment.
+#
+###############################################################################
+# Compiler executable file name. Use:
+#
+#                  tcc - For Borland Turbo C and Turbo C++
+#                  bcc - For Borland C++
+#                   cl - For Microsoft compilers
+#
+CC	:=	gcc
+#
+#------------------------------------------------------------------------------
+#
+# Linker executable file name. Use:
+#
+#                tlink - For Borland compilers
+#                 link - For Microsoft compilers
+#
+# Get OS name
+OS      :=      $(shell uname)
+os	:=	$(shell echo $(OS) | tr '[A-Z]' '[a-z]' | tr ' ' '_')
+OBJDIR	:=	objs-$(OS)/
+LIBDIR	:=	libs-$(OS)/
+EXEDIR	:=	exe-$(OS)/
+
+LD	:=	gcc
+
+ifdef DEBUG
+ CFLAGS	+=	-g -DOD_DEBUG
+ BUILDTYPE	:=	debug
+else
+ BUILDTYPE	:=	release
+endif
+#
+#------------------------------------------------------------------------------
+#
+# Compiler command-line flags.
+#
+CFLAGS	+=	-fPIC
+LDFLAGS	+=	-fPIC
+CFLAGS	+=	-O2 -L${LIBDIR} -I../xpdev -Wall
+ifeq ($(OS),Darwin)
+ CFLAGS		+=	-D__unix__
+ LDFLAGS	+=	$(CFLAGS) -dynamiclib -single_module
+else
+ LDFLAGS	+=	$(CFLAGS) -shared
+endif
+ifeq ($(shell if [ -f /usr/include/inttypes.h ] ; then echo YES ; fi),YES)
+ CFLAGS	+=	-DHAS_INTTYPES_H
+endif
+
+# /MTd /Zi - for debug
+#
+#------------------------------------------------------------------------------
+#
+# Link flags.
+#
+LDFLAGS	+=	-L../xpdev/$(LD).$(os).lib.$(BUILDTYPE)
+#
+#------------------------------------------------------------------------------
+#
+# Output directories. customize for your own preferences. Note that trailing
+# backslash (\} characters are required.
+#
+SHLIB		:=	.so
+STATICLIB	:=	.a
+OBJFILE 	:=	.o
+ifdef PROFILE
+	CFLAGS	+=	-pg
+	SHLIB	:=	_p${SHLIB}
+	STATICLIB	:=	_p.a
+endif
+#
+###############################################################################
+#
+# DEPENDENCIES
+#
+# You won't normally have to change anything after this point in this makefile.
+#
+###############################################################################
+#
+# Define primary target.
+#
+all: ${OBJDIR} ${LIBDIR} $(EXEDIR) ${LIBDIR}libODoors${SHLIB} \
+    ${LIBDIR}libODoors${STATICLIB} 
+#
+#------------------------------------------------------------------------------
+#
+# Name of all headers.
+#
+HEADERS= ${HEADERDIR}ODCom.h\
+         ${HEADERDIR}ODCore.h\
+         ${HEADERDIR}ODGen.h\
+         ${HEADERDIR}ODInEx.h\
+         ${HEADERDIR}ODInQue.h\
+         ${HEADERDIR}ODKrnl.h\
+         ${HEADERDIR}ODPlat.h\
+         ${HEADERDIR}ODRes.h\
+         ${HEADERDIR}ODScrn.h\
+         ${HEADERDIR}ODStat.h\
+         ${HEADERDIR}ODSwap.h\
+         ${HEADERDIR}ODTypes.h\
+         ${HEADERDIR}ODUtil.h\
+         ${HEADERDIR}OpenDoor.h
+#
+#------------------------------------------------------------------------------
+#
+# Build DLL from objects.
+#
+OBJECTS := ${OBJDIR}ODAuto${OBJFILE}\
+         ${OBJDIR}ODBlock${OBJFILE}\
+         ${OBJDIR}ODCFile${OBJFILE}\
+         ${OBJDIR}ODCmdLn${OBJFILE}\
+         ${OBJDIR}ODCom${OBJFILE}\
+         ${OBJDIR}ODCore${OBJFILE}\
+         ${OBJDIR}ODDrBox${OBJFILE}\
+         ${OBJDIR}ODEdit${OBJFILE}\
+         ${OBJDIR}ODEdStr${OBJFILE}\
+         ${OBJDIR}ODEmu${OBJFILE}\
+         ${OBJDIR}ODGetIn${OBJFILE}\
+         ${OBJDIR}ODGraph${OBJFILE}\
+         ${OBJDIR}ODInEx1${OBJFILE}\
+         ${OBJDIR}ODInEx2${OBJFILE}\
+         ${OBJDIR}ODInQue${OBJFILE}\
+         ${OBJDIR}ODKrnl${OBJFILE}\
+         ${OBJDIR}ODList${OBJFILE}\
+         ${OBJDIR}ODLog${OBJFILE}\
+         ${OBJDIR}ODMulti${OBJFILE}\
+         ${OBJDIR}ODPlat${OBJFILE}\
+         ${OBJDIR}ODPCB${OBJFILE}\
+         ${OBJDIR}ODPopup${OBJFILE}\
+         ${OBJDIR}ODPrntf${OBJFILE}\
+         ${OBJDIR}ODRA${OBJFILE}\
+         ${OBJDIR}ODScrn${OBJFILE}\
+         ${OBJDIR}ODSpawn${OBJFILE}\
+         ${OBJDIR}ODStand${OBJFILE}\
+         ${OBJDIR}ODStat${OBJFILE}\
+         ${OBJDIR}ODStr${OBJFILE}\
+         ${OBJDIR}ODUtil${OBJFILE}\
+         ${OBJDIR}ODWCat${OBJFILE}\
+	 ${OBJDIR}ODWin${OBJFILE}
+#         ${OBJDIR}ODoor.res
+#         ${OBJDIR}odsys${OBJFILE}\	this file is missing
+
+${OBJDIR}:
+	mkdir ${OBJDIR}
+
+${LIBDIR}:
+	mkdir ${LIBDIR}
+
+${EXEDIR}:
+	mkdir ${EXEDIR}
+
+$(OBJDIR)%$(OBJFILE) : %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
+${LIBDIR}libODoors${SHLIB} : ${OBJECTS}
+	$(LD) $(LDFLAGS) -o ${LIBDIR}libODoors${SHLIB}.6.2 ${OBJECTS}
+	ln -fs libODoors${SHLIB}.6.2 ${LIBDIR}libODoors${SHLIB}
+
+${LIBDIR}libODoors${STATICLIB} : ${OBJECTS}
+	ar -r ${LIBDIR}libODoors${STATICLIB} ${OBJECTS}
+	ranlib ${LIBDIR}libODoors${STATICLIB}
+	
+#
+#------------------------------------------------------------------------------

BIN
odoors/ODApp.ico


+ 214 - 0
odoors/ODAuto.c

@@ -0,0 +1,214 @@
+/* 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: ODAuto.c
+ *
+ * Description: Implements od_autodetect() for automatic detection of
+ *              terminal emulation supported by remote system.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Oct 14, 1994  6.00  BP   Standardized coding style.
+ *              Dec 31, 1994  6.00  BP   Use new millisecond timer functions.
+ *              Nov 12, 1995  6.00  BP   32-bit portability.
+ *              Nov 13, 1995  6.00  BP   Fixed non-functioning RIP autodetect.
+ *              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 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <ctype.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODTypes.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODCore.h"
+#include "ODKrnl.h"
+
+
+/* Private function prototypes. */
+static char ODWaitNoCase(char *pszWaitFor, tODMilliSec WaitTime);
+
+
+/* Number of attempts and timeout values for testing each terminal emulation */
+/* protocol.                                                                 */
+#define ANSI_TRIES      1
+#define ANSI_WAIT       660                         /* Time in milliseconds. */
+#define RIP_TRIES       1
+#define RIP_WAIT        660                         /* Time in milliseconds. */
+
+/* Strings to use for autodetection. */
+#define ANSI_QUERY      "\x1b[6n\r    \r"
+#define ANSI_RESPONSE   "\x1b["
+#define RIP_QUERY       "\r\x1b[!\r   \r"
+#define RIP_RESPONSE    "RIP"
+
+/* Maximum number of characters to match with _waitnocase(). */
+#define MATCH_LEN       3
+
+
+/* ----------------------------------------------------------------------------
+ * od_autodetect()
+ *
+ * Determines the terminal emulation capabilities of the remote communications
+ * software, when possible. Turns on ANSI and/or RIP modes if they are
+ * supported by the remote system.
+ *
+ * Parameters: nFlags - Currently unused.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_autodetect(INT nFlags)
+{
+   INT nCount;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_autodetect()");
+
+   /* Initialize OpenDoors if it hasn't aready been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Temporary code that will be optimized out, which prevents a compiler   */
+   /* warning from being generated for the currently unused flags parameter. */
+   (void)nFlags;
+
+   /* If operating in local mode, turn on ANSI mode, but not RIP. */
+   if(od_control.baud == 0)
+   {
+      od_control.user_ansi = TRUE;
+      OD_API_EXIT();
+      return;
+   }
+
+   /* If user_ansi is not set, attempt to determine ANSI capabilities. */
+   if(!od_control.user_ansi)
+   {
+      /* Clear inbound keyboard buffer. */
+      od_clear_keybuffer();
+
+      /* Try twice to test ANSI capabilities. */
+      for(nCount = 0; nCount < ANSI_TRIES; ++nCount)
+      {
+         /* Send a string that an ANSI capable terminal will usually */
+         /* respond to. */
+         od_disp(ANSI_QUERY, strlen(ANSI_QUERY), FALSE);
+
+         /* Wait for response expected from an ANSI terminal, for up to */
+         /* 12/18.2 second. */
+         if(ODWaitNoCase(ANSI_RESPONSE, ANSI_WAIT))
+         {
+            /* If expected sequence was received, turn on ANSI mode and */
+            /* exit the loop. */
+            od_control.user_ansi = TRUE;
+            break;
+         }
+      }
+      od_clear_keybuffer();
+   }
+
+   /* If user_rip is not set, attempt to determine RIP capabilities. */
+   if(!od_control.user_rip)
+   {
+      /* Clear inbound keyboard buffer. */
+      od_clear_keybuffer();
+
+      /* Try twice to test RIP capabilities. */
+      for(nCount = 0; nCount < RIP_TRIES; ++nCount)
+      {
+         /* Send a string that a RIP capable terminal will usually */
+         /* respond to. */
+         od_disp(RIP_QUERY, strlen(RIP_QUERY), FALSE);
+
+         /* Wait for response expected from a RIP terminal. */
+         if(ODWaitNoCase(RIP_RESPONSE, RIP_WAIT))
+         {
+            /* If expected sequence was received, turn on RIP mode and */
+            /* exit the loop. */
+            od_control.user_rip = TRUE;
+            break;
+         }
+      }
+      od_clear_keybuffer();
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODWaitNoCase()                                      *** PRIVATE FUNCTION ***
+ *
+ * Waits up to the specified maximum time for a specified string to be sent
+ * from the remote system. String matching is not case sensitive.
+ *
+ * Parameters: pszWaitFor - String to wait for.
+ *
+ *             WaitTime   - Maximum time, in milliseconds, to wait.
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+static char ODWaitNoCase(char *pszWaitFor, tODMilliSec WaitTime)
+{
+   tODTimer Timer;
+   char szReceived[MATCH_LEN + 1];
+   int nCount;
+   char chReceived;
+   int nMatchChars = MIN(MATCH_LEN, strlen(pszWaitFor));
+
+   ASSERT(pszWaitFor != NULL);
+   ASSERT(strlen(pszWaitFor) != 0);
+   ASSERT(WaitTime >= 0);
+
+   ODTimerStart(&Timer, WaitTime);
+
+   for(nCount = 0; nCount <= MATCH_LEN; ++nCount)
+   {
+      szReceived[nCount] = '\0';
+   }
+
+   do
+   {
+      if((chReceived = od_get_key(FALSE)) != 0)
+      {
+         for(nCount = 0; nCount < MATCH_LEN - 1; ++ nCount)
+         {
+            szReceived[nCount] = szReceived[nCount + 1];
+         }
+         szReceived[MATCH_LEN - 1] = chReceived;
+
+         if(strnicmp(szReceived + (MATCH_LEN - nMatchChars), pszWaitFor,
+            nMatchChars) == 0)
+         {
+            return(TRUE);
+         }
+      }
+   } while(!ODTimerElapsed(&Timer));
+
+   return(FALSE);
+}

+ 832 - 0
odoors/ODBlock.c

@@ -0,0 +1,832 @@
+/* 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: ODBlock.c
+ *
+ * Description: Implements the text block manipulation functions.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              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 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODKrnl.h"
+
+
+/* Set to TRUE when od_puttext() should leave the cursor in its original */
+/* position */
+static BOOL bScrollAction = TRUE;
+
+
+
+/* ----------------------------------------------------------------------------
+ * od_puttext()
+ *
+ * Displays the contents of the buffer passed in block. Leaves cursor in
+ * original position, unless bScrollAction is FALSE. Leaves colour at
+ * original value.
+ *
+ * Parameters: nLeft   - Column number of left edge of block of text to
+ *                       transfer, where 1 is the leftmost column of the
+ *                       screen.
+ *
+ *             nTop    - Row number of the top edge of block of text to
+ *                       to transfer, where 1 is the top row of the screen.
+ *
+ *             nRight  - Column number of the right edge of block.
+ *
+ *             nBottom - Row number of bottom edge of block.
+ *
+ *             pBlock  - Pointer to buffer that has been filled in the format
+ *                       used by od_gettext().
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_puttext(INT nLeft, INT nTop, INT nRight, INT nBottom,
+   void *pBlock)
+{
+   INT nRowLength = nRight - nLeft +1;
+   INT nRowBytes = nRowLength * 2;
+   char *pchTest;
+   char *pchMemory;
+   char *pBuffer=NULL;
+   char *pchScreenBlock;
+   INT nBlockRow = 0;
+   INT nOutRow;
+   INT nOutColour = 999;
+   INT nOutColumn, nCheckColumn;
+   char *pchMemBlock;
+   INT nMoveCost = od_control.user_avatar ? 4 : 7;
+   BYTE btMaxRight, btMaxBottom;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_puttext()");
+
+   /* Ensure that OpenDoors is initialized before proceeding. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Get current display setting profile. */
+   ODScrnGetTextInfo(&ODTextInfo);
+
+   /* Calculate the maximum values for bottom and right of block. */
+   btMaxRight=ODTextInfo.winright-ODTextInfo.winleft+1;
+   btMaxBottom=ODTextInfo.winbottom-ODTextInfo.wintop+1;
+
+   /* Check that parameters seem reasonable. */
+   if(nLeft<1 || nTop<1 || nRight>btMaxRight || nBottom>btMaxBottom
+      || nTop > nBottom || nLeft > nRight || pBlock==NULL)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Ensure that ANSI and/or AVATAR mode is available. */
+   if(!od_control.user_ansi && !od_control.user_avatar)
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* If OpenDoors is operating in remote mode. */
+   if(od_control.baud != 0)
+   {
+      /* Allocate temporary buffer to store original screen contents while */
+      /* buffer paste is being performed.                                  */
+      if((pBuffer=malloc(nRowBytes*(nBottom-nTop+1)))==NULL)
+      {
+         od_control.od_error = ERR_MEMORY;
+         OD_API_EXIT();
+         return(FALSE);
+      }
+
+      /* Get current screen contents of area to be pasted into, storing */
+      /* into the temporary buffer.                                     */
+      if(!ODScrnGetText((BYTE)nLeft, (BYTE)nTop, (BYTE)nRight, (BYTE)nBottom,
+         pBuffer))
+      {
+         od_control.od_error = ERR_PARAMETER;
+         free(pBuffer);
+         OD_API_EXIT();
+         return(FALSE);
+      }
+   }
+
+   /* Display the block to be pasted on the local screen. */
+   if(!ODScrnPutText((BYTE)nLeft, (BYTE)nTop, (BYTE)nRight, (BYTE)nBottom,
+      pBlock))
+   {
+      od_control.od_error = ERR_PARAMETER;
+      if(pBuffer)
+         free(pBuffer);
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* If operating in remote mode. */
+   if(od_control.baud != 0)
+   {
+      /* Loop for each line in the buffer to be pasted */
+      for(nOutRow=nTop;nOutRow<=nBottom;++nOutRow,++nBlockRow)
+      {
+         /* Setup pointer to beginning of line of original screen contents. */
+         pchScreenBlock=(char *)pBuffer+(nRowBytes*nBlockRow);
+
+         /* Setup pointer to beginning of line of block to be displayed. */
+         pchMemBlock=(char *)pBlock+(nRowBytes*nBlockRow);
+
+         /* Loop for each column on this line. */
+         for(nOutColumn=0;nOutColumn<nRowLength;)
+         {
+            /* Loop from this character onwards, counting number of */
+            /* characters that don't need to be changed. */
+            nCheckColumn=nOutColumn;
+            pchMemory=((char *)pchMemBlock)+(nCheckColumn<<1);
+            pchTest=((char *)pchScreenBlock)+(nCheckColumn<<1);
+            for(;nCheckColumn<nRowLength;++nCheckColumn)
+            {
+               if(od_control.od_full_put) break;
+
+               /* If both buffers have space characters. */
+               if((*pchMemory==' ' || *pchMemory==0) && (*pchTest==' ' || *pchTest=='\0'))
+               {
+                  /* If background colours differ, then stop comparison loop. */
+                  if((pchTest[1]&0x70) != (pchMemory[1]&0x70))
+                  {
+                     break;
+                  }
+               }
+
+               /* If both have different character and colour attributes. */
+               else if(*((WORD *)pchTest) != *((WORD *)pchMemory))
+               {
+                  /* Then stop comparison loop now. */
+                  break;
+               }
+
+               /* Increment source and background pointers by two bytes. */
+               pchTest+=2;
+               pchMemory+=2;
+            }
+
+            /* If no futher text to change on this line. */
+            if(nCheckColumn==nRowLength)
+            {
+               /* Move to the next line. */
+               goto next_line;
+            }
+
+            /* If this is the first text to be displayed on this line. */
+            if(nOutColumn == 0)
+            {
+               /* Move the cursor to the first text to be changed on line. */
+               nOutColumn = nCheckColumn;
+
+               /* If AVATAR mode is available. */
+               if(od_control.user_avatar)
+               {
+                  /* Send the avatar cursor positioning command. */
+                  szODWorkString[0]=22;
+                  szODWorkString[1]=8;
+                  szODWorkString[2]=nOutRow;
+                  szODWorkString[3]=nLeft+nOutColumn;
+                  od_disp(szODWorkString,4,FALSE);
+               }
+               else
+               {
+                  /* Otherwise, send the ANSI cursor positioning command. */
+                  sprintf(szODWorkString,"x[%d;%dH",nOutRow,nLeft + nOutColumn);
+                  szODWorkString[0]=27;
+                  od_disp(szODWorkString, strlen(szODWorkString), FALSE);
+               }
+            }
+
+            /* If the number of characters after current cursor position  */
+            /* which must be changed, is greater than the number of       */
+            /* characters required to reposition the cursor on this line, */
+            /* then move the cursor to skip unchanged characters.         */
+            else if((nCheckColumn-nOutColumn)>nMoveCost)
+            {
+               nOutColumn=nCheckColumn;
+               /* If AVATAR mode is available. */
+               if(od_control.user_avatar)
+               {
+                  /* Advance cursor appropriate number of characters */
+                  /* using the AVATAR cursor position command.       */
+                  szODWorkString[0]=22;
+                  szODWorkString[1]=8;
+                  szODWorkString[2]=nOutRow;
+                  szODWorkString[3]=nLeft+nOutColumn;
+                  od_disp(szODWorkString,4,FALSE);
+               }
+               else
+               {
+                  /* Otherwise, advance cursor appropriate number of      */
+                  /* characters using the ANSI cursor position command.   */
+                  sprintf(szODWorkString,"x[%d;%dH",nOutRow,nLeft + nOutColumn);
+                  szODWorkString[0]=27;
+                  od_disp(szODWorkString,strlen(szODWorkString),FALSE);
+               }
+            }
+
+            /* Output text for the number of characters found to be */
+            /* different.                                           */
+            pchTest=(char *)&pchMemBlock[nOutColumn*2];
+            for(;nOutColumn<=nCheckColumn;++nOutColumn)
+            {
+               if(pchTest[1] != nOutColour)
+               {
+                  od_set_attrib(nOutColour=pchTest[1]);
+               }
+               od_disp(pchTest,1,FALSE);
+               pchTest++;
+               pchTest++;
+            }
+         }
+next_line:
+         ;
+      }
+
+      /* If not disabled, update cursor position. */
+      if(bScrollAction)
+      {
+         od_set_cursor(ODTextInfo.cury,ODTextInfo.curx);
+      }
+
+      /* Deallocate temporary buffer. */
+      free(pBuffer);
+   }
+
+   /* Restore the original display attribute. */
+   od_set_attrib(ODTextInfo.attribute);
+
+   /* Return with success. */
+   OD_API_EXIT();
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_gettext()
+ *
+ * Retrieves text from the screen (based on what is current displayed on the
+ * local display), storing it in the buffer provided by the caller.
+ *
+ * Parameters: nLeft   - Column number of left edge of block of text to
+ *                       transfer, where 1 is the leftmost column of the
+ *                       screen.
+ *
+ *             nTop    - Row number of the top edge of block of text to
+ *                       to transfer, where 1 is the top row of the screen.
+ *
+ *             nRight  - Column number of the right edge of block.
+ *
+ *             nBottom - Row number of bottom edge of block.
+ *
+ *             pBlock  - Pointer to buffer large enough to hold two bytes
+ *                       for each character in the block.
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_gettext(INT nLeft, INT nTop, INT nRight, INT nBottom,
+   void *pBlock)
+{
+   BYTE btMaxRight, btMaxBottom;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_gettext()");
+
+   /* Initialize OpenDoors if not already done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   ODScrnGetTextInfo(&ODTextInfo);
+
+   btMaxRight=ODTextInfo.winright-ODTextInfo.winleft+1;
+   btMaxBottom=ODTextInfo.winbottom-ODTextInfo.wintop+1;
+   if(nLeft<1 || nTop<1 || nRight>btMaxRight || nBottom>btMaxBottom || !pBlock)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   if(!od_control.user_ansi && !od_control.user_avatar)
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   OD_API_EXIT();
+   return(ODScrnGetText((BYTE)nLeft, (BYTE)nTop, (BYTE)nRight, (BYTE)nBottom,
+      pBlock));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_scroll()
+ *
+ * Scrolls the specified area of the screen by the specified number of
+ * lines, in either the up or down directions. The cursor is left at its
+ * original locaiton, and the display attribute is left at its original
+ * setting. New lines are created in the current display colour.
+ *
+ * Parameters: nLeft     - Column number of left edge of area to scroll, where
+ *                         1 is the leftmost column of the screen.
+ *
+ *             nTop      - Row number of the top edge of the area to scroll,
+ *                         where 1 is the top row of the screen.
+ *
+ *             nRight    - Column number of the right edge of area to scroll.
+ *
+ *             nBottom   - Row number of bottom edge of area to scroll.
+ *
+ *             nDistance - Number of lines to scroll the text. A value of 0
+ *                         has no effect on the specified area, a positive
+ *                         value moves the text upwards (leaving blank lines
+ *                         at the bottom of the specified area), while a
+ *                         negative value moves the text upwards. If the
+ *                         distance is equal to the number of lines in the
+ *                         area, then the entire area is cleared.
+ *
+ *             nFlags    - Bitwise-or (|) of SCROLL_* flags. SCROLL_NORMAL
+ *                         is the default. SCROLL_NO_CLEAR does not clear
+ *                         the new area at the top/bottom of the scroll
+ *                         region if doing so would be slower.
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_scroll(INT nLeft, INT nTop, INT nRight, INT nBottom,
+   INT nDistance, WORD nFlags)
+{
+   BYTE btWidth, btHeight;
+   BYTE btCount;
+   BYTE btFirst, btLast;
+   char szAVTSeq[7];
+   void *pBlock;
+   char szBlank[81];
+   BYTE btKeepHeight;
+   BYTE btMaxRight;
+   BYTE btMaxBottom;
+   tODScrnTextInfo TextState;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_scroll()");
+
+   /* Ensure that OpenDoors has been initialized before proceeding. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Get current display setting information. */
+   ODScrnGetTextInfo(&TextState);
+
+   /* Determine the height and width of the area to be scrolled. */
+   btWidth=nRight-nLeft+1;
+   btHeight=nBottom-nTop+1;
+
+   /* Determine the number of lines currently in the area that will still */
+   /* be visible after scrolling.                                         */
+   btKeepHeight=btHeight-((nDistance>=0) ? nDistance : -nDistance);
+
+   /* Determine the maximum bottom and left coordinates of an area to be */
+   /* scrolled.                                                          */
+   btMaxRight=TextState.winright-TextState.winleft+1;
+   btMaxBottom=TextState.winbottom-TextState.wintop+1;
+
+   /* Check that parameters are valid. */
+   if(nLeft<1 || nTop<1 || nRight>btMaxRight || nBottom>btMaxBottom ||
+      nLeft > nRight || nTop > nBottom)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Check that ANSI or AVATAR graphics mode is available. */
+   if(!od_control.user_ansi && !od_control.user_avatar)
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* If distance to be scrolled is 0, then we are finished already and */
+   /* can return immediately.                                           */
+   if(nDistance == 0)
+   {
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* If distance is positive, then we are moving text upwards. */
+   if(nDistance>0)
+   {
+      /* Ensure that distance is not greater than size of scrolled area. */
+      if(nDistance>btHeight)
+      {
+         nDistance=btHeight;
+      }
+
+      /* Calculate first and last line to be moved. */
+      btFirst=nBottom-(nDistance-1);
+      btLast=nBottom;
+   }
+
+   /* If distance is negative, then we are moving text downwards. */
+   else /* if(nDistance<0) */
+   {
+      /* Ensure that distance is not greater than size of scrolled area. */
+      if(nDistance<-btHeight)
+      {
+         nDistance=-btHeight;
+      }
+
+      /* Calculate first and last line to be moved. */
+      btFirst=nTop;
+      btLast=nTop-nDistance-1;
+   }
+
+   /* If AVATAR mode is available */
+   if(od_control.user_avatar)
+   {
+      /* Generate AVATAR sequence which causes the remote terminal to */
+      /* scroll an area of its screen.                                */
+      szAVTSeq[0]=22;
+
+      /* If scrolling text upwards. */
+      if(nDistance>0)
+      {
+         /* Specify control sequence for scrolling upwards. */
+         szAVTSeq[1]=10;
+         szAVTSeq[2]=nDistance;
+
+         /* Move text appropriate direction on local screen. */
+         ODScrnCopyText((BYTE)nLeft, (BYTE)(nTop + nDistance), (BYTE)nRight,
+            (BYTE)nBottom, (BYTE)nLeft, (BYTE)nTop);
+      }
+      /* If scrolling text downwards. */
+      else /* if(disatnce<0) */
+      {
+         /* Specify control sequence for scrolling downwards. */
+         szAVTSeq[1]=11;
+         szAVTSeq[2]=-nDistance;
+
+         /* Move text appropriate direction on local screen. */
+         ODScrnCopyText((BYTE)nLeft, (BYTE)nTop, (BYTE)nRight,
+            (BYTE)(nBottom + nDistance), (BYTE)nLeft, (BYTE)(nTop - nDistance));
+      }
+
+      /* Specify area to be scrolled to the AVATAR terminal. */
+      szAVTSeq[3]=nTop;
+      szAVTSeq[4]=nLeft;
+      szAVTSeq[5]=nBottom;
+      szAVTSeq[6]=nRight;
+
+      /* Send the control sequence to the AVATAR terminal. */
+      od_disp(szAVTSeq,7,FALSE);
+
+      /* Generate string containing a blank line of text. */
+      for(btCount=0;btCount<btWidth;++btCount) szBlank[btCount]=' ';
+      szBlank[btCount]='\0';
+
+      /* Blank-out lines that will no longer be visiable. */
+      for(;btFirst<=btLast;++btFirst)
+      {
+         ODScrnSetCursorPos((BYTE)nLeft, btFirst);
+         ODScrnDisplayString(szBlank);
+      }
+
+      /* Reset cursor position on local display. */
+      ODScrnSetCursorPos(TextState.curx,TextState.cury);
+   }
+
+   /* Otherwise, we are using ANSI mode. */
+   else /* if(od_control.user_ansi) */
+   {
+      /* If any of the original text will still be available after */
+      /* scrolling.                                                */
+      if(btKeepHeight>0)
+      {
+         /* Allocate some temporary memory to hold text to be "got". */
+         if((pBlock=malloc(btKeepHeight*btWidth*2))==NULL)
+         {
+            /* If memory allocation failed, then scrolling fails. */
+            od_control.od_error = ERR_MEMORY;
+            OD_API_EXIT();
+            return(FALSE);
+         }
+
+         /* If we are scrolling text upwards. */
+         if(nDistance > 0)
+         {
+            /* Move text that will still be visible, using od_gettext() */
+            /* and od_puttext().                                        */
+            od_gettext(nLeft,nTop+nDistance,nRight,nBottom,pBlock);
+            bScrollAction=FALSE;
+            od_puttext(nLeft,nTop,nRight,nBottom-nDistance,pBlock);
+            bScrollAction=TRUE;
+         }
+
+         /* If we are scrolling text downwards. */
+         else /* if(nDistance < 0) */
+         {
+            /* Move text that will still be visible, using od_gettext() */
+            /* and od_puttext().                                        */
+            od_gettext(nLeft,nTop,nRight,nBottom+nDistance,pBlock);
+            bScrollAction=FALSE;
+            od_puttext(nLeft,nTop-nDistance,nRight,nBottom,pBlock);
+            bScrollAction=TRUE;
+         }
+
+         /* Deallocate temporary memory block. */
+         free(pBlock);
+      }
+
+      /* If new area clearing has not been disabled. */
+      if(!(nFlags&SCROLL_NO_CLEAR))
+      {
+         /* Loop for lines that should be blank. */
+         for(;btFirst<=btLast;++btFirst)
+         {
+            /* Move cursor to the beginning of this line. */
+            od_set_cursor(btFirst,nLeft);
+
+            /* If right boarder of area to be scrolled is the edge of the */
+            /* screen, then we can use a quick control sequence to clear  */
+            /* the rest of the line. Call od_clr_line() to do this.       */
+            if(nRight == 80)
+            {
+               od_clr_line();
+            }
+
+            /* If right boarder of area to be scrolled is not at the edge */
+            /* of the screen, then each line must be manually erased, by  */
+            /* sending the appropriate number of blanks (spaces).         */
+            else /* if(right != 80) */
+            {
+               od_repeat(' ',btWidth);
+            }
+         }
+      }
+
+      /* Reset the cursor to its original position. */
+      od_set_cursor(TextState.cury,TextState.curx);
+   }
+
+   /* Return with success */
+   OD_API_EXIT();
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_save_screen()
+ *
+ * Stores the contents of the entire screen into a buffer, along with
+ * the current cursor location and display colour. Supports all display
+ * modes.
+ *
+ * Parameters: pBuffer - Pointer to a buffer of at least 4004 bytes in size,
+ *                       where the information on the current screen state
+ *                       will be stored.
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_save_screen(void *pBuffer)
+{
+   char btHeight;
+   tODScrnTextInfo TextState;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_save_screen()");
+
+   /* Ensure that OpenDoors is initialized before proceeding. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check parameters and current output window size. */
+   ODScrnGetTextInfo(&TextState);
+   if(TextState.winleft!=1 || TextState.winright!=80 || !pBuffer)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Store current cursor location in buffer. */
+   ((char *)pBuffer)[0]=TextState.curx;
+   ((char *)pBuffer)[1]=TextState.cury;
+
+   /* Store current display colour in buffer. */
+   ((char *)pBuffer)[2]=TextState.attribute;
+
+   /* Store height of buffer stored. */
+   ((char *)pBuffer)[3]=btHeight=TextState.winbottom-TextState.wintop+1;
+
+   /* Store screen contents using local screen gettext() function. */
+   OD_API_EXIT();
+   return(ODScrnGetText(1,1,80,btHeight,(char *)pBuffer+4));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_restore_screen()
+ *
+ * Restores the screen contents, along with the current text colour and cursor
+ * location, as previously stored by od_save_screen().
+ *
+ * Parameters: pBuffer - Pointer to buffer which was filled by a previous call
+ *                       to od_save_screen().
+ *
+ *     Return: TRUE on success, FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_restore_screen(void *pBuffer)
+{
+   char *pch;
+   char btPos;
+   char chLast;
+   char *pchTextBuffer;
+   char btHeight;
+   int nToReturn=TRUE;
+   tODScrnTextInfo TextState;
+   char btLine;
+   char btDistance=0;
+   char btCursorRow;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_restore_screen()");
+
+   /* Ensure that OpenDoors is initialized before proceeding. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check parameters and current output window size. */
+   ODScrnGetTextInfo(&TextState);
+   if(TextState.winleft!=1 || TextState.winright!=80 || !pBuffer)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Determine current window height were text will be restored. */
+   btHeight=TextState.winbottom-TextState.wintop+1;
+
+   /* If current display window height is too small for entire buffer */
+   /* to be re-displayed.                                             */
+   if(btHeight<((char *)pBuffer)[3])
+   {
+      /* Then we will always display as much of the END of the buffer */
+      /* as possible.                                                 */
+      btDistance = btHeight - ((char *)pBuffer)[3];
+   }
+   else if(btHeight > ((char *)pBuffer)[3])
+   {
+      /* Otherwise, ensure that we don't try to display more lines that */
+      /* are in the buffer.                                             */
+      btHeight=((char *)pBuffer)[3];
+   }
+
+   /* Clear the remote and local screens. */
+   od_clr_scr();
+
+   /* If ANSI or AVATAR modes are available. */
+   if(od_control.user_avatar || od_control.user_ansi)
+   {
+      /* Then we can efficiently display the buffer using od_puttext(). */
+      bScrollAction=FALSE;
+      nToReturn=od_puttext(1,1,80,btHeight,(char *)pBuffer+(4+((int)btDistance*160)));
+      bScrollAction=TRUE;
+
+      /* Restore original cursor location. */
+      od_set_cursor(((char *)pBuffer)[1],((char *)pBuffer)[0]);
+
+      /* Restore original display attribute. */
+      od_set_attrib(((char *)pBuffer)[2]);
+   }
+
+   /* If we are operating in ASCII mode. */
+   else /* if (!od_control.od_avatar && !od_control.caller_ansi) */
+   {
+      /* Then the buffer is displayed one line at a time, beginning  */
+      /* at the top of the screen, up to the saved cusrsor location. */
+
+      /* Set pointer to beginning of buffer to be displayed. */
+      pchTextBuffer=(char *)pBuffer+4;
+
+      /* Get final cursor row number. */
+      btCursorRow=((char *)pBuffer)[1];
+
+      /* Loop for each line in the buffer. */
+      for(btLine=1;btLine<=btHeight;++btLine)
+      {
+         /* Set pointer to last character of line. */
+         pch=(char *)pchTextBuffer+158;
+
+         /* Loop backwards until a non-blank character is found, or we */
+         /* reach the beginning of the line.                           */
+         for(chLast=80;chLast>1;)
+         {
+            /* If this is a blank character. */
+            if(*pch==32 || *pch==0)
+            {
+               /* Move to previous character. */
+               --chLast;
+               pch-=2;
+            }
+
+            /* If this is not a blank character, then stop looping. */
+            else
+            {
+               break;
+            }
+         }
+
+         /* If this is the line on which the cursor resides. */
+         if(btLine==btCursorRow)
+         {
+            /* If last non-blank character of line is at or past the final */
+            /* cursor location, then we backup the last character to be    */
+            /* displayed to the cursor before the final cursor position.   */
+            /* This code could be improved to be able to display text on   */
+            /* the entire cursor line by displaying the entire line,       */
+            /* sending a C/R, and redisplaying first portion of line to    */
+            /* end up with the cursor in the desired position.             */
+            if(chLast>=((char *)pBuffer)[0])
+            {
+               chLast=((char *)pBuffer)[0]-1;
+            }
+         }
+
+         /* Display all characters on this line */
+         pch = (char *)pchTextBuffer;
+         for(btPos=1;btPos<=chLast;++btPos)
+         {
+            od_putch(*pch);
+            pch+=2;
+         }
+
+         /* If this is the row where the cursor should be left, then we */
+         /* stop displaying now.                                        */
+         if(btLine==btCursorRow)
+         {
+            break;
+         }
+
+         /* If cursor hasn't been wrapped, then we should send a C/R - */
+         /* L/F sequence.                                              */
+         if(chLast != 80)
+         {
+            od_disp_str("\n\r");
+            pchTextBuffer+=160;
+         }
+      }
+   }
+
+   /* Return with the appropriate success/failure status. */
+   OD_API_EXIT();
+   return(nToReturn);
+}

+ 933 - 0
odoors/ODCFile.c

@@ -0,0 +1,933 @@
+/* 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: ODCFile.c
+ *
+ * Description: Implements the configuration file sub-system.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Nov 11, 1995  6.00  BP   32-bit portability.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 01, 1996  6.00  BP   Added DisableDTR and NoDTRDisable.
+ *              Jan 19, 1996  6.00  BP   Display error if config file not found
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODInEx.h"
+#include "ODUtil.h"
+
+
+/* Internal private variables */
+static WORD awTimeVal[3];
+static BYTE btTimeNumVals;
+
+
+/* Local functions. */
+static WORD ODCfgGetWordDecimal(char *pszConfigText);
+static DWORD ODCfgGetDWordDecimal(char *pszConfigText);
+static WORD ODCfgGetWordHex(char *pszConfigText);
+static void ODCfgGetNextTime(char **ppchConfigText);
+static BOOL ODCfgIsTrue(char *pszConfigText);
+
+
+/* ----------------------------------------------------------------------------
+ * ODConfigInit()
+ *
+ * Called to perform OpenDoors initialization when the configuration file
+ * system is being used. This function is called from the normal od_init(),
+ * and also uses the normal od_init() to perform base initialization after
+ * the configuration file has been read, but before certain configuration
+ * settings are set in od_control.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL ODConfigInit(void)
+{
+   void (*custom_line_function)(char *keyword, char *options)
+      = od_control.config_function;
+   char *pchConfigText;
+   WORD wCurrent;
+   INT nConfigOption;
+   BOOL bConfigFileRequired = TRUE;
+   static FILE *pfConfigFile;
+   static FILE *pfCustomDropFile = NULL;
+   static char szConfigLine[257];
+   static char szToken[33];
+   static char szTempString[256];
+   static char szWorkDir[80];
+   static BOOL bWorkDirSet = FALSE;
+   static time_t nUnixTime;
+   static struct tm *TimeBlock;
+   static INT16 nPageStart;
+   static INT16 nPageEnd;
+   static BOOL bPageSet = FALSE;
+   static BOOL bInactivitySet = FALSE;
+   static INT16 nInactivity;
+   static char *pszWork;
+   static BOOL bPageLengthSet = FALSE;
+   static BYTE btPageLength;
+   static char *apszFileNames[1];
+
+   bIsCallbackActive = TRUE;
+
+   nUnixTime = time(NULL);
+   TimeBlock = localtime(&nUnixTime);
+
+   /* Use default configuration file filename if none has been specified. */
+   if(od_control.od_config_filename == NULL)
+   {
+      od_control.od_config_filename = "door.cfg";
+      bConfigFileRequired = FALSE;
+   }
+
+   if((pfConfigFile = fopen(od_control.od_config_filename, "rt")) == NULL)
+   {
+      if(strchr(od_control.od_config_filename, DIRSEP) != NULL
+         || strchr(od_control.od_config_filename, ':') != NULL)
+      {
+         wCurrent = strlen(od_control.od_config_filename);
+         pchConfigText = (char *)od_control.od_config_filename + (wCurrent - 1);
+         while(wCurrent > 0)
+         {
+            if(*pchConfigText == DIRSEP || *pchConfigText == ':')
+            {
+               strcpy(szConfigLine, (char *)pchConfigText + 1);
+               pfConfigFile = fopen(szConfigLine, "rt");
+               break;
+            }
+
+            --pchConfigText;
+            --wCurrent;
+         }
+      }
+      else
+      {
+         strcpy(szConfigLine, od_control.od_config_filename);
+      }
+   }
+
+   /* If we were able to open the configuration file. */
+   if(pfConfigFile != NULL)
+   {
+      /* Get configuration file strings in upper case. */
+      for(wCurrent = 0; wCurrent < TEXT_SIZE; ++wCurrent)
+      {
+         strupr(od_config_text[wCurrent]);
+      }
+      for(wCurrent = 0; wCurrent < LINES_SIZE; ++wCurrent)
+      {
+         strupr(od_config_lines[wCurrent]);
+      }
+
+      for(;;)
+      {
+         /* Read the next line from the configuration file. */
+         if(fgets(szConfigLine, 257, pfConfigFile) == NULL) break;
+
+         /* Ignore all of line after comments or CR/LF char. */
+         pchConfigText = (char *)szConfigLine;
+         while(*pchConfigText)
+         {
+            if(*pchConfigText == '\n' || *pchConfigText == '\r'
+               || *pchConfigText == ';')
+            {
+               *pchConfigText = '\0';
+               break;
+            }
+            ++pchConfigText;
+         }
+
+         /* Search for beginning of first token on line. */
+         pchConfigText = (char *)szConfigLine;
+         while(*pchConfigText
+            && (*pchConfigText == ' ' || *pchConfigText == '\t'))
+         {
+            ++pchConfigText;
+         }
+         if(!*pchConfigText) continue;
+
+         /* Get first token from line. */
+         wCurrent = 0;
+         while(*pchConfigText
+            && !(*pchConfigText == ' ' || *pchConfigText == '\t'))
+         {
+            if(wCurrent < 32) szToken[wCurrent++] = *pchConfigText;
+            ++pchConfigText;
+         }
+         if(wCurrent <= 32)
+         {
+            szToken[wCurrent] = '\0';
+         }
+         else
+         {
+            szToken[32] = '\0';
+         }
+         strupr(szToken);
+
+         /* Find beginning of configuration option parameters */
+         while(*pchConfigText && (*pchConfigText == ' '
+            || *pchConfigText == '\t'))
+         {
+            ++pchConfigText;
+         }
+
+         /* Trim trailing spaces from setting string. */
+         for(wCurrent = strlen(pchConfigText) - 1; wCurrent > 0; --wCurrent)
+         {
+            if(pchConfigText[wCurrent] == ' '
+               || pchConfigText[wCurrent] == '\t')
+            {
+               pchConfigText[wCurrent] = '\0';
+            }
+            else
+            {
+               break;
+            }
+         }
+
+
+         for(wCurrent = 0; wCurrent < TEXT_SIZE; ++wCurrent)
+         {
+            if(strcmp(szToken, od_config_text[wCurrent]) == 0)
+            {
+               switch(wCurrent)
+               {
+                  case 0:
+                     wODNodeNumber = ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 1:
+                     strcpy(od_control.info_path,pchConfigText);
+                     break;
+
+                  case 2:
+                     if(pchConfigText[strlen(pchConfigText) - 1] == DIRSEP
+                        && pchConfigText[strlen(pchConfigText) - 2] != ':'
+                        && strlen(pchConfigText) > 1)
+                     {
+                        pchConfigText[strlen(pchConfigText) - 1] = '\0';
+                     }
+
+                     szOriginalDir = (char *)malloc(256);
+                     if(szOriginalDir != NULL)
+                     {
+                        ODDirGetCurrent(szOriginalDir, 256);
+                     }
+
+                     strcpy(szWorkDir, pchConfigText);
+                     bWorkDirSet = TRUE;
+                     break;
+
+                  case 3:
+                     strcpy(od_control.od_logfile_name, pchConfigText);
+                     break;
+
+                  case 4:
+                     od_control.od_logfile_disable = TRUE;
+                     break;
+
+                  case 5:
+                  case 6:
+                  case 7:
+                  case 8:
+                  case 9:
+                  case 10:
+                  case 11:
+                      if((wCurrent - 5) == (WORD)TimeBlock->tm_wday)
+                      {
+                         ODCfgGetNextTime((char **)&pchConfigText);
+                         nPageStart = awTimeVal[0] * 60 + awTimeVal[1];
+                         ODCfgGetNextTime((char **)&pchConfigText);
+                         nPageEnd = awTimeVal[0] * 60 + awTimeVal[1];
+                         bPageSet = TRUE;
+                      }
+                     break;
+
+                  case 12:
+                     od_control.od_maxtime = ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 13:
+                     bSysopNameSet = TRUE;
+                     strncpy((char *)&szForcedSysopName, pchConfigText, 39);
+                     szForcedSysopName[39] = '\0';
+                     break;
+
+                  case 14:
+                     bSystemNameSet = TRUE;
+                     strncpy((char *)&szForcedSystemName, pchConfigText, 39);
+                     szForcedSystemName[39] = '\0';
+                     break;
+
+                  case 15:
+                     od_control.od_swapping_disable = TRUE;
+                     break;
+
+                  case 16:
+                     strncpy(od_control.od_swapping_path, pchConfigText, 79);
+                     od_control.od_swapping_path[79] = '\0';
+                     break;
+
+                  case 17:
+                     od_control.od_swapping_noems = TRUE;
+                     break;
+
+                  case 18:
+                     dwForcedBPS = ODCfgGetDWordDecimal(pchConfigText);
+                     break;
+
+                  case 19:
+                     nForcedPort = ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 20:
+                     if(pfCustomDropFile == NULL && !od_control.od_force_local)
+                     {
+                        apszFileNames[0] = (char *)pchConfigText;
+                        if(ODSearchForDropFile(apszFileNames, 1, szTempString,
+                           NULL) != -1)
+                        {
+                           if((pfCustomDropFile = fopen(szTempString, "rt"))
+                              != NULL)
+                           {
+                              od_control.od_info_type = CUSTOM;
+                              od_control.user_attribute = 0x06;
+                              od_control.user_screen_length = 23;
+                              od_control.user_ansi = TRUE;
+                              od_control.user_rip = FALSE;
+                              od_control.user_avatar = FALSE;
+                              od_control.od_page_pausing = TRUE;
+                              od_control.od_page_len = 15;
+                              od_control.user_timelimit = 0;
+                              strcpy(od_control.user_name, "Unknown User");
+                              strcpy(od_control.user_location,
+                                 "Unknown Location");
+                              od_control.user_security = 1;
+                           }
+                        }
+                     }
+                     break;
+
+                  case 21:
+                     if(pfCustomDropFile != NULL)
+                     {
+                        if(fgets(szTempString, 255, pfCustomDropFile)!=NULL)
+                        {
+                           if(szTempString[strlen(szTempString) - 1] == '\n')
+                           {
+                              szTempString[strlen(szTempString) - 1] = '\0';
+                           }
+                           else
+                           {
+                              INT ch;
+                              do
+                              {
+                                 ch = fgetc(pfCustomDropFile);
+                              } while(ch != '\n' && ch != EOF);
+                           }
+                           if(szTempString[strlen(szTempString) - 1] == '\r')
+                           {
+                              szTempString[strlen(szTempString) - 1] = '\0';
+                           }
+
+                           strupr(pchConfigText);
+
+                           for(nConfigOption = 0; nConfigOption < LINES_SIZE;
+                              ++nConfigOption)
+                           {
+                              if(strcmp(pchConfigText,
+                                 od_config_lines[nConfigOption]) == 0)
+                              {
+                                 switch(nConfigOption)
+                                 {
+                                    case 1:
+                                       od_control.port =
+                                          ODCfgGetWordDecimal(szTempString) - 1;
+                                       break;
+
+                                    case 2:
+                                       od_control.port =
+                                          ODCfgGetWordDecimal(szTempString);
+                                       break;
+
+                                    case 3:
+                                       od_control.baud =
+                                          ODCfgGetWordDecimal(szTempString);
+                                       break;
+
+                                    case 4:
+                                       if(ODCfgIsTrue(szTempString))
+                                       {
+#ifdef ODPLAT_NIX
+                                          od_control.baud = 1;
+#else
+                                          od_control.baud = 0;
+#endif
+                                       }
+                                       break;
+
+                                    case 5:
+                                    case 6:
+                                       ODStringToName(szTempString);
+                                       strncpy(od_control.user_name,
+                                          szTempString, 34);
+                                       od_control.user_name[34] = '\0';
+                                       break;
+
+                                    case 7:
+                                       strcat(od_control.user_name, " ");
+                                       ODStringToName(szTempString);
+                                       strncat(od_control.user_name,
+                                          szTempString,
+                                          35 - strlen(od_control.user_name));
+                                       od_control.user_name[35] = '\0';
+                                       break;
+
+                                    case 8:
+                                       ODStringToName(szTempString);
+                                       strncpy(od_control.user_handle,
+                                          szTempString, 35);
+                                       od_control.user_handle[35] = '\0';
+                                       break;
+
+                                    case 9:
+                                       pszWork = (char *)szTempString;
+                                       ODCfgGetNextTime((char **)&pszWork);
+                                       od_control.user_timelimit += 
+                                          (awTimeVal[0] * 60);
+                                       break;
+
+                                    case 10:
+                                       pszWork = (char *)szTempString;
+                                       ODCfgGetNextTime((char **)&pszWork);
+                                       if(btTimeNumVals <= 1)
+                                       {
+                                          od_control.user_timelimit +=
+                                             awTimeVal[0];
+                                       }
+                                       else
+                                       {
+                                          od_control.user_timelimit +=
+                                             awTimeVal[1] + (awTimeVal[0] * 60);
+                                       }
+                                       break;
+
+                                    case 11:
+                                       pszWork = (char *)szTempString;
+                                       ODCfgGetNextTime((char **)&pszWork);
+                                       if(btTimeNumVals <= 1)
+                                       {
+                                          od_control.user_timelimit +=
+                                             awTimeVal[0] / 60;
+                                       }
+                                       else if(btTimeNumVals == 2)
+                                       {
+                                          od_control.user_timelimit +=
+                                             (awTimeVal[1] / 60) + awTimeVal[0];
+                                       }
+                                       else
+                                       {
+                                          od_control.user_timelimit +=
+                                             (awTimeVal[2] / 60) + awTimeVal[1]
+                                             + (awTimeVal[0] * 60);
+                                       }
+                                       break;
+
+                                    case 12:
+                                       od_control.user_ansi =
+                                          ODCfgIsTrue(szTempString);
+                                       break;
+
+                                    case 13:
+                                       od_control.user_avatar =
+                                          ODCfgIsTrue(szTempString);
+                                       break;
+
+                                    case 14:
+                                       od_control.od_page_pausing =
+                                          ODCfgIsTrue(szTempString);
+                                       break;
+
+                                    case 15:
+                                       od_control.user_screen_length =
+                                          ODCfgGetWordDecimal(szTempString);
+                                       break;
+
+                                    case 16:
+                                       if(ODCfgIsTrue(szTempString))
+                                        {
+                                           od_control.user_attribute |= 0x02;
+                                        }
+                                       else
+                                        {
+                                           od_control.user_attribute &=~ 0x02;
+                                        }
+                                       break;
+
+                                    case 17:
+                                       od_control.user_security =
+                                          ODCfgGetWordDecimal(szTempString);
+                                       break;
+
+                                    case 18:
+                                       ODStringToName(szTempString);
+                                       strncpy(od_control.user_location,
+                                          szTempString, 25);
+                                       od_control.user_location[25] = '\0';
+                                       break;
+
+                                    case 19:
+                                       wODNodeNumber =
+                                          ODCfgGetWordDecimal(szTempString);
+                                       break;
+
+                                    case 20:
+                                    case 21:
+                                       ODStringToName(szTempString);
+                                       strncpy(od_control.sysop_name,
+                                          szTempString, 38);
+                                       od_control.sysop_name[38] = '\0';
+                                       break;
+
+                                    case 22:
+                                       strcat(od_control.sysop_name, " ");
+                                       ODStringToName(szTempString);
+                                       strncat(od_control.sysop_name,
+                                          szTempString,
+                                          39 - strlen(od_control.system_name));
+                                       od_control.sysop_name[39] = '\0';
+                                       break;
+
+                                    case 23:
+                                       strncpy(od_control.system_name,
+                                          szTempString, 39);
+                                       od_control.system_name[39] = '\0';
+                                       break;
+
+                                    case 24:
+                                       od_control.user_rip =
+                                          ODCfgIsTrue(szTempString);
+                                 }
+                              }
+                           }
+                        }
+                     }
+                     break;
+
+                  case 22:
+                     bInactivitySet = TRUE;
+                     nInactivity = ODCfgGetWordDecimal(pchConfigText);
+                     if(nInactivity < 0) nInactivity = 0;
+                     break;
+
+                  case 23:
+                     btPageLength = (BYTE)ODCfgGetWordDecimal(pchConfigText);
+                     bPageLengthSet = TRUE;
+                     break;
+
+                  case 24:
+                     od_control.od_chat_color2 = 
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 25:
+                     od_control.od_chat_color1 =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 26:
+                     od_control.od_list_title_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 27:
+                     od_control.od_list_name_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 28:
+                     od_control.od_list_size_col = 
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 29:
+                     od_control.od_list_comment_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 30:
+                     od_control.od_list_offline_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 31:
+                     strncpy(szDesiredPersonality, pchConfigText, 32);
+                     szDesiredPersonality[32] = '\0';
+                     break;
+
+                  case 32:
+                     /* "NoFossil" */
+                     od_control.od_no_fossil = TRUE;
+                     break;
+
+                  case 33:
+                     /* "PortAddress" */
+                     od_control.od_com_address = ODCfgGetWordHex(pchConfigText);
+                     break;
+
+                  case 34:
+                     /* "PortIRQ" */
+                     od_control.od_com_irq =
+                        (char)ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 35:
+                     /* "ReceiveBuffer" */
+                     od_control.od_com_rx_buf =
+                        ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 36:
+                     /* "TransmitBuffer" */
+                     od_control.od_com_tx_buf =
+                        ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 37:
+                     /* "PagePromptColour" */
+                     od_control.od_continue_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 38:
+                     /* "LocalMode" */
+                     od_control.od_force_local = TRUE;
+                     break;
+
+                  case 39:
+                     /* "PopupMenuTitleColour" */
+                     od_control.od_menu_title_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 40:
+                     /* "PopupMenuBorderColour" */
+                     od_control.od_menu_border_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 41:
+                     /* "PopupMenuTextColour" */
+                     od_control.od_menu_text_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 42:
+                     /* "PopupMenuKeyColour" */
+                     od_control.od_menu_key_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 43:
+                     /* "PopupMenuHighlightColour" */
+                     od_control.od_menu_highlight_col =
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 44:
+                     /* "PopupMenuHighKeyColour" */
+                     od_control.od_menu_highkey_col = 
+                        od_color_config(pchConfigText);
+                     break;
+
+                  case 45:
+                     /* "NoFIFO" */
+                     od_control.od_com_no_fifo = TRUE;
+                     break;
+
+                  case 46:
+                     /* "FIFOTriggerSize" */
+                     od_control.od_com_fifo_trigger =
+                        (BYTE)ODCfgGetWordDecimal(pchConfigText);
+                     break;
+
+                  case 47:
+                     /* "DisableDTR" */
+                     ODStringCopy(od_control.od_disable_dtr, pchConfigText,
+                        sizeof(od_control.od_disable_dtr));
+                     break;
+
+                  case 48:
+                     /* "NoDTRDisable" */
+                     od_control.od_disable |= DIS_DTR_DISABLE;
+                     break;
+               }
+            }
+         }
+
+         /* Check if command is a programmer customized option. */
+         if(wCurrent >= TEXT_SIZE && custom_line_function != NULL)
+         {
+            (*custom_line_function)((char *)&szToken, pchConfigText);
+         }
+      }
+
+      /* Close the configuration file. */
+      fclose(pfConfigFile);}
+   else
+   {
+      if(bConfigFileRequired)
+      {
+         od_control.od_error = ERR_FILEOPEN;
+         ODInitError("Unable to access configuration file.");
+         exit(od_control.od_errorlevel[1]);
+      }
+   }
+
+   /* Close custom door info file */
+   if(pfCustomDropFile != NULL)
+   {
+      fclose(pfCustomDropFile);
+   }
+
+   bIsCallbackActive = FALSE;
+
+   /* Carry out normal OpenDoors initialization. */
+   bCalledFromConfig = TRUE;
+   od_init();
+   bCalledFromConfig = FALSE;
+
+   /* Update any settings that need to be updated. */
+   if(bPageSet)
+   {
+      od_control.od_pagestartmin = nPageStart;
+      od_control.od_pageendmin = nPageEnd;
+   }
+
+   if(bInactivitySet && nInactivity != 0)
+   {
+      od_control.od_inactivity = nInactivity;
+   }
+
+   if(bSysopNameSet)
+   {
+      strcpy((char *)&od_control.sysop_name, (char *)&szForcedSysopName);
+   }
+
+   if(bSystemNameSet)
+   {
+      strcpy((char *)&od_control.system_name, (char *)&szForcedSystemName);
+   }
+
+   if(bPageLengthSet)
+   {
+      od_control.od_page_len = btPageLength;
+   }
+
+   if(bWorkDirSet)
+   {
+      ODDirChangeCurrent(szWorkDir);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODCfgGetWordDecimal()                               *** PRIVATE FUNCTION ***
+ *
+ * Obtains the value of the next decimal number in the provided string, in the
+ * form of a WORD (16 bit value).
+ *
+ * Parameters: pszConfigText - String to examine.
+ *
+ *     Return: The first number obtained from the string.
+ */
+static WORD ODCfgGetWordDecimal(char *pszConfigText)
+{
+   ASSERT(pszConfigText != NULL);
+
+   /* Skip any initial non-numerical characters. */
+   while(*pszConfigText && (*pszConfigText < '0' || *pszConfigText > '9'))
+   {
+      ++pszConfigText;
+   }
+
+   /* Return value of number. */
+   return(atoi(pszConfigText));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODCfgGetDWordDecimal()                              *** PRIVATE FUNCTION ***
+ *
+ * Obtains the value of the next decimal number in the provided string, in the
+ * form of a DWORD (32 bit value).
+ *
+ * Parameters: pszConfigText - String to examine.
+ *
+ *     Return: The first number obtained from the string.
+ */
+static DWORD ODCfgGetDWordDecimal(char *pszConfigText)
+{                                
+   ASSERT(pszConfigText != NULL);
+
+   /* Skip any initial non-numerical characters. */
+   while(*pszConfigText && (*pszConfigText < '0' || *pszConfigText > '9'))
+   {
+      ++pszConfigText;
+   }
+
+   /* Return value of number. */
+   return(atol(pszConfigText));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODCfgGetWordHex()                                   *** PRIVATE FUNCTION ***
+ *
+ * Obtains the value of the next hexidecimal number in the provided string, in
+ * the form of a WORD (16 bit value).
+ *
+ * Parameters: pszConfigText - String to examine.
+ *
+ *     Return: The first number obtained from the string.
+ */
+static WORD ODCfgGetWordHex(char *pszConfigText)
+{
+   WORD wToReturn;
+
+   ASSERT(pszConfigText != NULL);
+
+   /* Skip any initial non-hexidecimal characters. */
+   while(*pszConfigText && (*pszConfigText < '0' || *pszConfigText > '9')
+      && (toupper(*pszConfigText) < 'A' || toupper(*pszConfigText) > 'F'))
+   {
+      ++pszConfigText;
+   }
+
+   sscanf(pszConfigText, "%x", &wToReturn);
+
+   return(wToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODCfgGetNextTime()                                  *** PRIVATE FUNCTION ***
+ *
+ * Obtains the next time from a string, updating the string pointer to point to
+ * the position in the string after the end of the time. The time information
+ * is stored in the btTimeNumVals and awTimeVal private global variables.
+ *
+ * Parameters: ppchConfigText - Pointer to character pointer to the string,
+ *                              which is to be updated.
+ *
+ *     Return: void
+ */
+static void ODCfgGetNextTime(char **ppchConfigText)
+{
+   char *pchConfigText = (char *)(*ppchConfigText);
+
+   ASSERT(ppchConfigText != NULL);
+   ASSERT(*ppchConfigText != NULL);
+
+   btTimeNumVals = 0;
+   awTimeVal[0] = 0;
+   awTimeVal[1] = 0;
+   awTimeVal[2] = 0;
+
+
+   while(*pchConfigText && (*pchConfigText == ' ' || *pchConfigText == '\t'))
+   {
+      ++pchConfigText;
+   }
+
+
+   while(*pchConfigText && btTimeNumVals < 3)
+   {
+      if(*pchConfigText < '0' || *pchConfigText > '9') break;
+      awTimeVal[btTimeNumVals++] = atoi(pchConfigText);
+      while(*pchConfigText && *pchConfigText >= '0' && *pchConfigText <= '9')
+      {
+         ++pchConfigText;
+      }
+      if(*pchConfigText == ':' || *pchConfigText == '.' || *pchConfigText == ','
+         || *pchConfigText == ';')
+      {
+         ++pchConfigText;
+      }
+   }
+
+   *ppchConfigText = (char *)pchConfigText;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODCfgIsTrue()                                       *** PRIVATE FUNCTION ***
+ *
+ * Determines whether the specified string represents a TRUE or FALSE value.
+ * For example "Yes", "TRUE", "Y" and "1" all represent TRUE values, while
+ * "No", "FALSE", "N" and "0" all represent FALSE values.
+ *
+ * Parameters: pszConfigText - String to examine.
+ *
+ *     Return: The Boolean value represented by the string.
+ */
+static BOOL ODCfgIsTrue(char *pszConfigText)
+{
+   ASSERT(pszConfigText != NULL);
+
+   while(*pszConfigText && (*pszConfigText == ' ' || *pszConfigText == '\t'))
+   {
+      ++pszConfigText;
+   }
+
+   switch(*pszConfigText)
+   {
+      case '1':
+      case 't':
+      case 'T':
+      case 'y':
+      case 'Y':
+      case 'g':
+      case 'G':
+         return(TRUE);
+   }
+
+   return(FALSE);
+}

+ 664 - 0
odoors/ODCmdLn.c

@@ -0,0 +1,664 @@
+/* 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: ODCmdLn.c
+ *
+ * Description: Implementation of od_parse_cmd_line() function, which
+ *              parses standard command-line parameters for OpenDors programs.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Jan 29, 1995  6.00  BP   Created.
+ *              Aug 19, 1995  6.00  BP   Cleaned up indentations.
+ *              Nov 12, 1995  6.00  BP   32-bit portability.
+ *              Nov 12, 1995  6.00  BP   Added -CONFIG parameter.
+ *              Dec 21, 1995  6.00  BP   Added -HANDLE parameter.
+ *              Dec 24, 1995  6.00  BP   puts() -> printf().
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Recognize -D for -DROPFILE.
+ *              Jan 03, 1996  6.00  BP   Parameters must begin with - or /.
+ *              Feb 06, 1996  6.00  BP   Added -SILENT for od_silent_mode.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 20, 1996  6.00  BP   Added bParsedCmdLine.
+ *              Feb 21, 1996  6.00  BP   Make cmd line options overriding.
+ *              Feb 25, 1996  6.00  BP   Fix -P COMx.
+ *              Feb 27, 1996  6.00  BP   Add -P COMx to command line help.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Apr 08, 1996  6.10  BP   Added command-line parsing callbacks.
+ *					 Apr 24, 2002  6.22  RS   Added -SOCKET parameter.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODPlat.h"
+#include "ODCore.h"
+#include "ODInEx.h"
+
+
+/* Maximum number of command-line arguments. Any additional arguments will */
+/* simply be ignored.                                                      */
+#define MAX_ARGS 32
+
+/* Size of temporary string that will hold any options following custom      */
+/* command-line keywords that are passed to the client application's handler */
+/* function.                                                                 */
+#define CUSTOM_OPTION_SIZE 80
+
+/* Command-line parameter identifiers. */
+typedef enum
+{
+   kParamConfigFile,
+   kParamLocal,
+   kParamBPS,
+   kParamPort,
+   kParamNode,
+   kParamHelp,
+   kParamPersonality,
+   kParamMaxTime,
+   kParamAddress,
+   kParamIRQ,
+   kParamNoFOSSIL,
+   kParamNoFIFO,
+   kParamDropFile,
+   kParamUserName,
+   kParamTimeLeft,
+   kParamSecurity,
+   kParamLocation,
+   kParamGraphics,
+   kParamBBSName,
+   kParamPortHandle,
+   kParamSocketDescriptor,
+   kParamSilentMode,
+   kParamOption,
+   kParamUnknown
+} tCommandLineParameter;
+
+
+/* Private function prototypes. */
+static void ODAdvanceToNextArg(INT *pnCurrentArg, INT nArgCount,
+   char *pszOption);
+static void ODGetNextArgName(INT *pnCurrentArg, INT nArgCount,
+   char *papszArguments[], char *pszString, size_t nStringSize);
+static tCommandLineParameter ODGetCommandLineParameter(char *pszArgument);
+
+
+/* Private variables. */
+#define CONFIG_FILENAME_SIZE 80
+static char szConfigFilename[CONFIG_FILENAME_SIZE];
+
+
+/* ----------------------------------------------------------------------------
+ * od_parse_cmd_line()
+ *
+ * Function to parse an OpenDoors program's command-line, interpreting
+ * standard command-line parameters. This is one of the few OpenDoors APIs
+ * that will not automatically initialize OpenDoors if it hasn't already
+ * been done. This is because od_parse_cmd_line() performs setup that must
+ * be done prior to OpenDoors initialization.
+ *
+ * Parameters: FOR NON-WIN32 VERSIONS:
+ *
+ *             nArgCount      - Number of command line arguments, as passed to
+ *                              main() in argc.
+ *
+ *             papszArguments - Pointer to array of pointers to string
+ *                              arguments, as passed to main() in argv. The
+ *                              first element of this array (usually the
+ *                              full path and filename of the executable)
+ *                              is ignored.
+ *
+ *             FOR WIN32 VERSION:
+ *
+ *             pszCmdLine     - Pointer to the command line string, as passed
+ *                              to WinMain().
+ *
+ *     Return: void
+ */
+#ifdef ODPLAT_WIN32
+ODAPIDEF void ODCALL od_parse_cmd_line(LPSTR pszCmdLine)
+#else /* !ODPLAT_WIN32 */
+ODAPIDEF void ODCALL od_parse_cmd_line(INT nArgCount, char *papszArguments[])
+#endif /* !ODPLAT_WIN32 */
+{
+   char *pszCurrentArg;
+   INT nCurrentArg;
+   INT n;
+#ifdef ODPLAT_WIN32
+   INT nArgCount;
+   char *papszArguments[MAX_ARGS];
+   char *pszCmdLineCopy;
+   char *pchCurrent
+#endif /* ODPLAT_WIN32 */
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_parse_cmd_line()");
+
+#ifdef ODPLAT_WIN32
+   /* Attempt to allocate space for a copy of the command line. */
+   pszCmdLineCopy = malloc(strlen(pszCmdLine) + 1);
+   if(pszCmdLineCopy == NULL)
+   {
+      od_control.od_error = ERR_MEMORY;
+      return;
+   }
+
+   /* Copy the command line text into our working copy. */
+   strcpy(pszCmdLineCopy, pszCmdLine);
+
+   /* Loop, building papszArguments and nArgCount. */
+   pchCurrent = pszCmdLineCopy;
+   for(nArgCount = 0; nArgCount < MAX_ARGS && *pchCurrent != '\0'; ++nArgCount)
+   {
+      /* Store address of the next command line argument. */
+      papszArguments[nArgCount] = pchCurrent;
+
+      /* Skip forward to the next white space. */
+      while(*pchCurrent != '\0' && !isspace(*pchCurrent))
+      {
+         ++pchCurrent;
+      }
+
+      /* Replace white space characters with '\0' string terminators, until */
+      /* we reach the next command line argument, or the end of the string. */
+      while(*pchCurrent != '\0' && isspace(*pchCurrent))
+      {
+         *pchCurrent = '\0';
+         ++pchCurrent;
+      }
+   }
+#endif /* ODPLAT_WIN32 */
+
+#ifndef ODPLAT_WIN32
+   /* Check validity of parameters. */
+   if(papszArguments == NULL)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      return;
+   }
+#endif /* !ODPLAT_WIN32 */
+
+   /* Record that od_parse_cmd_line() has been called. */
+   bParsedCmdLine = TRUE;
+
+   /* Initialize variables that are not initialized in od_init() if */
+   /* od_parse_cmd_line is specified.                               */
+   od_control.user_ansi = TRUE;
+   od_control.user_timelimit = 60;
+
+#ifdef ODPLAT_WIN32
+   for(nCurrentArg = 0; nCurrentArg < nArgCount; ++nCurrentArg)
+#else /* !ODPLAT_WIN32 */
+   for(nCurrentArg = 1; nCurrentArg < nArgCount; ++nCurrentArg)
+#endif /* !ODPLAT_WIN32 */
+   {
+      pszCurrentArg = papszArguments[nCurrentArg];
+
+      switch(ODGetCommandLineParameter(pszCurrentArg))
+      {
+         case kParamConfigFile:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            strncpy(szConfigFilename, papszArguments[nCurrentArg],
+               sizeof(szConfigFilename) - 1);
+            szConfigFilename[sizeof(szConfigFilename) - 1] = '\0';
+            od_control.od_config_filename = szConfigFilename;
+            break;
+
+         case kParamLocal:
+            od_control.od_force_local = TRUE;
+            break;
+
+         case kParamBPS:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.baud = atol(papszArguments[nCurrentArg]);
+            wPreSetInfo |= PRESET_BPS;
+            break;
+
+         case kParamPort:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            if(strnicmp(papszArguments[nCurrentArg], "COM", 3) == 0)
+            {
+               od_control.port = atoi(papszArguments[nCurrentArg] + 3) - 1;
+            }
+            else
+            {
+               od_control.port = atoi(papszArguments[nCurrentArg]);
+            }
+            wPreSetInfo |= PRESET_PORT;
+            break;
+
+         case kParamHelp:
+            if(od_control.od_cmd_line_help_func != NULL)
+            {
+               (*od_control.od_cmd_line_help_func)();
+               exit(0);
+            }
+
+#ifdef ODPLAT_WIN32
+            sprintf(szODWorkString, "%s Command Line Options",
+               strlen(od_control.od_prog_name) > 0 ? od_control.od_prog_name
+               : OD_VER_SHORTNAME);
+            if(od_control.od_cmd_line_help != NULL)
+            {
+               MessageBox(NULL, od_control.od_cmd_line_help, szODWorkString,
+                  MB_ICONINFORMATION | MB_OK);
+            }
+            else
+            {
+               MessageBox(NULL,
+                  "(Note that some options can be overriden by configuration or drop files.)\n"
+                  "\n"
+                  "-C x or -CONFIG x\t- Specfies configuration filename.\n"
+                  "-L or -LOCAL\t- Causes door to operate in local mode, without requiring a drop file.\n"
+                  "-D or -DROPFILE x\t- Door information file directory and/or filename.\n"
+                  "-N x or -NODE x\t- Sets the node number to use.\n"
+                  "-B x or -BPS x\t- Sets the serial port <---> modem bps (baud) rate to use.\n"
+                  "-P x or -PORT x\t- Sets serial port to use. For COM1: use -P 0 or -P COM1, for COM2: use -P 1 or -P COM2, etc.\n"
+                  "-HANDLE x\t- Provides an already open serial port handle.\n"
+						"-SOCKET x\t- Provides an already open TCP/IP socket descriptor.\n"
+                  "-SILENT\t\t- Operate in silent mode, with no local display.\n"
+                  "-MAXTIME x\t- Sets the maximum number of minutes that user will be permitted to access the door.\n"
+                  "-G or -GRAPHICS\t- Unless followed by 0 or N, turns on ANSI display mode.\n"
+                  "-BBSNAME x\t- Name of BBS.\n"
+                  "-USERNAME x\t- Name of user who is currently online.\n"
+                  "-TIMELEFT x\t- User's time remaining online.\n"
+                  "-SECURITY x\t- User's security level.\n"
+                  "-LOCATION x\t- Location from which user is calling.\n"
+                  "-?, -H or -HELP\t- Displays command-line help and exits.",
+                  szODWorkString, MB_ICONINFORMATION | MB_OK);
+            }
+#else /* !ODPLAT_WIN32 */
+            printf("AVALIABLE COMMAND LINE OPTIONS ");
+            if(od_control.od_cmd_line_help != NULL)
+            {
+               printf(od_control.od_cmd_line_help);
+            }
+            else
+            {
+               printf("(Some can be overriden by config/drop file)\n");
+               printf(" -C or -CONFIG    - Specfies configuration filename.\n");
+               printf(" -L or -LOCAL     - Causes door to operate in local mode, without requiring a\n");
+               printf("                    door information (drop) file.\n");
+               printf(" -D or -DROPFILE  - Door information file directory and/or filename.\n");
+               printf(" -N x or -NODE x  - Sets the node number to use.\n");
+               printf(" -B x or -BPS x   - Sets the serial port <---> modem bps (baud) rate to use.\n");
+               printf(" -P x or -PORT x  - Sets serial port to use. For COM1: use -P 0 or -P COM1, for\n");
+               printf("                    COM2: use -P 1 or -P COM2, etc.\n");
+               printf(" -ADDRESS x       - Sets serial port address in HEXIDECIMAL (if no FOSSIL).\n");
+               printf(" -IRQ x           - Sets the serial port IRQ line (if FOSSIL is not used).\n");
+               printf(" -NOFOSSIL        - Disables use of FOSSIL driver, even if available.\n");
+               printf(" -NOFIFO          - Disables use of 16550 FIFO buffers (only if no FOSSIL).\n");
+               printf(" -MAXTIME x       - Sets the maximum number of minutes that any user will be\n");
+               printf("                    permitted to access the door, regardless of time left.\n");
+               printf(" -SILENT          - Operate in silent mode, with no local display.\n");
+               printf(" -G or -GRAPHICS  - Unless followed by 0 or N, turns on ANSI display mode.\n");
+               printf(" -BBSNAME x       - Name of BBS.\n");
+               printf(" -USERNAME x      - Name of user who is currently online.\n");
+               printf(" -TIMELEFT x      - User's time remaining online.\n");
+               printf(" -SECURITY x      - User's security level.\n");
+               printf(" -LOCATION x      - Location from which user is calling.\n");
+               printf(" -?, -H or -HELP  - Displays command-line help and exits.\n");
+            }
+#endif /* !ODPLAT_WIN32 */
+            exit(1);
+            break;
+
+         case kParamNode:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.od_node = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamMaxTime:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.od_maxtime = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamAddress:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.od_com_address =
+               (WORD)strtol(papszArguments[nCurrentArg], NULL, 16);
+            break;
+
+         case kParamIRQ:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.od_com_irq = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamNoFOSSIL:
+            od_control.od_no_fossil = TRUE;
+            break;
+
+         case kParamNoFIFO:
+            od_control.od_com_no_fifo = TRUE;
+            break;
+
+         case kParamDropFile:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            strncpy(od_control.info_path, papszArguments[nCurrentArg],
+               sizeof(od_control.info_path) - 1);
+            od_control.info_path[sizeof(od_control.info_path) - 1] = '\0';
+            break;
+
+         case kParamUserName:
+            ODGetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+            od_control.user_name, sizeof(od_control.user_name));
+            break;
+
+         case kParamTimeLeft:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.user_timelimit = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamSecurity:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.user_security = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamLocation:
+            ODGetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+            od_control.user_location, sizeof(od_control.user_location));
+            break;
+
+         case kParamGraphics:
+            n = nCurrentArg;
+            if(++n < nArgCount)
+            {
+               if(atoi(papszArguments[n]) == 0 ||
+                  stricmp(papszArguments[n], "N") == 0)
+               {
+                  od_control.user_ansi = FALSE;
+                  ++nCurrentArg;
+                  break;
+               }
+            }
+            od_control.user_ansi = TRUE;
+            break;
+
+         case kParamBBSName:
+            ODGetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+               od_control.system_name, sizeof(od_control.system_name));
+            break;
+
+         case kParamSocketDescriptor:
+				od_control.od_use_socket = TRUE;
+				/* fall through */
+
+         case kParamPortHandle:
+            ODAdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+            od_control.od_open_handle = atoi(papszArguments[nCurrentArg]);
+            break;
+
+         case kParamSilentMode:
+            od_control.od_silent_mode = TRUE;
+            break;
+
+         case kParamUnknown:
+            /* If the client application provided a custom command line   */
+            /* handler function, then pass this unrecognized command-line */
+            /* parameter and any options to that callback function.       */
+            if(od_control.od_cmd_line_handler != NULL)
+            {
+               char szCustomOptions[CUSTOM_OPTION_SIZE];
+               ODGetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+                  szCustomOptions, sizeof(szCustomOptions));
+               (*od_control.od_cmd_line_handler)(pszCurrentArg,
+                  szCustomOptions);
+            }
+            break;
+      }
+   }
+
+#ifdef ODPLAT_WIN32
+   free(pszCmdLineCopy);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODAdvanceToNextArg()                                *** PRIVATE FUNCTION ***
+ *
+ * Moves to the argument for a particular command line option.
+ *
+ * Parameters: pnCurrentArg - Pointer to current argument number.
+ *
+ *             nArgCount    - Total number of arguments available.
+ *
+ *             pszOption    - Pointer to command line option name.
+ *
+ *     Return: void
+ */
+static void ODAdvanceToNextArg(INT *pnCurrentArg, INT nArgCount,
+   char *pszOption)
+{
+   ASSERT(pnCurrentArg != NULL);
+   ASSERT(pszOption != NULL);
+
+   if(++*pnCurrentArg >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n", pszOption);
+      exit(1);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODGetNextArgName()                                  *** PRIVATE FUNCTION ***
+ *
+ * Obtains a multi-word name from command-line.
+ *
+ * Parameters: pnCurrentArg   - Pointer to integer storing current argument
+ *                              number.
+ *
+ *             nArgCount      - The total number of command-line argument
+ *
+ *             papszArguments - Pointer to array of pointers to string
+ *                              arguments, as passed to main() in argv.
+ *
+ *             pszString      - Pointer to string in which name will string
+ *                              be stored.
+ *
+ *             nStringSize    - Size of the string.
+ *
+ *     Return: void
+ */
+static void ODGetNextArgName(INT *pnCurrentArg, INT nArgCount,
+   char *papszArguments[], char *pszString, size_t nStringSize)
+{
+   BOOL bFirst = TRUE;
+
+   ASSERT(pnCurrentArg != NULL);
+   ASSERT(papszArguments != NULL);
+   ASSERT(pszString != NULL);
+   ASSERT(nStringSize > 0);
+
+   if((*pnCurrentArg) + 1 >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n",
+         papszArguments[(*pnCurrentArg) - 1]);
+      exit(1);
+   }
+
+   pszString[0] = '\0';
+
+   while(++*pnCurrentArg < nArgCount)
+   {
+      if(ODGetCommandLineParameter(papszArguments[*pnCurrentArg])
+         != kParamOption)
+      {
+         --*pnCurrentArg;
+         break;
+      }
+
+      if(strlen(pszString) >= nStringSize - 1)
+      {
+         break;
+      }
+
+      if(!bFirst)
+      {
+         strcat(pszString, " ");
+      }
+
+      strncat(pszString, papszArguments[*pnCurrentArg],
+         strlen(pszString) - nStringSize - 1);
+      pszString[nStringSize - 1] = '\0';
+
+      bFirst = FALSE;
+   }
+
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODGetCommandLineParameter()                         *** PRIVATE FUNCTION ***
+ *
+ * Determines which command-line option, if any, is specified by an argument
+ * string.
+ *
+ * Parameters: pszArgument - Pointer to string containing raw command-line
+ *                           argument.
+ *
+ *     Return: A tCommandLineParameter, identifying which command-line option,
+ *             if any, matches the argument string.
+ */
+static tCommandLineParameter ODGetCommandLineParameter(char *pszArgument)
+{
+   ASSERT(pszArgument != NULL);
+
+   if(*pszArgument == '-' || *pszArgument == '/')
+   {
+      ++pszArgument;
+   }
+   else
+   {
+      return(kParamOption);
+   }
+
+   if(stricmp(pszArgument, "C") == 0
+      || stricmp(pszArgument, "CONFIG") == 0
+      || stricmp(pszArgument, "CONFIGFILE") == 0
+      || stricmp(pszArgument, "CFGFILE") == 0
+      || stricmp(pszArgument, "CFG") == 0)
+   {
+      return(kParamConfigFile);
+   }
+   else if(stricmp(pszArgument, "L") == 0
+      || stricmp(pszArgument, "LOCAL") == 0)
+   {
+      return(kParamLocal);
+   }
+   else if(stricmp(pszArgument, "B") == 0
+      || stricmp(pszArgument, "BPS") == 0
+      || stricmp(pszArgument, "BAUD") == 0)
+   {
+      return(kParamBPS);
+   }
+   else if(stricmp(pszArgument, "P") == 0
+      || stricmp(pszArgument, "PORT") == 0)
+   {
+      return(kParamPort);
+   }
+   else if(stricmp(pszArgument, "N") == 0
+      || stricmp(pszArgument, "NODE") == 0)
+   {
+      return(kParamNode);
+   }
+   else if(stricmp(pszArgument, "?") == 0
+      || stricmp(pszArgument, "H") == 0
+      || stricmp(pszArgument, "HELP") == 0)
+   {
+      return(kParamHelp);
+   }
+   else if(stricmp(pszArgument, "PERSONALITY") == 0)
+   {
+      return(kParamPersonality);
+   }
+   else if(stricmp(pszArgument, "MAXTIME") == 0)
+   {
+      return(kParamMaxTime);
+   }
+   else if(stricmp(pszArgument, "ADDRESS") == 0)
+   {
+      return(kParamAddress);
+   }
+   else if(stricmp(pszArgument, "IRQ") == 0)
+   {
+      return(kParamIRQ);
+   }
+   else if(stricmp(pszArgument, "NOFOSSIL") == 0)
+   {
+      return(kParamNoFOSSIL);
+   }
+   else if(stricmp(pszArgument, "NOFIFO") == 0)
+   {
+      return(kParamNoFIFO);
+   }
+   else if(stricmp(pszArgument, "DROPFILE") == 0 ||
+      stricmp(pszArgument, "D") == 0)
+   {
+      return(kParamDropFile);
+   }
+   else if(stricmp(pszArgument, "USERNAME") == 0)
+   {
+      return(kParamUserName);
+   }
+   else if(stricmp(pszArgument, "TIMELEFT") == 0)
+   {
+      return(kParamTimeLeft);
+   }
+   else if(stricmp(pszArgument, "SECURITY") == 0)
+   {
+      return(kParamSecurity);
+   }
+   else if(stricmp(pszArgument, "LOCATION") == 0)
+   {
+      return(kParamLocation);
+   }
+   else if(stricmp(pszArgument, "GRAPHICS") == 0
+      || stricmp(pszArgument, "G") == 0)
+   {
+      return(kParamGraphics);
+   }
+   else if(stricmp(pszArgument, "BBSNAME") == 0)
+   {
+      return(kParamBBSName);
+   }
+   else if(stricmp(pszArgument, "HANDLE") == 0)
+   {
+      return(kParamPortHandle);
+   }
+   else if(stricmp(pszArgument, "SOCKET") == 0)
+   {
+      return(kParamSocketDescriptor);
+   }
+   else if(stricmp(pszArgument, "SILENT") == 0)
+   {
+      return(kParamSilentMode);
+   }
+   else
+   {
+      return(kParamUnknown);
+   }
+}

+ 3705 - 0
odoors/ODCom.c

@@ -0,0 +1,3705 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODCom.c
+ *
+ * Description: Generic serial I/O routines, provide a single interface to
+ *              serial ports on any platform.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Oct 20, 1994  6.00  BP   Handle BIOS missing port addrs.
+ *              Oct 20, 1994  6.00  BP   Standardized coding style.
+ *              Oct 21, 1994  6.00  BP   Further isolated com routines.
+ *              Dec 07, 1994  6.00  BP   Support for RTS/CTS flow control.
+ *              Dec 10, 1994  6.00  BP   Allow word frmt setting for intern I/O
+ *              Dec 13, 1994  6.00  BP   Remove include of dir.h.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Jan 01, 1995  6.00  BP   Integrate in Win32 code.
+ *              Jan 01, 1995  6.00  BP   Add FLOW_DEFAULT setting.
+ *              Jan 01, 1995  6.00  BP   Added ODComWaitEvent().
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 21, 1995  6.00  BP   Ported to Win32.
+ *              Dec 21, 1995  6.00  BP   Add ability to use already open port.
+ *              Jan 09, 1996  6.00  BP   Supply actual in/out buffer size used.
+ *              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   Initial support for Door32 interface.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Jan 13, 1997  6.10  BP   Fixes for Door32 support.
+ *              Oct 19, 2001  6.20  RS   Added TCP/IP socket (telnet) support.
+ *              Oct 22, 2001  6.21  RS   Fixed disconnected socket detection.
+ *              Aug 22, 2002  6.22  RS   Fixed bugs in ODComCarrier and ODComWaitEvent
+ *              Aug 22, 2002  6.22  MD   Modified socket functions for non-blocking use.
+ *              Sep 18, 2002  6.22  MD   Fixed bugs in ODComWaitEvent for non-blocking sockets.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#ifdef ODPLAT_NIX
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <termios.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#ifdef __sun
+#include <sys/filio.h>
+#endif
+#endif
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODCom.h"
+#include "ODUtil.h"
+
+
+/* The following define determines whether serial port function should */
+/* ASSERT or return an error code on programmer erorrs (e.g. invalid   */
+/* parameters.                                                         */
+#define ASSERT_ON_INVALID_CALLS
+
+/* The following code defines the VERIFY_CALL() macro, which maps to an */
+/* ASSERT if ASSERT_ON_INVALID_CALLS is defined. Otherwise, this macro  */
+/* maps to a test which will return an error code to the caller.        */
+#ifdef ASSERT_ON_INVALID_CALLS
+#define VERIFY_CALL(x) ASSERT(x)
+#else /* !ASSERT_ON_INVALID_CALLS */
+#define VERIFY_CALL(x) if(x) return(kODRCInvalidCall)
+#endif /* !ASSERT_ON_INVALID_CALLS */
+
+
+/* The following defines determine which serial I/O mechanisms should be */
+/* supported.                                                            */
+
+/* Serial I/O mechanisms supported under MS-DOS version. */
+#ifdef ODPLAT_DOS
+#define INCLUDE_FOSSIL_COM                      /* INT 14h FOSSIL-based I/O. */
+#define INCLUDE_UART_COM                   /* Internal interrupt driven I/O. */
+#endif /* ODPLAT_DOS */
+
+/* Serial I/O mechanisms supported under Win32 version. */
+#ifdef ODPLAT_WIN32
+#define INCLUDE_WIN32_COM                           /* Win32 API serial I/O. */
+#define INCLUDE_DOOR32_COM                          /* Door32 I/O interface. */
+#define INCLUDE_SOCKET_COM                          /* TCP/IP socket I/O.    */
+#endif /* ODPLAT_WIN32 */
+
+/* Serial I/O mechanisms supported inder *nix version */
+#ifdef ODPLAT_NIX
+#define INCLUDE_STDIO_COM
+#define INCLUDE_SOCKET_COM                          /* TCP/IP socket I/O.    */
+
+/* Win32 Compat. Stuff */
+#define SOCKET	int
+#define WSAEWOULDBLOCK	EAGAIN
+#define SOCKET_ERROR -1
+#define WSAGetLastError() errno
+#define ioctlsocket	ioctl
+#define closesocket	close
+#endif /* ODPLAT_NIX */
+
+/* Include "windows.h" for Win32-API based serial I/O. */
+#ifdef INCLUDE_WIN32_COM
+#include "windows.h"
+#endif /* INCLUDE_WIN32_COM */
+
+/* terminal variables */
+#ifdef INCLUDE_STDIO_COM
+struct termios tio_default;				/* Initial term settings */
+#endif
+
+
+#if defined(_WIN32) && defined(INCLUDE_SOCKET_COM)
+	#include <winsock.h>
+	static WSADATA WSAData;		/* WinSock data */
+#endif
+
+/* ========================================================================= */
+/* Serial port object structure.                                             */
+/* ========================================================================= */
+
+/* Win32-API serial I/O implementation requires current timeout setting */
+/* status variable in serial port object structure.                     */
+#ifdef INCLUDE_WIN32_COM
+typedef enum
+{
+   kNotSet,
+   kBlocking,
+   kNonBlocking
+} tReadTimeoutState;
+#endif /* INCLUDE_WIN32_COM */
+
+/* Structure associated with each serial port handle. */
+typedef struct
+{
+   BOOL bIsOpen;
+   BOOL bUsingClientsHandle;
+   BYTE btFlowControlSetting;
+   long lSpeed;
+   BYTE btPort;
+   int nPortAddress;
+   BYTE btIRQLevel;
+   BYTE btWordFormat;
+   int nReceiveBufferSize;
+   int nTransmitBufferSize;
+   BYTE btFIFOSetting;
+   tComMethod Method;
+   void (*pfIdleCallback)(void);
+#ifdef INCLUDE_WIN32_COM
+   HANDLE hCommDev;
+   tReadTimeoutState ReadTimeoutState;
+#endif /* INCLUDE_WIN32_COM */
+#ifdef INCLUDE_DOOR32_COM
+   HINSTANCE hinstDoor32DLL;
+   BOOL (WINAPI *pfDoorInitialize)(void);
+   BOOL (WINAPI *pfDoorShutdown)(void);
+   BOOL (WINAPI *pfDoorWrite)(const BYTE *pbData, DWORD dwSize);
+   DWORD (WINAPI *pfDoorRead)(BYTE *pbData, DWORD dwSize);
+   HANDLE (WINAPI *pfDoorGetAvailableEventHandle)(void);
+   HANDLE (WINAPI *pfDoorGetOfflineEventHandle)(void);
+#endif /* INCLUDE_DOOR32_COM */
+#ifdef INCLUDE_SOCKET_COM
+	SOCKET	socket;
+	int	old_delay;
+#endif
+} tPortInfo;
+
+/* ========================================================================= */
+/* Internal interrupt-driven serial I/O specific defintions & functions.     */
+/* ========================================================================= */
+
+#ifdef INCLUDE_UART_COM
+
+/* Private function prototypes, used by internal UART async serial I/O. */
+static void ODComSetVect(BYTE btVector, void (INTERRUPT far *pfISR)(void));
+static void (INTERRUPT far *ODComGetVect(BYTE btVector))(void);
+static void INTERRUPT ODComInternalISR();
+static BOOL ODComInternalTXReady(void);
+static void ODComInternalResetRX(void);
+static void ODComInternalResetTX(void);
+
+
+/* Offsets of UART registers. */
+#define TXBUFF  0                       /* Transmit buffer register. */
+#define RXBUFF  0                       /* Receive buffer register. */
+#define DLLSB   0                       /* Divisor latch LS byte. */
+#define DLMSB   1                       /* Divisor latch MS byte. */
+#define IER     1                       /* Interrupt enable register. */
+#define IIR     2                       /* Interrupt ID register. */
+#define LCR     3                       /* Line control register. */
+#define MCR     4                       /* Modem control register. */
+#define LSR     5                       /* Line status register. */
+#define MSR     6                       /* Modem status register. */
+
+/* FIFO control register bits. */
+#define FE      0x01                    /* FIFO enable. */
+#define RR      0x02                    /* FIFO receive buffer reset. */
+#define TR      0x04                    /* FIFO transmit buffer reset. */
+#define FTS_1   0x00                    /* FIFO trigger size 1 byte. */
+#define FTS_4   0x40                    /* FIFO trigger size 4 bytes. */
+#define FTS_8   0x80                    /* FIFO trigger size 8 bytes. */
+#define FTS_14  0xc0                    /* FIFO trigger size 14 bytes. */
+
+/* Modem control register (MCR) bits. */
+#define DTR     0x01                    /* Data terminal ready. */
+#define NOT_DTR 0xfe                    /* All bits other than DTR. */
+#define RTS     0x02                    /* Request to send. */
+#define NOT_RTS 0xfd                    /* All bits other than RTS. */
+#define OUT1    0x04                    /* Output #1. */
+#define OUT2    0x08                    /* Output #2. */
+#define LPBK    0x10                    /* Loopback mode bit. */
+
+/* Modem status register (MSR) bits. */
+#define DCTS    0x01                    /* Delta clear to send. */
+#define DDSR    0x02                    /* Delta data set ready. */
+#define TERI    0x04                    /* Trailing edge ring indicator. */
+#define DRLSD   0x08                    /* Delta Rx line signal detect. */
+#define CTS     0x10                    /* Clear to send. */
+#define DSR     0x20                    /* Data set ready. */
+#define RI      0x40                    /* Ring indicator. */
+#define RLSD    0x80                    /* Receive line signal detect. */
+
+/* Line control register (LCR) bits. */
+#define DATA5   0x00                    /* 5 Data bits. */
+#define DATA6   0x01                    /* 6 Data bits. */
+#define DATA7   0x02                    /* 7 Data bits. */
+#define DATA8   0x03                    /* 8 Data bits. */
+
+#define STOP1   0x00                    /* 1 Stop bit. */
+#define STOP2   0x04                    /* 2 Stop bits. */
+
+#define NOPAR   0x00                    /* No parity. */
+#define ODDPAR  0x08                    /* Odd parity. */
+#define EVNPAR  0x18                    /* Even parity. */
+#define STKPAR  0x28                    /* Sticky parity. */
+#define ZROPAR  0x38                    /* Zero parity. */
+
+#define DLATCH  0x80                    /* Baud rate divisor latch. */
+#define NOT_DL  0x7f                    /* Turns off divisor latch. */
+
+/* Line status register (LSR) bits. */
+#define RDR     0x01                    /* Receive data ready. */
+#define ERRS    0x1E                    /* All the error bits. */
+#define TXR     0x20                    /* Transmitter ready. */
+
+/* Interrupt enable register (IER) bits. */
+#define DR      0x01                    /* Data ready. */
+#define THRE    0x02                    /* Transmit holding register empty. */
+#define RLS     0x04                    /* Receive line status. */
+#define MS      0x08                    /* Modem status. */
+
+/* Flow control receive buffer limits. */
+#define RECEIVE_LOW_NUM     1           /* Numerator for low water mark. */
+#define RECEIVE_LOW_DENOM   4           /* Denominator for low water mark. */
+#define RECEIVE_HIGH_NUM    3           /* Numerator for high water mark. */
+#define RECEIVE_HIGH_DENOM  4           /* Denominator for high water mark. */
+
+
+/* Built-in async serial I/O global variables. */
+
+/* These variabes are shared throughout the functions that handle the      */
+/* built-in UART-base serial I/O, including the interrupt service routine. */
+/* Since only one copy of these variables exist, the built-in serial I/O   */
+/* routines may only be used to access one port at a time.                 */
+
+/* Default port addresses. */
+/* First 4 addresses are standard addresses used for PC/AT COM1 thru COM4. */
+/* Second 4 addresses are PS/2 standard addresses used for COM5 thru COM8. */
+static int anDefaultPortAddr[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8,
+                                  0x4220, 0x4228, 0x5220, 0x5228};
+
+/* UART address variables. */
+static int nDataRegAddr;                /* Data register address. */
+static int nIntEnableRegAddr;           /* Interrupt enable register. */
+static int nIntIDRegAddr;               /* Interrupt ID register address. */
+static int nLineCtrlRegAddr;            /* Line control register address. */
+static int nModemCtrlRegAddr;           /* Modem control register address. */
+static int nLineStatusRegAddr;          /* Line status register address. */
+static int nModemStatusRegAddr;         /* Modem status register address. */
+
+/* General variables. */
+static BYTE btIntVector;                /* Interrupt vector number for port. */
+static char btI8259Bit;                 /* 8259 bit mask. */
+static char btI8259Mask;                /* Copy as it was before open. */
+static int nI8259MaskRegAddr;           /* Address of i8259 mask register. */
+static int nI8259EndOfIntRegAddr;       /* Address of i8259 EOI register. */
+static int nI8259MasterEndOfIntRegAddr; /* Address of master PIC EOI reg. */
+static char btOldIntEnableReg;          /* Original IER contents. */
+static char btOldModemCtrlReg;          /* Original MCR contents. */
+static void (INTERRUPT far *pfOldISR)();/* Original ISR routine for IRQ. */
+static char bUsingFIFO = FALSE;         /* Are we using 16550 FIFOs? */
+static unsigned char btBaseFIFOCtrl;    /* FIFO control register byte. */
+
+
+/* Transmit queue variables. */
+static int nTXQueueSize;                /* Actual size of transmit queue. */
+static char *pbtTXQueue;                /* Pointer to transmit queue. */
+static int nTXInIndex;                  /* Location to store next byte. */
+static int nTXOutIndex;                 /* Location to get next byte. */
+static int nTXChars;                    /* Count of characters in queue. */
+
+/*  Receive queue variables. */
+static int nRXQueueSize;                /* Actual size of receive queue. */
+static char *pbtRXQueue;                /* Pointer to receive queue. */
+static int nRXInIndex;                  /* Location to store next byte. */
+static int nRXOutIndex;                 /* Location to retrieve next byte. */
+static int nRXChars;                    /* Count of characters in queue. */
+
+/* Flow control variables. */
+static int nRXHighWaterMark;            /* High water mark for queue size. */
+static int nRXLowWaterMark;             /* Low water mark for queue size. */
+static BYTE btFlowControl;              /* Flow control method. */
+static BOOL bStopTrans;                 /* Flag set to stop transmitting. */
+
+/* ----------------------------------------------------------------------------
+ * ODComSetVect()                                      *** PRIVATE FUNCTION ***
+ *
+ * Sets the function to be called for the specified interrupt level.
+ *
+ * Parameters: btVector - Interrupt vector level, a value from 0 to 255.
+ *
+ *             pfISR    - Pointer to the ISR function to be called.
+ *
+ *     Return: void
+ */
+static void ODComSetVect(BYTE btVector, void (INTERRUPT far *pfISR)(void))
+{
+   ASM   push ds
+   ASM   mov ah, 0x25
+   ASM   mov al, btVector
+   ASM   lds dx, pfISR
+   ASM   int 0x21
+   ASM   pop ds
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComGetVect()                                      *** PRIVATE FUNCTION ***
+ *
+ * Returns the address of the function that is currently called for the
+ * specified interrupt level.
+ *
+ * Parameters: btVector - Interrupt vector level, a value from 0 to 255.
+ *
+ *     Return: A pointer to the code that is currently executed on an interrupt
+ *             of the speceified level.
+ */
+static void (INTERRUPT far *ODComGetVect(BYTE btVector))(void)
+{
+   void (INTERRUPT far *pfISR)(void);
+
+   ASM   push es
+   ASM   mov ah, 0x35
+   ASM   mov al, btVector
+   ASM   int 0x21
+   ASM   mov word ptr pfISR, bx
+   ASM   mov word ptr [pfISR+2], bx
+   ASM   pop es
+
+   return(pfISR);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComInternalTXReady()                              *** PRIVATE FUNCTION ***
+ *
+ * Returns TRUE if the internal serial I/O transmit buffer is not full.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static BOOL ODComInternalTXReady(void)
+{
+   /* Return TRUE if tx_chars is less than total tx buffer size. */
+   return(nTXChars < nTXQueueSize);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComInternalResetTX()                              *** PRIVATE FUNCTION ***
+ *
+ * Clears transmit buffer used by internal serial I/O routines.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODComInternalResetTX(void)
+{
+   /* Disable interrupts. */
+   ASM cli
+
+   /* If we are using 16550A FIFO buffers, then clear the FIFO transmit */
+   /* buffer.                                                           */
+   if(bUsingFIFO)
+   {
+      ASM mov al, btBaseFIFOCtrl
+      ASM or al, TR
+      ASM mov dx, nIntIDRegAddr
+      ASM out dx, al
+   }
+
+   /* Reset start, end and total count of characters in buffer      */
+   /* If buffer is still empty on next transmit interrupt, transmit */
+   /* interrupts will be turned off.                                */
+   nTXChars = nTXInIndex = nTXOutIndex = 0;
+
+   /* Re-enable interrupts. */
+   ASM sti
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComInternalResetRX()                              *** PRIVATE FUNCTION ***
+ *
+ * Clears receive buffer used by internal serial I/O routines.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODComInternalResetRX(void)
+{
+   /* Disable interrupts. */
+   ASM cli
+
+   /* If we are using 16550A FIFO buffers, then clear the FIFO receive */
+   /* buffer.                                                          */
+   if(bUsingFIFO)
+   {
+      ASM mov al, btBaseFIFOCtrl
+      ASM or al, RR
+      ASM mov dx, nIntIDRegAddr
+      ASM out dx, al
+   }
+
+   /* Reset start, end and total count of characters in buffer           */
+   /* On the next receive interrupt, data will be added at the beginning */
+   /* of the buffer.                                                     */
+   nRXChars = nRXInIndex = nRXOutIndex = 0;
+
+   /* Re-enable interrupts. */
+   ASM sti
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComInternalISR()                                  *** PRIVATE FUNCTION ***
+ *
+ * Interrupt service routine for internal UART-based serial I/O.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void INTERRUPT ODComInternalISR()
+{
+   char btIIR;
+   BYTE btTemp;
+
+   /* Loop until there are no more pending operations to perform with the */
+   /* UART. */
+   for(;;)
+   {
+      /* While bit 0 of the UART IIR is 0, there remains pending operations. */
+      /* Read IIR. */
+      ASM mov dx, nIntIDRegAddr
+      ASM in al, dx
+      ASM mov btIIR, al
+
+      /* If IIR bit 0 is set, then UART processing is finished.             */
+      if(btIIR & 0x01) break;
+
+      /* Bits 1 and 2 of the IIR register identify the type of operation */
+      /* to be performed with the UART.                                  */
+
+      /* Switch on bits 1 and 2 of IIR register. */
+      switch(btIIR & 0x06)
+      {
+         case 0x00:
+            /* Operation: modem status has changed. */
+
+            /* Read modem status register. */
+            ASM mov dx, nModemStatusRegAddr
+            ASM in al, dx
+            ASM mov btTemp, al
+
+            /* We only care about the modem status register if we are */
+            /* using RTS/CTS flow control, and the CTS register has  */
+            /* changed.                                               */
+            if((btFlowControl & FLOW_RTSCTS) && (btTemp & DCTS))
+            {
+               if(btTemp & CTS)
+               {
+                  /* If CTS has gone high, then re-enable transmission. */
+                  bStopTrans = FALSE;
+
+                  /* Restart transmission if there is anything in the */
+                  /* transmit buffer.                                 */
+                  if(nTXChars > 0)
+                  {
+                     /* Enable transmit interrupt. */
+                     ASM mov dx, nIntEnableRegAddr
+                     ASM in al, dx
+                     ASM or al, THRE
+                     ASM out dx, al
+                  }
+               }
+               else
+               {
+                  /* If CTS has gone low, then stop transmitting. */
+                  bStopTrans = TRUE;
+               }
+            }
+
+            break;
+
+         case 0x02:
+            /* Operation: room in transmit register/FIFO. */
+            /* Check whether we can send further characters to transmit. */
+            if(nTXChars <= 0 || bStopTrans)
+            {
+               /* If we cannot send more characters, then turn off */
+               /* transmit interrupts.                             */
+               ASM mov dx, nIntEnableRegAddr
+               ASM in al, dx
+               ASM and al, 0xfd
+               ASM out dx, al
+            }
+            else
+            {
+               /* If we still have characters to transmit ... */
+
+               /* Check line status register to determine whether transmit  */
+               /* register/FIFO truly has room. Some UARTs trigger transmit */
+               /* interrupts before the character has been tranmistted,     */
+               /* causing transmitted characters to be lost.                */
+               ASM mov dx, nLineStatusRegAddr
+               ASM in al, dx
+               ASM mov btTemp, al
+
+               if(btTemp & TXR)
+               {
+                  /* There is room in the transmit register/FIFO. */
+
+                  /* Get next character to transmit. */
+                  btTemp = pbtTXQueue[nTXOutIndex++];
+
+                  /* Write character to UART data register. */
+                  ASM mov dx, nDataRegAddr
+                  ASM mov al, btTemp
+                  ASM out dx, al
+
+                  /* Wrap-around transmit buffer pointer, if needed. */
+                  if (nTXOutIndex == nTXQueueSize)
+                  {
+                     nTXOutIndex = 0;
+                  }
+
+                  /* Decrease count of characters in transmit buffer. */
+                  nTXChars--;
+               }
+            }
+            break;
+
+         case 0x04:
+            /* Operation: Receive Data. */
+
+            /* Get character from receive buffer ASAP. */
+            ASM mov dx, nDataRegAddr
+            ASM in al, dx
+            ASM mov btTemp, al
+
+            /* If receive buffer is above high water mark. */
+            if(nRXChars >= nRXHighWaterMark)
+            {
+               /* If we are using flow control, then stop sender from */
+               /* sending.                                            */
+               if(btFlowControl & FLOW_RTSCTS)
+               {
+                  /* If using RTS/CTS flow control, then lower RTS line. */
+                  ASM mov dx, nModemCtrlRegAddr
+                  ASM in al, dx
+                  ASM and al, NOT_RTS
+                  ASM out dx, al
+               }
+            }
+
+            /* If there is room in receive buffer. */
+            if(nRXChars < nRXQueueSize)
+            {
+               /* Store the new character in the receive buffer. */
+               pbtRXQueue[nRXInIndex++] = btTemp;
+
+               /* Wrap-around buffer index, if needed. */
+               if (nRXInIndex == nRXQueueSize)
+                  nRXInIndex = 0;
+
+               /* Increment count of characters in the buffer. */
+               nRXChars++;
+            }
+            break;
+
+         case 0x06:
+            /* Operation: Change in line status register. */
+
+            /* We just read the register to move on to further operations. */
+            ASM mov dx, nLineStatusRegAddr
+            ASM in al, dx
+            break;
+      }
+   }
+
+   /* Send end of interrupt to interrupt controller(s). */
+   ASM mov dx, nI8259EndOfIntRegAddr
+   ASM mov al, 0x20
+   ASM out dx, al
+
+   if(nI8259MasterEndOfIntRegAddr != 0)
+   {
+      ASM mov dx, nI8259MasterEndOfIntRegAddr
+      ASM mov al, 0x20
+      ASM out dx, al
+   }
+}
+#endif /* INCLUDE_UART_COM */
+
+
+
+/* ========================================================================= */
+/* Win32-API base serial I/O specific functions.                             */
+/* ========================================================================= */
+
+#ifdef INCLUDE_WIN32_COM
+
+/* Function prototypes. */
+static tODResult ODComWin32SetReadTimeouts(tPortInfo *pPortInfo,
+   tReadTimeoutState RequiredTimeoutState);
+
+
+/* ----------------------------------------------------------------------------
+ * ODComWin32SetReadTimeouts()                         *** PRIVATE FUNCTION ***
+ *
+ * Ensures that read timeout state is set appropriately.
+ *
+ * Parameters: pPortInfo            - Pointer to serial port handle structure.
+ *
+ *             RequiredTimeoutState - Timeout state that should be set.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+static tODResult ODComWin32SetReadTimeouts(tPortInfo *pPortInfo,
+   tReadTimeoutState RequiredTimeoutState)
+{
+   ASSERT(pPortInfo != NULL);
+
+   /* If timeout state must be changed ... */
+   if(RequiredTimeoutState != pPortInfo->ReadTimeoutState)
+   {
+      COMMTIMEOUTS CommTimeouts;
+
+      /* Obtain current timeout settings. */
+      if(!GetCommTimeouts(pPortInfo->hCommDev, &CommTimeouts))
+      {
+         return(kODRCGeneralFailure);
+      }
+
+      /* Setup timeout setting structure appropriately. */
+      switch(RequiredTimeoutState)
+      {
+         case kBlocking:
+            CommTimeouts.ReadIntervalTimeout = 0;
+            CommTimeouts.ReadTotalTimeoutMultiplier = 0;
+            CommTimeouts.ReadTotalTimeoutConstant = 0;
+            break;
+         case kNonBlocking:
+            CommTimeouts.ReadIntervalTimeout = INFINITE;
+            CommTimeouts.ReadTotalTimeoutMultiplier = 0;
+            CommTimeouts.ReadTotalTimeoutConstant = 0;
+            break;
+         default:
+            ASSERT(FALSE);
+      }
+
+      /* Write settings. */
+      if(!SetCommTimeouts(pPortInfo->hCommDev, &CommTimeouts))
+      {
+         return(kODRCGeneralFailure);
+      }
+
+      /* Record current read timeout setting state for subsequent */
+      /* calls to this function.                                  */
+      pPortInfo->ReadTimeoutState = RequiredTimeoutState;
+   }
+
+   return(kODRCSuccess);
+}
+
+#endif /* INCLUDE_WIN32_COM */
+
+
+
+/* ========================================================================= */
+/* Implementation of generic serial I/O functions.                           */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODComAlloc()
+ *
+ * Allocates a serial port handle, which can be passed to other ODCom...()
+ * functions.
+ *
+ * Parameters: phPort - Pointer to serial port handle.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComAlloc(tPortHandle *phPort)
+{
+   tPortInfo *pPortInfo;
+
+   VERIFY_CALL(phPort != NULL);
+
+   /* Attempt to allocate a serial port information structure. */
+   pPortInfo = malloc(sizeof(tPortInfo));
+
+   /* If memory allocation failed, return with failure. */
+   if(pPortInfo == NULL)
+   {
+      *phPort = ODPTR2HANDLE(NULL, tPortInfo);
+      return(kODRCNoMemory);
+   }
+
+   /* Initialize serial port information structure. */
+   pPortInfo->bIsOpen = FALSE;
+   pPortInfo->bUsingClientsHandle = FALSE;
+   pPortInfo->btFlowControlSetting = FLOW_DEFAULT;
+   pPortInfo->lSpeed = SPEED_UNSPECIFIED;
+   pPortInfo->btWordFormat = ODPARITY_NONE | DATABITS_EIGHT | STOP_ONE;
+   pPortInfo->nReceiveBufferSize = 1024;
+   pPortInfo->nTransmitBufferSize = 1024;
+   pPortInfo->btFIFOSetting = FIFO_ENABLE | FIFO_TRIGGER_8;
+   pPortInfo->Method = kComMethodUnspecified;
+   pPortInfo->pfIdleCallback = NULL;
+
+   /* Convert serial port information structure pointer to a handle. */
+   *phPort = ODPTR2HANDLE(pPortInfo, tPortInfo);
+
+   /* Set default port number. */
+   ODComSetPort(*phPort, 0);
+
+#if defined(INCLUDE_SOCKET_COM) && defined(_WINSOCKAPI_)
+	WSAStartup(MAKEWORD(1,1), &WSAData);
+#endif
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComFree()
+ *
+ * Deallocates a serial port handle that is no longer required.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComFree(tPortHandle hPort)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   /* Deallocate port information structure. */
+   free(pPortInfo);
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetIdleFunction()
+ *
+ * Sets function to call when serial I/O module is idle, or NULL for none.
+ *
+ * Parameters: hPort      - Handle to a serial port object.
+ *
+ *             pfCallback - Pointer to function to call when idle.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetIdleFunction(tPortHandle hPort,
+   void (*pfCallback)(void))
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->pfIdleCallback = pfCallback;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetFlowControl()
+ *
+ * Sets the flow control method(s) to use. If this function is not called,
+ * RTS/CTS flow control is used by default. This function should not be
+ * called while the port is open.
+ *
+ * Parameters: hPort                - Handle to a serial port object.
+ *
+ *             btFlowControlSetting - One or more FLOW_* settings, joined
+ *                                    by bitwise-or (|) operators. If
+ *                                    FLOW_DEFAULT is included, all other
+ *                                    settings are ignored, and the default
+ *                                    settings for this serial I/O method
+ *                                    are used.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetFlowControl(tPortHandle hPort, BYTE btFlowControlSetting)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->btFlowControlSetting = btFlowControlSetting;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetSpeed()
+ *
+ * Sets the serial port BPS (baud) rate to use. Depending upon the serial I/O
+ * method being used, this setting may be controlled by the user's system
+ * configuration, in which case the value passed to this function wil have
+ * no effect. A setting of SPEED_UNSPECIFIED, indicates that the serial port
+ * speed should not be changed, if it is possible not to do so with the serial
+ * I/O method being used. This function cannot be called while the port is
+ * open.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *             lSpeed - A valid BPS rate, or SPEED_UNSPECIFIED.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetSpeed(tPortHandle hPort, long lSpeed)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->lSpeed = lSpeed;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetPort()
+ *
+ * Sets the serial port number to be associated with this port handle. This
+ * function cannot be called while the port is open. Calling this function
+ * also sets the IRQ line number and serial port address to their defaults
+ * for this port number, if this values can be set for the serial I/O method
+ * being used.
+ *
+ * Parameters: hPort  - Handle to a serial port object.
+ *
+ *             btPort - Serial port identification, where 0 typically
+ *                      corresponds to COM1, 1 to COM2, and so on.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetPort(tPortHandle hPort, BYTE btPort)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   /* Store port number in port information structure. */
+   pPortInfo->btPort = btPort;
+
+
+#ifdef INCLUDE_UART_COM
+   /* Get default address for this port number, if possible. */
+   pPortInfo->nPortAddress = 0;
+
+   if(btPort < 4)
+   {
+      /* Get port address from BIOS data area. */
+      pPortInfo->nPortAddress = *(((int far *)0x400) + btPort);
+   }
+
+   /* If port address is still unknown, and we know the default */
+   /* address, then use that address. */
+   if(pPortInfo->nPortAddress == 0
+      && btPort < DIM(anDefaultPortAddr))
+   {
+      pPortInfo->nPortAddress = anDefaultPortAddr[btPort];
+   }
+
+
+   /* Set default IRQ number for this port number. */
+
+   /* Ports 0 and 2 (COM1:, COM3:) default to IRQ 4, all others */
+   /* default to IRQ 3. */
+   if(btPort == 0 || btPort == 2)
+   {
+      pPortInfo->btIRQLevel = 4;
+   }
+   else
+   {
+      pPortInfo->btIRQLevel = 3;
+   }
+#endif /* INCLUDE_UART_COM */
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetPortAddress()
+ *
+ * Sets address of the serial port, if it can be set for the serial I/O method
+ * being used. This function cannot be called when the port is open.
+ *
+ * Parameters: hPort        - Handle to a serial port object.
+ *
+ *             nPortAddress - Address of serial port.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetPortAddress(tPortHandle hPort, int nPortAddress)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->nPortAddress = nPortAddress;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetIRQ()
+ *
+ * Sets the IRQ line associated with this serial port, if applicable for the
+ * serial I/O method being used. This function cannot be called while the port
+ * is open.
+ *
+ * Parameters: hPort      - Handle to a serial port object.
+ *
+ *             btIRQLevel - A number from 1 to 15, specifying the IRQ line that
+ *                          the serial port is wired to.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetIRQ(tPortHandle hPort, BYTE btIRQLevel)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->btIRQLevel = btIRQLevel;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetWordFormat()
+ *
+ * Determine the word format (number of data bits, stop bits and parity bits)
+ * to use, if it can be set for the serial I/O method being used. If this
+ * function is not called, N81 word format is used. This function can only
+ * be called when the port is not open.
+ *
+ * Parameters: hPort        - Handle to a serial port object.
+ *
+ *             btWordFormat - Bitwise-or (|) of PARITY_*, STOP_* and DATABITS_*
+ *                            settings which determine the word format to use.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetWordFormat(tPortHandle hPort, BYTE btWordFormat)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->btWordFormat = btWordFormat;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetRXBuf()
+ *
+ * Sets the desired size of the receive buffer, if possible for the
+ * serial I/O method being used. Note that for some serial I/O methods, this
+ * buffer size is fixed, controlled by the user's system configuration.
+ * No error is generated when this function is called when such serial I/O
+ * methods will be used - in this case this setting will simply have no effect.
+ * This function cannot be called while the port is open.
+ *
+ * Parameters: hPort              - Handle to a serial port object.
+ *
+ *             nReceiveBufferSize - Number of bytes in the receive buffer.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetRXBuf(tPortHandle hPort, int nReceiveBufferSize)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->nReceiveBufferSize = nReceiveBufferSize;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetTXBuf()
+ *
+ * Sets the desired size of the transmit buffer, if possible for the
+ * serial I/O method being used. Note that for some serial I/O methods, this
+ * buffer size is fixed, controlled by the user's system configuration.
+ * No error is generated when this function is called when such serial I/O
+ * methods will be used - in this case this setting will simply have no effect.
+ * This function cannot be called while the port is open.
+ *
+ * Parameters: hPort               - Handle to a serial port object.
+ *
+ *             nTransmitBufferSize - Number of bytes in the transmit buffer.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetTXBuf(tPortHandle hPort, int nTransmitBufferSize)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->nTransmitBufferSize = nTransmitBufferSize;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetFIFO()
+ *
+ * Enables or disables use of the UART FIFO buffers (if applicable), and also
+ * sets the FIFO trigger level. This function cannot be called while the port
+ * is open.
+ *
+ * Parameters: hPort         - Handle to a serial port object.
+ *
+ *             btFIFOSetting - UART FIFO setting, including FIFO_ENABLE or
+ *                             FIDO_DISABLE, and a FIFO_TRIGGER_* setting,
+ *                             joined by bitwise-or (|) operators.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetFIFO(tPortHandle hPort, BYTE btFIFOSetting)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->btFIFOSetting = btFIFOSetting;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetPreferredMethod()
+ *
+ * Sets the method to be used to perform serial I/O.
+ *
+ * Parameters: hPort  - Handle to a serial port object.
+ *
+ *             Method - The method to be used for peforming serial I/O,
+ *                      or kComMethodUnspecified to have the serial I/O
+ *                      routines to automatically choose the method to use.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetPreferredMethod(tPortHandle hPort, tComMethod Method)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   pPortInfo->Method = Method;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComGetMethod()
+ *
+ * Returns the method being used to perform serial I/O, if this has been
+ * determined. You can only assume that this value will be set after
+ * ODComOpen() has been called.
+ *
+ * Parameters: hPort   - Handle to a serial port object.
+ *
+ *             pMethod - Pointer to a tComMethod, in which function will
+ *                       store the method of serial I/O being used, if this
+ *                       has been determined.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComGetMethod(tPortHandle hPort, tComMethod *pMethod)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pMethod != NULL);
+
+   *pMethod = pPortInfo->Method;
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComOpen()
+ *
+ * Initializes serial I/O for appropriate serial I/O mechanism (e.g. FOSSIL
+ * driver, internal async I/O, etc.)
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComOpen(tPortHandle hPort)
+{
+#if defined(INCLUDE_FOSSIL_COM) || defined(INCLUDE_UART_COM)
+   unsigned int uDivisor;
+   unsigned long ulQuotient, ulRemainder;
+   BYTE btTemp;
+#endif /* INCLUDE_FOSSIL_COM || INCLUDE_UART_COM */
+#ifdef INCLUDE_STDIO_COM
+	struct termios tio_raw;
+#endif
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   nPort = (int)pPortInfo->btPort;
+
+   /* Ensure that port is not already open. */
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+   /* The following code is used to handle FOSSIL-based serial I/O open */
+   /* operations.                                                       */
+#ifdef INCLUDE_FOSSIL_COM
+   /* If use of FOSSIL driver has not been disabled, then first attempt to */
+   /* use it.                                                              */
+   if(pPortInfo->Method == kComMethodFOSSIL ||
+      pPortInfo->Method == kComMethodUnspecified)
+   {
+      /* Attempt to open port with FOSSIL DRIVER. */
+      ASM    push si
+      ASM    push di
+      ASM    mov ah, 4
+      ASM    mov dx, nPort
+      ASM    mov bx, 0
+      ASM    int 20
+      ASM    pop di
+      ASM    pop si
+      ASM    cmp ax, 6484
+      ASM    je fossil
+      goto no_fossil;
+
+fossil:
+      pPortInfo->Method = kComMethodFOSSIL;
+
+      /* Enable flow control, if applicable. */
+
+      /* Generate flow control setting. All bits in high nibble of flow   */
+      /* control are set to 1, because some FOSSIL driver implementations */
+      /* use the high nibble as a control mask.                           */
+      if(pPortInfo->btFlowControlSetting & FLOW_DEFAULT)
+      {
+         btTemp = FLOW_RTSCTS | 0xf0;
+      }
+      else
+      {
+         btTemp = pPortInfo->btFlowControlSetting | 0xf0;
+      }
+
+      ASM    push si
+      ASM    push di
+      ASM    mov ah, 0x0f
+      ASM    mov al, btTemp
+      ASM    mov dx, nPort
+      ASM    int 20
+      ASM    pop di
+      ASM    pop si
+
+      /* If serial port speed is not to be set, then return now. */
+      if(pPortInfo->lSpeed == SPEED_UNSPECIFIED)
+      {
+         /* Set port state to open. */
+         pPortInfo->bIsOpen = TRUE;
+
+         /* Return with success. */
+         return(kODRCSuccess);
+      }
+
+      /* Set to current baud rate. */
+      switch(pPortInfo->lSpeed)
+      {
+         case 300L:
+            btTemp = 0x40;
+            break;
+         case 600L:
+            btTemp = 0x60;
+            break;
+         case 1200L:
+            btTemp = 0x80;
+            break;
+         case 2400L:
+            btTemp = 0xa0;
+            break;
+         case 4800L:
+            btTemp = 0xc0;
+            break;
+         case 9600L:
+            btTemp = 0xe0;
+            break;
+         case 19200L:
+            btTemp = 0x00;
+            break;
+         case 38400L:
+            btTemp = 0x20;
+            break;
+         default:
+            /* If invalid bps rate, don't change current bps setting. */
+            /* Set port state to open. */
+            pPortInfo->bIsOpen = TRUE;
+
+            /* Return with success. */
+            return(kODRCSuccess);
+      }
+
+      /* Add desired word format parameters to data to be passed to fossil. */
+      btTemp |= pPortInfo->btWordFormat;
+
+      /* Initialize fossil driver. */
+      ASM    push si
+      ASM    push di
+      ASM    mov al, btTemp
+      ASM    mov ah, 0
+      ASM    mov dx, nPort
+      ASM    int 20
+      ASM    pop di
+      ASM    pop si
+
+      /* Set port state to open. */
+      pPortInfo->bIsOpen = TRUE;
+
+      /* Return with success. */
+      return(kODRCSuccess);
+   }
+
+no_fossil:
+#endif /* INCLUDE_FOSSIL_COM */
+
+   /* The following code is used to carry out the serial port I/O open */
+   /* operations if built-in UART-based serial I/O is being used.      */
+#ifdef INCLUDE_UART_COM
+   if(pPortInfo->Method == kComMethodUART ||
+      pPortInfo->Method == kComMethodUnspecified)
+   {
+      /* Set internal serial I/O flow control variable from pre-set */
+      /* flow control options.                                      */
+      if(pPortInfo->btFlowControlSetting & FLOW_DEFAULT)
+      {
+         btFlowControl = FLOW_RTSCTS;
+      }
+      else
+      {
+         btFlowControl = pPortInfo->btFlowControlSetting;
+      }
+
+      /* Store serial I/O method being used. */
+      pPortInfo->Method = kComMethodUART;
+
+      /* Calculate receive buffer high and low water marks for use with */
+      /* flow control. */
+      nRXHighWaterMark = (pPortInfo->nReceiveBufferSize * RECEIVE_HIGH_NUM)
+         / RECEIVE_HIGH_DENOM;
+      nRXLowWaterMark = (pPortInfo->nReceiveBufferSize * RECEIVE_LOW_NUM)
+         / RECEIVE_LOW_DENOM;
+
+      /* Allocate transmit and receive buffers */
+      pbtTXQueue = malloc(nTXQueueSize = pPortInfo->nTransmitBufferSize);
+      pbtRXQueue = malloc(nRXQueueSize = pPortInfo->nReceiveBufferSize);
+
+      if(pbtTXQueue == NULL || pbtRXQueue == NULL)
+      {
+         return(kODRCNoMemory);
+      }
+
+      /* If serial port address is unknown. */
+      if(pPortInfo->nPortAddress == 0)
+      {
+         return(kODRCNoPortAddress);
+      }
+
+      /* Initialize table of UART register port addresses. */
+      nDataRegAddr = pPortInfo->nPortAddress;
+      nIntEnableRegAddr = nDataRegAddr + IER;
+      nIntIDRegAddr = nDataRegAddr + IIR;
+      nLineCtrlRegAddr = nDataRegAddr + LCR;
+      nModemCtrlRegAddr = nDataRegAddr + MCR;
+      nLineStatusRegAddr = nDataRegAddr + LSR;
+      nModemStatusRegAddr = nDataRegAddr + MSR;
+
+
+      /* Store interrupt vector number and PIC interrupt information for */
+      /* the specified IRQ line.                                         */
+      if(pPortInfo->btIRQLevel <= 7)
+      {
+         btIntVector = 0x08 + (pPortInfo->btIRQLevel);
+         btI8259Bit = 1 << (pPortInfo->btIRQLevel);
+         nI8259MaskRegAddr = 0x21;
+         nI8259EndOfIntRegAddr = 0x20;
+         nI8259MasterEndOfIntRegAddr = 0x00;
+      }
+      else
+      {
+         btIntVector = 0x68 + (pPortInfo->btIRQLevel);
+         btI8259Bit = 1 << (pPortInfo->btIRQLevel - 8);
+         nI8259MaskRegAddr = 0xA1;
+         nI8259EndOfIntRegAddr = 0xA0;
+         nI8259MasterEndOfIntRegAddr = 0x20;
+      }
+
+      /* Save original state of UART IER register. */
+      ASM mov dx, nIntEnableRegAddr
+      ASM in al, dx
+      ASM mov btOldIntEnableReg, al
+
+      /* Test that a UART is indeed installed at this port address. */
+      ASM mov dx, nIntEnableRegAddr
+      ASM mov al, 0
+      ASM out dx, al
+
+      ASM mov dx, nIntEnableRegAddr
+      ASM in al, dx
+      ASM mov btTemp, al
+
+      if (btTemp != 0)
+      {
+         return(kODRCNoUART);
+      }
+
+      /* Setup for RTS/CTS flow control, if it is to be used. */
+      if(btFlowControl & FLOW_RTSCTS)
+      {
+         /* Read modem status register. */
+         ASM mov dx, nModemStatusRegAddr
+         ASM in al, dx
+         ASM mov btTemp, al
+
+         /* Enable transmission only if CTS is high. */
+         bStopTrans = !(btTemp & CTS);
+      }
+
+      /* Save original PIC interrupt settings, and temporarily disable */
+      /* interrupts on this IRQ line while we perform initialization.  */
+      ASM cli
+
+      ASM mov dx, nI8259MaskRegAddr
+      ASM in al, dx
+      ASM mov btI8259Mask, al
+      ASM or  al, btI8259Bit
+      ASM out dx, al
+
+      /* Initialize transmit and recieve buffers. */
+      ODComInternalResetTX();
+      ODComInternalResetRX();
+
+      /* Re-enable interrupts. */
+      ASM sti
+
+      /* Save original interrupt vector. */
+      pfOldISR = ODComGetVect(btIntVector);
+
+      /* Set interrupt vector to point to our ISR. */
+#ifdef _MSC_VER
+      ODComSetVect(btIntVector, (void far *)ODComInternalISR);
+#else /* !_MSC_VER */
+      ODComSetVect(btIntVector, ODComInternalISR);
+#endif /* !_MSC_VER */
+
+      /* Set line control register to 8 data bits, no parity bits, 1 stop */
+      /* bit. */
+      btTemp = pPortInfo->btWordFormat;
+      ASM mov dx, nLineCtrlRegAddr
+      ASM mov al, btTemp
+      ASM out dx, al
+
+      /* Save original modem control register. */
+      ASM cli
+
+      ASM mov dx, nModemCtrlRegAddr
+      ASM in al, dx
+      ASM mov btOldModemCtrlReg, al
+
+      /* Keep current DTR setting, and activate RTS. */
+      btTemp = (btOldModemCtrlReg & DTR) | (OUT2 + RTS);
+      ASM mov dx, nModemCtrlRegAddr
+      ASM mov al, btTemp
+      ASM out dx, al
+
+      /* Enable use of 16550A FIFOs, if available. */
+      if(pPortInfo->btFIFOSetting & FIFO_ENABLE)
+      {
+         /* Set FIFO enable bit and trigger size. */
+         btBaseFIFOCtrl = pPortInfo->btFIFOSetting;
+
+         /* Attempt to enable use of FIFO buffers. */
+         ASM mov al, btBaseFIFOCtrl
+         ASM mov dx, nIntIDRegAddr
+         ASM out dx, al
+
+         /* Check whether a 16550A UART is actually present by reading */
+         /* state of FIFO buffer. */
+         ASM mov dx, nIntIDRegAddr
+         ASM in al, dx
+         ASM mov btTemp, al
+
+         bUsingFIFO = btTemp & 0xc0;
+      }
+
+      ASM sti
+
+      /* Enable receive and modem status interrupts on the UART. */
+      ASM mov dx, nIntEnableRegAddr
+      ASM mov al, DR + MS
+      ASM out dx, al
+
+      ASM cli
+
+      ASM mov dx, nI8259MaskRegAddr
+      ASM in al, dx
+      ASM mov ah, btI8259Bit
+      ASM not ah
+      ASM and al, ah
+      ASM out dx, al
+
+      ASM sti
+
+      /* Set baud rate, if possible. */
+
+      /* Calculate baud rate divisor. */
+      if(pPortInfo->lSpeed != SPEED_UNSPECIFIED)
+      {
+         ODDWordDivide(&ulQuotient, &ulRemainder, 115200UL, pPortInfo->lSpeed);
+
+         /* If division results in a remainder, then this is an invalid     */
+         /* baud rate. We only change the UART baud rate if we have a valid */
+         /* rate to set it to. Otherwise, we cross our fingers and proceed  */
+         /* with the currently set UART baud rate.                          */
+         if(ulRemainder == 0L)
+         {
+            uDivisor = (unsigned int)ulQuotient;
+
+            /* Disable interrupts. */
+            ASM cli
+
+            /* Set baud rate divisor latch. */
+            /* The data register now becomes the lower byte of the baud rate */
+            /* divisor, and the interrupt enable register becomes the upper  */
+            /* byte of the divisor.                                          */
+            ASM mov dx, nLineCtrlRegAddr
+            ASM in al, dx
+            ASM or al, DLATCH
+            ASM out dx, al
+
+            /* Write lower byte of baud rate divisor. */
+            ASM mov dx, nDataRegAddr
+            ASM mov ax, uDivisor
+            ASM out dx, al
+
+            /* Write upper byte of baud rate divisor. */
+            ASM mov dx, nIntEnableRegAddr
+            ASM mov al, ah
+            ASM out dx, al
+
+            /* Reset baud rate divisor latch. */
+            ASM mov dx, nLineCtrlRegAddr
+            ASM in al, dx
+            ASM and al, NOT_DL
+            ASM out dx, al
+
+            /* Re-enable interrupts. */
+            ASM sti
+         }
+      }
+
+      /* Remember the serial I/O method that we are using. */
+      pPortInfo->Method = kComMethodUART;
+
+      /* Store port state as open. */
+      pPortInfo->bIsOpen = TRUE;
+
+      /* Return with success. */
+      return(kODRCSuccess);
+   }
+#endif /* INCLUDE_UART_COM */
+
+   /* The following code is used to handle I/O using the Door32 interface. */
+#ifdef INCLUDE_DOOR32_COM
+   if(pPortInfo->Method == kComMethodDoor32 ||
+      pPortInfo->Method == kComMethodUnspecified)
+   {
+      /* Attempt to load the Door32 DLL. */
+      pPortInfo->hinstDoor32DLL = LoadLibrary("DOOR32.DLL");
+      if(pPortInfo->hinstDoor32DLL != NULL)
+      {
+         /* Obtain pointers to required Door32 API function entry points. */
+         pPortInfo->pfDoorInitialize = (BOOL (WINAPI *)(void))
+            GetProcAddress(pPortInfo->hinstDoor32DLL, "DoorInitialize");
+         pPortInfo->pfDoorShutdown = (BOOL (WINAPI *)(void))
+            GetProcAddress(pPortInfo->hinstDoor32DLL, "DoorShutdown");
+         pPortInfo->pfDoorWrite = (BOOL (WINAPI *)(const BYTE *, DWORD))
+            GetProcAddress(pPortInfo->hinstDoor32DLL, "DoorWrite");
+         pPortInfo->pfDoorRead = (DWORD (WINAPI *)(BYTE *, DWORD))
+            GetProcAddress(pPortInfo->hinstDoor32DLL, "DoorRead");
+         pPortInfo->pfDoorGetAvailableEventHandle = (HANDLE (WINAPI *)(void))
+            GetProcAddress(pPortInfo->hinstDoor32DLL,
+            "DoorGetAvailableEventHandle");
+         pPortInfo->pfDoorGetOfflineEventHandle = (HANDLE (WINAPI *)(void))
+            GetProcAddress(pPortInfo->hinstDoor32DLL,
+            "DoorGetOfflineEventHandle");
+
+         /* Check whether we have successfully been able to obtain all the */
+         /* required function entry points.                                */
+         if(pPortInfo->pfDoorInitialize != NULL
+            && pPortInfo->pfDoorShutdown != NULL
+            && pPortInfo->pfDoorWrite != NULL
+            && pPortInfo->pfDoorRead != NULL
+            && pPortInfo->pfDoorGetAvailableEventHandle != NULL
+            && pPortInfo->pfDoorGetOfflineEventHandle != NULL)
+         {
+            if((*pPortInfo->pfDoorInitialize)())
+            {
+               /* Set port state as open. */
+               pPortInfo->bIsOpen = TRUE;
+
+               /* Set serial I/O method. */
+               pPortInfo->Method = kComMethodDoor32;
+
+               /* Return with success. */
+               return(kODRCSuccess);
+            }
+         }
+
+         /* On failure to obtain all Door32 function entry points, unload */
+         /* the Door32 DLL.                                               */
+         FreeLibrary(pPortInfo->hinstDoor32DLL);
+      }
+
+      /* If our attempt to use the Door32 interface failed for any reason, */
+      /* then proceed, attempting to use the Win32 serial I/O interface.   */
+   }
+#endif /* INCLUDE_DOOR32_COM */
+
+   /* The following code is used to handle Win32 API-base serial I/O */
+   /* open operations.                                               */
+#ifdef INCLUDE_WIN32_COM
+   if(pPortInfo->Method == kComMethodWin32 ||
+      pPortInfo->Method == kComMethodUnspecified)
+   {
+      char szDevName[7];
+      DCB dcb;
+
+      /* Generate device name. */
+      sprintf(szDevName, "COM%u", (unsigned)pPortInfo->btPort + 1);
+
+      /* Attempt to create handle for device. */
+      pPortInfo->hCommDev = CreateFile(szDevName, GENERIC_READ | GENERIC_WRITE,
+         0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+      /* On open failure, return with an error code. */
+      if(pPortInfo->hCommDev == INVALID_HANDLE_VALUE)
+      {
+         return(kODRCGeneralFailure);
+      }
+
+      /* Note that read timeout settings have not been set. */
+      pPortInfo->ReadTimeoutState = kNotSet;
+      
+      /* Call SetupComm() to set queue sizes. */
+      if(!SetupComm(pPortInfo->hCommDev, pPortInfo->nReceiveBufferSize,
+         pPortInfo->nTransmitBufferSize))
+      {
+         CloseHandle(pPortInfo->hCommDev);
+         return(kODRCGeneralFailure);
+      }
+
+      /* Get current port state. */
+      if(!GetCommState(pPortInfo->hCommDev, &dcb))
+      {
+         CloseHandle(pPortInfo->hCommDev);
+         return(kODRCGeneralFailure);
+      }
+
+      /* Fill device control block. */
+
+      /* Set bps rate, if appropriate. */
+      if(pPortInfo->lSpeed != SPEED_UNSPECIFIED)
+      {
+         dcb.BaudRate = pPortInfo->lSpeed;
+      }
+
+      /* Set flow control, if appropriate. */
+      if(!(pPortInfo->btFlowControlSetting & FLOW_DEFAULT))
+      {
+         if(pPortInfo->btFlowControlSetting & FLOW_RTSCTS)
+         {
+            dcb.fOutxCtsFlow = 1;
+            dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
+         }
+         else
+         {
+            dcb.fOutxCtsFlow = 0;
+            dcb.fRtsControl = RTS_CONTROL_ENABLE;
+         }
+      }
+
+      /* Set word size. */
+      if((pPortInfo->btWordFormat & DATABITS_MASK) == DATABITS_FIVE)
+      {
+         dcb.ByteSize = 5;
+      }
+      else if((pPortInfo->btWordFormat & DATABITS_MASK) == DATABITS_SIX)
+      {
+         dcb.ByteSize = 6;
+      }
+      else if((pPortInfo->btWordFormat & DATABITS_MASK) == DATABITS_SEVEN)
+      {
+         dcb.ByteSize = 7;
+      }
+      else if((pPortInfo->btWordFormat & DATABITS_MASK) == DATABITS_EIGHT)
+      {
+         dcb.ByteSize = 8;
+      }
+
+      /* Set parity. */
+      if((pPortInfo->btWordFormat & ODPARITY_MASK) == ODPARITY_NONE)
+      {
+         dcb.Parity = NOPARITY;
+      }
+      else if((pPortInfo->btWordFormat & ODPARITY_MASK) == ODPARITY_ODD)
+      {
+         dcb.Parity = ODDPARITY;
+      }
+      else if((pPortInfo->btWordFormat & ODPARITY_MASK) == ODPARITY_EVEN)
+      {
+         dcb.Parity = EVENPARITY;
+      }
+
+      /* Enable DTR control. */
+      dcb.fDtrControl = DTR_CONTROL_ENABLE;
+
+      /* Set number of stop bits. */
+      if((pPortInfo->btWordFormat & STOP_MASK) == STOP_ONE)
+      {
+         dcb.StopBits = ONESTOPBIT;
+      }
+      else if((pPortInfo->btWordFormat & STOP_MASK) == STOP_ONE_POINT_FIVE)
+      {
+         dcb.StopBits = ONE5STOPBITS;
+      }
+      else if((pPortInfo->btWordFormat & STOP_MASK) == STOP_TWO)
+      {
+         dcb.StopBits = TWOSTOPBITS;
+      }
+
+      /* Set comm state from device control block. */
+      if(!SetCommState(pPortInfo->hCommDev, &dcb))
+      {
+         CloseHandle(pPortInfo->hCommDev);
+         return(kODRCGeneralFailure);
+      }
+
+      /* Store port state as open. */
+      pPortInfo->bIsOpen = TRUE;
+
+      /* Set serial I/O method. */
+      pPortInfo->Method = kComMethodWin32;
+
+      /* Return with success. */
+      return(kODRCSuccess);
+   }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_STDIO_COM
+   if(pPortInfo->Method == kComMethodStdIO ||
+      pPortInfo->Method == kComMethodUnspecified)
+   {
+		if (isatty(STDIN_FILENO))  {
+			tcgetattr(STDIN_FILENO,&tio_default);
+			tio_raw = tio_default;
+#ifdef __sun
+			tio_raw.c_iflag &= ~(IMAXBEL|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+			tio_raw.c_oflag &= ~OPOST;
+			tio_raw.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+			tio_raw.c_cflag &= ~(CSIZE|PARENB);
+			tio_raw.c_cflag |= CS8;
+			tio_raw.c_cc[VMIN] = 1;
+               		tio_raw.c_cc[VTIME] = 0;
+#else
+			cfmakeraw(&tio_raw);
+#endif
+			tcsetattr(STDIN_FILENO,TCSANOW,&tio_raw);
+			setvbuf(stdout, NULL, _IONBF, 0);
+		} else {
+			setvbuf(stdout, NULL, _IONBF, 0);
+		}
+
+      /* Set port state as open. */
+      pPortInfo->bIsOpen = TRUE;
+
+      /* Set serial I/O method. */
+      pPortInfo->Method = kComMethodStdIO;
+
+      /* Return with success. */
+      return(kODRCSuccess);
+
+   }
+#endif /* INCLUDE_STDIO_COM */
+
+   /* If we get to this point, then no form of serial I/O could be */
+   /* initialized.                                                 */
+   return(kODRCGeneralFailure);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComOpenFromExistingHandle()
+ *
+ * Initializes serial I/O using a serial port handle natvie to the current
+ * operating system, which has already been opened by another application.
+ *
+ * Parameters: hPort            - Handle to a serial port object.
+ *
+ *             dwExistingHandle - Native operating system's handle to an
+ *                                already open serial port.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComOpenFromExistingHandle(tPortHandle hPort,
+   DWORD dwExistingHandle)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(!pPortInfo->bIsOpen);
+
+#ifdef INCLUDE_SOCKET_COM
+	if(pPortInfo->Method == kComMethodSocket) {
+		socklen_t delay=FALSE;
+
+		pPortInfo->socket = dwExistingHandle;
+
+		getsockopt(pPortInfo->socket, IPPROTO_TCP, TCP_NODELAY, &(pPortInfo->old_delay), &delay);
+		delay=FALSE;
+		setsockopt(pPortInfo->socket, IPPROTO_TCP, TCP_NODELAY, &delay, sizeof(delay));
+
+        pPortInfo->bIsOpen = TRUE;
+
+		return(kODRCSuccess);
+	}
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_WIN32_COM
+
+   /* Store handle to the Win32 handle to the serial port. */
+   pPortInfo->hCommDev = (HANDLE)dwExistingHandle;
+
+   /* Remember that read timeout settings have not been set. */
+   pPortInfo->ReadTimeoutState = kNotSet;
+
+   /* Remember that we are using a handle provided by the client, rather  */
+   /* than one that we opened ourself. This flag prevents the handle from */
+   /* being closed by a call to ODComClose().                             */
+   pPortInfo->bUsingClientsHandle = TRUE;
+
+   /* Remember that the serial port is now open. */
+   pPortInfo->bIsOpen = TRUE;
+
+   return(kODRCSuccess);
+
+#else /* !INCLUDE_WIN32_COM */
+   UNUSED(dwExistingHandle);
+   UNUSED(pPortInfo);
+
+   /* If no form of serial I/O included in this build can use this handle, */
+   /* then return with a failure.                                          */
+   return(kODRCInvalidCall);
+
+#endif /* !INCLUDE_WIN32_COM */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComClose()
+ *
+ * Closes currently open serial port.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComClose(tPortHandle hPort)
+{
+#ifdef INCLUDE_UART_COM
+   BYTE btTemp;
+#endif /* INCLUDE_UART_COM */
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   /* If we are using the client's handle, then we should not close it. */
+   if(pPortInfo->bUsingClientsHandle)
+   {
+      pPortInfo->bIsOpen = FALSE;
+      return(kODRCSuccess);
+   }
+
+   nPort = (int)pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+         ASM    mov ah, 5
+         ASM    mov dx, nPort
+         ASM    int 20
+         break;
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         /* Reset UART registers to their original values. */
+         ASM mov dx, nModemCtrlRegAddr
+         ASM mov al, btOldModemCtrlReg
+         ASM out dx, al
+         ASM mov dx, nIntEnableRegAddr
+         ASM mov al, btOldIntEnableReg
+         ASM out dx, al
+
+         /* Disable interrupts. */
+         ASM cli
+
+         /* Reset this line's interrupt enable status on the PIC to its */
+         /* original state.                                             */
+         ASM mov dx, nI8259MaskRegAddr
+         ASM in al, dx
+         ASM mov btTemp, al
+
+         btTemp = (btTemp  & ~btI8259Bit) | (btI8259Mask &  btI8259Bit);
+
+         ASM mov dx, nI8259MaskRegAddr
+         ASM mov al, btTemp
+         ASM out dx, al
+
+         /* Re-enable interrupts. */
+         ASM sti
+
+         /* Reset vector to original interrupt handler. */
+#ifdef _MSC_VER
+         ODComSetVect(btIntVector, (void far *)pfOldISR);
+#else /* !_MSC_VER */
+         ODComSetVect(btIntVector, pfOldISR);
+#endif /* !_MSC_VER */
+
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+         CloseHandle(pPortInfo->hCommDev);
+         break;
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorShutdown != NULL);
+         (*pPortInfo->pfDoorShutdown)();
+         ASSERT(pPortInfo->hinstDoor32DLL != NULL);
+         FreeLibrary(pPortInfo->hinstDoor32DLL);
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		 setsockopt(pPortInfo->socket, IPPROTO_TCP, TCP_NODELAY, &(pPortInfo->old_delay), sizeof(pPortInfo->old_delay));
+         closesocket(pPortInfo->socket);
+         break;
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+	  case kComMethodStdIO:
+	     if(isatty(STDIN_FILENO))
+		    tcsetattr(STDIN_FILENO,TCSANOW,&tio_default);
+	     break;
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Store the fact that the port is now closed. */
+   pPortInfo->bIsOpen = FALSE;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComCarrier()
+ *
+ * Determines whether or not the carrier detect signal is present.
+ *
+ * Parameters: hPort       - Handle to a serial port object.
+ *
+ *             pbIsCarrier - Location to store result. Set to TRUE if carrier
+ *                           detect signal is high, FALSE if it is low.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComCarrier(tPortHandle hPort, BOOL *pbIsCarrier)
+{
+#ifdef ODPLAT_NIX
+   sigset_t	  sigs;
+#endif
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pbIsCarrier != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+      {
+         int to_return;
+
+         ASM    mov ah, 3
+         ASM    mov dx, nPort
+         ASM    int 20
+         ASM    and ax, 128
+         ASM    mov to_return, ax
+
+         *pbIsCarrier = to_return;
+
+         break;
+      }
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+      {
+         BYTE btMSR;
+
+         ASM mov dx, nModemStatusRegAddr
+         ASM in al, dx
+         ASM mov btMSR, al
+
+         *pbIsCarrier = btMSR & RLSD;
+         break;
+      }
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwModemStats;
+
+         /* Get modem status settings. */
+         if(!GetCommModemStatus(pPortInfo->hCommDev, &dwModemStats))
+         {
+            return(kODRCGeneralFailure);
+         }
+
+         *pbIsCarrier = (dwModemStats & MS_RLSD_ON) ? TRUE : FALSE;
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorGetOfflineEventHandle != NULL);
+         *pbIsCarrier = (WaitForSingleObject(
+            (*pPortInfo->pfDoorGetOfflineEventHandle)(),
+            0) != WAIT_OBJECT_0);
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			int		i;
+			char		ch;
+			fd_set	socket_set;
+			struct	timeval tv;
+
+			FD_ZERO(&socket_set);
+			FD_SET(pPortInfo->socket,&socket_set);
+
+			tv.tv_sec=0;
+			tv.tv_usec=0;
+			i=select(pPortInfo->socket+1,&socket_set,NULL,NULL,&tv);
+			if(i==0 
+				|| (i==1 && recv(pPortInfo->socket,&ch,1,MSG_PEEK)==1))
+				*pbIsCarrier = TRUE;
+			else
+				*pbIsCarrier = FALSE;
+			break;
+		}
+#endif
+
+#ifdef INCLUDE_STDIO_COM
+	  case kComMethodStdIO:
+	    {
+			sigpending(&sigs);
+			if(sigismember(&sigs,SIGHUP))
+				*pbIsCarrier = FALSE;
+			else
+				*pbIsCarrier = TRUE;
+			break;
+		}
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSetDTR()
+ *
+ * Raises or lowers the DTR signal on the port.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *             bHigh - TRUE to raise DTR, FALSE to lower it.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSetDTR(tPortHandle hPort, BOOL bHigh)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+         ASM    cmp byte ptr bHigh, 0
+         ASM    je lower
+         ASM    mov al, 1
+         ASM    jmp set_dtr
+
+lower:
+         ASM    xor al, al
+
+set_dtr:
+         ASM    mov ah, 6
+         ASM    mov dx, nPort
+         ASM    int 20
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         if(bHigh)
+         {
+            ASM cli
+
+            ASM mov dx, nModemCtrlRegAddr
+            ASM in al, dx
+            ASM or al, DTR
+            ASM out dx, al
+
+            ASM sti
+         }
+         else
+         {
+            ASM cli
+
+            ASM mov dx, nModemCtrlRegAddr
+            ASM in al, dx
+            ASM and al, NOT_DTR
+            ASM out dx, al
+
+            ASM sti
+         }
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+         /* Set DTR line appropriately. */
+         if(!EscapeCommFunction(pPortInfo->hCommDev, bHigh ? SETDTR : CLRDTR))
+         {
+            return(kODRCGeneralFailure);
+         }
+         break;
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         return(kODRCUnsupported);
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+         if(bHigh)
+            return(kODRCUnsupported);
+         closesocket(pPortInfo->socket);
+         break;
+#endif /* INCLUDE_SOCKET_CO */
+
+#ifdef INCLUDE_STDIO_COM
+	  case kComMethodStdIO:
+	     return(kODRCUnsupported);
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComOutbound()
+ *
+ * Determines the number of bytes waiting in the serial port outbound buffer.
+ *
+ * Parameters: hPort             - Handle to a serial port object.
+ *
+ *             pnOutboundWaiting - Location where result the number of bytes
+ *                                 waiting in the outbound buffer should be
+ *                                 stored. Under some I/O methods we can
+ *                                 determine whether data is still in the
+ *                                 buffer, but not the number of bytes in the
+ *                                 buffer. In this situation, this may be set
+ *                                 to SIZE_NON_ZERO.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComOutbound(tPortHandle hPort, int *pnOutboundWaiting)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pnOutboundWaiting != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+         ASM    mov ah, 0x03
+         ASM    mov dx, nPort
+         ASM    int 20
+         ASM    and ah, 0x40
+         ASM    jz  still_sending
+         *pnOutboundWaiting = 0;
+         break;
+
+still_sending:
+         *pnOutboundWaiting = SIZE_NON_ZERO;
+         break;
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         *pnOutboundWaiting = (int)nTXChars;
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwErrors;
+         COMSTAT ComStat;
+
+         /* Use ClearCommError() to obtain device status. */
+         if(!ClearCommError(pPortInfo->hCommDev, &dwErrors, &ComStat))
+         {
+            return(kODRCGeneralFailure);
+         }
+
+         /* Set pbIsInbound to TRUE if any bytes are in outbound queue. */
+         *pnOutboundWaiting = (int)ComStat.cbOutQue;
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         /* Door32 doesn't currently support this functionality, so we */
+         /* assume that all sent data is transmitted immediately.      */
+         *pnOutboundWaiting = 0;
+         return(kODRCUnsupported);
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+			*pnOutboundWaiting = 0;
+			return(kODRCUnsupported);
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+	  case kComMethodStdIO:
+			*pnOutboundWaiting = 0;
+			return(kODRCUnsupported);
+#endif	        
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComClearOutbound()
+ *
+ * Removes the current contents of the serial port outbound buffer.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComClearOutbound(tPortHandle hPort)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+         ASM    mov ah, 9
+         ASM    mov dx, nPort
+         ASM    int 20
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         ODComInternalResetTX();
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+         if(!PurgeComm(pPortInfo->hCommDev, PURGE_TXCLEAR))
+         {
+            return(kODRCGeneralFailure);
+         }
+         break;
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         return(kODRCUnsupported);
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+			return(kODRCUnsupported);
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+			return(kODRCUnsupported);
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComClearInbound()
+ *
+ * Removes the current contents of the serial port inbound buffer.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComClearInbound(tPortHandle hPort)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+         ASM    mov ah, 10
+         ASM    mov dx, nPort
+         ASM    int 20
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         ODComInternalResetRX();
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+         if(!PurgeComm(pPortInfo->hCommDev, PURGE_RXCLEAR))
+         {
+            return(kODRCGeneralFailure);
+         }
+         break;
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         return(kODRCUnsupported);
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+			return(kODRCUnsupported);
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+			return(kODRCUnsupported);
+#endif
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComInbound()
+ *
+ * Determines the number of bytes waiting in the serial port inbound buffer.
+ *
+ * Parameters: hPort            - Handle to a serial port object.
+ *
+ *             pnInboundWaiting - Location in which to store number of bytes
+ *                                waiting in the inbound buffer. Under some
+ *                                I/O methods (e.g. FOSSIL driver), we can
+ *                                determine whether data is still in the
+ *                                buffer, but not the number of bytes in the
+ *                                buffer. In this situation, this may be set
+ *                                to SIZE_NON_ZERO.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComInbound(tPortHandle hPort, int *pnInboundWaiting)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pnInboundWaiting != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+      {
+         BOOL bDataInBuffer = FALSE;
+
+         ASM    mov ah, 3
+         ASM    mov dx, nPort
+         ASM    push si
+         ASM    push di
+         ASM    int 20
+         ASM    pop di
+         ASM    pop si
+         ASM    and ah, 1
+         ASM    mov bDataInBuffer, ah
+
+         *pnInboundWaiting = bDataInBuffer ? SIZE_NON_ZERO : 0;
+
+         break;
+      }
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         *pnInboundWaiting = (int)nRXChars;
+
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwErrors;
+         COMSTAT ComStat;
+
+         /* Use ClearCommError() to obtain device status. */
+         if(!ClearCommError(pPortInfo->hCommDev, &dwErrors, &ComStat))
+         {
+            return(kODRCGeneralFailure);
+         }
+
+         /* Set pbIsInbound to TRUE if there are any bytes in inbound queue. */
+         *pnInboundWaiting = (int)ComStat.cbInQue;
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorGetAvailableEventHandle != NULL);
+         if(WaitForSingleObject(
+            (*pPortInfo->pfDoorGetAvailableEventHandle)(),
+            0) == WAIT_OBJECT_0)
+         {
+            *pnInboundWaiting = SIZE_NON_ZERO;
+         }
+         else
+         {
+            *pnInboundWaiting = 0;
+         }
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+			if(ioctlsocket(pPortInfo->socket,FIONREAD,pnInboundWaiting) != 0)
+				*pnInboundWaiting = 0;
+			break;
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+			if(ioctl(0,FIONREAD,pnInboundWaiting) == -1)
+				*pnInboundWaiting = 0;
+			break;
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComGetByte()
+ *
+ * Returns a single inbound byte. If there are characters waiting in the
+ * inbound buffer, the next character is returned immediately. If bWait is TRUE
+ * and no characters are waiting, this function will wait until a character is
+ * received (possibly forever, if no characters are ever received).
+ *
+ * Parameters: hPort   - Handle to a serial port object.
+ *
+ *             pbtNext - Location to store retrieved byte.
+ *
+ *             bWait   - If TRUE, function will only return after a character
+ *                       has been received. If FALSE, this function will return
+ *                       kODRCNothingWaiting if no characters are waiting.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComGetByte(tPortHandle hPort, char *pbtNext, BOOL bWait)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pbtNext != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+      {
+         BYTE btToReturn;
+         int nInboundSize;
+
+         /* If we should not wait for characters if inbound queue is empty. */
+         if(!bWait)
+         {
+            /* Determine whether there are any inbound characterse waiting. */
+            ODComInbound(hPort, &nInboundSize);
+
+            /* If there are no inbound characters waiting, then return */
+            /* without obtaining any characters.                       */
+            if(nInboundSize == 0) return(kODRCNothingWaiting);
+         }
+
+         ASM     mov ah, 2
+         ASM     mov dx, nPort
+         ASM     push si
+         ASM     push di
+         ASM     int 20
+         ASM     pop di
+         ASM     pop si
+         ASM     mov btToReturn, al
+
+         *pbtNext = btToReturn;
+
+         break;
+      }
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         /* If we should not wait for characters if inbound queue is empty. */
+         if(!bWait)
+         {
+            /* If there are no inbound characters waiting, then return */
+            /* without obtaining any characters.                       */
+            if(!nRXChars) return(kODRCNothingWaiting);
+         }
+
+         /* Loop, calling idle function, until next character arrives. */
+         while(!nRXChars)
+         {
+            if(pPortInfo->pfIdleCallback != NULL)
+            {
+               (*pPortInfo->pfIdleCallback)();
+            }
+         }
+
+         /* Disable interrupts. */
+         ASM cli
+
+         /* Get next character from receive queue. */
+         *pbtNext = pbtRXQueue[nRXOutIndex++];
+
+         /* Wrap queue index if needed. */
+         if (nRXOutIndex == nRXQueueSize)
+         {
+            nRXOutIndex = 0;
+         }
+
+         /* Decrement count of total character in the receive queue. */
+         nRXChars--;
+
+         /* Re-enable interrupts. */
+         ASM sti
+
+         /* If receive buffer is below low water mark. */
+         if(nRXChars <= nRXLowWaterMark)
+         {
+            /* If we are using flow control, then stop sender from */
+            /* sending.                                            */
+            if(btFlowControl & FLOW_RTSCTS)
+            {
+               /* If using RTS/CTS flow control, then raise RTS line. */
+               ASM mov dx, nModemCtrlRegAddr
+               ASM in al, dx
+               ASM or al, RTS
+               ASM out dx, al
+            }
+         }
+
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwBytesRead;
+         DWORD dwErrors;
+
+         /* Ensure read timeout state is set appropriately for bWait value. */
+         if(bWait)
+         {
+            ODComWin32SetReadTimeouts(pPortInfo, kBlocking);
+         }
+         else
+         {
+            ODComWin32SetReadTimeouts(pPortInfo, kNonBlocking);
+         }
+
+         /* Perform read operation. */
+         if(!ReadFile(pPortInfo->hCommDev, pbtNext, 1, &dwBytesRead, NULL))
+         {
+            ClearCommError(pPortInfo->hCommDev, &dwErrors, NULL);
+            return(kODRCGeneralFailure);
+         }
+
+         /* Determine whether or not a byte was read. */
+         if(dwBytesRead == 0)
+         {
+            /* If no bytes where read, then this is a general error if bWait */
+            /* is TRUE. If bWait is FALSE, then we should return             */
+            /* waiting kODRCNothingWaiting.                                  */
+            return(bWait ? kODRCGeneralFailure : kODRCNothingWaiting);
+         }
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         if(WaitForSingleObject((*pPortInfo->pfDoorGetAvailableEventHandle)(),
+            bWait ? INFINITE : 0) == WAIT_OBJECT_0)
+         {
+            (*pPortInfo->pfDoorRead)(pbtNext, 1);
+            break;
+         }
+
+         return(bWait ? kODRCGeneralFailure : kODRCNothingWaiting);
+
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			fd_set	socket_set;
+			struct	timeval tv;
+			int		select_ret, recv_ret;
+
+			FD_ZERO(&socket_set);
+			FD_SET(pPortInfo->socket,&socket_set);
+
+			tv.tv_sec=0;
+			tv.tv_usec=100;
+
+			select_ret = select(pPortInfo->socket+1, &socket_set, NULL, NULL, bWait ? NULL : &tv);
+			if (select_ret == SOCKET_ERROR)
+				return (kODRCGeneralFailure);
+			if (select_ret == 0)
+				return (kODRCNothingWaiting);
+
+			do {
+				recv_ret = recv(pPortInfo->socket, pbtNext, 1, 0);
+				if(recv_ret != SOCKET_ERROR)
+					break;
+				if(WSAGetLastError() != WSAEWOULDBLOCK)
+					return (kODRCGeneralFailure);
+				od_sleep(50);
+			} while (bWait);
+
+			if (recv_ret == 0)
+				 return (kODRCNothingWaiting);
+
+			break;
+		}
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+		{
+			fd_set	socket_set;
+			struct	timeval tv;
+			int		select_ret=-1;
+			int		recv_ret;
+
+			while(select_ret==-1) {
+				FD_ZERO(&socket_set);
+				FD_SET(STDIN_FILENO,&socket_set);
+
+				tv.tv_sec=0;
+				tv.tv_usec=100;
+
+				select_ret = select(STDIN_FILENO+1, &socket_set, NULL, NULL, bWait ? NULL : &tv);
+				if (select_ret == -1) {
+					if(errno==EINTR)
+						continue;
+					return (kODRCGeneralFailure);
+				}
+				if (select_ret == 0)
+					return (kODRCNothingWaiting);
+			}
+
+			recv_ret = read(STDIN_FILENO, pbtNext, 1);
+			if(recv_ret == 1)
+				break;
+			return (kODRCGeneralFailure);
+
+			break;
+		}
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSendByte()
+ *
+ * Sends a single byte to the serial port outbound buffer.
+ *
+ * Parameters: hPort - Handle to a serial port object.
+ *
+ *             btToSend - The byte to transmit.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSendByte(tPortHandle hPort, BYTE btToSend)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+try_again:
+         ASM    mov ah, 0x0b
+         ASM    mov dx, nPort
+         ASM    mov al, btToSend
+         ASM    int 20
+         ASM    cmp ax, 0
+         ASM    jne keep_going
+
+         /* Call idle function, if any. */
+         if(pPortInfo->pfIdleCallback != NULL)
+         {
+            (*pPortInfo->pfIdleCallback)();
+         }
+
+         goto try_again;
+keep_going:
+         break;
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+         /* Loop, calling idle function, until characters are waiting in */
+         /* the transmit buffer.                                         */
+         while(!ODComInternalTXReady())
+         {
+            /* Call idle function, if any. */
+            if(pPortInfo->pfIdleCallback != NULL)
+            {
+               (*pPortInfo->pfIdleCallback)();
+            }
+         }
+
+         /* Disable interrupts. */
+         ASM cli
+
+         /* Place the character in the queue. */
+         pbtTXQueue[nTXInIndex++] = btToSend;
+
+         /* Wrap transmit queue index, if needed. */
+         if (nTXInIndex == nTXQueueSize)
+         {
+            nTXInIndex = 0;
+         }
+
+         /* Increment count of total characters in the queue. */
+         nTXChars++;
+
+         /* Enable transmit interrupt on the UART. */
+         ASM mov dx, nIntEnableRegAddr
+         ASM in al, dx
+         ASM or al, THRE
+         ASM out dx, al
+
+         ASM sti
+
+         break;
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwErrors;
+         DWORD dwBytesWritten;
+
+         /* Attempt to perform write operation. */
+         if(!WriteFile(pPortInfo->hCommDev, &btToSend, 1, &dwBytesWritten,
+            NULL) || dwBytesWritten != 1)
+         {
+            ClearCommError(pPortInfo->hCommDev, &dwErrors, NULL);
+            return(kODRCGeneralFailure);
+         }
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorWrite != NULL);
+         if(!(*pPortInfo->pfDoorWrite)(&btToSend, 1))
+         {
+            return(kODRCGeneralFailure);
+         }
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			fd_set	socket_set;
+			struct	timeval tv;
+			int		send_ret;
+
+			FD_ZERO(&socket_set);
+			FD_SET(pPortInfo->socket,&socket_set);
+
+			tv.tv_sec=1;
+			tv.tv_usec=0;
+
+			if(select(pPortInfo->socket+1,NULL,&socket_set,NULL,&tv) != 1)
+	         return(kODRCGeneralFailure);
+
+			do {
+				send_ret = send(pPortInfo->socket, &btToSend, 1, 0);
+				if (send_ret != 1)
+					od_sleep(50);
+			} while ((send_ret == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK));
+
+			if (send_ret == SOCKET_ERROR)
+				return (kODRCGeneralFailure);
+
+			break;
+		}
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+	  case kComMethodStdIO:
+	    {
+		fd_set  fdset;
+		struct  timeval tv;
+		int             retval=-1;
+		int	loopcount=0;
+
+		while(retval==-1 && loopcount < 10) {
+			FD_ZERO(&fdset);
+			FD_SET(STDOUT_FILENO,&fdset);
+
+			tv.tv_sec=1;
+			tv.tv_usec=0;
+
+			retval=select(STDOUT_FILENO+1,NULL,&fdset,NULL,&tv);
+			if(retval!=1) {
+				if(retval==0)  {
+					retval=-1;
+					loopcount++;
+					continue;
+				}
+				if(retval==-1 && errno==EINTR)
+					continue;
+				return(kODRCGeneralFailure);
+			}
+		}
+
+	    if(fwrite(&btToSend,1,1,stdout)!=1)
+		   return(kODRCGeneralFailure);
+		break;
+		}
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComGetBuffer()
+ *
+ * Retreives received data into a buffer, filling the buffer with as much data
+ * as possible that has been received, returning immediately.
+ *
+ * Parameters: hPort       - Handle to a serial port object.
+ *
+ *             pbtBuffer   - Pointer to a contiguous array of bytes.
+ *
+ *             nSize       - Size of buffer, in bytes. This is the maximum
+ *                           number of characters that will be returned.
+ *
+ *             pnBytesRead - Pointer to an int where function will store the
+ *                           number of bytes actually read.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComGetBuffer(tPortHandle hPort, BYTE *pbtBuffer, int nSize,
+   int *pnBytesRead)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pbtBuffer != NULL);
+   VERIFY_CALL(nSize > 0);
+   VERIFY_CALL(pnBytesRead != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+      {
+         int nReceived;
+
+         ASM    push di
+         ASM    mov cx, nSize
+         ASM    mov dx, nPort
+
+
+#ifdef LARGEDATA
+         ASM    les di, pbtBuffer
+#else
+         ASM    mov ax, ds
+         ASM    mov es, ax
+         ASM    mov di, pbtBuffer
+#endif
+
+         ASM    mov ah, 0x18
+         ASM    int 20
+         ASM    pop di
+         ASM    mov nReceived, ax
+
+         *pnBytesRead = nReceived;
+
+         break;
+      }
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+      {
+         int nTransferSize;
+         int nFirstHalfSize;
+         int nSecondHalfSize;
+         char *pbtSource;
+
+         /* Disable interrupts. */
+         ASM cli
+
+         /* Number of bytes to transfer is minimum of buffer size, and */
+         /* number of bytes in receive queue.                          */
+         nTransferSize = MIN(nRXChars, nSize);
+
+         /* First half of transfer is minimum of number of bytes from here */
+         /* to the end of the buffer, and the total transfer size.         */
+         nFirstHalfSize = nRXQueueSize - nRXOutIndex;
+         nFirstHalfSize = MIN(nFirstHalfSize, nTransferSize);
+
+         /* Second half of transfer is remaining bytes, if any. */
+         nSecondHalfSize = nTransferSize - nFirstHalfSize;
+
+         /* Perform first half of transfer. */
+         pbtSource = pbtRXQueue + nRXOutIndex;
+         while(nFirstHalfSize--)
+         {
+            *pbtBuffer++ = *pbtSource++;
+         }
+
+         /* If there is a second half to transfer. */
+         if(nSecondHalfSize)
+         {
+            /* Copy source will begin at beginning of queue. */
+            pbtSource = pbtRXQueue;
+
+            /* Set final queue out index. */
+            nRXOutIndex = nSecondHalfSize;
+
+            /* Perform second half of transfer. */
+            while(nSecondHalfSize--)
+            {
+               *pbtBuffer++ = *pbtSource++;
+            }
+         }
+
+         /* If entire transfer was performed in first half. */
+         else
+         {
+            /* Set final queue out index. */
+            nRXOutIndex += nTransferSize;
+
+            /* Wrap queue out index, if needed. */
+            if(nRXOutIndex == nRXQueueSize) nRXOutIndex = 0;
+         }
+
+         /* Subtract number of bytes retrieved from number of bytes in */
+         /* receive queue.                                             */
+         nRXChars -= nTransferSize;
+
+         /* Return number of bytes copied into buffer. */
+         *pnBytesRead = nTransferSize;
+
+         /* Re-enable interrupts. */
+         ASM sti
+
+         break;
+      }
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwBytesRead;
+         DWORD dwErrors;
+
+         /* Ensure read timeout state is set for non-blocking read */
+         ODComWin32SetReadTimeouts(pPortInfo, kNonBlocking);
+
+         /* Perform read operation. */
+         if(!ReadFile(pPortInfo->hCommDev, pbtBuffer, nSize, &dwBytesRead,
+            NULL))
+         {
+            ClearCommError(pPortInfo->hCommDev, &dwErrors, NULL);
+            return(kODRCGeneralFailure);
+         }
+
+         /* Pass number of bytes read back to caller. */
+         *pnBytesRead = (int)dwBytesRead;
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorRead != NULL);
+         *pnBytesRead = (int)((*pPortInfo->pfDoorRead)(pbtBuffer, nSize));
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			fd_set	socket_set;
+			struct	timeval tv;
+
+			FD_ZERO(&socket_set);
+			FD_SET(pPortInfo->socket,&socket_set);
+
+			tv.tv_sec=0;
+			tv.tv_usec=100;
+
+			if(select(pPortInfo->socket+1,&socket_set,NULL,NULL,&tv) != 1) {
+				*pnBytesRead = 0;
+	         break;
+			}
+
+			*pnBytesRead = recv(pPortInfo->socket,pbtBuffer,nSize,0);
+			break;
+		}
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+	    {
+		    for(*pnBytesRead=0;
+				*pnBytesRead<nSize && (ODComGetByte(hPort, (pbtBuffer+*pnBytesRead), FALSE)==kODRCSuccess);
+				*pnBytesRead++);
+		}
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComSendBuffer()
+ *
+ * Sends the contents of an entire buffer to the serial port, waiting until
+ * there is enough room in the serial port outbound buffer.
+ *
+ * Parameters: hPort     - Handle to a serial port object.
+ *
+ *             pbtBuffer - Pointer to the first byte in the buffer to transmit.
+ *
+ *             nSize     - Number of bytes to transmit from the buffer.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComSendBuffer(tPortHandle hPort, BYTE *pbtBuffer, int nSize)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+   int nPort;
+
+   VERIFY_CALL(pPortInfo != NULL);
+   VERIFY_CALL(pbtBuffer != NULL);
+   VERIFY_CALL(nSize >= 0);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   nPort = pPortInfo->btPort;
+
+   /* If there are no characters to transmit, then there is no need to */
+   /* proceed further.                                                 */
+   if(nSize == 0)
+   {
+      return(kODRCSuccess);
+   }
+
+   switch(pPortInfo->Method)
+   {
+#ifdef INCLUDE_FOSSIL_COM
+      case kComMethodFOSSIL:
+      {
+         int nCount;
+
+try_again:
+         ASM    push di
+         ASM    mov cx, nSize
+         ASM    mov dx, nPort
+
+
+#ifdef LARGEDATA
+         ASM    les di, pbtBuffer
+#else
+         ASM    mov ax, ds
+         ASM    mov es, ax
+         ASM    mov di, pbtBuffer
+#endif
+
+         ASM    mov ah, 0x19
+         ASM    int 20
+         ASM    pop di
+         ASM    mov nCount, ax
+
+         if(nCount<nSize)
+         {
+            /* Call idle function, if any. */
+            if(pPortInfo->pfIdleCallback != NULL)
+            {
+               (*pPortInfo->pfIdleCallback)();
+            }
+
+            nSize-=nCount;
+            pbtBuffer+=nCount;
+            goto try_again;
+         }
+         break;
+      }
+#endif /* INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_UART_COM
+      case kComMethodUART:
+      {
+         int nTransferSize;
+         int nFirstHalfSize;
+         int nSecondHalfSize;
+         char *pbtDest;
+
+         /* Loop, copying as much of buffer to transmit queue as possible, */
+         /* then waiting for some characters to be transmitted, and copy   */
+         /* more of buffer to transmit queue, until entire buffer has been */
+         /* transferred.                                                   */
+         for(;;)
+         {
+            /* Disable interrupts. */
+            ASM cli
+
+            /* Try to transfer all of buffer if possible. */
+            nTransferSize = nSize;
+
+            /* Adjust number of character to transfer down if there isn't */
+            /* enough space in transmit queue.                            */
+            if(nTransferSize > (nTXQueueSize - nTXChars))
+            {
+               nTransferSize = (nTXQueueSize - nTXChars);
+            }
+
+            /* Block transfer is divided into two segments - everything from */
+            /* current in index to end of queue, and everything from         */
+            /* beginning of queue to end of free space in queue.             */
+
+            /* Calculate size of first half of transfer. */
+            nFirstHalfSize = nTXQueueSize - nTXInIndex;
+            if(nFirstHalfSize > nTransferSize) nFirstHalfSize = nTransferSize;
+
+            /* Calculate size of second half of transfer. */
+            nSecondHalfSize = nTransferSize - nFirstHalfSize;
+
+            /* Transfer characters at current queue in index. */
+            pbtDest = pbtTXQueue + nTXInIndex;
+            while(nFirstHalfSize--)
+            {
+               *pbtDest++ = *pbtBuffer++;
+            }
+
+            /* If there is a second half to transfer. */
+            if(nSecondHalfSize)
+            {
+               /* Copy destination will begin at beginning of queue. */
+               pbtDest = pbtTXQueue;
+
+               /* Set final queue in index. */
+               nTXInIndex = nSecondHalfSize;
+
+               /* Perform second half of transfer. */
+               while(nSecondHalfSize--)
+               {
+                  *pbtDest++ = *pbtBuffer++;
+               }
+            }
+
+            /* If entire transfer was performed in first half. */
+            else
+            {
+               /* Set final queue in index. */
+               nTXInIndex += nTransferSize;
+
+               /* Wrap queue in index if we just happened to fill characters */
+               /* up to end of physical queue. If there was one less         */
+               /* character transferred, no wrap would be necessary, and if  */
+               /* there was one more character to be transferred, transfer   */
+               /* would have to be performed in two halves.                  */
+               if(nTXInIndex == nTXQueueSize) nTXInIndex = 0;
+            }
+
+            /* Update count of total characters in the queue. */
+            nTXChars += nTransferSize;
+
+            /* Enable transmit interrupt on the UART. */
+            ASM mov dx, nIntEnableRegAddr
+            ASM in al, dx
+            ASM or al, THRE
+            ASM out dx, al
+
+            /* Re-enable interrupts. */
+            ASM sti
+
+            /* Adjust count of characters left to transfer down by number of */
+            /* characters transferred.                                       */
+            nSize -= nTransferSize;
+
+            /* If there are no characters left to transfer, then we are */
+            /* done.                                                    */
+            if(nSize == 0) break;
+
+            /* Call idle function, if any. */
+            if(pPortInfo->pfIdleCallback != NULL)
+            {
+               (*pPortInfo->pfIdleCallback)();
+            }
+         }
+         break;
+      }
+#endif /* INCLUDE_UART_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwErrors;
+         DWORD dwBytesWritten;
+
+         /* Attempt to perform write operation. */
+         if(!WriteFile(pPortInfo->hCommDev, pbtBuffer, nSize, &dwBytesWritten,
+            NULL) || dwBytesWritten != (DWORD)nSize)
+         {
+            ClearCommError(pPortInfo->hCommDev, &dwErrors, NULL);
+            return(kODRCGeneralFailure);
+         }
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         ASSERT(pPortInfo->pfDoorWrite != NULL);
+         if(!(*pPortInfo->pfDoorWrite)(pbtBuffer, nSize))
+         {
+            return(kODRCGeneralFailure);
+         }
+         break;
+         return(kODRCUnsupported);
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			fd_set	socket_set;
+			struct	timeval tv;
+			int     send_ret;
+
+			FD_ZERO(&socket_set);
+			FD_SET(pPortInfo->socket,&socket_set);
+
+			tv.tv_sec=1;
+			tv.tv_usec=0;
+
+			if(select(pPortInfo->socket+1,NULL,&socket_set,NULL,&tv) != 1)
+	         return(kODRCGeneralFailure);
+
+			do {
+				send_ret = send(pPortInfo->socket, pbtBuffer, nSize, 0);
+				if (send_ret != SOCKET_ERROR)
+					break;
+				od_sleep(25);
+			} while (WSAGetLastError() == WSAEWOULDBLOCK);
+
+			if (send_ret != nSize)
+				return (kODRCGeneralFailure);
+      break;
+		}
+#endif /* INCLUDE_SOCKET_COM */
+
+#ifdef INCLUDE_STDIO_COM
+      case kComMethodStdIO:
+	    {
+			int pos=0;
+			fd_set  fdset;
+			struct  timeval tv;
+			int     retval;
+			int	loopcount=0;
+
+			while(pos<nSize) {
+				FD_ZERO(&fdset);
+				FD_SET(STDOUT_FILENO,&fdset);
+
+				tv.tv_sec=1;
+				tv.tv_usec=0;
+
+				retval=select(STDOUT_FILENO+1,NULL,&fdset,NULL,&tv);
+				if(retval!=1) {
+					if(retval==0) {
+						if(++loopcount>10)
+							return(kODRCGeneralFailure);
+						continue;
+					}
+					if(retval==-1 && errno==EINTR)
+						continue;
+					return(kODRCGeneralFailure);
+				}
+
+				retval=fwrite(pbtBuffer+pos,1,nSize-pos,stdout);
+				if(retval!=nSize-pos) {
+					od_sleep(1);
+				}
+
+				pos+=retval;
+			}
+		    break;
+		}
+#endif
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODComWaitEvent()
+ *
+ * Blocks until the specified serial I/O event occurs, or an error condition
+ * is encountered.
+ *
+ * Parameters: hPort - Handle to an open port.
+ *
+ *             Event - Event type to wait for.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODComWaitEvent(tPortHandle hPort, tComEvent Event)
+{
+   tPortInfo *pPortInfo = ODHANDLE2PTR(hPort, tPortInfo);
+
+   VERIFY_CALL(pPortInfo != NULL);
+
+   VERIFY_CALL(pPortInfo->bIsOpen);
+
+   switch(pPortInfo->Method)
+   {
+#if defined(INCLUDE_UART_COM) || defined(INCLUDE_FOSSIL_COM) || defined(INCLUDE_STDIO_COM)
+      case kComMethodFOSSIL:
+      case kComMethodUART:
+	  case kComMethodStdIO:
+         switch(Event)
+         {
+            case kNoCarrier:
+            {
+               BOOL bCarrier;
+               for(;;)
+               {
+                  ODComCarrier(hPort, &bCarrier);
+                  if(!bCarrier) break;
+
+                  if(pPortInfo->pfIdleCallback != NULL)
+                  {
+                     (*pPortInfo->pfIdleCallback)();
+                  }
+               }
+               break;
+            }
+            default:
+               VERIFY_CALL(FALSE);
+         }
+         break;
+#endif /* INCLUDE_UART_COM || INCLUDE_FOSSIL_COM */
+
+#ifdef INCLUDE_WIN32_COM
+      case kComMethodWin32:
+      {
+         DWORD dwEvtMask;
+
+         /* Obtain current event mask. */
+         if(!GetCommMask(pPortInfo->hCommDev, &dwEvtMask))
+         {
+            return(kODRCGeneralFailure);
+         }
+
+         /* Turn on event to be waited for. */
+         switch(Event)
+         {
+            case kNoCarrier:
+               dwEvtMask |= EV_RLSD;
+               break;
+            default:
+               VERIFY_CALL(FALSE);
+         }
+
+         /* Write new event mask. */
+         if(!SetCommMask(pPortInfo->hCommDev, dwEvtMask))
+         {
+            return(kODRCGeneralFailure);
+         }
+
+         /* Wait until event occurs. */
+         for(;;)
+         {
+            /* Block until some event occurs. */
+            if(!WaitCommEvent(pPortInfo->hCommDev, &dwEvtMask, NULL))
+            {
+               return(kODRCGeneralFailure);
+            }
+
+            /* Determine whether this is what we are waiting for. */
+            switch(Event)
+            {
+               case kNoCarrier:
+                  if(dwEvtMask | EV_RLSD)
+                  {
+                     BOOL bCarrier;
+                     ODComCarrier(hPort, &bCarrier);
+                     if(!bCarrier)
+                     {
+                        return(kODRCSuccess);
+                     }
+                  }
+                  break;
+            }
+
+            /* If we get here, the event we are waiting for hasn't occurred */
+            /* yet, so loop and block waiting for next event.               */
+         }
+
+         break;
+      }
+#endif /* INCLUDE_WIN32_COM */
+
+#ifdef INCLUDE_DOOR32_COM
+      case kComMethodDoor32:
+         switch(Event)
+         {
+            case kNoCarrier:
+               ASSERT(pPortInfo->pfDoorGetOfflineEventHandle != NULL);
+               WaitForSingleObject(
+                  (*pPortInfo->pfDoorGetOfflineEventHandle)(), INFINITE);
+               break;
+            default:
+               VERIFY_CALL(FALSE);
+         }
+         break;
+#endif /* INCLUDE_DOOR32_COM */
+
+#ifdef INCLUDE_SOCKET_COM
+      case kComMethodSocket:
+		{
+			if(Event == kNoCarrier)
+			{
+  			/* Wait for socket disconnect */
+				fd_set	socket_set;
+				char		ch;
+				int recv_ret;
+
+				while(1) 
+				{
+
+					FD_ZERO(&socket_set);
+					FD_SET(pPortInfo->socket,&socket_set);
+					if(select(pPortInfo->socket+1,&socket_set,NULL,NULL,NULL)
+						==SOCKET_ERROR)
+						break;
+					recv_ret = recv(pPortInfo->socket, &ch, 1, MSG_PEEK);
+					if(recv_ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
+						continue;
+					if (recv_ret != 1)
+						break;
+				}
+			}
+			else
+			{
+				VERIFY_CALL(FALSE);
+			}
+			break;
+		}
+#endif /* INCLUDE_SOCKET_COM */
+
+
+      default:
+         /* If we get here, then the current serial I/O method is not */
+         /* handled by this function.                                 */
+         ASSERT(FALSE);
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}

+ 137 - 0
odoors/ODCom.h

@@ -0,0 +1,137 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODCom.h
+ *
+ * Description: Public definitions for serial communications module, which
+ *              is implemented in odcom.c
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 21, 1994  6.00  BP   Created.
+ *              Jan 01, 1995  6.00  BP   Added ODComWaitEvent().
+ *              Dec 21, 1995  6.00  BP   Add ability to use already open port.
+ *              Jan 09, 1996  6.00  BP   Supply actual in/out buffer size used.
+ *              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   Initial support for Door32 interface.
+ *              Oct 19, 2001  6.20  RS   Added TCP/IP socket (telnet) support.
+ */
+
+#ifndef _INC_ODCOM
+#define _INC_ODCOM
+
+/* odtypes.h is needed for definitions of tODHandle, and BOOL. */
+#include "ODTypes.h"
+
+/* Serial I/O handle. */
+typedef tODHandle tPortHandle;
+
+/* Flow Control setting manifest constants. */
+#define FLOW_NONE           0x00
+#define FLOW_RTSCTS         0x02
+#define FLOW_DEFAULT        0x80
+
+/* Parity bit setting manifest constants. */
+#define ODPARITY_NONE         0x00
+#define ODPARITY_ODD          0x08
+#define ODPARITY_EVEN         0x18
+
+#define ODPARITY_MASK         0x18
+
+/* Stop bit setting manifest constants. */
+#define STOP_ONE            0x00
+#define STOP_ONE_POINT_FIVE 0x04                 /* Only with DATABITS_FIVE. */
+#define STOP_TWO            0x04         /* Only if not using DATABITS_FIVE. */
+
+#define STOP_MASK           0x04
+
+/* Character length manifest constants. */
+#define DATABITS_FIVE       0x00
+#define DATABITS_SIX        0x01
+#define DATABITS_SEVEN      0x02
+#define DATABITS_EIGHT      0x03
+
+#define DATABITS_MASK       0x03
+
+/* FIFO setting constants. */
+#define FIFO_DISABLE        0x00
+#define FIFO_ENABLE         0x01
+#define FIFO_TRIGGER_1      0x00
+#define FIFO_TRIGGER_4      0x40
+#define FIFO_TRIGGER_8      0x80
+#define FIFO_TRIGGER_14     0xc0
+
+/* Misc. manifest constants. */
+#define SPEED_UNSPECIFIED   0
+#define SIZE_NON_ZERO       -1
+
+/* Serial I/O method settings. */
+typedef enum
+{
+   kComMethodUnspecified,
+   kComMethodFOSSIL,
+   kComMethodUART,
+   kComMethodWin32,
+   kComMethodDoor32,
+	kComMethodSocket,
+	kComMethodStdIO
+} tComMethod;
+
+/* Serial I/O event types. */
+typedef enum
+{
+   kNoCarrier
+} tComEvent;
+
+/* Serial I/O function prototypes. */
+tODResult ODComAlloc(tPortHandle *phPort);
+tODResult ODComFree(tPortHandle hPort);
+tODResult ODComSetIdleFunction(tPortHandle hPort,
+   void (*pfCallback)(void));
+tODResult ODComSetFlowControl(tPortHandle hPort, BYTE btFlowControlSetting);
+tODResult ODComSetSpeed(tPortHandle hPort, long lSpeed);
+tODResult ODComSetPort(tPortHandle hPort, BYTE btPort);
+tODResult ODComSetIRQ(tPortHandle hPort, BYTE btIRQLevel);
+tODResult ODComSetPortAddress(tPortHandle hPort, int nPortAddress);
+tODResult ODComSetWordFormat(tPortHandle hPort, BYTE btWordFormat);
+tODResult ODComSetRXBuf(tPortHandle hPort, int nReceiveBufferSize);
+tODResult ODComSetTXBuf(tPortHandle hPort, int nTransmitBufferSize);
+tODResult ODComSetFIFO(tPortHandle hPort, BYTE btFIFOSetting);
+tODResult ODComSetPreferredMethod(tPortHandle hPort, tComMethod Method);
+tODResult ODComGetMethod(tPortHandle hPort, tComMethod *pMethod);
+tODResult ODComOpen(tPortHandle hPort);
+tODResult ODComOpenFromExistingHandle(tPortHandle hPort,
+   DWORD dwExistingHandle);
+tODResult ODComClose(tPortHandle hPort);
+tODResult ODComClearInbound(tPortHandle hPort);
+tODResult ODComClearOutbound(tPortHandle hPort);
+tODResult ODComInbound(tPortHandle hPort, int *pnInboundWaiting);
+tODResult ODComOutbound(tPortHandle hPort, int *pnOutboundWaiting);
+tODResult ODComCarrier(tPortHandle hPort, BOOL *pbIsCarrier);
+tODResult ODComSetDTR(tPortHandle hPort, BOOL bHigh);
+tODResult ODComSendByte(tPortHandle hPort, BYTE btToSend);
+tODResult ODComGetByte(tPortHandle hPort, char *pbtNext, BOOL bWait);
+tODResult ODComSendBuffer(tPortHandle hPort, BYTE *pbtBuffer, int nSize);
+tODResult ODComGetBuffer(tPortHandle hPort, BYTE *pbtBuffer, int nSize,
+   int *pnBytesRead);
+tODResult ODComWaitEvent(tPortHandle hPort, tComEvent Event);
+
+#endif /* !_INC_ODCOM */

+ 1620 - 0
odoors/ODCore.c

@@ -0,0 +1,1620 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODCore.c
+ *
+ * Description: Implements the core of OpenDoors, including chat mode
+ *              and standard input/output functions that are
+ *              used throughout OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Oct 19, 1994  6.00  BP   Changed paging hours logic.
+ *              Oct 21, 1994  6.00  BP   Further isolated com routines.
+ *              Oct 22, 1994  6.00  BP   Name case conversion /w punct.
+ *              Dec 08, 1994  6.00  BP   Allow custom chat mode deactivation.
+ *              Dec 09, 1994  6.00  BP   Remove global dir entry structure.
+ *              Dec 13, 1994  6.00  BP   Remove include of dir.h.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Dec 31, 1994  6.00  BP   Remove old multitasker definitions.
+ *              Jan 01, 1995  6.00  BP   Don't use ODComInbound().
+ *              Jan 01, 1995  6.00  BP   _waitdrain() -> ODWaitDrain().
+ *              Jan 01, 1995  6.00  BP   Use new millisecond timer functions.
+ *              Jan 01, 1995  6.00  BP   Remove od_init() from _remotechar()
+ *              Jan 01, 1995  6.00  BP   Split off odkrnl.c from odcore.c
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              Nov 11, 1995  6.00  BP   Moved first_word() to odlist.c
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 16, 1995  6.00  BP   Create odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Dec 12, 1995  6.00  BP   Added od_set_color().
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 13, 1995  6.00  BP   Moved chat mode code to ODKrnl.h.
+ *              Dec 19, 1995  6.00  BP   Request reason for chat outside hours.
+ *              Dec 23, 1995  6.00  BP   Allow space to continue at page pause.
+ *              Dec 24, 1995  6.00  BP   Added abtGreyBlock.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Use OD_API_VAR_DEFN for od_control.
+ *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
+ *              Jan 23, 1996  6.00  BP   No od_set_statusline() under Win32.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
+ *              Jan 09, 1996  6.00  BP   ODComOutbound() returns actual size.
+ *              Jan 09, 1996  6.00  BP   Reduce kernel calls from od_disp...().
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Mar 21, 1996  6.10  BP   Added od_control_get().
+ *              Sep 01, 1996  6.10  BP   Update output area on od_set_per...().
+ *              Oct 19, 2001  6.20  RS   od_get_key now ignores linefeeds.
+ *              Mar 14, 2002  6.22  RS   Fixed od_get_key(bWait=FALSE)
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <errno.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODCom.h"
+#include "ODKrnl.h"
+#include "ODScrn.h"
+#include "ODCore.h"
+#include "ODInQue.h"
+#ifdef ODPLAT_WIN32
+#include "ODFrame.h"
+#endif /* ODPLAT_WIN32 */
+
+
+/* GLOBAL VARIABLES SHARED THROUGHOUT OPENDOORS. */
+
+/* Global declaration of the OpenDoors control structure. */
+OD_API_VAR_DEFN tODControl 
+#ifndef _WIN32	/* warning C4229: anachronism used : modifiers on data are ignored */
+OD_GLOBAL_CONV 
+#endif
+od_control;
+
+/* OpenDoors global initialized flag. */
+BOOL bODInitialized = FALSE;
+
+/* Global serial port object handle. */
+tPortHandle hSerialPort;
+
+/* Global input queue object handle. */
+tODInQueueHandle hODInputQueue;
+
+/* Reentrancy control. */
+BOOL bIsCallbackActive = FALSE;
+BOOL bShellChatActive = FALSE;
+
+/* Global working space. */
+char szODWorkString[OD_GLOBAL_WORK_STRING_SIZE];
+
+/* Global instance of the text information structure for general use. */
+tODScrnTextInfo ODTextInfo;
+
+/* Logfile function hooks. */
+BOOL (*pfLogWrite)(INT) = NULL;
+void (*pfLogClose)(INT) = NULL;
+
+/* od_color_config() support for od_printf(). */
+char chColorCheck = 0;
+char *pchColorEndPos;
+
+/* Status line information. */
+BYTE btCurrentStatusLine = STATUS_NONE;
+OD_PERSONALITY_CALLBACK *pfCurrentPersonality = NULL;
+char szDesiredPersonality[33] = "";
+SET_PERSONALITY_FUNC *pfSetPersonality = NULL;
+
+/* Commonly used character sequences. */
+char abtBlackBlock[2] = {' ', 0x07};
+char abtGreyBlock[2] = {' ', 0x70};
+char szBackspaceWithDelete[4] = {8, ' ', 8, 0};
+
+/* Current output area on screen. */
+BYTE btOutputTop = 1;
+BYTE btOutputBottom = 23;
+
+
+/* PRIVATE VARIABLES. */
+
+/* Display color varaibles. */
+char bAnyColorChangeYet;
+
+/* Static character sequences. */
+static char szClearScreen[2] = {12, 0};
+
+/* Lookup table to map colors from PC values to ANSI color values. */
+static BYTE abtPCToANSIColorTable[8] = {30, 34, 32, 36, 31, 35, 33, 37};
+
+
+/* LOCAL HELPER FUNCTIONS. */
+static void ODAddANSIParameter(char *szControlSequence, int nParameterValue);
+
+
+/* ----------------------------------------------------------------------------
+ * ODWaitDrain()
+ *
+ * Waits for up to the specified number of milliseconds for the output serial
+ * buffer to drain.
+ *
+ * Parameters: MaxWait - Specifies the maximum number of milliseconds to wait
+ *                       before timing out.
+ *
+ *     Return: void
+ */
+void ODWaitDrain(tODMilliSec MaxWait)
+{
+   int nOutboundSize;
+   tODTimer Timer;
+
+   /* If we are operating in local mode, then don't do anything. */
+   if(od_control.baud == 0) return;
+
+   /* Otherwise, start a timer that is set to elapse after the maximum */
+   /* wait period.                                                     */
+   ODTimerStart(&Timer, MaxWait);
+
+   /* Loop until either the outbound buffer is empty, or the */
+   /* timer has elapsed.                                     */
+   for(;;)
+   {
+      /* Check whether any data is in the outbound serial queue. */
+      ODComOutbound(hSerialPort, &nOutboundSize);
+
+      /* If the queue is empty or the timer has elapsed, then stop */
+      /* waiting.                                                  */
+      if(nOutboundSize == 0 || ODTimerElapsed(&Timer)) break;
+
+      /* Otherwise, give other tasks a chance to run. */
+      od_sleep(0);
+
+      /* Give od_kernel() activities a chance to run. */
+      CALL_KERNEL_IF_NEEDED();
+   } 
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_clr_scr()
+ *
+ * Clears the contents of the local and remote screens, if screen clearing is
+ * enabled.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_clr_scr(void)
+{
+   INT16 nOriginalAttrib;
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_clr_scr()");
+
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Don't clear screen if disabled. */
+   if(!od_control.od_always_clear && !(od_control.user_attribute & 2)
+      && (od_control.od_extended_info || od_control.od_info_type == CUSTOM))
+   {
+      OD_API_EXIT();
+      return;
+   }
+
+   if(od_control.user_rip)
+   {
+      od_disp("!|*", 3, FALSE);
+      if(!od_control.od_default_rip_win)
+      {
+         od_disp("!|w0000270M12", 13, FALSE);
+      }
+   }
+   
+   if(od_control.user_ansi)
+   {
+      od_disp("\x1b[2J\x1b[1;1H", 10, FALSE);
+   }
+   else {
+	   /* Send ascii 12 to modem, no local echo. */
+	   od_disp(szClearScreen, 1, FALSE);
+   }
+
+   /* Clear local window. */
+   ODScrnClear();
+
+   /* Get color set prior to screen clear. */
+   nOriginalAttrib = od_control.od_cur_attrib;
+
+   /* Current color state is unknown. */
+   od_control.od_cur_attrib = -1;
+
+   /* Set color to original value. This gurantees that local and */
+   /* remote systems both have the same current color set.       */
+   od_set_attrib(nOriginalAttrib);
+
+   OD_API_EXIT();
+}
+
+
+
+/* ----------------------------------------------------------------------------
+ * od_input_str()
+ *
+ * Allows the user to input a string up to the specified length, using
+ * characters in the specified range. This string input function is designed
+ * to be compatible with all terminal types.
+ *
+ * Parameters: pszInput   - Pointer to string to store input in.
+ *
+ *             nMaxLength - Maximum number of characters to permit the user
+ *                          to input.
+ *
+ *             chMin      - The minimum character value to permit. This must
+ *                          be at least ASCII 32.
+ *
+ *             chMax      - The maximum character value to permit.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_input_str(char *pszInput, 
+   INT nMaxLength,
+   unsigned char chMin,
+   unsigned char chMax)
+{
+   char chKeyPressed;
+   INT nPosition;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_input_str()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Start at the beginning of the string. */
+   nPosition = 0;
+
+   /* Check that input parameters are valid. */
+   if(pszInput == NULL || nMaxLength < 1 || chMin > chMax)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return;
+   }
+
+   for(;;)
+   {
+      chKeyPressed = od_get_key(TRUE);
+
+      /* If user pressed enter. */
+      if(chKeyPressed == '\r' || chKeyPressed == '\n')
+      {
+         /* Terminate the string. */
+         pszInput[nPosition] = '\0';
+
+         /* Display CR-LF sequence. */
+         od_disp_str("\n\r");
+
+         /* Exit the function. */
+         OD_API_EXIT();
+         return;
+      }
+
+      /* If the user pressed backspace. */
+      else if(chKeyPressed == 8)
+      {
+         /* If we are not currently at the beginning of the string. */
+         if(nPosition > 0)
+         {
+            /* Send backspace sequence. */
+            od_disp_str(szBackspaceWithDelete);
+
+            /* Move current position back by one position in the string. */
+            --nPosition;
+         }
+      }
+
+      /* If this is a valid character to place in the string and we have */
+      /* not reached the maximum size of the string yet.                 */
+      else if(chKeyPressed >= chMin && chKeyPressed <= chMax
+         && nPosition < nMaxLength)
+      {
+         /* Display key that was pressed. */
+         od_putch(chKeyPressed);
+
+         /* Add the entered character to the string and increment our */
+         /* current position in the string.                           */
+         pszInput[nPosition++] = chKeyPressed;
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_clear_keybuffer()
+ *
+ * Clears any keystrokes from the inbound buffers. Both input from local and
+ * remote systems is discarded, by clearing both OpenDoors' common input
+ * event queue, and the serial port inbound buffer. This function is called
+ * to cause any input by the user prior to the time the function was called
+ * to be ignored.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_clear_keybuffer(void)
+{
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_clear_keybuffer()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Empty any events in the common input event queue. */
+   ODInQueueEmpty(hODInputQueue);
+
+   /* If we are not operating in local mode ... */
+   if(od_control.baud != 0)
+   {
+      /* ... then remove any items in the serial port inbound buffer. */
+      ODComClearInbound(hSerialPort);
+   }
+
+   /* Call the OpenDoors kernel function. */
+   CALL_KERNEL_IF_NEEDED();
+
+   OD_API_EXIT();
+}
+
+/* ----------------------------------------------------------------------------
+ * od_key_pending()
+ *
+ * Returns TRUE if there's a key pending, FALSE otherwise.
+ *
+ * Parameters: none
+ *
+ *     Return: TRUE if character is waiting, FALSE if no character is waiting.
+ */
+ODAPIDEF BOOL ODCALL od_key_pending(void)
+{
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_get_key()");
+
+   OD_API_ENTRY();
+
+   /* Call the OpenDoors kernel. */
+   CALL_KERNEL_IF_NEEDED();
+
+   if(!ODInQueueWaiting(hODInputQueue))
+   {
+         OD_API_EXIT();
+         return(FALSE);
+   }
+
+   OD_API_EXIT();
+   return(TRUE);
+}
+
+/* ----------------------------------------------------------------------------
+ * od_get_key()
+ *
+ * Inputs a single character, optionally waiting for the next character if no
+ * character has been received yet. This function returns data received from
+ * either the local or remote system, in the order in which it was received.
+ *
+ * Parameters: bWait - FALSE if od_get_key() should return right away with
+ *                     a value of 0 if no characters have been received, or
+ *                     TRUE if od_get_key() should wait for the next received
+ *                     character.
+ *
+ *     Return: Character that was received, or 0 if no character is waiting.
+ */
+ODAPIDEF char ODCALL od_get_key(BOOL bWait)
+{
+   tODInputEvent InputEvent;
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_get_key()");
+
+   OD_API_ENTRY();
+
+   /* Call the OpenDoors kernel. */
+   CALL_KERNEL_IF_NEEDED();
+
+	do {
+
+	   /* If we aren't supposed to wait for input, then check whether any   */
+	   /* input is waiting in the input queue, and if not return right away */
+	   /* without any data.                                                 */
+	   if(!bWait)
+	   {
+		  if(!ODInQueueWaiting(hODInputQueue))
+		  {
+			 OD_API_EXIT();
+			 return(0);
+		  }
+	   }
+
+		/* Obtain the next character from the input queue. If we get to this */
+		/* point and there is no data waiting in the input queue, then the   */
+		/* ODInQueueGetNextEvent() function will block until a character     */
+		/* is available in the input queue.                                  */
+		ODInQueueGetNextEvent(hODInputQueue, &InputEvent, OD_NO_TIMEOUT);
+
+		/* Only keyboard input events are currently supported by od_get_key(). */
+		ASSERT(InputEvent.EventType == EVENT_CHARACTER);
+
+		/* Update OpenDoors control structure member that records whether the */
+		/* last input came from the local or remote user.                     */
+		od_control.od_last_input = InputEvent.bFromRemote ? 0 : 1;
+
+	} while(InputEvent.chKeyPress == '\n');	/* Ignore line-feed char */
+
+   /* Return the character that was pressed by the user. */
+   OD_API_EXIT();
+   return(InputEvent.chKeyPress);
+}
+
+
+
+/* ----------------------------------------------------------------------------
+ * od_carrier()
+ *
+ * Allows programs to determine the current state of the carrier detect
+ * signal when OpenDoors' automatic carrier detection has been disabled.
+ *
+ * Parameters: none
+ *
+ *     Return: TRUE if the carrier detct signal is present, FALSE if it
+ *             isn't. When operating in local mode, this function always
+ *             returns FALSE.
+ */
+ODAPIDEF BOOL ODCALL od_carrier(void)
+{
+   BOOL bIsCarrier;
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_carrier()");
+
+   /* If we are operating in local mode, then return FALSE. */
+   if(od_control.baud == 0)
+   {
+      od_control.od_error = ERR_NOREMOTE;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* In remote mode, obtain the current state of the carrier detect signal. */
+   ODComCarrier(hSerialPort, &bIsCarrier);
+
+   /* Return the current state of the carrier detect signal. */
+   OD_API_EXIT();
+   return(bIsCarrier);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_repeat()
+ *
+ * This function displays the same character the specified number of times on
+ * the local and remote screens, using any available optimal control sequences
+ * under the current display mode.
+ *
+ * Parameters: chValue - Character to repeat.
+ *
+ *             btTimes - Number of times to repeat the character.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_repeat(char chValue, BYTE btTimes)
+{
+   char *pchCurStringPos;
+   BYTE btLeft;
+   char szBuffer[3];
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_repeat()");
+
+   /* Ensure that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* If the caller asked to repeat the character 0 times, then we can */
+   /* safely return right away without doing anything.                 */
+   if(btTimes == 0)
+   {
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Generate string of repeat characters. */
+   pchCurStringPos = szODWorkString; 
+   for(btLeft = btTimes; btLeft--;)
+   {
+      *pchCurStringPos++ = chValue;
+   }
+   *pchCurStringPos = '\0';
+
+   /* Display repeated string on local screen. */
+   ODScrnDisplayString(szODWorkString);
+
+   /* If we are operating in AVATAR mode. */
+   if(od_control.user_avatar)
+   {
+      /* Generate the AVATAR control sequence to repeat this character */
+      /* the specified number of times.                                */
+      szBuffer[0] = 25;
+      szBuffer[1] = chValue;
+      szBuffer[2] = btTimes;
+      od_disp(szBuffer, 3, FALSE);
+   }
+
+   /* If AVATAR mode is not available. */
+   else
+   {
+      /* Send the entire repeated string to the remote system. */
+      od_disp(szODWorkString, btTimes, FALSE);
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_page()
+ *
+ * This function is called when the user wished to page the system operator.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_page(void)
+{
+   INT16 nCount;
+   tODTimer Timer;
+   time_t nUnixTime;
+   struct tm *TimeBlock;
+   INT16 nMinute;
+   BOOL bFailed = FALSE;
+   INT16 nOriginalAttrib;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_page()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Save current display color attribute. */
+   nOriginalAttrib = od_control.od_cur_attrib;
+
+   /* Clear the screen. */
+   od_clr_scr();
+   od_set_attrib(od_control.od_chat_color1);
+
+   /* Ask reason for chat. */
+   od_disp_str(od_control.od_chat_reason);
+   od_set_attrib(od_control.od_chat_color2);
+   od_putch('[');
+
+   /* Use extended ASCII characters if operating in ANSI or AVATAR mode. */
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      od_repeat('Ä',77);
+   }
+   else
+   {
+      od_repeat('-',77);
+   }
+   od_disp_str("]\n\r ");
+   od_input_str(od_control.user_reasonforchat,77,32,255);
+
+   /* If the user did not abort sysop paging by entering a blank reason */
+   /* for chat.                                                         */
+   if(strlen(od_control.user_reasonforchat) != 0)
+   {
+      /* Indicate that the user wants to chat. */
+      od_control.user_wantchat = TRUE;
+#ifdef ODPLAT_WIN32
+      ODFrameUpdateWantChat();
+#endif /* ODPLAT_WIN32 */
+
+      /* Determine whether or not sysop paging should be permitted at */
+      /* the current time.                                            */
+      nUnixTime = time(NULL);
+      TimeBlock = localtime(&nUnixTime);
+      nMinute = (60 * TimeBlock->tm_hour) + TimeBlock->tm_min;
+      if(od_control.od_pagestartmin < od_control.od_pageendmin)
+      {
+         if(nMinute < od_control.od_pagestartmin
+            || nMinute >= od_control.od_pageendmin)
+         {
+            bFailed = TRUE;
+         }
+      }
+      else if(od_control.od_pagestartmin > od_control.od_pageendmin)
+      {
+         if(nMinute < od_control.od_pagestartmin
+            && nMinute >= od_control.od_pageendmin)
+         {
+            bFailed = TRUE;
+         }
+      }
+      else
+      {
+         bFailed = FALSE;
+      }
+
+      /* If paging is set to PAGE_ENABLE, meaning that sysop paging should */
+      /* be permitted regardless of the time of day, then allow paging.    */
+      if(od_control.od_okaytopage == PAGE_ENABLE)
+      {
+         bFailed = FALSE;
+      }
+
+      /* If paging is explicitly disable by PAGE_DISABLE, or the current */
+      /* time of the day is not normally permitted for paging.           */
+      if(od_control.od_okaytopage == PAGE_DISABLE || bFailed)
+      {
+         /* Indicate this to user. */
+         od_disp_str("\n\r");
+         od_disp_str(od_control.od_no_sysop);
+         od_disp_str(od_control.od_press_key);
+         od_get_answer("\x0d\x0a");
+
+         /* Return from this function. */
+         goto cleanup;
+      }
+
+      /* Update status line right away. */
+      bForceStatusUpdate = TRUE;
+      CALL_KERNEL_IF_NEEDED();
+
+      /* Write sysop page information to the logfile, if the log file */
+      /* system is hooked up.                                         */
+      if(pfLogWrite != NULL)
+      {
+         (*pfLogWrite)(8);
+      }
+
+      /* Tell the user that we are now paging the system operator. */
+      od_set_attrib(od_control.od_chat_color1);
+      od_disp_str(od_control.od_paging);
+
+#ifdef OD_TEXTMODE
+      /* Display sysop page status line if it exists and the sysop status */
+      /* line is currently active.                                        */
+      if(od_control.od_page_statusline != -1 && btCurrentStatusLine != 8)
+      {
+         od_set_statusline(od_control.od_page_statusline);
+      }
+#endif /* OD_TEXTMODE */
+
+      /* Increment the total number of times that the user has paged */
+      /* the sysop.                                                  */
+      ++od_control.user_numpages;
+
+      /* Sysop hasn't responded yet. */
+      bChatted=FALSE;
+
+      /* Loop for length of sysop page. */
+      for(nCount = 0; nCount < od_control.od_page_len; ++nCount)
+      {
+         /* Start a timer that is set to elapse in exactly one second. */
+         ODTimerStart(&Timer, 1000);
+
+         /* Display another period character. */
+         od_putch('.');
+
+         /* Abort page if system operator answered */
+         if(bChatted) goto cleanup;
+
+         /* Send beep to local and remote systems. */
+         od_putch('\a');
+
+         /* Check whether system operator has answered after playing beep. */
+         if (bChatted) goto cleanup;
+
+         /* Wait for the timer to elapse, calling od_kernel() so that */
+         /* chat mode will start as soon as the sysop presses the     */
+         /* chat key.                                                 */
+         while(!ODTimerElapsed(&Timer))
+         {
+            CALL_KERNEL_IF_NEEDED();
+         }
+      }
+
+      /* If sysop page time has elapsed without a response from the */
+      /* sysop, then notify the user.                               */
+      od_disp_str(od_control.od_no_response);
+      od_disp_str(od_control.od_press_key);
+      od_get_answer("\x0d\x0a");
+      od_disp_str("\n\r\n\r");
+   }
+
+cleanup:
+   /* Restore original display color attribute. */
+   od_set_attrib(nOriginalAttrib);
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_disp()
+ *
+ * Function to send one or more character to the remote system, optionally
+ * also echoing the same characters to the local screen.
+ *
+ * Parameters: pachBuffer - Pointer to buffer of characters to send.
+ *
+ *             nSize      - Number of characters to send from the buffer.
+ *
+ *             bLocalEcho - TRUE to also echo the characters to the local
+ *                          screen, FALSE to just send the characters to the
+ *                          remote system.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_disp(const char *pachBuffer, INT nSize, BOOL bLocalEcho)
+{
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_disp()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Call the OpenDoors kernel, if needed. */
+#ifndef OD_MULTITHREADED
+   if(ODTimerElapsed(&RunKernelTimer))
+   {
+      CALL_KERNEL_IF_NEEDED();
+   }
+#endif /* !OD_MULTITHREADED */
+
+   /* If we are operating in remote mode, then send the buffer to the */
+   /* remote system.                                                  */
+   if(od_control.baud != 0)
+   {
+      ODComSendBuffer(hSerialPort, (BYTE *)pachBuffer, nSize);
+   }
+
+   /* If we are also to display the character on the local screen, then */
+   /* display the buffer on the local screen.                           */
+   if(bLocalEcho)
+   {
+      ODScrnDisplayBuffer(pachBuffer, nSize);
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_disp_str()
+ *
+ * Displays a string on both the local and remote systems.
+ *
+ * Parameters: pszToDisplay - Pointer to the string to be displayed.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_disp_str(const char *pszToDisplay)
+{
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_disp_str()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Call the OpenDoors kernel, if needed. */
+#ifndef OD_MULTITHREADED
+   if(ODTimerElapsed(&RunKernelTimer))
+   {
+      CALL_KERNEL_IF_NEEDED();
+   }
+#endif /* !OD_MULTITHREADED */
+
+   /* Send the string to the remote system, if we are running in remote mode. */
+   if(od_control.baud != 0)
+   {
+      ODComSendBuffer(hSerialPort, (BYTE *)pszToDisplay, strlen(pszToDisplay));
+   }
+
+   /* Display the screen on the local screen. */
+   ODScrnDisplayString(pszToDisplay);
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_statusline()
+ *
+ * Switches to one of the available status lines provided by the current
+ * personality, or turns off the status line altogether.
+ *
+ * Parameters: nSetting - Indicates which status line (if any) should be
+ *                        activated.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_set_statusline(INT nSetting)
+{
+#ifdef OD_TEXTMODE
+   INT nDistance;
+   BYTE btCount
+#endif /* OD_TEXTMODE */
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_set_statusline()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY()
+
+#ifdef OD_TEXTMODE
+
+   /* If status line is disabled, then don't do anything. */
+   if(!od_control.od_status_on)
+   {
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Ensure that the parameter is within the valid range. */
+   if(nSetting < 0 || nSetting > 8)
+   {
+      nSetting = 0;
+   }
+
+   /* If the specified status line is already active, and status line */
+   /* update isn't being forced, then return without doing anything.  */
+   if(!od_control.od_update_status_now && nSetting == btCurrentStatusLine)
+   {
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Save the current cursor settings. */
+   ODStoreTextInfo();
+
+   /* Reset screen boundary to allow access to the entire screen. */
+   ODScrnSetBoundary(1,1,80,25);
+
+   /* If status line is being turned off. */
+   if(btCurrentStatusLine == STATUS_NONE)
+   {
+      if((nDistance = (INT)ODTextInfo.cury - ( 1 + (INT)btOutputBottom
+         - (INT)btOutputTop)) > 0)
+      {
+         ODScrnCopyText(1, (BYTE)((INT)btOutputTop + nDistance), 80,
+            (BYTE)((INT)btOutputBottom + nDistance), (BYTE)btOutputTop, 1);
+         ODTextInfo.cury = 1 + btOutputBottom - btOutputTop;
+      }
+      else if(ODTextInfo.cury < btOutputTop)
+      {
+         ODTextInfo.cury = btOutputTop;
+         ODScrnCopyText(1, (BYTE)(btOutputTop + 24 - btOutputBottom), 80, 25,
+            btOutputTop, 1);
+      }
+   }
+
+   od_control.od_current_statusline = btCurrentStatusLine = nSetting;
+
+   if(nSetting == 8)
+   {
+      ODScrnSetAttribute(0x07);
+
+      for(btCount = 1; btCount <= 25; ++btCount)
+      {
+         if(btCount < btOutputTop || btCount > btOutputBottom)
+         {
+            if(btCount == 25)
+            {
+               ODScrnPutText(80, 25, 80, 25, abtBlackBlock);
+               ODScrnSetCursorPos(1, 25);
+               ODScrnDisplayString("                                                                               ");
+            }
+            else
+            {
+               ODScrnSetCursorPos(1, 24);
+               ODScrnDisplayString("                                                                                ");
+            }
+         }
+      }
+
+      ODScrnSetAttribute(ODTextInfo.attribute);
+      ODScrnSetCursorPos(ODTextInfo.curx, ODTextInfo.cury);
+   }
+
+   else
+   {
+      ODScrnEnableCaret(FALSE);
+      ODScrnEnableScrolling(FALSE);
+
+      (*pfCurrentPersonality)((BYTE)nSetting);
+
+      ODScrnEnableCaret(TRUE);
+      ODScrnEnableScrolling(TRUE);
+
+      ODScrnSetBoundary(1, btOutputTop, 80, btOutputBottom);
+      ODScrnSetAttribute(ODTextInfo.attribute);
+      ODScrnSetCursorPos(ODTextInfo.curx, ODTextInfo.cury);
+   }
+
+#else /* !OD_TEXTMODE */
+
+   od_control.od_error = ERR_UNSUPPORTED;
+
+#endif /* !OD_TEXTMODE */
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStoreTextInfo()
+ *
+ * Stores the current text settings into the OpenDoors global text information
+ * structure.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODStoreTextInfo(void)
+{
+   ODScrnGetTextInfo(&ODTextInfo);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODRestoreTextInfo()
+ *
+ * Restores display settings previously stored by ODStoreTextInfo()
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODRestoreTextInfo(void)
+{
+   ODScrnSetBoundary(ODTextInfo.winleft, ODTextInfo.wintop,
+      ODTextInfo.winright, ODTextInfo.winbottom);
+   ODScrnSetAttribute(ODTextInfo.attribute);
+   ODScrnSetCursorPos(ODTextInfo.curx, ODTextInfo.cury);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStringToName()
+ *
+ * Reformats a string so that it has the correct capitalization for a name,
+ * and removes any trailing line break character.
+ *
+ * Parameters: pszToConvert - Pointer to the string to reformat.
+ *
+ *     Return: void
+ */
+void ODStringToName(char *pszToConvert)
+{
+   /* Begin by changing the entire string to lower case. */
+   strlwr(pszToConvert);
+
+   /* Trim any newline character that may be at the end of the string. */
+   if(pszToConvert[strlen(pszToConvert) - 1] == '\n')
+   {
+      pszToConvert[strlen(pszToConvert) - 1] = '\0';
+   }
+   /* Trim any CR character that may be at the end of the string. */
+   if(pszToConvert[strlen(pszToConvert) - 1] == '\r')
+   {
+      pszToConvert[strlen(pszToConvert) - 1] = '\0';
+   }
+
+   /* Change the first character to lower case. */
+   *pszToConvert = toupper(*pszToConvert);
+
+   /* Loop through the rest of the string, capitalizing any other words */
+   /* in the string.                                                    */
+   while(*pszToConvert)
+   {
+      switch(*pszToConvert++)
+      {
+         case ' ':
+         case '\t':
+         case ',':
+         case '.':
+         case '-':
+            *pszToConvert = toupper(*pszToConvert);
+            break;
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_color()
+ *
+ * Sets the current display color for both local and remote output.
+ *
+ * Parameters: nForeground - New foreground (text) color.
+ *
+ *             nBackground - New background color.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_set_color(INT nForeground, INT nBackground)
+{
+   /* Use od_set_attrib() to perform the actual color setting.          */
+   /* Here, we rely on od_set_attrib() to look after initialization,    */
+   /* API_ENTRY() and API_EXIT() calls, etc. This allows od_set_color() */
+   /* (which was previously just a macro) to be implemented with as     */
+   /* little overhead as possible.                                      */
+   od_set_attrib(nForeground | (nBackground << 4));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_attrib()
+ *
+ * Sets the current display color for both local and remote output.
+ *
+ * Parameters: nColor - New Display color to set, or -1 for no change.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_set_attrib(INT nColor)
+{
+   char szControlSequence[40];
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_set_attrib()");
+
+   /* Ensure that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* If color value is -1, then make no change. */
+   if(nColor == -1)
+   {
+      OD_API_EXIT();
+      return;
+   }
+
+   /* If we are operating in AVATAR mode. */
+   if(od_control.user_avatar)
+   {
+      if(od_control.od_cur_attrib != nColor || od_control.od_full_color)
+      {
+         /* Change local text color. */
+         ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib = nColor));
+
+         /* Generate AVATAR control sequence. */
+         szControlSequence[0] = 22;
+         szControlSequence[1] = 1;
+         szControlSequence[2] = nColor;
+
+         /* Send AVATAR control sequence. */
+         od_disp(szControlSequence, 3, FALSE);
+      }
+   }
+
+   /* If we are operating in ANSI mode. */
+   else if(od_control.user_ansi)
+   {
+      bAnyColorChangeYet = FALSE;
+
+      if(od_control.od_cur_attrib == -1 || od_control.od_full_color)
+      {
+ansi_reset:
+         /* Reset ANSI terminal status. */
+         ODAddANSIParameter(szControlSequence, 0);
+
+         /* If blink attribute is set. */
+         if(nColor & 0x80)
+         {
+            /* Add it to the ANSI color sequence. */
+            ODAddANSIParameter(szControlSequence, 5);
+         }
+
+         /* If high intensity attribute is set. */
+         if(nColor & 0x08)
+         {
+            /* Add it to the ANSI color sequence. */
+            ODAddANSIParameter(szControlSequence, 1);
+         }
+      }
+
+      /* If current color is known. */
+      else                             
+      {
+         /* If have to reset flashing or bright. */
+         if(((od_control.od_cur_attrib&0x80) &&
+            !(nColor & 0x80)) || ((od_control.od_cur_attrib & 0x08)
+            && !(nColor & 0x08)))
+         {
+            /* Must reset entire colour settings. */
+            od_control.od_cur_attrib = -1;
+            goto ansi_reset;
+         }
+                                       
+         /* If flashing has to be turned on. */
+         if((nColor & 0x80) != (od_control.od_cur_attrib & 0x80))
+         {
+            /* Add it to the ANSI color sequence. */
+            ODAddANSIParameter(szControlSequence, 5);
+         }
+
+         /* If bright has to be turned on. */
+         if((nColor & 0x08) != (od_control.od_cur_attrib & 0x08)
+            || od_control.od_cur_attrib == -1)
+         {
+            /* Add it to the ANSI color sequence. */
+            ODAddANSIParameter(szControlSequence, 1);
+         }
+      }
+
+
+      /* If foreground color has changed. */
+      if((nColor & 0x07) != (od_control.od_cur_attrib & 0x07)
+         || od_control.od_cur_attrib == -1 || od_control.od_full_color)
+      {
+         /* Add translated color to sequence. */
+         ODAddANSIParameter(szControlSequence,
+            abtPCToANSIColorTable[nColor&0x07]);
+      }
+
+      /* If background color has changed. */
+      if((nColor & 0x70) != (od_control.od_cur_attrib & 0x70)
+         || od_control.od_cur_attrib == -1 || od_control.od_full_color)
+      {
+         /* Add translated color to sequence. */
+         ODAddANSIParameter(szControlSequence,
+            abtPCToANSIColorTable[(nColor & 0x70) >> 4] + 10);
+      }
+
+      /* If any change in color. */
+      if(bAnyColorChangeYet)
+      {
+         /* Append change-attribute command. */
+         strcat(szControlSequence, "m");
+
+         /* Send ANSI sequence to the modem. */
+         od_disp(szControlSequence, strlen(szControlSequence), FALSE);
+      }
+
+      /* Change local text color. */
+      ODScrnSetAttribute((BYTE)(od_control.od_cur_attrib = nColor));
+   }
+   else
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODAddANSIParameter()                                *** PRIVATE FUNCTION ***
+ *
+ * Adds a parameter to an ANSI color sequence.
+ *
+ * Parameters: szControlSequence - The contents of the control sequence string
+ *                                 generated so far.
+ *
+ *             nParameterValue   - Value of the parameter to add.
+ *
+ *     Return: void
+ */
+static void ODAddANSIParameter(char *szControlSequence, int nParameterValue)
+{
+   char szTemp[5];
+
+   if(bAnyColorChangeYet)
+   {
+      sprintf(szTemp, ";%d", nParameterValue);
+      strcat(szControlSequence, szTemp);
+   }
+   else
+   {
+      bAnyColorChangeYet = TRUE;
+      sprintf(szControlSequence, "x[%d", nParameterValue);
+      szControlSequence[0] = 27;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_putch()
+ *
+ * Displays a character on the local and remote screens.
+ *
+ * Parameters: chToDisplay - The character to display.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_putch(char chToDisplay)
+{
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_putch()");
+
+   /* Initialize OpenDoors if it hasn't been done already. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Display the character on the local screen. */
+   ODScrnDisplayChar(chToDisplay);
+
+   /* If not operating in local mode, then send the character to the */
+   /* serial port.                                                   */
+   if(od_control.baud)
+   {
+      ODComSendByte(hSerialPort, chToDisplay);
+   }
+
+   /* If it is time to call the kernel, then do so. */
+   if(ODTimerElapsed(&RunKernelTimer))
+   {
+      CALL_KERNEL_IF_NEEDED();
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_dtr()
+ *
+ * Changes the state of the DTR line to the modem, if not running in local
+ * mode.
+ *
+ * Parameters: bHigh - TRUE to raise DTR, FALSE to lower it.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_set_dtr(BOOL bHigh)
+{
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_set_dtr()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* If we are running in local mode, then return with an error. */
+   if(!od_control.baud)
+   {
+      od_control.od_error = ERR_NOREMOTE;
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Otherwise, change the state of the DTR line. */
+   ODComSetDTR(hSerialPort, bHigh);
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_get_answer()
+ *
+ * Waits for the user to press one of the keys listed in pszOptions. Case is
+ * not sensitive, although the pressed key is returned in the same case as it
+ * is specified in pszOptions.
+ *
+ * Parameters: pszOptions - String listing characters to accept.
+ *
+ *     Return: void
+ */
+ODAPIDEF char ODCALL od_get_answer(const char *pszOptions)
+{
+   char *pchPossibleOption;
+   char chPressed;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_get_answer()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   for(;;)
+   {
+      /* Wait for the next key press by the user. */
+      chPressed = od_get_key(TRUE);
+      chPressed = tolower(chPressed);
+
+      /* Loop through list of possible options. */
+      pchPossibleOption = (char *)pszOptions;
+      while(*pchPossibleOption)
+      {
+         /* If the key pressed matches this possible option. */
+         if(tolower(*pchPossibleOption) == chPressed)
+         {
+            /* Then return the character in the case originally specified */
+            /* by the caller.                                             */
+            OD_API_EXIT();
+            return(*pchPossibleOption);
+         }
+
+         /* Move on to the next possible option. */
+         ++pchPossibleOption;
+      }
+
+      /* If the key pressed did not match a possible option, then we */
+      /* just loop again, getting the next key.                      */
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_color_config()
+ *
+ * Determines the color attribute that is described by the provided string.
+ * This string is in the same format that is used for specifying colors in the
+ * OpenDoors configuration file.
+ *
+ * Parameters: pszColorDesc - Color description string.
+ *
+ *     Return: The PC-style color attribute corresponding to the color
+ *             description string.
+ */
+ODAPIDEF BYTE ODCALL od_color_config(char *pszColorDesc)
+{
+   BYTE btColor = 0x07;
+   char szToken[40];
+   char *pszStart=(char *)pszColorDesc;
+   char *pszEnd;
+   BYTE btLength;
+   BYTE btIdentifier;
+   BOOL bForeground = TRUE;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_color_config()");
+
+   /* Initialize OpenDoros if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   while(*pszStart && *pszStart!=chColorCheck)
+   {
+      if(*pszStart == ' ' || *pszStart== '\t')
+      {
+         ++pszStart;
+      }
+      else
+      {
+         btLength = 0;
+         pszEnd = (char *)pszStart;
+         while(*pszEnd && *pszEnd != chColorCheck && *pszEnd != ' '
+            && *pszEnd != '\t')
+         {
+            ++btLength;
+            ++pszEnd;
+         }
+
+         if(btLength > 39) btLength = 39;
+         strncpy(szToken, pszStart, btLength);
+         szToken[btLength] = '\0';
+         strupr(szToken);
+
+         for(btIdentifier = 0; btIdentifier < 12; ++btIdentifier)
+            if(strcmp(od_config_colours[btIdentifier], szToken) == 0)
+            {
+               if(btIdentifier <= 9)
+               {
+                  if(btIdentifier >= 8) btIdentifier -= 2;
+
+                  if(bForeground)
+                  {
+                     bForeground=FALSE;
+                     btColor &=~ 0x07;
+                     btColor |= btIdentifier;
+                  }
+                  else
+                  {
+                     btColor &=~ 0x70;
+                     btColor |= (btIdentifier << 4);
+                  }
+               }
+
+               else if(btIdentifier == 10)
+               {
+                  btColor |= 0x08;
+               }
+
+               else if(btIdentifier == 11)
+               {
+                  btColor |= 0x80;
+               }
+
+               break;
+            }
+
+         pszStart = (char *)pszEnd;
+      }
+   }
+
+   pchColorEndPos = (char *)pszStart;
+
+   OD_API_EXIT();
+
+   return(btColor);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODPagePrompt()
+ *
+ * Called to display the page prompt at the end of a screen of text. This page
+ * prompt allows the user to stop further display, to display the next page,
+ * or to display in continuous (non-stop) mode with page pausing disabled.
+ *
+ * Parameters: pbPausing   - Pointer to current page pausing enabled flag.
+ *
+ *     Return: FALSE if display should be continued, or TRUE to abort display.
+ */
+BOOL ODPagePrompt(BOOL *pbPausing)
+{
+   INT nPromptLength = strlen(od_control.od_continue);
+   tODScrnTextInfo TextInfo;
+   BOOL bToReturn = FALSE;
+   char chKeyPressed;
+   BYTE btCount;
+
+   /* Return right away if page pausing is disabled. */
+   if(!*pbPausing) return(FALSE);
+
+   /* Get current text color. */
+   ODScrnGetTextInfo(&TextInfo);
+
+   /* Set to prompt color. */
+   od_set_attrib(od_control.od_continue_col);
+
+   /* Display page prompt string. */
+   od_disp_str(od_control.od_continue);
+
+   /* Restore original text color. */
+   od_set_attrib(TextInfo.attribute);
+
+   /* Loop until the user makes a valid choice. */
+   for(;;)
+   {
+      /* Obtain the next key from the user. */
+      chKeyPressed = od_get_key(TRUE);
+
+      /* If user chooses to continue. */
+      if(chKeyPressed == tolower(od_control.od_continue_yes) ||
+         chKeyPressed == toupper(od_control.od_continue_yes) ||
+         chKeyPressed == 13 ||
+         chKeyPressed == ' ')
+      {
+         /* Remove the prompt and return. */
+         goto finished_pausing;
+      }
+
+      /* If user requested nonstop display. */
+      else if(chKeyPressed == tolower(od_control.od_continue_nonstop) ||
+              chKeyPressed == toupper(od_control.od_continue_nonstop))
+      {
+         /* Disable page pausing. */
+         *pbPausing = FALSE;
+
+         /* Remove the prompt and return. */
+         goto finished_pausing;
+      }
+
+      /* If user chooses to stop display. */
+      else if(chKeyPressed == tolower(od_control.od_continue_no) ||
+              chKeyPressed == toupper(od_control.od_continue_no) ||
+              chKeyPressed == 's' || chKeyPressed == 'S' || chKeyPressed == 3
+              || chKeyPressed == 11 || chKeyPressed == 0x18)
+      {
+         /* If we are operating in remote mode. */
+         if(od_control.baud)
+         {
+            /* Clear the output buffer. */
+            ODComClearOutbound(hSerialPort);
+         }
+
+         /* Tell the caller to stop displaying more text. */
+         bToReturn = TRUE;
+
+         /* Remove the prompt and return. */
+         goto finished_pausing;
+      }
+   }
+
+finished_pausing:
+   /* Remove the pause prompt. */
+   for(btCount = 0; btCount < nPromptLength; ++btCount)
+   {
+      od_disp_str(szBackspaceWithDelete);
+   }
+
+   return(bToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_control_get()
+ *
+ * Returns a pointer to the od_control structure containing information
+ * and settings associated with the current session.
+ *
+ * Parameters: None.
+ *
+ *     Return: A pointer to the od_control structure associated with this
+ *             session.
+ */
+ODAPIDEF tODControl * ODCALL od_control_get(void)
+{
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_disp_str()");
+
+   return(&od_control);
+}

+ 109 - 0
odoors/ODCore.h

@@ -0,0 +1,109 @@
+/* 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: ODCore.h
+ *
+ * Description: Global functions and variables provide by the odcore.c
+ *              module. These core facilities are used throughout OpenDoors,
+ *              and are required regardless of what OpenDoors features that
+ *              a given program uses.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 16, 1995  6.00  BP   Created.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Dec 24, 1995  6.00  BP   Added abtGreyBlock.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 01, 1996  6.00  BP   Changed TEXT_SIZE to 49.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Sep 01, 1996  6.10  BP   Update output area on od_set_per...().
+ */
+
+#ifndef _INC_ODCORE
+#define _INC_ODCORE
+
+
+/* Include other header files that have definitions neede by this one. */
+#include "ODInQue.h"
+#include "ODCom.h"
+#include "ODPlat.h"
+#include "ODScrn.h"
+
+
+/* OpenDoors global initialized flag. */
+extern BOOL bODInitialized;
+
+/* Global serial port object handle. */
+extern tPortHandle hSerialPort;
+
+/* Global input queue object handle. */
+extern tODInQueueHandle hODInputQueue;
+
+/* Reentrancy control. */
+extern BOOL bIsCallbackActive;
+extern BOOL bShellChatActive;
+
+/* Global working space. */
+#define OD_GLOBAL_WORK_STRING_SIZE  1025
+extern char szODWorkString[OD_GLOBAL_WORK_STRING_SIZE];
+
+/* Global instance of the text information structure for general use. */
+extern tODScrnTextInfo ODTextInfo;
+
+/* Logfile function hooks. */
+extern BOOL (*pfLogWrite)(INT);
+extern void (*pfLogClose)(INT);
+
+/* od_colour_config() support for od_printf(). */
+extern char chColorCheck;
+extern char *pchColorEndPos;
+
+/* Status line information. */
+extern BYTE btCurrentStatusLine;
+extern OD_PERSONALITY_CALLBACK *pfCurrentPersonality;
+extern char szDesiredPersonality[33];
+typedef BOOL ODCALL SET_PERSONALITY_FUNC(char *pszName);
+extern SET_PERSONALITY_FUNC *pfSetPersonality;
+
+/* Commonly used character sequences. */
+extern char abtBlackBlock[2];
+extern char abtGreyBlock[2];
+extern char szBackspaceWithDelete[4];
+
+/* Current output area on screen. */
+extern BYTE btOutputTop;
+extern BYTE btOutputBottom;
+
+
+/* Core functions used throughout OpenDoors. */
+void ODWaitDrain(tODMilliSec MaxWait);
+void ODStoreTextInfo(void);
+void ODRestoreTextInfo(void);
+void ODStringToName(char *pszToConvert);
+BOOL ODPagePrompt(BOOL *pbPausing);
+
+
+/* Number of built-in configuration file options. */
+#define TEXT_SIZE 49
+
+/* Number of user-defined info file options. */
+#define LINES_SIZE 25
+
+
+#endif /* _INC_ODCORE */

+ 187 - 0
odoors/ODDrBox.c

@@ -0,0 +1,187 @@
+/* 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: ODDrBox.c
+ *
+ * Description: Implements the od_draw_box() function.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              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 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODKrnl.h"
+
+
+/* ----------------------------------------------------------------------------
+ * od_draw_box()
+ *
+ * Draws a box on the local and remote screens, using the box characters
+ * specified in od_control.od_box_chars. Unlike the window functions, this
+ * function does not store the original contents of the screen where the box
+ * is drawn.
+ *
+ * Parameters: btLeft   - Column number of the left side of the box.
+ *
+ *             btTop    - Row number of the top side of the box.
+ *
+ *             btRight  - Column number of hte right side of the box.
+ *
+ *             btBottom - Row number of the bottom side of the box.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_draw_box(BYTE btLeft, BYTE btTop, BYTE btRight,
+   BYTE btBottom)
+{
+   /* Number of current line being drawn. */
+   BYTE btLine;
+
+   /* X size of window. */
+   BYTE btBetweenSize = (btRight - btLeft) - 1;
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_draw_box()");
+
+   /* Ensure that OpenDoors has been initialized */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Setup od_box_chars appropriately */
+   if(od_control.od_box_chars[BOX_BOTTOM] == 0)
+   {
+      od_control.od_box_chars[BOX_BOTTOM] = od_control.od_box_chars[BOX_TOP];
+   }
+   if(od_control.od_box_chars[BOX_RIGHT] == 0)
+   {
+      od_control.od_box_chars[BOX_RIGHT] = od_control.od_box_chars[BOX_LEFT];
+   }
+
+   /* Check that required display capabilities are supported. */
+   if(!(od_control.user_ansi || od_control.user_avatar))
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Check that parameters are within valid range. */
+   if(btLeft<1 || btTop<1 || btRight>80 || btBottom>25)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Move to top corner, if needed. */
+   od_set_cursor(btTop, btLeft);
+
+   /* Display left corner character. */
+   od_putch(od_control.od_box_chars[BOX_UPPERLEFT]);
+
+   /* Display top line. */
+   od_repeat(od_control.od_box_chars[BOX_TOP], btBetweenSize);
+
+   /* Display right corner character. */
+   od_putch(od_control.od_box_chars[BOX_UPPERRIGHT]);
+
+   /* If AVATAR display mode is available. */
+   if(od_control.user_avatar)                    
+   {
+      /* Display first left vertical line. */
+      od_set_cursor(btTop + 1, btLeft);
+      od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+      /* Fill in the center of the window. */
+      od_emulate(22);
+      od_emulate(12);
+      od_emulate((BYTE)od_control.od_cur_attrib);
+      od_emulate((BYTE)((btBottom - btTop) - 1));
+      od_emulate(btBetweenSize);
+
+      /* Display first right vertical line. */
+      od_set_cursor(btTop + 1, btRight);
+      od_putch(od_control.od_box_chars[BOX_RIGHT]);
+
+      /* Display remaining vertical lines. */
+      for(btLine = btTop + 2; btLine < btBottom; ++btLine)
+      {
+         /* Move to the start of the line. */
+         od_set_cursor(btLine, btLeft);
+
+         /* Display left line character. */
+         od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+         /* Move to line start. */
+         od_set_cursor(btLine, btRight);
+
+         /* Display right line character. */
+         od_putch(od_control.od_box_chars[BOX_RIGHT]);
+      }
+   }
+
+   /* If AVATAR mode is not available. */
+   else
+   {
+      /* Loop through middle lines of window. */
+      for(btLine = btTop + 1; btLine < btBottom; ++btLine)
+      {
+         /* Move to the start of the line. */
+         od_set_cursor(btLine,btLeft);
+
+         /* Display left line character. */
+         od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+         /* Display the blank area. */
+         od_repeat(' ', btBetweenSize);
+
+         /* Display the right line character. */
+         od_putch(od_control.od_box_chars[BOX_RIGHT]);
+      }
+   }
+
+   /* Move to bottom corner. */
+   od_set_cursor(btBottom, btLeft);
+
+   /* Display left corner character. */
+   od_putch(od_control.od_box_chars[BOX_LOWERLEFT]);
+
+   /* Display bottom line. */
+   od_repeat(od_control.od_box_chars[BOX_BOTTOM], btBetweenSize);
+
+   /* Display right corner character. */
+   od_putch(od_control.od_box_chars[BOX_LOWERRIGHT]);
+
+   /* Return with success. */
+   OD_API_EXIT();
+   return(TRUE);
+}

+ 1239 - 0
odoors/ODEdStr.c

@@ -0,0 +1,1239 @@
+/* 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: ODEdStr.c
+ *
+ * Description: Implementation of od_edit_str(). This is the advanced line
+ *              editing function which requires ANSI or AVATAR graphics.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Dec 31, 1994  6.00  BP   Use ODTimerSleep() instead of loop.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 23, 1995  6.00  BP   Added EDIT_FLAG_SHOW_SIZE.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 04, 1996  6.00  BP   Use od_get_input().
+ *              Jan 12, 1996  6.00  BP   Claim exclusive use of arrow keys.
+ *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
+ *              Feb 10, 1996  6.00  BP   Fixed ...SHOW_SIZE /w ...PERMALITERAL.
+ *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Apr 08, 1996  6.10  BP   Make 'C' use word capitalization.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <ctype.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODKrnl.h"
+#include "ODStat.h"
+
+
+/* Current od_edit_str() state and settings. */
+static INT anCurrentFormatOffset[80];
+static BOOL abCurrentFormatLiteral[80];
+static char szCurrentOriginalString[81];
+static char *pszCurrentInput;
+static char *pszCurrentFormat;
+static unsigned char nCurrentStringLength;
+static char chCurrentBlank;
+
+
+/* Private helper functions used by od_edit_str(). */
+static BOOL ODEditIsCharValidForPos(char chEntered, INT nPosition);
+static char ODEditAsCharForPos(char chEntered, INT nPosition);
+static void ODEditDisplayPermaliteral(WORD nFlags);
+
+
+/* ----------------------------------------------------------------------------
+ * od_edit_str()
+ *
+ * Provides more advanced editing capabilities than od_get_str(), requiring
+ * ANSI, AVATAR or RIP modes.
+ *
+ * Parameters: pszInput          - Pointer to string where inputted text is
+ *                                 stored.
+ *
+ *             pszFormat         - Pointer to format string, which specifies
+ *                                 the format of inputted text.
+ *
+ *             nRow              - The row number where the input field should
+ *                                 begin.
+ *
+ *             nColumn           - The column number where the input field
+ *                                 should begin.
+ *
+ *             btNormalColour    - Color of normal text.
+ *
+ *             btHighlightColour - Color of highlighted text.
+ *
+ *             chBlank           - Character to display blanks with.
+ *
+ *             nFlags            - Specifies one or more flags, combined with
+ *                                 the bitwise-or operator.
+ *
+ *     Return: One of a number of possible EDIT_RETURN_ values, which indicate
+ *             why the function returned.
+ */
+ODAPIDEF WORD ODCALL od_edit_str(char *pszInput, char *pszFormat, INT nRow,
+   INT nColumn, BYTE btNormalColour, BYTE btHighlightColour,
+   char chBlank, WORD nFlags)
+{
+   char chTemp;
+   unsigned int nCount;
+   unsigned char chCurrentValue;
+   char *pchCurrent;
+   unsigned int nCursorPos;
+   INT nKeysPressed = 0;
+   WORD wToReturn;
+   BOOL bInsertMode = TRUE;
+   char chAddAtEnd = '\0';
+   BOOL bNormal = TRUE;
+   tODInputEvent InputEvent;
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_edit_str()");
+
+   /* Verify that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Store pointers to current input string and current format string. */
+   pszCurrentInput=(char *)pszInput;
+   pszCurrentFormat=(char *)pszFormat;
+
+   /* Check that the parameters passed in are valid. */
+   if(pszCurrentInput == NULL || pszCurrentFormat == NULL || nRow < 1
+      || nColumn < 1)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(EDIT_RETURN_ERROR);
+   }
+
+   /* Initially, the maximum length of input string is 0. */
+   nCurrentStringLength = 0;
+
+   /* The type that is being examined. */
+   chCurrentValue = 0;
+
+   /* Counter of position in format string. */
+   nCount = 0;
+
+   /* Loop until we reach the end fo the format string. */
+   for(pchCurrent = pszCurrentFormat; *pchCurrent;)
+   {
+      /* Get next character from format string. */
+      chTemp = *pchCurrent++;
+
+      /* If current character is not a literal value. */
+      if(chCurrentValue == '\0')
+      {
+         /* If format string has " or ' characters, then this is the */
+         /* beginning of a literal string.                           */
+         if(chTemp == 39 || chTemp == 34)
+         {
+            chCurrentValue = chTemp;
+         }
+
+         /* If this is not a literal character, and not a space character... */
+         else if(chTemp != 32)
+         {
+            /* Check that we haven't exceeded the maximum allowable string */
+            /* length.                                                     */
+            if(nCurrentStringLength >= 80)
+            {
+               od_control.od_error = ERR_PARAMETER;
+               OD_API_EXIT();
+               return(EDIT_RETURN_ERROR);
+            }
+
+            /* Record format character's position. */
+            anCurrentFormatOffset[nCurrentStringLength] = nCount;
+
+            /* Record that this character is not a literal. */
+            abCurrentFormatLiteral[nCurrentStringLength] = FALSE;
+
+            /* Increment length of input string. */
+            ++nCurrentStringLength;
+         }
+      }
+
+      /* If this is a literal character. */
+      else
+      {
+         /* Check for end of literal string. */
+         if(chTemp == chCurrentValue)
+         {
+            /* If found, stop literal string processing */
+            chCurrentValue = '\0';
+         }
+         else
+         {
+            /* Check that we haven't exceeded the maximum allowable string */
+            /* length.                                                     */
+            if(nCurrentStringLength >= 80)
+            {
+               od_control.od_error = ERR_PARAMETER;
+               OD_API_EXIT();
+               return(EDIT_RETURN_ERROR);
+            }
+
+            /* Record character's position. */
+            anCurrentFormatOffset[nCurrentStringLength] = nCount;
+
+            /* Record that character IS a literal value. */
+            abCurrentFormatLiteral[nCurrentStringLength] = TRUE;
+
+            /* Increment length of input string. */
+            ++nCurrentStringLength;
+         }
+      }
+
+      /* Increment format string position. */
+      ++nCount;
+   }
+
+   /* Check that there is at least one character permitted in the input */
+   /* string. If not, return with a parameter error.                    */
+   if(nCurrentStringLength==0)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(EDIT_RETURN_ERROR);
+   }
+
+   /* If editing an existing string. */
+   if(nFlags & EDIT_FLAG_EDIT_STRING)
+   {
+      /* Check for valid existing input string. */
+      if(strlen(pszCurrentInput) > nCurrentStringLength)
+      {
+         pszCurrentInput[nCurrentStringLength] = '\0';
+      }
+
+      /* Start with cursor at the end of the string. */
+      nCursorPos = strlen(pszCurrentInput);
+   }
+
+   /* If we are not editing an existing string. */
+   else
+   {
+      /* Blank-out current string contents. */
+      pszCurrentInput[0] = '\0';
+
+      /* Set cursor to beginning of string. */
+      nCursorPos = 0;
+   }
+
+   /* Store original string, in case user cancels. */
+   strcpy(szCurrentOriginalString,pszCurrentInput);
+
+   /* Set appropriate text color. */
+   od_set_attrib(btHighlightColour);
+
+   /* Determine appropriate blank character */
+   chCurrentBlank = (nFlags & EDIT_FLAG_PASSWORD_MODE) ? ' ' : chBlank;
+
+   /* Turn off insert mode if the strict input or permaliteral flags were */
+   /* specified.                                                          */
+   if((nFlags & EDIT_FLAG_STRICT_INPUT) || (nFlags & EDIT_FLAG_PERMALITERAL))
+   {
+      bInsertMode = FALSE;
+   }
+
+   /* If the no-initial-redraw flag is not set, then do initial redraw. */
+   if(!(nFlags & EDIT_FLAG_NO_REDRAW))
+   {
+      /* Set to redraw position. */
+      od_set_cursor(nRow, nColumn);
+
+      if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+      {
+         /* If we are in password mode, then just draw password blanks. */
+         od_repeat(chBlank, (BYTE)strlen(pszCurrentInput));
+      }
+      else
+      {
+         /* Otherwise, display the actual string. */
+         od_disp_str(pszCurrentInput);
+      }
+
+      if(nFlags & EDIT_FLAG_PERMALITERAL)
+      {
+         /* If we are in permaliteral mode, then fill the remaining edit */
+         /* field with the literal characters.                           */
+         ODEditDisplayPermaliteral(nFlags);
+      }
+      else
+      {
+         /* Otherwise, fill the remaining edit field with the blank */
+         /* character.                                              */
+         BYTE btRemaining
+            = (BYTE)(nCurrentStringLength - strlen(pszCurrentInput));
+         if(!(nFlags & EDIT_FLAG_SHOW_SIZE)) ++btRemaining;
+         od_repeat(chCurrentBlank, btRemaining);
+      }
+   }
+
+   /* Claim exclusive use of arrow keys. */
+   ODStatStartArrowUse();
+
+   /* Set the cursor to appropriate position. */
+   od_set_cursor(nRow, nColumn + nCursorPos);
+
+   /* Normally, we start the input loop at the keep_going tag. */
+   if(bNormal) goto keep_going;
+
+   for(;;)
+   {
+      /* If auto-accept mode has been specified ... */
+      if(nFlags & EDIT_FLAG_AUTO_ENTER)
+      {
+         /* ... then check whether we have reached the end of the string. */
+         if(strlen(pszCurrentInput) == nCurrentStringLength)
+         {
+            /* Indicate that input has been accepted, rather than cancelled. */
+            wToReturn = EDIT_RETURN_ACCEPT;
+
+            /* Return the current string to the caller, if it is valid. */
+            goto try_to_accept;
+         }
+      }
+
+keep_going:
+      /* Check whether we have reached a literal character in permaliteral */
+      /* mode. If so, we will move past the permanent literal characters   */
+      /* automatically.                                                    */
+      if((nFlags & EDIT_FLAG_PERMALITERAL)
+         && (nCursorPos < nCurrentStringLength))
+      {
+         if(abCurrentFormatLiteral[nCursorPos])
+         {
+            if(nCursorPos < strlen(pszCurrentInput))
+            {
+               goto pressed_right_arrow;
+            }
+            chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
+            ++nKeysPressed;
+            goto try_this_character;
+         }
+      }
+
+get_another_key:
+      /* Block, waiting for the next key pressed by the user. */
+
+      od_get_input(&InputEvent, OD_NO_TIMEOUT, GETIN_NORMAL);
+      
+      /* Increment total number of keystrokes. */
+      ++nKeysPressed;
+
+      if(InputEvent.EventType == EVENT_EXTENDED_KEY)
+      {
+         switch(InputEvent.chKeyPress)
+         {
+            case OD_KEY_UP:
+            case OD_KEY_SHIFTTAB:
+               if(nFlags & EDIT_FLAG_FIELD_MODE)
+               {
+                  wToReturn = EDIT_RETURN_PREVIOUS;
+                  goto try_to_accept;
+               }
+               break;
+
+            case OD_KEY_DOWN:
+pressed_down_arrow:
+               if(nFlags & EDIT_FLAG_FIELD_MODE)
+               {
+                  wToReturn = EDIT_RETURN_NEXT;
+                  goto try_to_accept;
+               }
+               break;
+
+            case OD_KEY_RIGHT:
+pressed_right_arrow:
+               /* If we are not at the end of the string. */
+               if(nCursorPos < strlen(pszCurrentInput))
+               {
+                  /* Move input position right. */
+                  nCursorPos++;
+
+                  /* Move the cursor on screen. */
+                  od_set_cursor(nRow, nColumn + nCursorPos);
+               }
+               if(chAddAtEnd)
+               {
+                  chAddAtEnd = 0;
+                  goto add_another_key;
+               }
+               break;
+
+            case OD_KEY_LEFT:
+pressed_left_arrow:
+               /* If we are not at the beginning of the string. */
+               if(nCursorPos > 0)
+               {
+                  /* Move input position left. */
+                  nCursorPos--;
+
+                  /* Move cursor on screen. */
+                  od_set_cursor(nRow, nColumn + nCursorPos);
+               }
+
+               /* If we are moving past a permanent literal character, */
+               /* then continue moving further left, if possible.      */
+               if((nFlags & EDIT_FLAG_PERMALITERAL)
+                  && abCurrentFormatLiteral[nCursorPos] && nCursorPos > 0)
+               {
+                  goto pressed_left_arrow;
+               }
+               break;
+
+         case OD_KEY_HOME:
+            /* If we are not at the beginning of the string. */
+            if(nCursorPos != 0)
+            {
+               /* Move input position to the beginning of the string. */
+               nCursorPos = 0;
+
+               /* Move the cursor on the screen. */
+               od_set_cursor(nRow, nColumn);
+            }
+            break;
+
+         case OD_KEY_END:
+            /* If we are not at the end of the string .*/
+            if(nCursorPos != strlen(pszCurrentInput))
+            {
+               /* Set the input position to the end of the string. */
+               nCursorPos=strlen(pszCurrentInput);
+
+               /* Move cursor on screen. */
+               od_set_cursor(nRow,nColumn+nCursorPos);
+            }
+            break;
+
+         case OD_KEY_DELETE:
+pressed_delete:
+            /* Check whether delete key is permitted at this time. */
+            if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
+               && nCursorPos < strlen(pszCurrentInput)
+               && !(nFlags & EDIT_FLAG_PERMALITERAL))
+            {
+               /* Move remaining line, if any, to the left */
+               chCurrentValue = strlen(pszCurrentInput) - 1;
+               for(nCount = nCursorPos; nCount < chCurrentValue; ++nCount)
+               {
+                  od_putch(
+                     pszCurrentInput[nCount] = pszCurrentInput[nCount + 1]);
+               }
+
+               /* Erase the last character. */
+               pszCurrentInput[chCurrentValue] = '\0';  
+
+               /* Blank out last character. */
+               od_putch(chCurrentBlank);
+
+               /* Move the cursor on the screen. */
+               od_set_cursor(nRow, nColumn + nCursorPos);
+
+               /* Update changes to string. */
+               goto check_cursor_char;
+            }
+            break;
+
+         case OD_KEY_INSERT:
+            if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
+               && !(nFlags & EDIT_FLAG_PERMALITERAL))
+            {
+               /* Toggle insert setting. */
+               bInsertMode = !bInsertMode;
+            }
+            break;
+
+         }
+      }
+      else if(InputEvent.EventType == EVENT_CHARACTER)
+      {
+         chTemp = InputEvent.chKeyPress;
+try_this_character:
+
+         if(chTemp == 27)
+         {
+            /* If cancel key is allowed ... */
+            if(nFlags & EDIT_FLAG_ALLOW_CANCEL)
+            {
+               /* Reset the input string to the original contents. */
+               strcpy(pszCurrentInput, szCurrentOriginalString);
+
+               /* Indicate that return reason was due to user cancelling. */
+               wToReturn = EDIT_RETURN_CANCEL;
+
+               /* Return after redrawing the original string in the input */
+               /* field.                                                  */
+               goto exit_and_redraw;
+            }
+         }
+
+
+         /* If user pressed [Enter] or [Ctrl]-[Z]. */
+         else if(chTemp == 13 || chTemp == 26)
+         {
+            /* User has accepted input. */
+            wToReturn = EDIT_RETURN_ACCEPT;
+
+            /* Return if input string is valid. */
+            goto try_to_accept;
+         }
+
+         /* If the backspace key has been pressed. */
+         else if(chTemp == 8)
+         {
+   backspace_again:
+            /* If we are not already at the beginning of the string. */
+            if(nCursorPos > 0)
+            {
+               if(nFlags & EDIT_FLAG_PERMALITERAL)
+               {
+                  for(nCount = 0;nCount < nCursorPos; ++nCount)
+                  {
+                     if(!abCurrentFormatLiteral[nCount]) goto continue_deletion;
+                  }
+                  goto get_another_key;
+               }
+
+   continue_deletion:
+               /* If we are at the end of the string. */
+               if(nCursorPos == strlen(pszCurrentInput))
+               {
+                  /* Erase last char in string. */
+                  pszCurrentInput[--nCursorPos] = '\0';
+
+                  if((nFlags & EDIT_FLAG_PERMALITERAL)
+                     && abCurrentFormatLiteral[nCursorPos])
+                  {
+                     goto backspace_again;
+                  }
+                  else
+                  {
+                     /* Move to new cursor pos. */
+                     od_set_cursor(nRow,nColumn+nCursorPos);
+
+                     /* Blank old character. */
+                     od_putch(chCurrentBlank);
+
+                     /* Move again to cursor pos. */
+                     od_set_cursor(nRow,nColumn+nCursorPos);
+                  }
+               }
+
+               /* If we are in the middle of the string and we are not in */
+               /* string input mode.                                      */
+               else if(!(nFlags & EDIT_FLAG_STRICT_INPUT)
+                  && !(nFlags & EDIT_FLAG_PERMALITERAL))
+               {
+                  /* Move cursor left. */
+                  --nCursorPos;
+
+                  /* Move cursor on screen. */
+                  od_set_cursor(nRow, nColumn + nCursorPos);
+
+                  /* Goto standard delete handler. */
+                  goto pressed_delete;
+               }
+            }
+         }
+
+         /* If this is a next field request. */
+         else if(chTemp == 9)
+         {
+            /* Goto down arrow handler. */
+            goto pressed_down_arrow;
+         }
+
+         /* If Control-Y. */
+         else if(chTemp == 25)
+         {
+            /* Erase entire contents of line. */
+            goto kill_whole_line;
+         }
+
+         else
+         {
+            /* If this is the first key pressed, and we are in autodelete mode. */
+            if(nKeysPressed == 1 && (nFlags & EDIT_FLAG_AUTO_DELETE))
+            {
+   kill_whole_line:
+               /* If string is not empty. */
+               if(strlen(pszCurrentInput) != 0)
+               {
+                  /* Move to beginning of string. */
+                  od_set_cursor(nRow,nColumn);
+
+                  /* Blank out the entire string contents. */
+                  od_repeat(chCurrentBlank, (BYTE)strlen(pszCurrentInput));
+               }
+
+               /* Move to new cursor position. */
+               od_set_cursor(nRow,nColumn);
+
+               /* Update insert position. */
+               nCursorPos = 0;
+
+               /* Blank out the current string contents. */
+               pszCurrentInput[0] = '\0';
+            }
+
+   add_another_key:
+            if(!ODEditIsCharValidForPos(chTemp,nCursorPos))
+            {
+               /* If character is not a valid input char. */
+               if(abCurrentFormatLiteral[nCursorPos])
+               {
+                  if(nCursorPos < strlen(pszCurrentInput))
+                  {
+                     if(pszCurrentInput[nCursorPos] == 
+                        pszCurrentFormat[anCurrentFormatOffset[nCursorPos]])
+                     {
+                        chAddAtEnd = chTemp;
+                        goto pressed_right_arrow;
+                     }
+                  }
+                  chAddAtEnd = chTemp;
+                  chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
+               }
+               else
+               {
+                  continue;
+               }
+            }
+
+            /* Convert character to correct value, if applicable. */
+            chTemp = ODEditAsCharForPos(chTemp, nCursorPos);
+
+            /* If we are at end of string. */
+            if(nCursorPos >= strlen(pszCurrentInput))
+            {
+               /* Reset original cursor position */
+               nCursorPos = strlen(pszCurrentInput);
+
+               /* If there is room to add a char. */
+               if(nCursorPos < nCurrentStringLength)
+               {
+                  /* If password mode */
+                  if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+                  {
+                     /* Display the password character. */
+                     od_putch(chBlank);
+                  }
+                  /* If not in password mode. */
+                  else
+                  {
+                     /* Display the character. */
+                     od_putch(chTemp);
+                  }
+
+                  /* Store the character. */
+                  pszCurrentInput[nCursorPos] = chTemp;
+
+                  /* Add a new string terminator. */
+                  pszCurrentInput[++nCursorPos] = '\0';
+               }
+            }
+
+            /* If in insert mode, but not at end of string. */
+            else if(bInsertMode)
+            {
+               /* If room in string. */
+               if(strlen(pszCurrentInput) < nCurrentStringLength)
+               {
+                  /* If in password mode. */
+                  if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+                  {
+                     /* Move to end. */
+                     od_set_cursor(nRow,nColumn+strlen(pszCurrentInput));
+
+                     /* Add another password character. */
+                     od_putch(chBlank);
+                  }
+
+                  /* If not in password mode. */
+                  else
+                  {
+                     /* Display the new character. */
+                     od_putch(chTemp);
+
+                     /* Loop through rest of string. */
+                     for(nCount = nCursorPos; nCount < strlen(pszCurrentInput);
+                        ++nCount)
+                     {
+                        /* Display the next remaining character. */
+                        od_putch(pszCurrentInput[nCount]);
+                     }
+                  }
+
+                  pszCurrentInput[(strlen(pszCurrentInput) + 1)] = '\0';
+
+                  /* Sift remaining characters forward. */
+                  for(nCount = strlen(pszCurrentInput); nCount > nCursorPos;
+                     --nCount)
+                  {                     
+                     pszCurrentInput[nCount] = pszCurrentInput[nCount-1];
+                  }
+
+                  /* Add new char in space. */
+                  pszCurrentInput[nCursorPos++] = chTemp;
+
+                  /* Move to new cursor position. */
+                  od_set_cursor(nRow, nColumn + nCursorPos);
+               }
+               else
+               {
+                  goto get_another_key;
+               }
+            }
+
+            /* If we are in overwrite mode, but not at end of string. */
+            else
+            {
+               /* If password mode. */
+               if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+               {
+                  /* Display the password character. */
+                  od_putch(chBlank);
+               }
+               /* If not in password mode. */
+               else
+               {
+                  /* Display the character. */
+                  od_putch(chTemp);
+               }
+
+               /* Add character to string. */
+               pszCurrentInput[nCursorPos++] = chTemp;
+            }
+
+            /* If not at end of possible string. */
+            if(nCursorPos < nCurrentStringLength)
+            {
+               /* If the next character is literal constant. */
+               if(abCurrentFormatLiteral[nCursorPos])
+               {
+                  chTemp = pszCurrentFormat[anCurrentFormatOffset[nCursorPos]];
+                  goto add_another_key;
+               }
+            }
+
+            if(chAddAtEnd)
+            {
+               chTemp = chAddAtEnd;
+               chAddAtEnd = 0;
+               goto add_another_key;
+            }
+
+
+   check_cursor_char:
+            /* If there is a character under cursor. */
+            if(nCursorPos < strlen(pszCurrentInput))
+            {
+               /* If character corresponds to the format string. */
+               if(ODEditIsCharValidForPos(pszCurrentInput[nCursorPos],
+                  nCursorPos))
+               {
+                  /* Determine correct character for this position. */
+                  chTemp = ODEditAsCharForPos(pszCurrentInput[nCursorPos],
+                     nCursorPos);
+
+                  /* If actual character is not correct. */
+                  if(chTemp != pszCurrentInput[nCursorPos])
+                  {                        
+                     /* Change character to correct value. */
+                     pszCurrentInput[nCursorPos] = chTemp;
+
+                     /* If password mode. */
+                     if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+                     {
+                        /* Display the password character. */
+                        od_putch(chBlank);
+                     }
+
+                     /* If not in password mode. */
+                     else
+                     {
+                        /* Display the character. */
+                        od_putch(chTemp);
+                     }
+
+                     /* Reset cursor position. */
+                     od_set_cursor(nRow, nColumn + nCursorPos);
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   /* Accept string if it is valid. */
+try_to_accept:
+   /* If string must be filled. */
+   if(nFlags & EDIT_FLAG_FILL_STRING)
+   {
+      /* If string is not filled, don't return. */
+      if(strlen(pszCurrentInput) != nCurrentStringLength) goto keep_going;
+   }
+
+   /* Loop through string .... */
+   for(nCount = 0; nCount < strlen(pszCurrentInput); ++nCount)
+   {
+      /* ... testing each character for validity. */
+      if(!ODEditIsCharValidForPos(pszCurrentInput[nCount], nCount))
+         goto keep_going;
+   }
+
+   /* Initially, assume that the string has not been changed. */
+   chCurrentValue = FALSE;
+
+   /* Loop through the string. */
+   for(nCount = 0; nCount < strlen(pszCurrentInput); ++nCount)
+   {
+      /* Find correct value for each character. */
+      chTemp = ODEditAsCharForPos(pszCurrentInput[nCount], nCount);
+
+      /* If character is not correct. */
+      if(chTemp != pszCurrentInput[nCount])           
+      {
+         /* Change char to correct value */
+         pszCurrentInput[nCount] = chTemp;
+
+         /* Remember that string has been changed. */
+         chCurrentValue = TRUE;
+      }
+   }
+
+   /* If permaliteral mode. */
+   if(nFlags & EDIT_FLAG_LEAVE_BLANK)
+   {
+      /* Count # of literal characters. */
+      nCount = 0;
+      while(nCount<strlen(pszCurrentInput))
+      {
+         if(abCurrentFormatLiteral[nCount])
+         {
+            ++nCount;
+         }
+         else
+         {
+            break;
+         }
+      }
+
+      /* If only literals in string. */
+      if(strlen(pszCurrentInput) == nCount && nCount > 0)
+      {
+         /* Then they shouldn't be here. */
+         pszCurrentInput[0] = '\0';
+         goto exit_and_redraw;
+      }
+   }
+
+   /* Always redraw if string was changed. */
+   if(chCurrentValue) goto exit_and_redraw;
+
+   /* If no-redraw flag not set. */
+   if(!(nFlags & EDIT_FLAG_NO_REDRAW))
+   {
+exit_and_redraw:
+      /* Set appropriate text colour. */
+      od_set_attrib(btNormalColour);
+
+      /* Set to redraw position. */
+      od_set_cursor(nRow,nColumn);
+
+      /* If password mode. */
+      if(nFlags & EDIT_FLAG_PASSWORD_MODE)
+      {
+         /* Display blanked-out string. */
+         od_repeat(chBlank, (BYTE)strlen(pszCurrentInput));
+      }
+      else
+      {
+         /* Display actual string. */
+         od_disp_str(pszCurrentInput);
+      }
+
+      /* If we should keep the background. */
+      if(nFlags & EDIT_FLAG_KEEP_BLANK)
+      {
+         /* Then redraw background. */
+         if(nFlags & EDIT_FLAG_PERMALITERAL)
+         {
+            ODEditDisplayPermaliteral(nFlags);
+         }
+         else
+         {
+            od_repeat(chCurrentBlank,
+               (BYTE)(nCurrentStringLength - strlen(pszCurrentInput) + 1));
+         }
+      }
+      /* If we should erase the background ... */
+      else
+      {
+         /* ... then do it. */
+         od_repeat(' ',
+            (BYTE)(nCurrentStringLength - strlen(pszCurrentInput) + 1));
+      }
+   }
+
+   /* Release exclusive use of arrow keys. */
+   ODStatEndArrowUse();
+
+   /* Return with appropriate return value. */
+   OD_API_EXIT();
+   return(wToReturn);
+}
+
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditIsCharValidForPos()                           *** PRIVATE FUNCTION ***
+ *
+ * Determines whether or not the entered character can be accepted as a valid
+ * character (after any possible conversion by ODEditAsCharForPos() is applied)
+ * for the specified position in the string.
+ *
+ * Parameters: chEntered   - The character entered by the user.
+ *
+ *             nPosition   - The position in the string where this character
+ *                           would be inserted.
+ *
+ *     Return: TRUE if this character should be accepted, FALSE if not.
+ */
+static BOOL ODEditIsCharValidForPos(char chEntered, INT nPosition)
+{
+   /* If this character is a literal. */
+   if(abCurrentFormatLiteral[nPosition])
+   {                                     
+      /* Check required literal character. */
+      if(chEntered != pszCurrentFormat[anCurrentFormatOffset[nPosition]])
+      {                                          
+         /* If this is not the correct literal character, then do not */
+         /* permit it to be entered in this position. */
+         return(FALSE);
+      }
+      return(TRUE);
+   }
+
+   /* If this position has a corresponding format control character, */
+   /* then check that control character. The execution path will     */
+   /* continue out of this switch statement (rather than returning   */
+   /* to the calling function) if and only if the entered character  */
+   /* is valid for the format character specified.                   */
+   switch(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
+   {
+      /* Only numerical characters are to be permitted. */
+      case '#':
+         if(chEntered < '0' || chEntered > '9') return(FALSE);
+         break;
+
+      /* Only numerical and space characters are to be permitted. */
+      case '%':
+         if((chEntered < '0' || chEntered > '9') && chEntered != ' ')
+         {
+            return(FALSE);
+         }
+         break;
+
+      /* Only floating point number characters are to be permitted. */
+      case '9':
+         if(chEntered >= '0' && chEntered <= '9') break;
+         if(chEntered == '.' || chEntered == '+' || chEntered == '-') break;
+         return(FALSE);
+
+      /* Only "printable" characters are to be permitted. */
+      case '*':
+         if(chEntered < 32) return(FALSE);
+         break;
+
+      /* City name characters are to be permitted. */
+      case 'C':
+      case 'c':
+         if(chEntered >= 'A' && chEntered <= 'Z') break;
+         if(chEntered >= 'a' && chEntered <= 'z') break;
+         if(chEntered == ' ' || chEntered == ',' || chEntered == '.') break;
+         if(chEntered == '*' || chEntered == '?') break;
+         return(FALSE);
+
+      /* If only alphabetic characters are to be permitted. */
+      case 'A':
+      case 'a':
+      case 'L':
+      case 'l':
+      case 'M':
+      case 'm':
+      case 'U':
+      case 'u':
+         if(chEntered>='A' && chEntered<='Z') break;
+         if(chEntered>='a' && chEntered<='z') break;
+         if(chEntered==' ') break;
+         return(FALSE);
+
+      /* If only date characters are to be permitted. */
+      case 'D':
+      case 'd':
+         if(chEntered>='0' && chEntered<='9') break;
+         if(chEntered=='-' || chEntered=='/') break;
+         return(FALSE);
+
+      /* If only MS-DOS filename characters are to be permitted. */
+      case 'F':
+      case 'f':
+         if(chEntered >= 'A' && chEntered <= 'Z') break;
+         if(chEntered >= '0' && chEntered <= '9') break;
+         if(chEntered >= 'a' && chEntered <= 'z') break;
+         switch(chEntered)
+         {
+            /* Filename separators. */
+            case ':':
+            case '.':
+            case DIRSEP:
+
+            /* Wildcard characters. */
+            case '?':
+            case '*':
+
+            /* Other valid symbols in filenames */
+            case '#':
+            case '$':
+            case '&':
+            case '\'':
+            case '(':
+            case '>':
+            case '-':
+            case '@':
+            case '_':
+            case '!':
+            case '{':
+            case '}':
+            case '~':
+               return(TRUE);
+         }
+
+         return(FALSE);
+
+      /* If only hexidecimal characters are to be permitted. */
+      case 'H':
+      case 'h':
+         if(chEntered>='0' && chEntered<='9') break;
+         if(chEntered>='A' && chEntered<='F') break;
+         if(chEntered>='a' && chEntered<='f') break;
+         return(FALSE);
+
+      /* If only telephone number characters are to be permitted. */
+      case 'T':                                          
+      case 't':
+         if(chEntered >= '0' && chEntered <= '9') break;
+         if(chEntered == '-' || chEntered == '(' || chEntered == ')'
+            || chEntered == ' ' || chEntered == '+')
+         {
+            break;
+         }
+         return(FALSE);
+
+      /* If filenames with wildcards are to be permitted. */
+      case 'W':
+      case 'w':
+         if(chEntered >= 'A' && chEntered <= 'Z') break;
+         if(chEntered >= 'a' && chEntered <= 'z') break;
+         if(chEntered == ':' || chEntered == '.' || chEntered == DIRSEP
+            || chEntered == '*' || chEntered == '?')
+         {
+            break;
+         }
+         return(FALSE);
+
+      /* If alpha-numeric characters are to be permitted. */
+      case 'X':
+      case 'x':
+         if(chEntered >= 'A' && chEntered <= 'Z') break;
+         if(chEntered >= 'a' && chEntered <= 'z') break;
+         if(chEntered >= '0' && chEntered <= '9') break;
+         if(chEntered == ' ') break;
+         return(FALSE);
+
+      /* If this is a Yes/No field. */
+      case 'Y':
+      case 'y':
+         if(chEntered == 'y' || chEntered == 'n' || chEntered == 'Y'
+            || chEntered == 'N')
+         {
+            break;
+         }
+         return(FALSE);
+   }
+
+   /* If execution gets to this point, then the character has been approved. */
+   return(TRUE);
+}
+
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditAsCharForPos()                                *** PRIVATE FUNCTION ***
+ *
+ * Converts the character entered by the user to a valid character for this
+ * position in the string. For example, for fields that are set to all
+ * upper case, this function converts the entered characte to its upper case
+ * equivalent.
+ *
+ * Parameters: chEntered   - Character that was entered by the user.
+ *
+ *             nPosition   - Position in the string where the character is to
+ *                           be entered.
+ *
+ *     Return: The actual character to add to the input string at this
+ *             position.
+ */
+static char ODEditAsCharForPos(char chEntered, INT nPosition)
+{
+   /* If this character is a literal. */
+   if(abCurrentFormatLiteral[nPosition])
+   {                                      
+      /* Return the only valid char for this position. */
+      return(pszCurrentFormat[anCurrentFormatOffset[nPosition]]);
+   }
+
+   /* If this position has a corresponding format control character, */
+   /* then check that control character. */
+   switch(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
+   {
+      /* If Yes/No characters are required. */
+      case 'Y':
+      case 'y':
+         return(toupper(chEntered));
+
+      /* If filename characters are required. */
+      case 'F':                                       
+      case 'f':
+         return(toupper(chEntered));
+
+      /* If lower case characters are required. */
+      case 'L':
+      case 'l':
+         return(tolower(chEntered));
+
+      /* If upper case characters are required. */
+      case 'U':
+      case 'u':
+         return(toupper(chEntered));
+
+      /* If automatic capitalization is required. */
+      case 'M':
+      case 'm':
+      case 'C':
+      case 'c':
+         /* First character is always upper case. */
+         if(nPosition == 0) return(toupper(chEntered));
+
+         /* Check for other base cases. */
+         if(abCurrentFormatLiteral[nPosition-1]) return(toupper(chEntered));
+         if(toupper(pszCurrentFormat[anCurrentFormatOffset[nPosition]]) != 'M'
+            && toupper(pszCurrentFormat[anCurrentFormatOffset[nPosition]])
+            != 'C')
+         {
+            return(toupper(chEntered));
+         }
+
+         /* If previous character is a word delimiter, then this character */
+         /* should be uppper case.                                         */
+         if(pszCurrentInput[nPosition-1] == ' '
+            || pszCurrentInput[nPosition-1] == '.'
+            || pszCurrentInput[nPosition-1] == ','
+            || pszCurrentInput[nPosition-1] == '-')
+         {
+            return(toupper(chEntered));                                             /* Otherwise, this should be lower */
+         }
+
+         /* Otherwise, this character should be lower-case. */
+         return(tolower(chEntered));
+   }
+
+   return(chEntered);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditDisplayPermaliteral()                         *** PRIVATE FUNCTION ***
+ *
+ * Displays permaliterals (characters specified in the format string that
+ * should be returned in the input string, but which the user may never
+ * change).
+ *
+ * Parameters: nFlags - Flags parameter that was passed into od_edit_str().
+ *
+ *     Return: void
+ */
+static void ODEditDisplayPermaliteral(WORD nFlags)
+{
+   INT nCount;
+   BYTE btRepeat = 0;
+
+   for(nCount = strlen(pszCurrentInput); nCount <= nCurrentStringLength;
+      ++nCount)
+   {
+      if(nCount != nCurrentStringLength)
+      {
+         if(abCurrentFormatLiteral[nCount])
+         {
+            if(btRepeat > 0)
+            {
+               od_repeat(chCurrentBlank, btRepeat);
+               btRepeat = 0;
+            }
+            od_putch(pszCurrentFormat[anCurrentFormatOffset[nCount]]);
+         }
+         else
+         {
+            ++btRepeat;
+         }
+      }
+      else
+      {
+         if(!(nFlags & EDIT_FLAG_SHOW_SIZE))
+         {
+            ++btRepeat;
+         }
+      }
+   }
+
+   if(btRepeat > 0) od_repeat(chCurrentBlank, btRepeat);
+}

+ 2650 - 0
odoors/ODEdit.c

@@ -0,0 +1,2650 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODEdit.c
+ *
+ * Description: Implementation of the OpenDoors multi-line editor, which
+ *              allows the user to edit strings which may span many lines.
+ *              Provides standard text editor features, such as word wrap.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Dec 07, 1995  6.00  BP   Created.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 04, 1996  6.00  BP   Use od_get_input().
+ *              Jan 12, 1996  6.00  BP   Claim exclusive use of arrow keys.
+ *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
+ *              Feb 08, 1996  6.00  BP   Finished implementation details.
+ *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
+ *              Feb 16, 1996  6.00  BP   New trans. size estimation heuristics.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 24, 1996  6.00  BP   Fixed garbage on [Enter] after w-wrap.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 13, 1996  6.10  BP   Restore cursor position after menu.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Jun 08, 1996  6.10  BP   Added cast in call to alloc function.
+ *              Oct 19, 2001  6.20  RS   Eliminated MSVC 6.0 warning.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODTypes.h"
+#include "ODGen.h"
+#include "ODCore.h"
+#include "ODKrnl.h"
+#include "ODStat.h"
+#include "ODCom.h"
+#include "ODScrn.h"
+
+
+/* ========================================================================= */
+/* Misc. definitions.                                                        */
+/* ========================================================================= */
+
+/* Macros used by this module. */
+#define IS_EOL_CHAR(ch)       ((ch) == '\n' || (ch) == '\r' || (ch) == '\0')
+
+/* Configurable constants. */
+#define LINE_ARRAY_GROW_SIZE     20
+#define BUFFER_GROW_SIZE         4096
+#define ANSI_SCROLL_DISTANCE     7
+#define PRE_DRAIN_TIME           10000
+#define MAX_TAB_STOP_SIZE        8
+#define DEFAULT_TAB_STOP_SIZE    8
+#define DEFAULT_LINE_BREAK       "\n"
+
+/* Other manifest constants. */
+#define REDRAW_NO_BOUNDARY       0xffff
+
+/* Default editor options. */
+static tODEditOptions ODEditOptionsDefault =
+{
+   1, 1, 80, 23,
+   FORMAT_PARAGRAPH_BREAKS,
+   NULL,
+   NULL,
+   EFLAG_NORMAL,
+};
+
+
+
+/* ========================================================================= */
+/* Multiline editor instance structure.                                      */
+/* ========================================================================= */
+
+typedef struct
+{
+   char *pszEditBuffer;
+   UINT unBufferSize;
+   tODEditOptions *pUserOptions;
+   UINT unCurrentLine;
+   UINT unCurrentColumn;
+   UINT unLineScrolledToTop;
+   UINT unAreaWidth;
+   UINT unAreaHeight;
+   char **papchStartOfLine;
+   UINT unLineArraySize;
+   UINT unLinesInBuffer;
+   BOOL bInsertMode;
+   UINT unTabStopSize;
+   UINT unScrollDistance;
+   char *pszLineBreak;
+   char *pszParagraphBreak;
+   BOOL bWordWrapLongLines;
+   void *pRememberBuffer;
+} tEditInstance;
+
+
+
+/* ========================================================================= */
+/* Editor function prototypes.                                               */
+/* ========================================================================= */
+
+/* High level implementation. */
+static BOOL ODEditSetupInstance(tEditInstance *pEditInstance,
+   char *pszBufferToEdit, UINT unBufferSize, tODEditOptions *pUserOptions);
+static void ODEditRedrawArea(tEditInstance *pEditInstance);
+static void ODEditDrawAreaLine(tEditInstance *pEditInstance,
+   UINT unAreaLineToDraw);
+static INT ODEditMainLoop(tEditInstance *pEditInstance);
+static void ODEditGotoPreviousLine(tEditInstance *pEditInstance);
+static void ODEditGotoNextLine(tEditInstance *pEditInstance);
+static BOOL ODEditScrollArea(tEditInstance *pEditInstance, INT nDistance);
+static BOOL ODEditRecommendFullRedraw(tEditInstance *pEditInstance,
+   UINT unEstPartialRedrawBytes, BOOL bDefault);
+static UINT ODEditEstDrawBytes(tEditInstance *pEditInstance,
+   UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine,
+   UINT unFinishRedrawColumn);
+static UINT ODEditGetCurrentLineInArea(tEditInstance *pEditInstance);
+static void ODEditUpdateCursorPos(tEditInstance *pEditInstance);
+static void ODEditUpdateCursorIfMoved(tEditInstance *pEditInstance);
+static tODResult ODEditEnterText(tEditInstance *pEditInstance,
+   char *pszEntered, BOOL bInsertMode);
+static void ODEditSetBreakSequence(tEditInstance *pEditInstance,
+   char chFirstEOLChar, char chSecondEOLChar);
+static BOOL ODEditCursorLeft(tEditInstance *pEditInstance);
+static void ODEditDeleteCurrentChar(tEditInstance *pEditInstance);
+static void ODEditDeleteCurrentLine(tEditInstance *pEditInstance);
+static BOOL ODEditPastEndOfCurLine(tEditInstance *pEditInstance);
+static size_t ODEditRememberBufferSize(tEditInstance *pEditInstance);
+static void ODEditRememberArea(tEditInstance *pEditInstance,
+   void *pRememberedArea);
+static void ODEditRedrawChanged(tEditInstance *pEditInstance,
+   void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary);
+static BOOL ODEditDetermineChanged(tEditInstance *pEditInstance,
+   void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary, 
+   UINT *punStartRedrawLine, UINT *punStartRedrawColumn,
+   UINT *punFinishRedrawLine, UINT *punFinishRedrawColumn);
+static void ODEditRedrawSubArea(tEditInstance *pEditInstance,
+   UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine,
+   UINT unFinishRedrawColumn);
+static void ODEditGetActualCurPos(tEditInstance *pEditInstance,
+   UINT *punRow, UINT *punColumn);
+static BOOL ODEditIsEOLForMode(tEditInstance *pEditInstance, char chToTest);
+
+/* Low level buffer manipulation functions. */
+static BOOL ODEditBufferFormatAndIndex(tEditInstance *pEditInstance);
+static UINT ODEditBufferGetLineLength(tEditInstance *pEditInstance,
+   UINT unBufferLine);
+static UINT ODEditBufferGetTotalLines(tEditInstance *pEditInstance);
+static char *ODEditBufferGetCharacter(tEditInstance *pEditInstance,
+   UINT unBufferLine, UINT unBufferColumn);
+static tODResult ODEditBufferMakeSpace(tEditInstance *pEditInstance,
+   UINT unLine, UINT unColumn, UINT unNumChars);
+static tODResult ODEditTryToGrow(tEditInstance *pEditInstance,
+   UINT unSizeNeeded);
+
+
+
+/* ========================================================================= */
+/* High level editor implementation.                                         */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * od_multiline_edit()
+ *
+ * Multiline editor function, allows the user to enter or change text that
+ * spans multiple lines.
+ *
+ * Parameters: pszBufferToEdit  - Pointer to '\0'-terminated buffer of text
+ *                                to edit.
+ *
+ *             unBufferSize     - Size of the buffer, in characters.
+ *
+ *             pEditOptions     - Pointer to a tODEditOptions structure, or
+ *                                NULL to use default settings.
+ *
+ *     Return: An od_multiline_edit()-specific result code.
+ */
+ODAPIDEF INT ODCALL od_multiline_edit(char *pszBufferToEdit, UINT unBufferSize,
+   tODEditOptions *pEditOptions)
+{
+   tEditInstance EditInstance;
+   INT nToReturn;
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_node_open()");
+
+   /* Initialize OpenDoors if not already done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Validate parameters. */
+   if(pszBufferToEdit == NULL || unBufferSize == 0)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(OD_MULTIEDIT_ERROR);
+   }
+
+   /* Check the user's terminal supports the required capabilities. */
+   if(!(od_control.user_ansi || od_control.user_avatar))
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(OD_MULTIEDIT_ERROR);
+   }
+
+   /* Initialize editor instance information structure. */
+   if(!ODEditSetupInstance(&EditInstance, pszBufferToEdit, unBufferSize,
+      pEditOptions))
+   {
+      OD_API_EXIT();
+      return(OD_MULTIEDIT_ERROR);
+   }
+
+   /* Attempt to build the buffer line index and ensure that the buffer */
+   /* conforms to the format specified by the client application.       */
+   if(!ODEditBufferFormatAndIndex(&EditInstance))
+   {
+      od_control.od_error = ERR_MEMORY;
+      OD_API_EXIT();
+      return(OD_MULTIEDIT_ERROR);
+   }
+
+   /* Claim exclusive use of arrow keys. */
+   ODStatStartArrowUse();
+
+   /* Ensure that all information in the outbound communications buffer  */
+   /* has been sent before starting. This way, we can safely purge the   */
+   /* outbound buffer at any time without loosing anything that was sent */
+   /* before od_multiline_edit() was called.                             */
+   ODWaitDrain(PRE_DRAIN_TIME);
+
+   /* Draw the initial edit area. */
+   ODEditRedrawArea(&EditInstance);
+
+   /* Run the main editor loop. */
+   nToReturn = ODEditMainLoop(&EditInstance);
+
+   /* Release exclusive use of arrow keys. */
+   ODStatEndArrowUse();
+
+   /* Set final information which will be available in the user options */
+   /* structure for the client application to access.                   */
+   EditInstance.pUserOptions->pszFinalBuffer = EditInstance.pszEditBuffer;
+   EditInstance.pUserOptions->unFinalBufferSize = unBufferSize;
+
+   OD_API_EXIT();
+   return(nToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditSetupInstance()                               *** PRIVATE FUNCTION ***
+ *
+ * Initializes editor instance information structure.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *             pszBufferToEdit - Buffer pointer provided by client.
+ *
+ *             unBufferSize    - Initial buffer size, as specified by the
+ *                               client.
+ *
+ *             pUserOptions    - Editor options specified by the client.
+ *
+ *     Return: TRUE on success, FALSE on failure. In the case of failure,
+ *             od_control.od_error is set appropriately.
+ */
+static BOOL ODEditSetupInstance(tEditInstance *pEditInstance,
+   char *pszBufferToEdit, UINT unBufferSize, tODEditOptions *pUserOptions)
+{
+   ASSERT(pEditInstance != NULL);
+   ASSERT(pszBufferToEdit != NULL);
+
+   /* Setup editor instance structure. */
+   pEditInstance->pszEditBuffer = pszBufferToEdit;
+   pEditInstance->unBufferSize = unBufferSize;
+   if(pUserOptions == NULL)
+   {
+      /* Edit options is just the defaults. */
+      pEditInstance->pUserOptions = &ODEditOptionsDefault;
+   }
+   else
+   {
+      /* Edit options are supplied by the user. */
+      pEditInstance->pUserOptions = pUserOptions;
+
+      /* Initialize any edit options that the user did not setup. */
+      /* Check that edit area has been initialized. */
+      if(pUserOptions->nAreaLeft == 0)
+      {
+         pUserOptions->nAreaLeft = ODEditOptionsDefault.nAreaLeft;
+      }
+      if(pUserOptions->nAreaRight == 0)
+      {
+         pUserOptions->nAreaRight = ODEditOptionsDefault.nAreaRight;
+      }
+        if(pUserOptions->nAreaTop == 0)
+      {
+         pUserOptions->nAreaTop = ODEditOptionsDefault.nAreaTop;
+      }
+      if(pUserOptions->nAreaBottom == 0)
+      {
+         pUserOptions->nAreaBottom = ODEditOptionsDefault.nAreaBottom;
+      }
+   }
+   pEditInstance->unCurrentLine = 0;
+   pEditInstance->unCurrentColumn = 0;
+   pEditInstance->unLineScrolledToTop = 0;
+   pEditInstance->papchStartOfLine = NULL;
+   pEditInstance->unLineArraySize = 0;
+   pEditInstance->unLinesInBuffer = 0;
+   pEditInstance->unAreaWidth = (UINT)pEditInstance->pUserOptions->
+      nAreaRight - (UINT)pEditInstance->pUserOptions->nAreaLeft + 1;
+   pEditInstance->unAreaHeight = (UINT)pEditInstance->pUserOptions->
+      nAreaBottom - (UINT)pEditInstance->pUserOptions->nAreaTop + 1;
+   pEditInstance->bInsertMode = TRUE;
+   pEditInstance->unTabStopSize = DEFAULT_TAB_STOP_SIZE;
+
+   /* Setup line break and paragraph break sequences, if they can be  */
+   /* determined at this point. If they can't be determined, set them */
+   /* to NULL.                                                        */
+   switch(pEditInstance->pUserOptions->TextFormat)
+   {
+      case FORMAT_FTSC_MESSAGE:
+         /* FTSC compliant messages use \r as a paragraph break, and do */
+         /* not have any line break characters.                         */
+         pEditInstance->pszLineBreak = "";
+         pEditInstance->pszParagraphBreak = "\r";
+         break;
+
+      case FORMAT_PARAGRAPH_BREAKS:
+         /* Paragraph break mode only inserts CR/LF sequences at the end  */
+         /* of a paragrah, and word-wraps the text that forms a paragrah. */
+         pEditInstance->pszLineBreak = "";
+         pEditInstance->pszParagraphBreak = NULL;
+         break;
+
+      case FORMAT_LINE_BREAKS:
+      case FORMAT_NO_WORDWRAP:
+         /* Line break mode and no word wrap mode both terminate every    */
+         /* line of the file with a CR/LF sequence, and have no paragrah  */
+         /* terminator. In line break mode, word wrap is enabled, whereas */
+         /* it is not in FORMAT_NO_WORDWRAP mode.                         */
+         pEditInstance->pszLineBreak = NULL;
+         pEditInstance->pszParagraphBreak = "";
+         break;
+
+      default:
+         /* An invalid text format was specified. */
+         od_control.od_error = ERR_PARAMETER;
+         return(FALSE);
+   }
+
+   /* Determine whether long lines sould be word wrapped or character */
+   /* wrapped.                                                        */
+   pEditInstance->bWordWrapLongLines = (pEditInstance->pUserOptions->TextFormat
+      != FORMAT_NO_WORDWRAP);
+
+   /* Attempt to allocate abuffer for remembered data. */
+   pEditInstance->pRememberBuffer =
+      malloc(ODEditRememberBufferSize(pEditInstance));
+   
+   if(pEditInstance->pRememberBuffer == NULL)
+   {
+      od_control.od_error = ERR_MEMORY;
+      return(FALSE);
+   }
+
+   /* If AVATAR mode or local mode is active, then scroll up or down one */
+   /* line at a time.                                                    */
+   if(od_control.user_avatar || od_control.baud == 0)
+   {
+      pEditInstance->unScrollDistance = 1;
+   }
+   /* In ANSI mode with a remote connection, scroll multiple lines at a */
+   /* time. This is the minimum of the default scroll distance, and the */
+   /* current height of the edit area - 1.                              */
+   else
+   {
+      pEditInstance->unScrollDistance = MIN(ANSI_SCROLL_DISTANCE,
+         pEditInstance->pUserOptions->nAreaBottom
+         - pEditInstance->pUserOptions->nAreaTop);
+   }
+
+   /* Return with success. */
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRedrawArea()                                  *** PRIVATE FUNCTION ***
+ *
+ * Redraws the area of the screen used by the editor.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditRedrawArea(tEditInstance *pEditInstance)
+{
+   UINT unAreaLine;
+
+   ASSERT(pEditInstance != NULL);
+
+   ODScrnEnableCaret(FALSE);
+
+   /* First, remove anything that is still in the outbound communications */
+   /* buffer, since whatever it was, it will no longer be visible after   */
+   /* the screen redraw anyhow.                                           */
+   if(od_control.baud != 0) ODComClearOutbound(hSerialPort);
+
+   /* Loop, drawing every line in the edit area. */
+   for(unAreaLine = 0; unAreaLine < pEditInstance->unAreaHeight; ++unAreaLine)
+   {
+      ODEditDrawAreaLine(pEditInstance, unAreaLine);
+   }
+
+   ODScrnEnableCaret(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditDrawAreaLine()                                *** PRIVATE FUNCTION ***
+ *
+ * Redraws the specified line in the area of the screen used by the editor.
+ *
+ * Parameters: pEditInstance    - Editor instance information structure.
+ *
+ *             unAreaLineToDraw - 0-based line number in the edit area to be
+ *                                redrawn.
+ *
+ *     Return: void
+ */
+static void ODEditDrawAreaLine(tEditInstance *pEditInstance,
+   UINT unAreaLineToDraw)
+{
+   UINT unBufferLine;
+   UINT unLineLength;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(unAreaLineToDraw >= 0);
+   ASSERT(unAreaLineToDraw < pEditInstance->unAreaHeight);
+
+   /* Determine the buffer line that is displayed on this screen line. */   
+   unBufferLine = unAreaLineToDraw + pEditInstance->unLineScrolledToTop;
+
+   /* Position the cursor to the beginning of this line. */
+   od_set_cursor((UINT)pEditInstance->pUserOptions->nAreaTop
+      + unAreaLineToDraw, (UINT)pEditInstance->pUserOptions->nAreaLeft);
+
+   /* If this line is not beyond the end of the buffer. */
+   if(unBufferLine < pEditInstance->unLinesInBuffer)
+   {
+      /* Determine the length of this buffer line. */
+      unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine);
+
+      od_disp(ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0),
+         unLineLength, TRUE);
+   }
+   else
+   {
+      unLineLength = 0;
+   }
+
+   /* If right edge of edit area aligns with the right edge of the screen. */
+   if(pEditInstance->pUserOptions->nAreaRight == OD_SCREEN_WIDTH)
+   {
+      /* Clear the remainder of this line on the screen. */
+      od_clr_line();
+   }
+   else
+   {
+      /* Place spaces after the end of the current line, up to right edge of */
+      /* the edit area.                                                      */
+      od_repeat(' ', (BYTE)(pEditInstance->unAreaWidth - unLineLength));
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditMainLoop()                                    *** PRIVATE FUNCTION ***
+ *
+ * Implements the main editor loop, which repeatedly waits for input from the
+ * user, until the user chooses to exit.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: Value to be returned by od_multiline_edit.
+ */
+static INT ODEditMainLoop(tEditInstance *pEditInstance)
+{
+   tODInputEvent InputEvent;
+
+   ASSERT(pEditInstance != NULL);
+
+   /* Set initial cursor position. */
+   ODEditUpdateCursorPos(pEditInstance);
+
+   /* Loop, obtaining keystrokes until the user chooses to exit. */
+   for(;;)
+   {
+      od_get_input(&InputEvent, OD_NO_TIMEOUT, GETIN_NORMAL);
+      if(InputEvent.EventType == EVENT_EXTENDED_KEY)
+      {
+         switch(InputEvent.chKeyPress)
+         {
+            case OD_KEY_UP:
+               /* If we aren't at the start of the file, then move to the */
+               /* previous line.                                          */
+               if(pEditInstance->unCurrentLine > 0)
+               {
+                  ODEditGotoPreviousLine(pEditInstance);
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               break;
+
+            case OD_KEY_DOWN:
+               /* If we aren't at the end of the file, then move to the */
+               /* next line.                                            */
+               if(pEditInstance->unCurrentLine
+                  < ODEditBufferGetTotalLines(pEditInstance) - 1)
+               {
+                  ODEditGotoNextLine(pEditInstance);
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               break;
+
+            case OD_KEY_LEFT:
+               /* Attempt to move the cursor left. */
+               if(ODEditCursorLeft(pEditInstance))
+               {
+                  /* If it was possible to move the cursor, then update the */
+                  /* cursor position on the screen.                         */
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               break;
+
+            case OD_KEY_RIGHT:
+               /* In word wrap mode, we allow the cursor to move up to the */
+               /* end of this line, and then wrap around to the next line. */
+               if(pEditInstance->bWordWrapLongLines)
+               {
+                  if(pEditInstance->unCurrentColumn < ODEditBufferGetLineLength
+                     (pEditInstance, pEditInstance->unCurrentLine))
+                  {
+                     ++pEditInstance->unCurrentColumn;
+                     ODEditUpdateCursorPos(pEditInstance);
+                  }
+                  else if(pEditInstance->unCurrentLine
+                     < ODEditBufferGetTotalLines(pEditInstance) - 1)
+                  {
+                     ODEditGotoNextLine(pEditInstance);
+                     pEditInstance->unCurrentColumn = 0;
+                     ODEditUpdateCursorPos(pEditInstance);
+                  }
+               }
+
+               /* In character wrap mode, we allow the cursor to move up to */
+               /* the right edge of the edit area.                          */
+               else
+               {
+                  if(pEditInstance->unCurrentColumn
+                     < pEditInstance->unAreaWidth - 1)
+                  {
+                     ++pEditInstance->unCurrentColumn;
+                     ODEditUpdateCursorPos(pEditInstance);
+                  }
+               }
+               break;
+
+            case OD_KEY_HOME:
+               pEditInstance->unCurrentColumn = 0;
+               ODEditUpdateCursorPos(pEditInstance);
+               break;
+
+            case OD_KEY_END:
+               pEditInstance->unCurrentColumn = ODEditBufferGetLineLength(
+                  pEditInstance, pEditInstance->unCurrentLine);
+               ODEditUpdateCursorPos(pEditInstance);
+               break;
+
+            case OD_KEY_PGUP:
+               if(pEditInstance->unLineScrolledToTop > 0)
+               {
+                  UINT unDistance = MIN(pEditInstance->unAreaHeight - 1,
+                     pEditInstance->unLineScrolledToTop);
+                  ODEditScrollArea(pEditInstance, -((INT)unDistance));
+                  pEditInstance->unCurrentLine -= unDistance;
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               else if(pEditInstance->unCurrentLine != 0)
+               {
+                  pEditInstance->unCurrentLine = 0;
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               break;
+
+            case OD_KEY_PGDN:
+               if(pEditInstance->unLineScrolledToTop <
+                  pEditInstance->unLinesInBuffer - 1)
+               {
+                  UINT unDistance = MIN(pEditInstance->unAreaHeight - 1,
+                     pEditInstance->unLinesInBuffer
+                     - pEditInstance->unLineScrolledToTop - 1);
+                  ODEditScrollArea(pEditInstance, (INT)unDistance);
+                  pEditInstance->unCurrentLine = MIN(
+                     pEditInstance->unCurrentLine + unDistance,
+                     pEditInstance->unLinesInBuffer - 1);
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+            break;
+
+            case OD_KEY_INSERT:
+               /* If the insert key is pressed, then toggle insert mode. */
+               pEditInstance->bInsertMode = !pEditInstance->bInsertMode;
+               break;
+
+            case OD_KEY_DELETE:
+               /* Delete the character at the current position. */
+               
+               /* If we are currently past the end of this line. */
+               if(ODEditPastEndOfCurLine(pEditInstance))
+               {
+                  /* Add spaces to this line to fill it up to the current */
+                  /* cursor position.                                     */
+                  switch(ODEditBufferMakeSpace(pEditInstance,
+                     pEditInstance->unCurrentLine,
+                     pEditInstance->unCurrentColumn, 0))
+                  {
+                     case kODRCUnrecoverableFailure:
+                        /* If we encountered an unrecoverable failure, then */
+                        /* exit from the editor with a memory allocation    */
+                        /* error.                                           */
+                        od_control.od_error = ERR_MEMORY;
+                        return(OD_MULTIEDIT_ERROR);
+
+                     case kODRCSuccess:
+                        /* On success, delete the current character. */
+                        ODEditDeleteCurrentChar(pEditInstance);
+                        break;
+
+                     default:
+                        /* On any other failure, just beep and continue. */
+                        od_putch('\a');
+                  }
+               }
+               else
+               {
+                  /* If we aren't pas the end of the line, then just do a */
+                  /* simple delete character operation.                   */
+                  ODEditDeleteCurrentChar(pEditInstance);
+               }
+               break;
+         }
+      }
+      else if(InputEvent.EventType == EVENT_CHARACTER)
+      {
+         if(InputEvent.chKeyPress == 25)
+         {
+            /* Control-Y (delete line) has been pressed. */
+            ODEditDeleteCurrentLine(pEditInstance);
+         }
+         else if(InputEvent.chKeyPress == 26
+            || InputEvent.chKeyPress == 27)
+         {
+            /* Escape or control-Z has been pressed. */
+
+            /* If a menu callback function has been provided by */
+            /* the client, then call it.                        */
+            if(pEditInstance->pUserOptions->pfMenuCallback != NULL)
+            {
+               /* Call the menu callback function. */
+               switch((*pEditInstance->pUserOptions->pfMenuCallback)(NULL))
+               {
+                  case EDIT_MENU_EXIT_EDITOR:
+                     return(OD_MULTIEDIT_SUCCESS);
+
+                  case EDIT_MENU_DO_NOTHING:
+                     /* Continue in the editor without doing anything. */
+                     break;
+
+                  default:
+                     ASSERT(FALSE);
+               }
+
+               /* If we are continuing, then restore initial cursor pos. */
+               ODEditUpdateCursorPos(pEditInstance);
+            }
+            else
+            {
+               /* If a menu key callback function has not been provided, */
+               /* then we exit the editor unconditionally.               */
+               return(OD_MULTIEDIT_SUCCESS);
+            }
+         }
+         else if(InputEvent.chKeyPress == '\b')
+         {
+            /* Backspace key has been pressed. */
+
+            /* If the cursor is past the end of the line, then we just move */
+            /* the cursor left, without deleting any characters. */
+            BOOL bDelete = !ODEditPastEndOfCurLine(pEditInstance);
+
+            /* Backup the cursor one space. */
+            if(ODEditCursorLeft(pEditInstance))
+            {
+               /* If there was space to move the cursor back to, then       */
+               /* proceed and remove the character at the current position. */
+               if(bDelete)
+               {
+                  ODEditDeleteCurrentChar(pEditInstance);
+               }
+               else
+               {
+                  /* In this case, we must still show the new cursor */
+                  /* position.                                       */
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+            }
+         }
+         else if(InputEvent.chKeyPress == '\t')
+         {
+            char szTextToAdd[MAX_TAB_STOP_SIZE + 1];
+            UINT unTargetColumn;
+            UINT unTargetDistance;
+
+            /* A tab key has been entered. */
+
+            /* Determine the column that this will move the cursor to. */
+            ASSERT(pEditInstance->unTabStopSize <= MAX_TAB_STOP_SIZE);
+            unTargetColumn = ((pEditInstance->unCurrentColumn / pEditInstance->
+               unTabStopSize) + 1) * pEditInstance->unTabStopSize;
+
+            /* In insert mode, then insert spaces into the buffer. */
+            if(pEditInstance->bInsertMode)
+            {
+               /* Determine the number of columns that we need to advance in */
+               /* order to reach this target column.                         */
+               unTargetDistance = unTargetColumn -
+                  pEditInstance->unCurrentColumn;
+               ASSERT(unTargetDistance <= MAX_TAB_STOP_SIZE);
+
+               /* Translate this to a string with the appropriate number of */
+               /* spaces.                                                   */
+               memset(szTextToAdd, ' ', unTargetDistance);
+               szTextToAdd[unTargetDistance] = '\0';
+
+               /* Add this to the buffer. */
+               if(ODEditEnterText(pEditInstance, szTextToAdd, TRUE) ==
+                  kODRCUnrecoverableFailure)
+               {
+                  od_control.od_error = ERR_MEMORY;
+                  return(OD_MULTIEDIT_ERROR);
+               }
+            }
+
+            /* In overwrite mode, then just advance the cursor position. */
+            else
+            {
+               /* Determine the column where the cursor should be wrapped. */
+               UINT unWrapColumn = pEditInstance->bWordWrapLongLines ?
+                  ODEditBufferGetLineLength(pEditInstance,
+                  pEditInstance->unCurrentLine)
+                  : pEditInstance->unAreaWidth;
+
+               if(unTargetColumn < unWrapColumn)
+               {
+                  pEditInstance->unCurrentColumn = unTargetColumn;
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+               else if(pEditInstance->unCurrentLine
+                  < ODEditBufferGetTotalLines(pEditInstance) - 1)
+               {
+                  ODEditGotoNextLine(pEditInstance);
+                  pEditInstance->unCurrentColumn = 0;
+                  ODEditUpdateCursorPos(pEditInstance);
+               }
+            }
+         }
+         else if(InputEvent.chKeyPress == '\r')
+         {
+            char *pszTextToAdd;
+
+            /* The enter key has been pressed. In insert mode, or at the end */
+            /* of the buffer in overwrite mode, this adds a new line.        */
+            if(pEditInstance->bInsertMode || pEditInstance->unCurrentLine
+                  >= ODEditBufferGetTotalLines(pEditInstance) - 1)
+            {
+               if(!pEditInstance->bInsertMode)
+               {
+                  /* If we are not in insert mode, begin by positioning the */
+                  /* cursor at the end of this line.                        */
+                  pEditInstance->unCurrentColumn = ODEditBufferGetLineLength(
+                     pEditInstance, pEditInstance->unCurrentLine);
+               }
+
+               /* Determine the line/paragraph break sequence to use. */
+               if(pEditInstance->pszLineBreak != NULL
+                  && strlen(pEditInstance->pszLineBreak) > 0)
+               {
+                  pszTextToAdd = pEditInstance->pszLineBreak;
+               }
+               else if(pEditInstance->pszParagraphBreak != NULL
+                  && strlen(pEditInstance->pszParagraphBreak) > 0)
+               {
+                  pszTextToAdd = pEditInstance->pszParagraphBreak;
+               }
+               else
+               {
+                  pszTextToAdd = DEFAULT_LINE_BREAK;
+               }
+
+               /* Insert the sequence into the buffer. */
+               if(ODEditEnterText(pEditInstance, pszTextToAdd, TRUE) ==
+                  kODRCUnrecoverableFailure)
+               {
+                  od_control.od_error = ERR_MEMORY;
+                  return(OD_MULTIEDIT_ERROR);
+               }
+            }
+            else
+            {
+               /* Pressing the enter key in overwrite mode just moves the   */
+               /* cursor to the beginning of the next line. In other words, */
+               /* it is equivalent to pressing Down arrow followed by home. */
+               ODEditGotoNextLine(pEditInstance);
+               pEditInstance->unCurrentColumn = 0;
+               ODEditUpdateCursorPos(pEditInstance);
+            }
+         }
+         else if(InputEvent.chKeyPress >= 32)
+         {
+            char szTextToAdd[2];
+            szTextToAdd[0] = InputEvent.chKeyPress;
+            szTextToAdd[1] = '\0';
+
+            /* A valid buffer character has been entered. */
+            if(ODEditEnterText(pEditInstance, szTextToAdd,
+               (BOOL)(pEditInstance->bInsertMode
+               || ODEditPastEndOfCurLine(pEditInstance)))
+               == kODRCUnrecoverableFailure)
+            {
+               od_control.od_error = ERR_MEMORY;
+               return(OD_MULTIEDIT_ERROR);
+            }
+         }
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditGotoPreviousLine()                            *** PRIVATE FUNCTION ***
+ *
+ * Moves the current cursor position to the previous line, scrolling the screen
+ * if necessary to keep the cursor visible.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditGotoPreviousLine(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   /* If we are already at the first line, then return without doing */
+   /* anything.                                                      */
+   if(pEditInstance->unCurrentLine == 0) return;
+
+   /* If cursor is at top of edit area, then scroll area */
+   /* first.                                             */
+   if(ODEditGetCurrentLineInArea(pEditInstance) == 0)
+   {
+      ODEditScrollArea(pEditInstance,
+         -(INT)(MIN(pEditInstance->unScrollDistance,
+         pEditInstance->unCurrentLine)));
+   }
+
+   /* Move cursor to previous line. */
+   --pEditInstance->unCurrentLine;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditGotoNextLine()                                *** PRIVATE FUNCTION ***
+ *
+ * Advances the current cursor position to the next line, scrolling the screen
+ * if necessary to keep the cursor visible.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditGotoNextLine(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   /* If we are already at the end of the file, then return without */
+   /* doing anything.                                               */
+   if(pEditInstance->unCurrentLine
+      >= ODEditBufferGetTotalLines(pEditInstance) - 1)
+   {
+      return;
+   }
+
+   /* If cursor is at the bottom of the edit area, then scroll area first. */
+   if(ODEditGetCurrentLineInArea(pEditInstance)
+      == pEditInstance->unAreaHeight - 1)
+   {
+      ODEditScrollArea(pEditInstance,
+         (INT)MIN(pEditInstance->unScrollDistance,
+         ODEditBufferGetTotalLines(pEditInstance)
+         - pEditInstance->unCurrentLine));
+   }
+
+   /* Move cursor to next line. */
+   ++pEditInstance->unCurrentLine;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditScrollArea()                                  *** PRIVATE FUNCTION ***
+ *
+ * Scrolls the edit area up or down the specified distance, redrawing newly
+ * "exposed" lines.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *             nDistance     - Number of lines to scroll, where a positive
+ *                             value moves the text upwards, and a negative
+ *                             value moves the text down.
+ *
+ *     Return: FALSE if a full redraw has been performed, TRUE if an efficient
+ *             scroll command has been used.
+ */
+static BOOL ODEditScrollArea(tEditInstance *pEditInstance, INT nDistance)
+{
+   BOOL bUseScrollCommand = FALSE;
+   UINT unAreaLine;
+   UINT unBufferLine;
+   UINT unFirstAreaLineToDraw;
+   UINT unLastAreaLineToDraw;
+   UINT unPositiveDistance;
+
+   ASSERT(pEditInstance);
+
+   /* If scroll distance is zero, then we don't need to do anything at all. */
+   if(nDistance == 0)
+   {
+      return(TRUE);
+   }
+   /* Otherwise, obtain the absolute value of the distance as an unsigned */
+   /* integer.                                                            */
+   else if(nDistance < 0)
+   {
+      unPositiveDistance = (UINT)-nDistance;
+   }
+   else
+   {
+      unPositiveDistance = (UINT)nDistance;
+   }
+
+   /* In AVATAR mode, if more than one of the currently visible lines will */
+   /* still be visible after scrolling, then we will consider using the    */
+   /* scroll operation.                                                    */
+   if(od_control.user_avatar
+      && ((INT)pEditInstance->unAreaHeight) - ((INT)unPositiveDistance) > 1)
+   {
+      /* Even under this situation, we only want to use the scroll operation */
+      /* if the amount of data still in the outbound buffer + our estimate   */
+      /* of the amount of data that will be sent to perform the scroll       */
+      /* operation is less than our estimate of the amount of data that      */
+      /* would be sent by a complete screen redraw.                          */
+      UINT unEstimatedScrollData = ((pEditInstance->unAreaWidth + 4) *
+         unPositiveDistance) + 7;
+
+      if(!ODEditRecommendFullRedraw(pEditInstance, unEstimatedScrollData,
+         TRUE))
+      {
+         bUseScrollCommand = TRUE;
+      }
+   }
+
+   /* In local mode, we can also use the scroll command for efficiency. */
+   if(od_control.baud == 0)
+   {
+      bUseScrollCommand = TRUE;
+   }
+
+   /* Area scroll is achieved by one of two means. We either use the scroll */
+   /* command, and then draw just the newly visible lines, or we redraw the */
+   /* entire edit area, after removing any data from the outbound           */
+   /* communications buffer.                                                */
+
+   if(bUseScrollCommand)
+   {
+      /* Use the od_scroll() function to scroll the screen contents. */
+      od_scroll(pEditInstance->pUserOptions->nAreaLeft,
+         pEditInstance->pUserOptions->nAreaTop,
+         pEditInstance->pUserOptions->nAreaRight,
+         pEditInstance->pUserOptions->nAreaBottom,
+         nDistance, SCROLL_NO_CLEAR);
+
+      /* Fill the newly visible lines. First, the portion of the area that   */
+      /* requires redrawing is determined, and then a loop redraws the lines */
+      /* that must be drawn.                                                 */
+
+      /* If we are moving text upwards, exposing new lines at the bottom of */
+      /* the area:                                                          */
+      if(nDistance > 0)
+      {
+         ASSERT(pEditInstance->unLineScrolledToTop + unPositiveDistance
+            < pEditInstance->unLinesInBuffer);
+         pEditInstance->unLineScrolledToTop += unPositiveDistance;
+         unFirstAreaLineToDraw = pEditInstance->unAreaHeight
+            - (UINT)unPositiveDistance;
+         unLastAreaLineToDraw = pEditInstance->unAreaHeight - 1;
+      }
+      /* Otherwise, we have moved text downwards, exposing new lines at the */
+      /* top of the edit area.                                              */
+      else
+      {
+         ASSERT(pEditInstance->unLineScrolledToTop >= unPositiveDistance);
+         pEditInstance->unLineScrolledToTop -= unPositiveDistance;
+         unFirstAreaLineToDraw = 0;
+         unLastAreaLineToDraw = unPositiveDistance - 1;
+      }
+
+      ODScrnEnableCaret(FALSE);
+
+      /* Now, redraw the new lines. */
+      unBufferLine = unFirstAreaLineToDraw
+         + pEditInstance->unLineScrolledToTop;
+      for(unAreaLine = unFirstAreaLineToDraw; unAreaLine <=
+         unLastAreaLineToDraw; ++unAreaLine, ++unBufferLine)
+      {
+         /* Draw the entire line. */
+         ODEditDrawAreaLine(pEditInstance, unAreaLine);
+      }
+
+      ODScrnEnableCaret(TRUE);
+   }
+
+   /* Just redraw the entire edit area. */
+   else
+   {
+      /* Adjust the line number that is scrolled to the top of the screen. */
+      if(nDistance > 0)
+      {
+         pEditInstance->unLineScrolledToTop += unPositiveDistance;
+      }
+      else
+      {
+         pEditInstance->unLineScrolledToTop -= unPositiveDistance;
+      }
+
+      /* Perform redraw, first purging outbound buffer. */
+      ODEditRedrawArea(pEditInstance);
+   }
+
+   return(bUseScrollCommand);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRecommendFullRedraw()                         *** PRIVATE FUNCTION ***
+ *
+ * Determines whether it would be more efficient to add the specified number
+ * of bytes to the outbound buffer as part of an incremental redraw, or if
+ * it would be more efficient to just purge the outbound buffer and do a
+ * complete redraw of the edit area.
+ *
+ * Parameters: pEditInstance           - Editor instance information structure.
+ *
+ *             unEstPartialRedrawBytes - Estimate of the number of bytes that
+ *                                       would be transmitted if an incremental
+ *                                       redraw is performed.
+ *
+ *             bDefault                - The default action (TRUE for full
+ *                                       redraw, FALSE for incremental) if the
+ *                                       number of bytes in the outbound buffer
+ *                                       cannot be determined.
+ *
+ *     Return: TRUE if a full redraw is recommended, FALSE if an the
+ *             incremental redraw is recommended.
+ */
+static BOOL ODEditRecommendFullRedraw(tEditInstance *pEditInstance,
+   UINT unEstPartialRedrawBytes, BOOL bDefault)
+{
+   int nOutboundBufferBytes;
+   UINT unEstFullRedrawBytes;
+
+   /* In local mode, just return the default action. */
+   if(od_control.baud == 0)
+   {
+      return(bDefault);
+   }
+
+   /* Attempt to obtain the number of bytes in the communications outbound */
+   /* buffer. Unfortunately, this information may not be available. For    */
+   /* example, FOSSIL drivers will only report whether or not there is     */
+   /* still data in the outbound buffer, but not a count of the number of  */
+   /* bytes in the buffer. Under such a situation, ODComOutbound() returns */
+   /* SIZE_NON_ZERO if there is data in the buffer, and 0 if there is no   */
+   /* data in the buffer. This is not a problem under OpenDoor's internal  */
+   /* serial I/O code, nor is it a problem under Win32's communications    */
+   /* facilities.                                                          */
+   ODComOutbound(hSerialPort, &nOutboundBufferBytes);
+
+   if(nOutboundBufferBytes == SIZE_NON_ZERO)
+   {
+      /* We know that there is data in the outbound buffer, but we don't */
+      /* know how much, and so we cannot make a recommendation. Instead, */
+      /* the default course of action will be taken.                     */
+      return(bDefault);
+   }
+
+   /* Estimate the # of bytes required for a full redraw of the edit area. */
+   unEstFullRedrawBytes = ODEditEstDrawBytes(pEditInstance, 0,
+      0, pEditInstance->unAreaHeight - 1, pEditInstance->unAreaWidth);
+
+   /* Recommend a full redraw if the number of bytes for an incremental */
+   /* redraw plus the number of bytes already in the outbound buffer    */
+   /* exceed the number of bytes required for a full redraw.            */
+   if(unEstPartialRedrawBytes + (UINT)nOutboundBufferBytes
+      > unEstFullRedrawBytes)
+   {
+      return(TRUE);
+   }
+   else
+   {
+      return(FALSE);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditEstDrawBytes()                                *** PRIVATE FUNCTION ***
+ *
+ * Estimates the number of bytes which will be transmitted in order to redraw
+ * the specified portion of the edit area.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *             unStartRedrawLine    - Line of first character to draw.
+ *
+ *             unStartRedrawColumn  - Column of first character to draw.
+ *
+ *             unFinishRedrawLine   - Line of last character to draw.
+ *
+ *             unFinishRedrawColumn - Column after last character to draw.
+ *
+ *     Return: A rough estimate of the number of bytes required for the redraw.
+ */
+static UINT ODEditEstDrawBytes(tEditInstance *pEditInstance,
+   UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine,
+   UINT unFinishRedrawColumn)
+{
+   UINT unAreaLine;
+   UINT unBufferLine;
+   UINT unLineLength;
+   UINT unByteTally = 0;
+
+   /* If we are only drawing text on a single line, then estimate is just  */
+   /* the distance between the start and finish redraw column. This number */
+   /* is precise only if the cursor is already at the location where       */
+   /* output is to begin, and the final cursor position is the location    */
+   /* where output finishes. This is in fact the situation when the user   */
+   /* is entering new text in the middle of the line - the most common     */
+   /* situation that will be encountered.                                  */
+   if(unStartRedrawLine == unFinishRedrawLine)
+   {
+      return(unFinishRedrawColumn - unStartRedrawColumn);
+   }
+
+   /* If we are drawing text on multiple lines, then inspect the contents */
+   /* of those lines to estimate the number of bytes to be transmitted.   */
+   for(unAreaLine = unStartRedrawLine,
+      unBufferLine = pEditInstance->unLineScrolledToTop + unStartRedrawLine;
+      unAreaLine <= unFinishRedrawLine;
+      ++unAreaLine, ++unBufferLine)
+   {
+      /* Determine the length of this line. */
+      if(unBufferLine < pEditInstance->unLinesInBuffer)
+      {
+         unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine);
+         if(unAreaLine == unStartRedrawLine)
+         {
+            unLineLength -= unStartRedrawColumn;
+         }
+      }
+      else
+      {
+         unLineLength = 0;
+      }
+
+      /* Add the number of characters on this line, along with the number of */
+      /* bytes required to reposition the cursor and to clear the unused     */
+      /* portion of this line to the tally. This assumes that the edit area  */
+      /* spans the entire screen.                                            */
+      unByteTally += unLineLength + 7;
+   }
+
+   return(unByteTally);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditGetCurrentLineInArea()                        *** PRIVATE FUNCTION ***
+ *
+ * Determines which line of the edit area the cursor is currently located in.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: 0-based index of the distance from the top of the edit area
+ *             (as specified in the user options structure) where the
+ *             cursor is currently located.
+ */
+static UINT ODEditGetCurrentLineInArea(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   return(pEditInstance->unCurrentLine - pEditInstance->unLineScrolledToTop);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditUpdateCursorPos()                             *** PRIVATE FUNCTION ***
+ *
+ * Unconditionally updates the position of the cursor on the screen to 
+ * reflect its actual position. Compare with ODEditUpdateCursorIfMoved().
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditUpdateCursorPos(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   /* Reposition the cursor on the screen. */
+   od_set_cursor(ODEditGetCurrentLineInArea(pEditInstance)
+      + pEditInstance->pUserOptions->nAreaTop,
+      pEditInstance->unCurrentColumn + pEditInstance->pUserOptions->nAreaLeft);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditUpdateCursorIfMoved()                         *** PRIVATE FUNCTION ***
+ *
+ * Updates the position of the cursor on the screen to reflec its actual
+ * position only if we belive it isn't already there. Compare with
+ * ODEditUpdateCursorPos().
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditUpdateCursorIfMoved(tEditInstance *pEditInstance)
+{
+   UINT unActualRow;
+   UINT unActualColumn;
+   ODEditGetActualCurPos(pEditInstance, &unActualRow, &unActualColumn);
+
+   if(!(unActualRow == ODEditGetCurrentLineInArea(pEditInstance)
+         + pEditInstance->pUserOptions->nAreaTop
+      && unActualColumn == pEditInstance->unCurrentColumn
+         + pEditInstance->pUserOptions->nAreaLeft))
+   {
+      ODEditUpdateCursorPos(pEditInstance);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditEnterText()                                   *** PRIVATE FUNCTION ***
+ *
+ * Inserts new text at the current cursor position, updating the cursor
+ * position accordingly.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *             pszEntered    - The character(s) to be inserted.
+ *
+ *     Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to
+ *             obtain enough buffer space before making any changes, or
+ *             kODRCUnrecoverableFailure if the buffer has been changed, but
+ *             there was insufficient memory to re-index the buffer.
+ */
+static tODResult ODEditEnterText(tEditInstance *pEditInstance,
+   char *pszEntered, BOOL bInsertMode)
+{
+   UINT unNumCharsToAdd;
+   char *pch;
+   tODResult Result;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(pszEntered != NULL);
+
+   /* Remember initial edit area contents, to permit selective redraw. */
+   ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer);
+
+   /* Determine the number of characters that are to be added to the buffer. */
+   unNumCharsToAdd = strlen(pszEntered);
+
+   /* Make room in the buffer for the new characters, if needed. */
+   if(bInsertMode)
+   {
+      Result = ODEditBufferMakeSpace(pEditInstance,
+         pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn,
+         unNumCharsToAdd);
+
+      if(Result != kODRCSuccess)
+      {
+         /* Beep on failure. */
+         od_putch('\a');
+         return(Result);
+      }
+   }
+
+   /* Copy the new characters to the buffer. */
+   pch = ODEditBufferGetCharacter(pEditInstance,
+      pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn);
+   memcpy(pch, pszEntered, unNumCharsToAdd);
+
+   /* Move the cursor position to the end of the newly added text. The  */
+   /* cursor position may be temporarily assigned to a position that is */
+   /* past the end of the edit area or past the end of the buffer.      */
+   for(pch = pszEntered; *pch != '\0'; ++pch)
+   {
+      if(IS_EOL_CHAR(*pch))
+      {
+         /* A carriage return character advances the cursor to the */
+         /* leftmost column on the next line.                      */
+         pEditInstance->unCurrentColumn = 0;
+         pEditInstance->unCurrentLine++;
+
+         /* If the next character is a different EOL character, and is */
+         /* not the end of the string, then skip that character.       */
+         if(IS_EOL_CHAR(pch[1]) && pch[1] != '\0' && pch[1] != *pch)
+         {
+            ++pch;
+         }
+      }
+      else
+      {
+         /* All other characters move the cursor ahead one column. */
+         pEditInstance->unCurrentColumn++;
+      }
+   }
+
+   /* Reindex and reformat the buffer based on its new contents. */
+   if(!ODEditBufferFormatAndIndex(pEditInstance))
+   {
+      return(kODRCUnrecoverableFailure);
+   }
+
+   /* Check whether the cursor is now positioned past the end of the edit */
+   /* area, requiring the edit area to be scrolled up.                    */
+   if(ODEditGetCurrentLineInArea(pEditInstance)
+      >= pEditInstance->unAreaHeight)
+   {
+      /* We need to scroll the area accordingly. */
+
+      /* Distance to scroll is maximum of the current single-step scroll */
+      /* distance, and the distance that the cursor is positioned below  */
+      /* the bottom of the current edit area.                            */
+      UINT unScrollDistance = MAX(pEditInstance->unScrollDistance,
+         ODEditGetCurrentLineInArea(pEditInstance) -
+         pEditInstance->unAreaHeight + 1);
+
+      /* Perform actual scroll operation. */
+      if(ODEditScrollArea(pEditInstance, (INT)unScrollDistance))
+      {
+         /* Entire area wasn't redrawn by operation, so some redrawing */
+         /* may still be required, within the area of text that was    */
+         /* visible before the scroll operation.                       */
+         ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer,
+            0, pEditInstance->unAreaHeight - unScrollDistance);
+      }
+   }
+
+   /* Perform necessary redrawing and reposition the cursor if needed. */
+   ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer,
+      REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY);
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditSetBreakSequence()                            *** PRIVATE FUNCTION ***
+ *
+ * If the default line or paragraph break sequence has not yet been set, then
+ * this function sets it based on the break sequence that was encountered in
+ * the buffer supplied by the client application.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *             chFirstEOLChar  - First character in the encountered sequence.
+ *
+ *             chSecondEOLChar - Second character in the encountered sequence.
+ *
+ *     Return: void
+ */
+static void ODEditSetBreakSequence(tEditInstance *pEditInstance,
+   char chFirstEOLChar, char chSecondEOLChar)
+{
+   char *pszSequence;
+
+   ASSERT(pEditInstance != NULL);
+
+   if(pEditInstance->pszParagraphBreak != NULL
+      && pEditInstance->pszLineBreak != NULL)
+   {
+      /* In this situation, both the default line break sequence and default */
+      /* paragraph break sequence have been set, so there is nothing for us  */
+      /* to do.                                                              */
+      return;
+   }
+
+   /* Obtain a pointer to the encountered sequence. We want to use a static */
+   /* string constant for this, so that the string will continue to exist,  */
+   /* in unchanged form.                                                    */
+   if(chFirstEOLChar == '\r' && chSecondEOLChar == '\0')
+   {
+      pszSequence = "\r";
+   }
+   else if(chFirstEOLChar == '\n' && chSecondEOLChar == '\0')
+   {
+      pszSequence = "\n";
+   }
+   else if(chFirstEOLChar == '\n' && chSecondEOLChar == '\r')
+   {
+      pszSequence = "\n\r";
+   }
+   else if(chFirstEOLChar == '\r' && chSecondEOLChar == '\n')
+   {
+      pszSequence = "\r\n";
+   }
+   else
+   {
+      /* This should never happen: an invalid end of line sequence was */
+      /* passed in.                                                    */
+      pszSequence = NULL;
+      ASSERT(FALSE);
+   }
+
+   /* Set the as yet undetermined line/paragraph terminators. */
+   if(pEditInstance->pszParagraphBreak == NULL)
+   {
+      pEditInstance->pszParagraphBreak = pszSequence;
+   }
+
+   if(pEditInstance->pszLineBreak == NULL)
+   {
+      pEditInstance->pszLineBreak = pszSequence;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditCursorLeft()                                  *** PRIVATE FUNCTION ***
+ *
+ * Attempts to move the cursor left. Called when the user presses the left
+ * arrow key, and is also called as part of the process of handling the
+ * backspace key.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *     Return: TRUE on success, or FALSE if the cursor cannot be moved left any
+ *             further.
+ */
+static BOOL ODEditCursorLeft(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   /* In word wrap mode, pressing the left key when the cursor */
+   /* is past the end of the line jumps the cursor to the end  */
+   /* of the line.                                             */
+   if(pEditInstance->bWordWrapLongLines &&
+      pEditInstance->unCurrentColumn > ODEditBufferGetLineLength
+      (pEditInstance, pEditInstance->unCurrentLine))
+   {
+      pEditInstance->unCurrentColumn = ODEditBufferGetLineLength
+         (pEditInstance, pEditInstance->unCurrentLine);
+      return(TRUE);
+   }
+
+   /* If we are not already at the leftmost column. */
+   else if(pEditInstance->unCurrentColumn > 0)
+   {
+      /* Move left one column. */
+      --pEditInstance->unCurrentColumn;
+      return(TRUE);
+   }
+   else if(pEditInstance->bWordWrapLongLines)
+   {
+      /* In word wrap mode, this will move us up to the end of */
+      /* the previous line.                                    */
+      if(pEditInstance->unCurrentLine > 0)
+      {
+         ODEditGotoPreviousLine(pEditInstance);
+         pEditInstance->unCurrentColumn = ODEditBufferGetLineLength(
+            pEditInstance, pEditInstance->unCurrentLine);
+         return(TRUE);
+      }
+   }
+
+   /* It wasn't possible to move the cursor. */
+   return(FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditDeleteCurrentChar()                           *** PRIVATE FUNCTION ***
+ *
+ * Deletes the character at the current cursor position, performing necessary
+ * redraw. Pressing the delete key causes just this function to be called.
+ * Pressing the backspace key causes ODEditCursorLeft() to be called to first
+ * move the cursor left before calling ODEditDeleteCurrentChar().
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditDeleteCurrentChar(tEditInstance *pEditInstance)
+{
+   char *pch;
+
+   ASSERT(pEditInstance != NULL);
+
+   /* Remember initial edit area contents, to permit selective redraw. */
+   ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer);
+
+   /* Backup the entire buffer contents by one character. */
+   pch = ODEditBufferGetCharacter(pEditInstance,
+      pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn);
+   memmove(pch, pch + 1, strlen(pch + 1) + 1);
+
+   /* Reindex and reformat the buffer based on its new contents. */
+   ODEditBufferFormatAndIndex(pEditInstance);
+
+   /* Perform necessary redrawing and reposition the cursor if needed. */
+   ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer,
+      REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditDeleteCurrentLine()                           *** PRIVATE FUNCTION ***
+ *
+ * Removes the entire current line from the buffer.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: void
+ */
+static void ODEditDeleteCurrentLine(tEditInstance *pEditInstance)
+{
+   char *pszStartOfThisLine;
+   char *pszStartOfNextLine;
+
+   ASSERT(pEditInstance != NULL);
+
+   /* Remember initial edit area contents, to permit selective redraw. */
+   ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer);
+
+   /* Determine start of this line. */
+   pszStartOfThisLine = ODEditBufferGetCharacter(pEditInstance,
+      pEditInstance->unCurrentLine, 0);
+
+   if(pEditInstance->unLinesInBuffer == pEditInstance->unCurrentLine + 1)
+   {
+      /* If this is the last line of the buffer, then we just remove    */
+      /* everything from this line, without actually removing the line. */
+      *pszStartOfThisLine = '\0';
+   }
+   else
+   {
+      /* If this is not the last line of the buffer, then remove this */
+      /* entire line, so that the next line will become the current   */
+      /* line.                                                        */
+      pszStartOfNextLine = ODEditBufferGetCharacter(pEditInstance,
+         pEditInstance->unCurrentLine + 1, 0);
+      memmove(pszStartOfThisLine, pszStartOfNextLine,
+         strlen(pszStartOfNextLine) + 1);
+   }
+
+   /* Reset the cursor position to the beginning of the current line. */
+   pEditInstance->unCurrentColumn = 0;
+
+   /* Reindex and reformat the buffer based on its new contents. */
+   ODEditBufferFormatAndIndex(pEditInstance);
+
+   /* Perform necessary redrawing and reposition the cursor if needed. */
+   ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer,
+      REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditPastEndOfCurLine()                            *** PRIVATE FUNCTION ***
+ *
+ * Determines whether the cursor is currently past the end of the current line.
+ * The end of the line is considered to be the first column after the last
+ * character on the line. So, on a blank line, a cursor is considered to be
+ * past the end if it is in or past the second column (column 0).
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *     Return: TRUE if the cursor is past the end of the current line,
+ *             FALSE if it is not.
+ */
+static BOOL ODEditPastEndOfCurLine(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   return(pEditInstance->unCurrentColumn >
+      ODEditBufferGetLineLength(pEditInstance, pEditInstance->unCurrentLine));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRememberBufferSize()                          *** PRIVATE FUNCTION ***
+ *
+ * Determines the buffer size required by ODEditRememberArea().
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *     Return: the required buffer size, in size_t units (bytes).
+ */
+static size_t ODEditRememberBufferSize(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+
+   return((pEditInstance->unAreaWidth + 1)
+         * pEditInstance->unAreaHeight);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRememberArea()                                *** PRIVATE FUNCTION ***
+ *
+ * Stores a copy of the text currently displayed in the edit area in the
+ * provided buffer, so that it can later be reused to redraw only the portion
+ * of the edit area which has been changed by an operation.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *             pRememberedArea - Pointer to a buffer, which is at least
+ *                               the number of bytes specified by
+ *                               ODEditRememberBufferSize() in size.
+ *
+ *     Return: void
+ */
+static void ODEditRememberArea(tEditInstance *pEditInstance,
+   void *pRememberedArea)
+{
+   UINT unDataLineOffset = 0;
+   UINT unDataLineSize;
+   UINT unAreaLine;
+   UINT unBufferLine;
+   UINT unLineLength;
+   char *pchStartOfLine;
+   char *pchDataLocation;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(pRememberedArea != NULL);
+
+   /* Determine the length of a single line in the remember buffer. */
+   unDataLineSize = pEditInstance->unAreaWidth + 1;
+
+   pchDataLocation = (char *)pRememberedArea + unDataLineOffset;
+   for(unBufferLine = pEditInstance->unLineScrolledToTop, unAreaLine = 0;
+      unAreaLine < pEditInstance->unAreaHeight;
+      ++unAreaLine, ++unBufferLine)
+   {
+      /* If this line is not beyond the end of the buffer. */
+      if(unBufferLine < pEditInstance->unLinesInBuffer)
+      {
+         /* Determine the length of this buffer line. */
+         unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine);
+
+         /* Determine the start location of this buffer line. */
+         pchStartOfLine = ODEditBufferGetCharacter(pEditInstance, unBufferLine,
+            0);
+      }
+      else
+      {
+         /* If this line is beyond the end of the buffer, then it is empty. */
+         unLineLength = 0;
+         pchStartOfLine = "";
+      }
+
+      /* Copy the contents of this line to the data buffer. */
+      memcpy(pchDataLocation, pchStartOfLine, unLineLength);
+      pchDataLocation[unLineLength] = '\0';
+
+      /* Update the location where data is being stored in the buffer. */
+      pchDataLocation += unDataLineSize;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRedrawChanged()                               *** PRIVATE FUNCTION ***
+ *
+ * Redraws the portion of the edit area which has been changed by an operation,
+ * based on the original edit area contents as stored in a buffer by the
+ * ODEditRememberArea() function.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *             pRememberedArea - Pointer to a buffer that was filled by a
+ *                               previous call to ODEditRememberArea().
+ *
+ *             unUpperBoundary - The first line in the edit area to consider
+ *                               redrawing, or REDRAW_NO_BOUNDARY to specify
+ *                               the top of the edit area.
+ *
+ *             unLowerBoundary - The last line in the edit area to consider
+ *                               redrawing, or REDRAW_NO_BOUNDARY to specify
+ *                               the bottom of the edit area.
+ *
+ *     Return: void
+ */
+static void ODEditRedrawChanged(tEditInstance *pEditInstance,
+   void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary)
+{
+   UINT unStartRedrawLine;
+   UINT unStartRedrawColumn;
+   UINT unFinishRedrawLine;
+   UINT unFinishRedrawColumn;
+   UINT unEstPartialRedrawBytes;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(pRememberedArea != NULL);
+
+   /* Determine what portion of the edit area, within the specified upper */
+   /* and lower boundaries, has been changed.                             */
+   if(!ODEditDetermineChanged(pEditInstance, pRememberedArea, unUpperBoundary,
+      unLowerBoundary, &unStartRedrawLine, &unStartRedrawColumn,
+      &unFinishRedrawLine, &unFinishRedrawColumn))
+   {
+      /* Nothing has changed in the edit area. */
+      ODEditUpdateCursorIfMoved(pEditInstance);
+      return;
+   }
+
+   /* Now that we have determined the portion of the edit area that would */
+   /* be redraw by a partial (incremental) redraw, we compare the amount  */
+   /* of data involved + the amount of data still in the outbound buffer  */
+   /* with the amount of data involved in a full redraw. If the amount of */
+   /* data in the outbound buffer cannot be determined, we default to     */
+   /* using an incremental redraw.                                        */
+   unEstPartialRedrawBytes = ODEditEstDrawBytes(pEditInstance,
+      unStartRedrawLine, unStartRedrawColumn, unFinishRedrawLine,
+      unFinishRedrawColumn);
+   if(ODEditRecommendFullRedraw(pEditInstance, unEstPartialRedrawBytes,
+      FALSE))
+   {
+      /* Purge the outbound buffer and do a full redraw. */
+      ODEditRedrawArea(pEditInstance);
+
+      /* Move the cursor back to its appropriate position. */
+      ODEditUpdateCursorPos(pEditInstance);
+   }
+   else
+   {
+      /* Perform a partial (incremental) redraw. */
+
+      /* Now, redraw the portion of the edit area that has been determined to */
+      /* require redrawing.                                                   */
+      ODEditRedrawSubArea(pEditInstance, unStartRedrawLine, unStartRedrawColumn,
+         unFinishRedrawLine, unFinishRedrawColumn);
+
+      /* Now, move the cursor back to its appropriate position, if it isn't */
+      /* already there.                                                     */
+      ODEditUpdateCursorIfMoved(pEditInstance);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditDetermineChanged()                            *** PRIVATE FUNCTION ***
+ *
+ * Determines what portion of the edit area, within the specifiede upper and
+ * lower boundary, has been changed. Area is specified as row and column
+ * position of the first changed characer between boundaries, and the row
+ * and column position past the last changed character between the boundaries.
+ *
+ * Parameters: pEditInstance         - Editor instance information structure.
+ *
+ *             pRememberedArea       - Pointer to a buffer that was filled by a
+ *                                     previous call to ODEditRememberArea().
+ *
+ *             unUpperBoundary       - The first line in the edit area to
+ *                                     consider redrawing, or
+ *                                     REDRAW_NO_BOUNDARY to specify the top of 
+ *                                     the edit area.
+ *
+ *             unLowerBoundary       - The last line in the edit area to
+ *                                     consider redrawing, or
+ *                                     REDRAW_NO_BOUNDARY to specify the bottom
+ *                                     of the edit area.
+ *
+ *             punStartRedrawLine    - Output: Line of first changed character.
+ *
+ *             punStartRedrawColumn  - Output: Column of first changed char.
+ *
+ *             punFinishRedrawLine   - Output: Line of last changed character.
+ *
+ *             punFinishRedrawColumn - Output: Column after last changed char.
+ *
+ *     Return: TRUE if some text in the edit area has been changed, FALSE
+ *             if there is no change.
+ */
+static BOOL ODEditDetermineChanged(tEditInstance *pEditInstance,
+   void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary, 
+   UINT *punStartRedrawLine, UINT *punStartRedrawColumn,
+   UINT *punFinishRedrawLine, UINT *punFinishRedrawColumn)
+{
+   BOOL bFoundStart = FALSE;
+   BOOL bFoundFinish = FALSE;
+   UINT unDataLineOffset = 0;
+   UINT unDataLineSize;
+   UINT unAreaLine;
+   UINT unLineLength;
+   UINT unColumn;
+   UINT unBufferLine;
+   char *pchCurrent;
+   char *pchRemembered;
+
+   /* Determine the length of a single line in the remember buffer. */
+   unDataLineSize = pEditInstance->unAreaWidth + 1;
+
+   /* If caller specified no upper boundary, then reset upper boundary */
+   /* to 0.                                                            */
+   if(unUpperBoundary == REDRAW_NO_BOUNDARY) unUpperBoundary = 0;
+
+   /* Likewise, iff caller specified no lower boundary, then reset the */
+   /* lower boundary to the bottom of the edit area.                   */
+   if(unLowerBoundary == REDRAW_NO_BOUNDARY)
+   {
+      unLowerBoundary = pEditInstance->unAreaHeight;
+   }
+
+   /* Loop through the area within boundaries, determining which */
+   /* portion of the edit area has changed.                      */
+   for(unBufferLine = pEditInstance->unLineScrolledToTop + unUpperBoundary,
+      unAreaLine = unUpperBoundary; unAreaLine < unLowerBoundary;
+      ++unAreaLine, ++unBufferLine)
+   {
+      /* Determine location of corresponding line in remembered data. */
+      pchRemembered = (char *)pRememberedArea + unDataLineOffset
+         + unDataLineSize * unAreaLine;
+
+      /* If this line is not beyond the end of the buffer. */
+      if(unBufferLine < pEditInstance->unLinesInBuffer)
+      {
+         /* Determine the start location of this buffer line. */
+         pchCurrent = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0);
+
+         /* Determine the length of this buffer line. */
+         unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine);
+      }
+      else
+      {
+         pchCurrent = "";
+         unLineLength = 0;
+      }
+
+      /* Start at the first column on this line. */
+      unColumn = 0;
+
+      /* Look for any characters that differ. */
+      for(;; ++unColumn, ++pchCurrent, ++pchRemembered)
+      {
+         /* Determine if we are at the end of the remembered line. */
+         BOOL bEndOfRemembered = (*pchRemembered == '\0');
+
+         /* Determine if we are at the end of the current buffer line. */
+         BOOL bEndOfCurrent = (unColumn == unLineLength);
+
+         /* If we are at the end of either of the buffers (but not both), */
+         /* or if these two characters differ, then we have found the     */
+         /* start of the area that must be redrawn.                       */
+         if(!(bEndOfRemembered && bEndOfCurrent))
+         {
+            if(bEndOfRemembered || bEndOfCurrent
+               || *pchCurrent != *pchRemembered)
+            {
+               if(bFoundStart)
+               {
+                  bFoundFinish = FALSE;
+               }
+               else
+               {
+                  /* We have found a character that differs. */
+                  bFoundStart = TRUE;
+                  *punStartRedrawLine = unAreaLine;
+                  *punStartRedrawColumn = unColumn;
+               }
+            }
+         }
+
+         /* If we have found the first changed text in the buffer, then we */
+         /* are now looking for the last changed text in the buffer.       */
+         if(bFoundStart && !bFoundFinish)
+         {
+            if(*pchCurrent == *pchRemembered)
+            {
+               bFoundFinish = TRUE;
+               *punFinishRedrawLine = unAreaLine;
+               *punFinishRedrawColumn = unColumn;
+            }
+            else if(bEndOfRemembered)
+            {
+               bFoundFinish = TRUE;
+               *punFinishRedrawLine = unAreaLine;
+               *punFinishRedrawColumn = unLineLength;
+            }
+            else if(bEndOfCurrent)
+            {
+               bFoundFinish = TRUE;
+               *punFinishRedrawLine = unAreaLine;
+               *punFinishRedrawColumn = unColumn + strlen(pchRemembered);
+            }
+         }
+
+         /* If we are at the end of either buffers. */
+         if(bEndOfRemembered || bEndOfCurrent)
+         {
+            /* Now, proceed to processing the next line in the edit area. */
+            break;
+         }
+      }
+   }
+
+   /* If we haven't found any text in the edit area that has changed. */
+   if(!bFoundStart)
+   {
+      /* Then return indicating no change. */
+      return(FALSE);
+   }
+
+   /* If we haven't found an end to the portion of the area that has */
+   /* changed, then we must redraw up to the end of the edit area.   */
+   if(!bFoundFinish)
+   {
+      *punFinishRedrawLine = unLowerBoundary;
+      *punFinishRedrawColumn = unColumn;
+   }
+
+   /* Return indicating that ther has been some change. */
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditRedrawSubArea()                               *** PRIVATE FUNCTION ***
+ *
+ * Redraws the portion of the edit area within the specified range. Redrawing
+ * is performed from the location of the start redraw row and column, up to
+ * but not includin gthe finish redraw row and column.
+ *
+ * Parameters: pEditInstance        - Editor instance information structure.
+ *
+ *             unStartRedrawLine    - Line of first character to draw.
+ *
+ *             unStartRedrawColumn  - Column of first character to draw.
+ *
+ *             unFinishRedrawLine   - Line of last character to draw.
+ *
+ *             unFinishRedrawColumn - Column after last character to draw.
+ *
+ *     Return: void
+ */
+static void ODEditRedrawSubArea(tEditInstance *pEditInstance,
+   UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine,
+   UINT unFinishRedrawColumn)
+{
+   UINT unAreaLine;
+   UINT unLineLength;
+   UINT unBufferLine;
+   char *pchCurrent;
+   UINT unStartColumn;
+   UINT unFinishColumn;
+   UINT unScrnStartColumn;
+   UINT unTextLength;
+
+   /* Now, perform actual redraw in area that requires redrawing. */
+   for(unBufferLine = pEditInstance->unLineScrolledToTop + unStartRedrawLine,
+      unAreaLine = unStartRedrawLine; unAreaLine <= unFinishRedrawLine;
+      ++unBufferLine, ++unAreaLine)
+   {
+      BOOL bFirstLine = (unAreaLine == unStartRedrawLine);
+      BOOL bLastLine = (unAreaLine == unFinishRedrawLine);
+      UINT unScrnRow = (UINT)pEditInstance->pUserOptions->nAreaTop
+         + unAreaLine;
+
+      /* If this line is not beyond the end of the buffer. */
+      if(unBufferLine < pEditInstance->unLinesInBuffer)
+      {
+         pchCurrent = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0);
+         unTextLength = unLineLength =
+            ODEditBufferGetLineLength(pEditInstance, unBufferLine);
+      }
+      else
+      {
+         pchCurrent = "";
+         unTextLength = unLineLength = 0;
+      }
+
+      /* Move to the position on the first line to begin redraw. */
+      if(bFirstLine)
+      {
+         UINT unActualRow;
+         UINT unActualColumn;
+         ODEditGetActualCurPos(pEditInstance, &unActualRow, &unActualColumn);
+
+         unStartColumn = unStartRedrawColumn;
+         unScrnStartColumn = (UINT)pEditInstance->pUserOptions->nAreaLeft
+            + unStartColumn;
+
+         if(unScrnRow != unActualRow || unScrnStartColumn != unActualColumn)
+         {
+            od_set_cursor(unScrnRow, unScrnStartColumn);
+         }
+
+         pchCurrent += unStartRedrawColumn;
+         unTextLength -= unStartRedrawColumn;
+      }
+      else
+      {
+         unStartColumn = 0;
+         unScrnStartColumn = (UINT)pEditInstance->pUserOptions->nAreaLeft;
+         od_set_cursor(unScrnRow, unScrnStartColumn);
+      }
+
+      /* If this is the last line to redraw, then adjust accordingly. */
+      if(bLastLine)
+      {
+         if(unFinishRedrawColumn < unLineLength)
+         {
+            unTextLength -= unLineLength - unFinishRedrawColumn;
+         }
+         unFinishColumn = unFinishRedrawColumn;
+      }
+      else
+      {
+         unFinishColumn = pEditInstance->unAreaWidth;
+      }
+
+      /* Output changed text. */
+      if(unStartColumn < unLineLength)
+      {
+         od_disp(pchCurrent, unTextLength, TRUE);
+         unStartColumn += unTextLength;
+      }
+
+      /* If we need to clear the rest of the line. */
+      if(unFinishColumn == pEditInstance->unAreaWidth)
+      {
+         /* If right edge of edit area aligns with the right edge of the */
+         /* screen.                                                      */
+         if(pEditInstance->pUserOptions->nAreaRight == OD_SCREEN_WIDTH)
+         {
+            /* Clear the remainder of this line on the screen. */
+            od_clr_line();
+         }
+         else
+         {
+            /* Place spaces after the end of the current line, up to right */
+            /* edge of the edit area.                                      */
+            od_repeat(' ', (BYTE)(pEditInstance->unAreaWidth - unLineLength));
+         }
+      }
+      else if(unStartColumn < unFinishColumn)
+      {
+         od_repeat(' ', (BYTE)(unFinishColumn - unStartColumn));
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditGetActualCurPos()                             *** PRIVATE FUNCTION ***
+ *
+ * Estimates the actual position of the cursor on the screen.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *             punRow          - Pointer to location where cursor row number
+ *                               should be stored.
+ *
+ *             punColumn       - Pointer to location where cursor column number
+ *                               should be stored.
+ *
+ *     Return: void
+ */
+static void ODEditGetActualCurPos(tEditInstance *pEditInstance,
+   UINT *punRow, UINT *punColumn)
+{
+   tODScrnTextInfo TextInfo;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(punRow != NULL);
+   ASSERT(punColumn != NULL);
+
+   UNUSED(pEditInstance);
+
+   /* Obtain current cursor position information from the ODScrn module. */
+   ODScrnGetTextInfo(&TextInfo);
+   *punRow = (UINT)TextInfo.cury;
+   *punColumn = (UINT)TextInfo.curx;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditIsEOLForMode()                                *** PRIVATE FUNCTION ***
+ *
+ * Determines whether the specified character should be treated as an EOL
+ * character for the current mode.
+ *
+ * Parameters: pEditInstance   - Editor instance information structure.
+ *
+ *     Return: TRUE if this is an EOL character, FALSE otherwise.
+ */
+static BOOL ODEditIsEOLForMode(tEditInstance *pEditInstance, char chToTest)
+{
+   switch(pEditInstance->pUserOptions->TextFormat)
+   {
+      case FORMAT_FTSC_MESSAGE:
+         return(chToTest == '\r' || chToTest == '\0');
+
+      default:
+         return(IS_EOL_CHAR(chToTest));
+   }
+}
+
+
+
+/* ========================================================================= */
+/* Low level buffer manipulation functions.                                  */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODEditBufferFormatAndIndex()                        *** PRIVATE FUNCTION ***
+ *
+ * Regenerates the count of lines in the buffer, and the array of pointers to
+ * the start of each line.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: TRUE on success, or FALSE if there is not enough memory
+ *             available to complete this operation.
+ */
+static BOOL ODEditBufferFormatAndIndex(tEditInstance *pEditInstance)
+{
+   char *pch;
+   char *pchLastSpace;
+   UINT unProcessingColumn;
+   UINT unProcessingLine;
+   BOOL bAtEndOfBuffer = FALSE;
+   BOOL bLineEndedByBreak;
+   BOOL bFTSCMode =
+      (pEditInstance->pUserOptions->TextFormat == FORMAT_FTSC_MESSAGE);
+
+   ASSERT(pEditInstance != NULL);
+
+   /* Reset current line count. */
+   unProcessingLine = 0;
+
+   /* Begin at the beginning of the buffer to edit. */
+   pch = pEditInstance->pszEditBuffer;
+   ASSERT(pch != NULL);
+
+   /* Loop for each line in the buffer. */
+   while(!bAtEndOfBuffer)   
+   {
+      /* In FTSC mode, skip a line if it begins with a ^A ("kludge lines"). */
+      if(bFTSCMode)
+      {
+         /* Loop while the current line begins with a ^A. */
+         while(*pch == 0x01)
+         {
+            /* Loop until the end of the line is found. */
+            while(!ODEditIsEOLForMode(pEditInstance, *pch)) ++pch;
+
+            if(*pch == '\0')
+            {
+               /* If the line was ended by a null character, then note that */
+               /* the end of the buffer has been reached.                   */
+               bAtEndOfBuffer = TRUE;
+            }
+            else
+            {
+               /* If the line was not ended by a null character, then skip */
+               /* the end of line character.                               */
+               ++pch;
+            }
+         }
+
+         continue;
+      }
+
+      /* Add the address of the start of this line to the line array. */
+      
+      /* If the line array is full, then attempt to grow it. */
+      ASSERT(unProcessingLine <= pEditInstance->unLineArraySize);
+      if(unProcessingLine == pEditInstance->unLineArraySize)
+      {
+         /* Determine the size to grow the array to. */
+         UINT unNewArraySize = pEditInstance->unLineArraySize
+            + LINE_ARRAY_GROW_SIZE;
+
+         /* Attempt to reallocate this memory block. */
+         char **papchNewLineArray = (char **)realloc(
+            pEditInstance->papchStartOfLine, unNewArraySize * sizeof(char *));
+
+         /* If reallocation failed, then return with failure. */
+         if(papchNewLineArray == NULL)
+         {
+            return(FALSE);
+         }
+
+         /* Otherwise, update the editor instance information with the new */
+         /* array address and array size information.                      */
+         pEditInstance->papchStartOfLine = papchNewLineArray;
+         pEditInstance->unLineArraySize = unNewArraySize;
+      }
+
+      /* Add the address of the start of this line to the array. */
+      pEditInstance->papchStartOfLine[unProcessingLine] = pch;
+
+      /* Reset word wrap information. */
+      pchLastSpace = NULL;
+
+      /* Now, find the end of this line. */
+      bLineEndedByBreak = TRUE;
+      unProcessingColumn = 0;
+      while(!ODEditIsEOLForMode(pEditInstance, *pch))
+      {
+         /* If this character is a space, then record the location of the */
+         /* last space characters.                                        */
+         if(*pch == ' ') pchLastSpace = pch;
+
+         /* Check for characters which must be filtered from the buffer */
+         /* in FTSC message mode.                                       */
+         if(bFTSCMode)
+         {
+            if(*pch == 0x0a || ((unsigned char)*pch) == 0x8d)
+            {
+               /* If this character must be removed, then move rest of */
+               /* buffer up by one character, and proceed to next loop */
+               /* iteration.                                           */
+               memmove(pch, pch + 1, strlen(pch + 1) + 1);
+               continue;
+            }
+         }
+
+         /* Increment count of characters on this line. */
+         ++unProcessingColumn;
+
+         /* Check whether we have reached the maximum number of characters */
+         /* that will fit on this line.                                    */
+         if(unProcessingColumn >= pEditInstance->unAreaWidth - 1)
+         {
+            if(pEditInstance->bWordWrapLongLines)
+            {
+               /* If we are to word wrap long lines, then back up to the */
+               /* beginning of the last word, if we have encountered any */
+               /* space characters.                                      */
+               if(pchLastSpace != NULL && pchLastSpace < pch)
+               {
+                  /* Update current column number accordingly. */
+                  unProcessingColumn -= (UINT)(pch - pchLastSpace);
+
+                  /* Back up to position to perform word wrap at. */
+                  pch = pchLastSpace;
+               }
+            }
+
+            /* If we are wrapping text where the cursor is located, then we */
+            /* will have to reposition the cursor accordingly.              */
+            if(unProcessingLine == pEditInstance->unCurrentLine
+               && unProcessingColumn < pEditInstance->unCurrentColumn)
+            {
+               /* Move the cursor to the next line. */
+               pEditInstance->unCurrentLine++;
+
+               /* Adjust the cursor column number to the position where the */
+               /* corresponding wrapped text will appear.                   */
+               pEditInstance->unCurrentColumn -= unProcessingColumn;
+            }
+
+            /* Note that line was not ended by en explicit line break. */
+            bLineEndedByBreak = FALSE;
+
+            break;
+         }
+
+         /* Move to the next character in the buffer. */
+         ++pch;
+      }
+
+      /* If we the line was terminated by a '\0', then note that the end of */
+      /* the buffer has been reached.                                       */
+      if(*pch == '\0')
+      {
+         bAtEndOfBuffer = TRUE;
+      }
+
+      /* If the line was not terminated by a '\0', then find the first */
+      /* character of the next line.                                   */
+      else
+      {
+         char chFirstEOLChar = *pch;
+         char chSecondEOLChar = '\0';
+
+         /* Move to the next character in the buffer. */
+         ++pch;
+         
+         /* If this character is a different EOL sequence character from the */
+         /* already encountered EOL character, then skip past it too.        */
+         if(ODEditIsEOLForMode(pEditInstance, chFirstEOLChar) && *pch != '\0'
+            && ODEditIsEOLForMode(pEditInstance, *pch)
+            && *pch != chFirstEOLChar)
+         {
+            chSecondEOLChar = *pch;
+            ++pch;
+         }
+         
+         /* If we don't already know what form of line/paragraph break to */
+         /* use (just a CR, just a LF, a CR/LF sequence or a LF/CR        */
+         /* sequence), then use this line termination as an example of    */
+         /* what should be used.                                          */
+         if(bLineEndedByBreak)
+         {
+            ODEditSetBreakSequence(pEditInstance, chFirstEOLChar,
+               chSecondEOLChar);
+         }
+      }
+
+      /* Increment the count of the current line number. */
+      unProcessingLine++;
+   }
+
+   /* Update count of number of lines in the buffer, based on the number */
+   /* that we have found.                                                */
+   pEditInstance->unLinesInBuffer = unProcessingLine;
+
+   /* Return with success. */
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditBufferGetLineLength()                         *** PRIVATE FUNCTION ***
+ *
+ * Determines the length of the specified line in the buffer, not including
+ * any line terminator characters.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *             unBufferLine  - 0-based index of the line in question.
+ *
+ *     Return: The number of characters on this line.
+ */
+static UINT ODEditBufferGetLineLength(tEditInstance *pEditInstance,
+   UINT unBufferLine)
+{
+   char *pch;
+   char *pchStartOfLine;
+   UINT unCharsBeforeEOL;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(unBufferLine < pEditInstance->unLinesInBuffer);
+   ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize);
+
+   /* Get the address of the start of this line in the buffer. */
+   pchStartOfLine
+      = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0);
+
+   /* Count the number of characters before the next end of line character. */
+   for(pch = pchStartOfLine, unCharsBeforeEOL = 0;
+      !ODEditIsEOLForMode(pEditInstance, *pch);
+      ++unCharsBeforeEOL, ++pch);
+
+   /* If this is the last line in the buffer, then the number of characers */
+   /* before the next end of line character is the length of this line.    */
+   if(unBufferLine >= pEditInstance->unLinesInBuffer - 1)
+   {
+      return(unCharsBeforeEOL);
+   }
+
+   /* If this is not the last line in the buffer, then the length of this  */
+   /* line is the minimum of the number of characters before the next end  */
+   /* of line character and the number of characters before the next line, */
+   /* according to the line index information. This is because all lines   */
+   /* do not necessarily end with an end-of-line character.                */
+   else
+   {
+       return(MIN(unCharsBeforeEOL, (UINT)(ODEditBufferGetCharacter(
+          pEditInstance, unBufferLine + 1, 0) - pchStartOfLine)));
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditBufferGetTotalLines()                         *** PRIVATE FUNCTION ***
+ *
+ * Determines the number of lines in the current edit buffer.
+ *
+ * Parameters: pEditInstance - Editor instance information structure.
+ *
+ *     Return: The total number of lines in the buffer.
+ */
+static UINT ODEditBufferGetTotalLines(tEditInstance *pEditInstance)
+{
+   ASSERT(pEditInstance != NULL);
+   ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize);
+
+   /* Return the total number of lines in the buffer. */
+   return(pEditInstance->unLinesInBuffer);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditBufferGetCharacter()                          *** PRIVATE FUNCTION ***
+ *
+ * Obtains the character at the specified position in the buffer.
+ *
+ * Parameters: pEditInstance  - Editor instance information structure.
+ *
+ *             unBufferLine   - 0-based index of the line in question.
+ *
+ *             unBufferColumn - The position in the line of the required
+ *                              character.
+ *
+ *     Return: A pointer to the character at the specified position in the
+ *             specified line. The caller can assume that any remaining
+ *             character(s) in the line follow this character, but should
+ *             not assume that the pointer can be used to access following
+ *             lines in the buffer.
+ */
+static char *ODEditBufferGetCharacter(tEditInstance *pEditInstance,
+   UINT unBufferLine, UINT unBufferColumn)
+{
+   ASSERT(pEditInstance != NULL);
+   ASSERT(unBufferLine < pEditInstance->unLinesInBuffer);
+   ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize);
+   ASSERT(unBufferColumn <= ODEditBufferGetLineLength(pEditInstance, unBufferLine));
+
+   /* The position of this character is the position of this line, plus */
+   /* the number of characters into the line.                           */
+   return(pEditInstance->papchStartOfLine[unBufferLine] + unBufferColumn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditBufferMakeSpace()                             *** PRIVATE FUNCTION ***
+ *
+ * Moves the remaining buffer contents by the specified distance to make room
+ * for new text. The new space is filled by space (' ') characters. Does not
+ * necessarily reindex the buffer before returning, and so this should be done
+ * by the caller after the new buffer space has been filled.
+ *
+ * Parameters: pEditInstance       - Editor instance information structure.
+ *
+ *             unLine              - Line number to make more room on.
+ *
+ *             unColumn            - Column number to insert the space.
+ *
+ *             unNumChars          - Number of characters to make room for.
+ *
+ *     Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to
+ *             obtain enough buffer space before making any changes, or
+ *             kODRCUnrecoverableFailure if the buffer has been changed, but
+ *             there was insufficient memory to re-index the buffer.
+ */
+static tODResult ODEditBufferMakeSpace(tEditInstance *pEditInstance,
+   UINT unLine, UINT unColumn, UINT unNumChars)
+{
+   UINT unLineLength;
+   UINT unBufferUsed;
+   UINT unBufferUnused;
+   UINT unRemainingBufferBytes;
+   UINT unCount;
+   char *pchBufferPos;
+   tODResult Result;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(unLine < pEditInstance->unLinesInBuffer);
+
+   /* Determine the current length of the specified line. */
+   unLineLength = ODEditBufferGetLineLength(pEditInstance, unLine);
+
+   /* If a column past the current end of this line was specified, then    */
+   /* adjust column and number of characters in order to extend the line   */
+   /* up to the specified column as well as adding space beginning at that */
+   /* column.                                                              */
+   if(unColumn > unLineLength)
+   {
+      UINT unExtendLineBy = unColumn - unLineLength;
+      unColumn -= unExtendLineBy;
+      unNumChars += unExtendLineBy;
+   }
+
+   /* Now, determine whether the buffer is large enough for the additional */
+   /* space that will be added.                                            */
+   unBufferUsed = strlen(pEditInstance->pszEditBuffer) + 1;
+   unBufferUnused = pEditInstance->unBufferSize - unBufferUsed;
+   if(unBufferUnused < unNumChars)
+   {
+      /* There is not currently sufficient room in the buffer for the new */
+      /* characters, then attempt to grow the buffer to make more room.   */
+      Result = ODEditTryToGrow(pEditInstance, unBufferUsed + unNumChars);
+      if(Result != kODRCSuccess)
+      {
+         /* On failure, return the result code that indicates the nature */
+         /* of the failure.                                              */
+         return(Result);
+      }
+   }
+
+   /* Now, shift the buffer contents from this location forward by */
+   /* unNumChars characters.                                       */
+   pchBufferPos = ODEditBufferGetCharacter(pEditInstance, unLine, unColumn);
+   unRemainingBufferBytes = strlen(pchBufferPos) + 1;
+   memmove(pchBufferPos + unNumChars, pchBufferPos, unRemainingBufferBytes);
+
+   /* Next, we fill the new buffer area with space characters. */
+   for(unCount = 0; unCount < unNumChars; ++unCount)
+   {
+      *pchBufferPos++ = ' ';
+   }
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODEditTryToGrow()                                   *** PRIVATE FUNCTION ***
+ *
+ * Attempts to reallocate the buffer to a larger size. This function is called
+ * if it is found that the current buffer isn't large enough to complete some
+ * operation. If the client application has setup the editor to permit buffer
+ * reallocation, then this function will call the realloc callback function
+ * supplied by the client application. If buffer growing is not possible, then
+ * this function automatically fails.
+ *
+ * Parameters: pEditInstance       - Editor instance information structure.
+ *
+ *             unSizeNeeded        - The minimum buffer size that is needed.
+ *
+ *     Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to
+ *             obtain enough buffer space before making any changes, or
+ *             kODRCUnrecoverableFailure if the buffer has been changed, but
+ *             there was insufficient memory to re-index the buffer.
+ */
+static tODResult ODEditTryToGrow(tEditInstance *pEditInstance,
+   UINT unSizeNeeded)
+{
+   BOOL bFullReIndexRequired = FALSE;
+
+   ASSERT(pEditInstance != NULL);
+   ASSERT(unSizeNeeded > pEditInstance->unBufferSize);
+
+   if(pEditInstance->pUserOptions->pfBufferRealloc != NULL)
+   {
+      /* If the buffer is growable, then attempt to grow it using the */
+      /* realloc function provided by the client application.         */
+      UINT unNewBufferSize = MAX(pEditInstance->unBufferSize
+         + BUFFER_GROW_SIZE, unSizeNeeded);
+      char *pszNewBuffer = (char *)((*pEditInstance->pUserOptions->
+         pfBufferRealloc)(pEditInstance->pszEditBuffer, unNewBufferSize));
+
+      /* If we were unable to grow the buffer, then fail now. At this */
+      /* point, nothing has changed, and so the buffer information    */
+      /* is still intact and valid.                                   */
+      if(pszNewBuffer == NULL)
+      {
+         return(kODRCSafeFailure);
+      }
+
+      /* Otherwise, determine whether the entire buffer will now have to */
+      /* be reindexed. This is necessary if the reallocated buffer is at */
+      /* a new location than the original was.                           */
+      if(pszNewBuffer != pEditInstance->pszEditBuffer)
+      {
+         bFullReIndexRequired = TRUE;
+      }
+
+      /* Now, store the new buffer pointer and buffer size information. */
+      pEditInstance->pszEditBuffer = pszNewBuffer;
+      pEditInstance->unBufferSize = unNewBufferSize;
+   }
+   else
+   {
+      /* If the buffer is not growable, then fail right away. */
+      return(kODRCSafeFailure);
+   }
+
+   /* If a full reindex is required due to buffer reallocation, then do so. */
+   if(bFullReIndexRequired)
+   {
+      if(!ODEditBufferFormatAndIndex(pEditInstance))
+      {
+         /* If this fails, then return with failure. */
+         return(kODRCUnrecoverableFailure);
+      }
+      bFullReIndexRequired = FALSE;
+   }
+
+   /* If we get to this point, we suceeded in growing the buffer to the */
+   /* required size, so return with success.                            */
+   return(kODRCSuccess);
+}

+ 2212 - 0
odoors/ODEmu.c

@@ -0,0 +1,2212 @@
+/* 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);
+}

+ 1759 - 0
odoors/ODFrame.c

@@ -0,0 +1,1759 @@
+/* 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: ODFrame.c
+ *
+ * Description: Implements the OpenDoors frame window which provides the
+ *              menu, toolbar, and status bar. The frame window's client
+ *              area contains the display window which shows the door's
+ *              output as the remote user would see it. This file should
+ *              not be built into non-Windows versions of OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Aug 20, 1995  6.00  BP   Created.
+ *              Dec 20, 1995  6.00  BP   Remember toolbar & statusbar settings.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Jan 20, 1996  6.00  BP   Made ODFrameCenter...() shared.
+ *              Jan 21, 1996  6.00  BP   Added ODScrnShowMessage() and related.
+ *              Feb 17, 1996  6.00  BP   Add ...Accelerator() return value.
+ *              Feb 17, 1996  6.00  BP   Pass WM_MENUSELECT to DefWindowProc().
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 21, 1996  6.00  BP   Fixed user keyboard off command.
+ *              Feb 22, 1996  6.00  BP   Allow escape to close Help About box.
+ *              Feb 23, 1996  6.00  BP   Properly update when toolbar turned on
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 14, 1996  6.10  BP   Added configuration menu option.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+
+#include "windows.h"
+#include "commctrl.h"
+
+#include "OpenDoor.h"
+#include "ODRes.h"
+#include "ODFrame.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODKrnl.h"
+
+#ifdef ODPLAT_WIN32
+
+/* Frame window information structure. */
+typedef struct
+{
+   HINSTANCE hInstance;
+   BOOL bToolbarOn;
+   HWND hwndToolbar;
+   BOOL bStatusBarOn;
+   HWND hwndStatusBar;
+   HWND hwndTimeEdit;
+   HWND hwndTimeUpDown;
+   BOOL bWantsChatIndicator;
+   HACCEL hacclFrameCommands;
+   HWND hwndMessageWindow;
+   char *pszCurrentMessage;
+   int nCurrentMessageFlags;
+} tODFrameWindowInfo;
+
+/* Toolbar button information. */
+TBBUTTON atbButtons[] =
+{
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {0,   ID_DOOR_CHATMODE,          TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {1,   ID_DOOR_USERKEYBOARDOFF,   TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+   {2,   ID_DOOR_SYSOPNEXT,         TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+   {0,   0,                         TBSTATE_ENABLED,  TBSTYLE_SEP,    0, 0},
+   {3,   ID_DOOR_HANGUP,            TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+   {4,   ID_DOOR_LOCKOUT,           TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+   {5,   ID_DOOR_EXIT,              TBSTATE_ENABLED,  TBSTYLE_BUTTON, 0, 0},
+};
+
+/* Other toolbar settings. */
+#define NUM_TOOLBAR_BITMAPS   6
+#define MIN_TIME              0
+#define MAX_TIME              1440
+
+/* Pointer to default edit box window procedure. */
+WNDPROC pfnDefEditProc = NULL;
+WNDPROC pfnDefToolbarProc = NULL;
+
+/* Global frame window handle. */
+static HWND hwndCurrentFrame;
+
+/* Status bar settings. */
+#define NUM_STATUS_PARTS      2
+#define NODE_PART_WIDTH       65
+
+/* Child window IDs. */
+#define ID_TOOLBAR            1000
+#define ID_TIME_EDIT          1001
+#define ID_TIME_UPDOWN        1002
+#define ID_STATUSBAR          1003
+
+
+/* Private function prototypes. */
+static HWND ODFrameCreateToolbar(HWND hwndParent, HANDLE hInstance,
+   tODFrameWindowInfo *pWindowInfo);
+static void ODFrameDestroyToolbar(HWND hwndToolbar,
+   tODFrameWindowInfo *pWindowInfo);
+static HWND ODFrameCreateStatusBar(HWND hwndParent, HANDLE hInstance);
+static void ODFrameSetMainStatusText(HWND hwndStatusBar);
+static void ODFrameDestroyStatusBar(HWND hwndStatusBar);
+static void ODFrameSizeStatusBar(HWND hwndStatusBar);
+LRESULT CALLBACK ODFrameWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+LRESULT CALLBACK ODFrameToolbarProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+static void ODFrameUpdateTimeLeft(tODFrameWindowInfo *pWindowInfo);
+LRESULT CALLBACK ODFrameTimeEditProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+BOOL CALLBACK ODFrameAboutDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+static HWND ODFrameCreateWindow(HANDLE hInstance);
+static void ODFrameDestroyWindow(HWND hwndFrame);
+static void ODFrameMessageLoop(HANDLE hInstance, HWND hwndFrame);
+DWORD OD_THREAD_FUNC ODFrameThreadProc(void *pParam);
+BOOL CALLBACK ODFrameMessageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameCreateWindow()                               *** PRIVATE FUNCTION ***
+ *
+ * Creates the OpenDoors frame window and its children.
+ *
+ * Parameters: hInstance   - Handle to application instance.
+ *
+ *     Return: A handle to the newly created window, or NULL on failure.
+ */
+static HWND ODFrameCreateWindow(HANDLE hInstance)
+{
+   HWND hwndFrameWindow = NULL;
+   WNDCLASS wcFrameWindow;
+   tODFrameWindowInfo *pWindowInfo = NULL;
+   tODThreadHandle hScreenThread;
+   HKEY hOpenDoorsKey;
+   DWORD cbData;
+
+   /* Register the main frame window's window class. */
+   memset(&wcFrameWindow, 0, sizeof(wcFrameWindow));
+   wcFrameWindow.style = CS_HREDRAW | CS_VREDRAW;
+   wcFrameWindow.lpfnWndProc = ODFrameWindowProc;
+   wcFrameWindow.cbClsExtra = 0;
+   wcFrameWindow.cbWndExtra = 0;
+   wcFrameWindow.hInstance = hInstance;
+   if(od_control.od_app_icon != NULL)
+   {
+      wcFrameWindow.hIcon = od_control.od_app_icon;
+   }
+   else
+   {
+      wcFrameWindow.hIcon
+         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_OPENDOORS));
+   }
+   wcFrameWindow.hCursor = LoadCursor(NULL, IDC_ARROW);
+   wcFrameWindow.hbrBackground = NULL;
+   wcFrameWindow.lpszMenuName = MAKEINTRESOURCE(IDR_FRAME_MENU);
+   wcFrameWindow.lpszClassName = "ODFrame";
+
+   RegisterClass(&wcFrameWindow);
+
+   /* Setup window information structure. */
+   pWindowInfo = malloc(sizeof(tODFrameWindowInfo));
+   if(!pWindowInfo)
+   {
+      return(NULL);
+   }
+   pWindowInfo->hInstance = hInstance;
+   pWindowInfo->hwndTimeEdit = NULL;
+   pWindowInfo->hwndTimeUpDown = NULL;
+   pWindowInfo->bWantsChatIndicator = FALSE;
+   pWindowInfo->hwndMessageWindow = NULL;
+
+   /* Determine whether or not the toolbar and status bar are on. */
+   RegCreateKey(HKEY_CURRENT_USER, "Software\\Pirie\\OpenDoors",
+      &hOpenDoorsKey);
+
+   cbData = sizeof(pWindowInfo->bToolbarOn);
+   if(RegQueryValueEx(hOpenDoorsKey, "ToolBarOn", NULL, NULL, 
+      (LPBYTE)&pWindowInfo->bToolbarOn,
+      &cbData) != ERROR_SUCCESS)
+   {
+      pWindowInfo->bToolbarOn = TRUE;
+      RegSetValueEx(hOpenDoorsKey, "ToolBarOn", 0, REG_DWORD,
+         (LPBYTE)&pWindowInfo->bToolbarOn,
+         sizeof(pWindowInfo->bToolbarOn));
+   }
+
+   cbData = sizeof(pWindowInfo->bStatusBarOn);
+   if(RegQueryValueEx(hOpenDoorsKey, "StatusBarOn", NULL, NULL, 
+      (LPBYTE)&pWindowInfo->bStatusBarOn,
+      &cbData) != ERROR_SUCCESS)
+   {
+      pWindowInfo->bStatusBarOn = TRUE;
+      RegSetValueEx(hOpenDoorsKey, "StatusBarOn", 0, REG_DWORD,
+         (LPBYTE)&pWindowInfo->bStatusBarOn,
+         sizeof(pWindowInfo->bStatusBarOn));
+   }
+
+   RegCloseKey(hOpenDoorsKey);
+
+   /* Create the main frame window. */
+   if((hwndFrameWindow = CreateWindowEx(
+      0L,
+      wcFrameWindow.lpszClassName,
+      od_control.od_prog_name,
+      WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX,
+      CW_USEDEFAULT,
+      0,
+      0,
+      0,
+      NULL,
+      NULL,
+      hInstance,
+      pWindowInfo)) == NULL)
+   {
+      /* On window creation failure, return NULL. */
+      return(NULL);
+   }
+
+   /* Load accelerator table for the  frame window. */
+   pWindowInfo->hacclFrameCommands
+      = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_FRAME));
+
+   /* Create the OpenDoors toolbar. On failure, we will continue anyhow. */
+   if(pWindowInfo->bToolbarOn)
+   {
+      pWindowInfo->hwndToolbar =
+         ODFrameCreateToolbar(hwndFrameWindow, hInstance, pWindowInfo);
+   }
+
+   /* Create the status bar. On failure, we will continue anyhow. */
+   if(pWindowInfo->bStatusBarOn)
+   {
+      pWindowInfo->hwndStatusBar =
+         ODFrameCreateStatusBar(hwndFrameWindow, hInstance);
+   }
+
+   /* Updates state of the window from whether or not the user has */
+   /* requested a chat with the sysop.                             */
+   ODFrameUpdateWantChat();
+
+   /* Create the local screen window, which occupies the remaining */
+   /* client area of the frame window.                             */
+   ODScrnStartWindow(hInstance, &hScreenThread, hwndFrameWindow);
+
+   return(hwndFrameWindow);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameCreateToolbar()                              *** PRIVATE FUNCTION ***
+ *
+ * Creates the OpenDoors toolbar.
+ *
+ * Parameters: hwndParent  - Handle to the parent window.
+ *
+ *             hInstance   - Handle to the executable file's module instance.
+ *
+ *             pWindowInfo - Pointer to frame window information structure.
+ *
+ *     Return: A handle to the toolbar on success, or NULL on failure.
+ */
+static HWND ODFrameCreateToolbar(HWND hwndParent, HANDLE hInstance,
+   tODFrameWindowInfo *pWindowInfo)
+{
+   HWND hwndToolbar = NULL;
+   HWND hwndTimeEdit = NULL;
+   HWND hwndTimeUpDown = NULL;
+   HWND hwndToolTip;
+   BOOL bSuccess = FALSE;
+
+   ASSERT(hwndParent != NULL);
+   ASSERT(hInstance != NULL);
+   ASSERT(pWindowInfo != NULL);
+
+   /* First, attempt to create the toolbar window. */
+   hwndToolbar = CreateToolbarEx(hwndParent,
+      WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_TOOLTIPS,
+      ID_TOOLBAR, NUM_TOOLBAR_BITMAPS, hInstance, IDB_TOOLBAR,
+      atbButtons, DIM(atbButtons), 0, 0, 0, 0, sizeof(TBBUTTON));
+
+   if(hwndToolbar == NULL)
+   {
+      goto CleanUp;
+   }
+
+   /* Change the window proc for the toolbar window to our own, keeping a */
+   /* pointer to the original window proc.                                */
+   pfnDefToolbarProc = (WNDPROC)GetWindowLong(hwndToolbar, GWL_WNDPROC);
+   SetWindowLong(hwndToolbar, GWL_WNDPROC, (LONG)ODFrameToolbarProc);
+
+   /* Next, create an edit control on the toolbar, to allow the user's */
+   /* time remaining online to be adjusted.                            */
+   hwndTimeEdit = CreateWindowEx(WS_EX_STATICEDGE, "EDIT", "",
+      WS_CHILD | WS_BORDER | WS_VISIBLE | ES_LEFT,
+      0, 0, 70, 22, hwndToolbar, (HMENU)ID_TIME_EDIT, hInstance, NULL);
+
+   if(hwndTimeEdit == NULL)
+   {
+      goto CleanUp;
+   }
+
+   /* Now that the edit window has the appropriate parent, we set its */
+   /* position accordingly. */
+   SetWindowPos(hwndTimeEdit, NULL, 2, 2, 0, 0,
+      SWP_NOZORDER | SWP_NOSIZE);
+
+   /* Set font of the edit control to be the standard non-bold font. */
+   SendMessage(hwndTimeEdit, WM_SETFONT,
+      (WPARAM)GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(FALSE, 0));
+
+   /* Change the window proc for the edit window to our own, keeping a */
+   /* pointer to the original window proc. */
+   pfnDefEditProc = (WNDPROC)GetWindowLong(hwndTimeEdit, GWL_WNDPROC);
+   SetWindowLong(hwndTimeEdit, GWL_WNDPROC, (LONG)ODFrameTimeEditProc);
+
+   /* Add the time edit control to the tooltip control. */
+
+   /* Obtain a handle to the toolbar's tooltip control. */
+   hwndToolTip = (HWND)SendMessage(hwndToolbar, TB_GETTOOLTIPS, 0, 0);
+   if(hwndToolTip)
+   {
+      TOOLINFO ToolInfo;
+
+      /* Fill TOOLINFO structure. */
+      ToolInfo.cbSize = sizeof(ToolInfo);
+      ToolInfo.uFlags = TTF_IDISHWND | TTF_CENTERTIP;
+      ToolInfo.lpszText = "User's Time Remaining";
+      ToolInfo.hwnd = hwndParent;
+      ToolInfo.uId = (UINT)hwndTimeEdit;
+      ToolInfo.hinst = hInstance;
+
+      /* Setup tooltips for the time edit box. */
+      SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ToolInfo);
+   }
+
+   /* Now, we create an up-down control to buddy with the edit control. */
+   hwndTimeUpDown = CreateWindowEx(0L, UPDOWN_CLASS, "",
+      WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_ARROWKEYS |
+      UDS_ALIGNRIGHT, 0, 0, 8, 8,
+         hwndToolbar, (HMENU)ID_TIME_UPDOWN, hInstance, NULL);
+
+   if(hwndTimeUpDown == NULL)
+   {
+      goto CleanUp;
+   }
+
+   /* Set the up-down control's buddy control to be the edit control that */
+   /* we just created.                                                    */
+   SendMessage(hwndTimeUpDown, UDM_SETBUDDY, (LONG)hwndTimeEdit, 0L);
+
+   /* Set the valid range of values for the edit control. */
+   SendMessage(hwndTimeUpDown, UDM_SETRANGE, 0L, MAKELONG(MAX_TIME, MIN_TIME));
+
+   /* Store handles to time limit edit and up-down controls. */
+   pWindowInfo->hwndTimeEdit = hwndTimeEdit;
+   pWindowInfo->hwndTimeUpDown = hwndTimeUpDown;
+
+   /* Next, we set the default text for the edit control. */
+   ODFrameUpdateTimeLeft(pWindowInfo);
+
+   /* Return with success. */
+   bSuccess = TRUE;
+
+CleanUp:
+   if(!bSuccess)
+   {
+      /* On failure, free any allocated resources. */
+      if(hwndTimeUpDown != NULL)
+      {
+         DestroyWindow(hwndTimeUpDown);
+      }
+      if(hwndTimeEdit != NULL)
+      {
+         DestroyWindow(hwndTimeUpDown);
+      }
+      if(hwndToolbar != NULL)
+      {
+         DestroyWindow(hwndToolbar);
+         hwndToolbar = NULL;
+      }
+   }
+
+   /* Return handle to newly created toolbar, or NULL on failure. */
+   return(hwndToolbar);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameDestroyToolbar()                             *** PRIVATE FUNCTION ***
+ *
+ * Destroys the OpenDoors toolbar.
+ *
+ * Parameters: hwndToolbar - Handle to previously created toolbar.
+ *
+ *             pWindowInfo - Pointer to frame window information structure.
+ *
+ *     Return: void.
+ */
+static void ODFrameDestroyToolbar(HWND hwndToolbar,
+   tODFrameWindowInfo *pWindowInfo)
+{
+   ASSERT(hwndToolbar != NULL);
+   ASSERT(pWindowInfo != NULL);
+
+   /* Destroy the time up-down control, and NULL its handle in the frame */
+   /* window information structure.                                      */
+   DestroyWindow(pWindowInfo->hwndTimeUpDown);
+   pWindowInfo->hwndTimeUpDown = NULL;
+
+   /* Destroy the time edit control, and NULL its handle in the frame window */
+   /* information structure.                                                 */
+   DestroyWindow(pWindowInfo->hwndTimeEdit);
+   pWindowInfo->hwndTimeEdit = NULL;
+
+   /* Now, destroy the toolbar itself. */
+   DestroyWindow(hwndToolbar);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameCreateStatusBar()                            *** PRIVATE FUNCTION ***
+ *
+ * Creates the OpenDoors status bar.
+ *
+ * Parameters: hwndParent  - Handle to the parent window.
+ *
+ *             hInstance   - Handle to the executable file's module instance.
+ *
+ *     Return: A handle to the status bar on success, or NULL on failure.
+ */
+static HWND ODFrameCreateStatusBar(HWND hwndParent, HANDLE hInstance)
+{
+   HWND hwndStatusBar = NULL;
+   char szStatusText[20];
+
+   ASSERT(hwndParent != NULL);
+
+   /* Create the status bar window. */
+   hwndStatusBar = CreateWindowEx(0L, STATUSCLASSNAME, "",
+      WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
+      hwndParent, (HMENU)ID_STATUSBAR, hInstance, NULL);
+
+   if(hwndStatusBar == NULL)
+   {
+      return(NULL);
+   }
+
+   /* Set the size of the status bar parts from the size of the frame */
+   /* window.                                                         */
+   ODFrameSizeStatusBar(hwndStatusBar);
+
+   /* Add the user's name, location and connection info string. */
+   ODFrameSetMainStatusText(hwndStatusBar);
+
+   /* Add the node number string. */
+   sprintf(szStatusText, "Node %d", od_control.od_node);
+   SendMessage(hwndStatusBar, SB_SETTEXT, (WPARAM)1, (LPARAM)szStatusText);
+
+   return(hwndStatusBar);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameSetMainStatusText()                          *** PRIVATE FUNCTION ***
+ *
+ * Updates the text that is displayed in the main pane of the status bar.
+ *
+ * Parameters: hwndStatusBar - Handle to the status bar.
+ *
+ *     Return: void.
+ */
+static void ODFrameSetMainStatusText(HWND hwndStatusBar)
+{
+   char szStatusText[160];
+
+   ASSERT(hwndStatusBar != NULL);
+
+   /* Generate base status bar text, with the user's name, location and */
+   /* connection information.                                           */
+   if(od_control.baud == 0)
+   {
+      sprintf(szStatusText, "%s of %s in local mode",
+         od_control.user_name,
+         od_control.user_location);
+   }
+   else
+   {
+      sprintf(szStatusText, "%s of %s at %ldbps",
+         od_control.user_name,
+         od_control.user_location,
+         od_control.od_connect_speed);
+   }
+
+   /* If the user has paged the sysop, then include reason for chat if */
+   /* it is available.                                                 */
+   if(od_control.user_wantchat && strlen(od_control.user_reasonforchat) > 0)
+   {
+      strcat(szStatusText, " (Reason for chat: \"");
+      strcat(szStatusText, od_control.user_reasonforchat);
+      strcat(szStatusText, "\")");
+   }
+
+   /* Update status bar text in the main status bar pane with the newly */
+   /* generated string.                                                 */
+   SendMessage(hwndStatusBar, SB_SETTEXT, (WPARAM)0, (LPARAM)szStatusText);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameDestroyStatusBar()                           *** PRIVATE FUNCTION ***
+ *
+ * Destroys the OpenDoors status bar.
+ *
+ * Parameters: hwndStatusBar    - Handle to previously created status bar.
+ *
+ *     Return: void.
+ */
+static void ODFrameDestroyStatusBar(HWND hwndStatusBar)
+{
+   DestroyWindow(hwndStatusBar);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameSizeStatusBar()                              *** PRIVATE FUNCTION ***
+ *
+ * Creates the OpenDoors status bar.
+ *
+ * Parameters: hwndStatusBar  - Handle to existing status bar window.
+ *
+ *     Return: void.
+ */
+static void ODFrameSizeStatusBar(HWND hwndStatusBar)
+{
+   int anWidths[NUM_STATUS_PARTS];
+   int nStatusWidth;
+   RECT rcStatusBar;
+
+   /* Determine the total width of the status bar. */
+   GetWindowRect(hwndStatusBar, &rcStatusBar);
+   nStatusWidth = rcStatusBar.right - rcStatusBar.left;
+
+   /* Calculate the width of the parts from the total width. */
+   anWidths[0] = nStatusWidth - NODE_PART_WIDTH;
+   anWidths[1] = -1;
+
+   /* Update the status bar part settings. */
+   SendMessage(hwndStatusBar, SB_SETPARTS, NUM_STATUS_PARTS,
+      (LPARAM)anWidths);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameGetUsedClientAtTop()
+ *
+ * Determines height in pixels of the space used at the top of the
+ * frame window's client area, by the toolbar, etc.
+ *
+ * Parameters: hwndFrame - Handle to the OpenDoors frame window.
+ *
+ *     Return: The height of the used space, in pixels.
+ */
+INT ODFrameGetUsedClientAtTop(HWND hwndFrame)
+{
+   tODFrameWindowInfo *pWindowInfo;
+   RECT rcWindow;
+
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndFrame, GWL_USERDATA);
+
+   if(!pWindowInfo->bToolbarOn) return(0);
+
+   GetWindowRect(pWindowInfo->hwndToolbar, &rcWindow);
+
+   return(rcWindow.bottom - rcWindow.top - 2);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameGetUsedClientAtBottom()
+ *
+ * Determines height in pixels of the space used at the bottom of the
+ * frame window's client area, by the status bar, etc.
+ *
+ * Parameters: hwndFrame - Handle to the OpenDoors frame window.
+ *
+ *     Return: The height of the used space, in pixels.
+ */
+INT ODFrameGetUsedClientAtBottom(HWND hwndFrame)
+{
+   tODFrameWindowInfo *pWindowInfo;
+   RECT rcWindow;
+
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndFrame, GWL_USERDATA);
+
+   if(!pWindowInfo->bStatusBarOn) return(0);
+
+   GetWindowRect(pWindowInfo->hwndStatusBar, &rcWindow);
+
+   return(rcWindow.bottom - rcWindow.top - 1);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameWindowProc()                                 *** PRIVATE FUNCTION ***
+ *
+ * The OpenDoors frame window proceedure.
+ *
+ * Parameters: hwnd   - Handle to the OpenDoors frame window.
+ *
+ *             uMsg   - Specifies the message.
+ *
+ *             wParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *             lParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *     Return: The return value is the result of the message processing and
+ *             depends on the message.
+ */
+LRESULT CALLBACK ODFrameWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   tODFrameWindowInfo *pWindowInfo;
+
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwnd, GWL_USERDATA);
+
+   switch(uMsg)
+   {
+      case WM_CREATE:
+      {
+         /* At window creation time, store a pointer to the window */
+         /* information structure in window's user data.           */
+         CREATESTRUCT *pCreateStruct = (CREATESTRUCT *)lParam;
+         pWindowInfo = (tODFrameWindowInfo *)pCreateStruct->lpCreateParams;
+         SetWindowLong(hwnd, GWL_USERDATA, (LONG)pWindowInfo);
+
+         /* Update the enabled and checked states of frame window commands. */
+         ODFrameUpdateCmdUI();
+
+         /* If the client has not provided a help callback function, then */
+         /* remove the Contents item from the help menu.                  */
+         if(od_control.od_help_callback == NULL)
+         {
+            RemoveMenu(GetMenu(hwnd), ID_HELP_CONTENTS, MF_BYCOMMAND);
+         }
+
+         if(od_control.od_config_callback == NULL)
+         {
+            RemoveMenu(GetMenu(hwnd), ID_DOOR_CONFIG, MF_BYCOMMAND);
+         }
+         break;
+      }
+
+      case WM_CLOSE:
+         /* If door exit has been chosen, confirm with local user. */
+         if(MessageBox(hwnd,
+            "You are about to terminate this session and return the user to the BBS.\nDo you wish to proceed?",
+            od_control.od_prog_name,
+            MB_ICONQUESTION | MB_YESNO) == IDYES)
+         {
+            /* Normal door exit (drop to BBS) is implemented by the */
+            /* WM_DESTROY handler.                                  */
+            ODFrameDestroyWindow(hwnd);
+         }
+         break;
+
+      case WM_DESTROY:
+         /* If toolbar is on, then it must be destroyed when the frame */
+         /* window is destroyed.                                       */
+         if(pWindowInfo->bToolbarOn)
+         {
+            ODFrameDestroyToolbar(GetDlgItem(hwnd, ID_TOOLBAR), pWindowInfo);
+         }
+
+         /* If status bar is on, then it must be destroyed when the frame */
+         /* window is destroyed.                                          */
+         if(pWindowInfo->bStatusBarOn)
+         {
+            ODFrameDestroyStatusBar(GetDlgItem(hwnd, ID_STATUSBAR));
+         }
+
+         /* Now, force OpenDoors to shutdown. */
+         ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_DROPTOBBS);
+
+         /* When the frame window is destroyed, it is the window proc's   */
+         /* responsiblity to deallocate the window information structure. */
+         free(pWindowInfo);
+         SetWindowLong(hwnd, GWL_USERDATA, (LONG)NULL);
+
+         /* Reset current frame window handle. */
+         hwndCurrentFrame = NULL;
+         break;
+
+      case WM_SETFOCUS:
+         /* Whenver input focus is set to the frame window, pass the input */
+         /* focus on to the screen window, which fills most of our client  */
+         /* area.                                                          */
+         ODScrnSetFocusToWindow();
+         break;
+
+      case WM_TIMER:
+         /* If the window flash timer has elapsed, then flash the window. */
+         FlashWindow(hwnd, TRUE);
+         break;
+
+      case WM_COMMAND:
+         /* An OpenDoors-defined command has been selected, so switch on */
+         /* the command ID.                                              */
+         switch(LOWORD(wParam))
+         {
+            case ID_HELP_ABOUT:
+               /* Display the OpenDoors default about box. */
+               DialogBox(pWindowInfo->hInstance, MAKEINTRESOURCE(IDD_ABOUT),
+                  hwnd, ODFrameAboutDlgProc);
+               break;
+
+            case ID_HELP_CONTENTS:
+               /* Call the client's help callback function, if one was */
+               /* provided.                                            */
+               if(od_control.od_help_callback != NULL)
+               {
+                  (*od_control.od_help_callback)();
+               }
+               break;
+
+            case ID_DOOR_CONFIG:
+               if(od_control.od_config_callback != NULL)
+               {
+                  (*od_control.od_config_callback)();
+               }
+               break;
+
+            case ID_DOOR_EXIT:
+               /* On request for normal door exit (drop to BBS), just send  */
+               /* a close message to this window. This will prompt to       */
+               /* confirm exit, and then shutdown OpenDoors if appropriate. */
+               PostMessage(hwnd, WM_CLOSE, 0, 0L);
+               break;
+
+            case ID_DOOR_CHATMODE:
+               /* If chat mode is currently active, then end it. */
+               if(od_control.od_chat_active)
+               {
+                  ODKrnlEndChatMode();
+               }
+               /* If chat mode is not currently active, then start it. */
+               else
+               {
+                  ODKrnlStartChatThread(TRUE);
+               }
+               break;
+
+            case ID_DOOR_USERKEYBOARDOFF:
+               /* If user keyboard off command has been chosen, then toggle */
+               /* keyboard off mode on or off.                              */
+               od_control.od_user_keyboard_on
+                  = !od_control.od_user_keyboard_on;
+
+               /* Update the keyboard off menu item and toolbar button. */
+               CheckMenuItem(GetMenu(hwnd), ID_DOOR_USERKEYBOARDOFF,
+                  MF_BYCOMMAND | (od_control.od_user_keyboard_on
+                  ? MF_UNCHECKED : MF_CHECKED));
+               SendMessage(GetDlgItem(hwnd, ID_TOOLBAR), TB_CHECKBUTTON,
+                  ID_DOOR_USERKEYBOARDOFF,
+                  MAKELONG(!od_control.od_user_keyboard_on, 0));
+               break;
+
+            case ID_DOOR_SYSOPNEXT:
+               /* If sysop next command has been chosen, then toggle the */
+               /* sysop next flag on or off.                             */
+               od_control.sysop_next = !od_control.sysop_next;
+
+               /* Update the sysop next menu item and toolbar button. */
+               CheckMenuItem(GetMenu(hwnd), ID_DOOR_SYSOPNEXT, MF_BYCOMMAND |
+                  (od_control.sysop_next ? MF_CHECKED : MF_UNCHECKED));
+               SendMessage(GetDlgItem(hwnd, ID_TOOLBAR), TB_CHECKBUTTON,
+                  ID_DOOR_SYSOPNEXT, MAKELONG(od_control.sysop_next, 0));
+               break;
+
+            case ID_DOOR_HANGUP:
+               /* If hangup command has been chosen, then confirm with the */
+               /* local user.                                              */
+               if(MessageBox(hwnd,
+                  "You are about to disconnect this user. Do you wish to proceed?",
+                  od_control.od_prog_name,
+                  MB_ICONQUESTION | MB_YESNO) == IDYES)
+               {
+                  ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP);
+               }
+               break;
+
+            case ID_DOOR_LOCKOUT:
+               /* If lockout command has been chosen, the confirm with the */
+               /* local user.                                              */
+               if(MessageBox(hwnd,
+                  "You are about to lock out this user. Do you wish to proceed?",
+                  od_control.od_prog_name,
+                  MB_ICONQUESTION | MB_YESNO) == IDYES)
+               {
+                  /* Set the user's access security level to 0. */
+                  od_control.user_security = 0;
+
+                  ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP);
+               }
+               break;
+
+            case ID_VIEW_TOOL_BAR:
+            {
+               HKEY hOpenDoorsKey;
+
+               /* If toolbar on/off command has been chosen ... */
+               if(pWindowInfo->bToolbarOn)
+               {
+                  /* If the toolbar is on, then turn it off. */
+                  ODFrameDestroyToolbar(GetDlgItem(hwnd, ID_TOOLBAR),
+                     pWindowInfo);
+                  pWindowInfo->bToolbarOn = FALSE;
+                  CheckMenuItem(GetMenu(hwnd), ID_VIEW_TOOL_BAR,
+                     MF_BYCOMMAND | MF_UNCHECKED);
+               }
+               else
+               {
+                  /* If the toolbar is off, then turn it on. */
+                  pWindowInfo->hwndToolbar = ODFrameCreateToolbar(hwnd,
+                     pWindowInfo->hInstance, pWindowInfo);
+                  pWindowInfo->bToolbarOn = TRUE;
+                  CheckMenuItem(GetMenu(hwnd), ID_VIEW_TOOL_BAR,
+                     MF_BYCOMMAND | MF_CHECKED);
+                  ODFrameUpdateCmdUI();
+               }
+
+               /* Adjust window sizes accordingly. */
+               ODScrnAdjustWindows();
+
+               /* Update the toolbar setting in the registry. */
+               RegCreateKey(HKEY_CURRENT_USER, "Software\\Pirie\\OpenDoors",
+                  &hOpenDoorsKey);
+               RegSetValueEx(hOpenDoorsKey, "ToolBarOn", 0, REG_DWORD,
+                  (LPBYTE)&pWindowInfo->bToolbarOn,
+                  sizeof(pWindowInfo->bToolbarOn));
+               RegCloseKey(hOpenDoorsKey);
+               break;
+            }
+
+            case ID_VIEW_STAT_BAR:
+            {
+               HKEY hOpenDoorsKey;
+
+               /* If the status bar on/off command has been chosen ... */
+               if(pWindowInfo->bStatusBarOn)
+               {
+                  /* If the status bar is on, then turn it off. */
+                  pWindowInfo->bStatusBarOn = FALSE;
+                  CheckMenuItem(GetMenu(hwnd), ID_VIEW_STAT_BAR,
+                     MF_BYCOMMAND | MF_UNCHECKED);
+                  ODFrameDestroyStatusBar(GetDlgItem(hwnd, ID_STATUSBAR));
+               }
+               else
+               {
+                  /* If the status bar is off, then turn it on. */
+                  pWindowInfo->bStatusBarOn = TRUE;
+                  CheckMenuItem(GetMenu(hwnd), ID_VIEW_STAT_BAR,
+                     MF_BYCOMMAND | MF_CHECKED);
+                  pWindowInfo->hwndStatusBar =
+                     ODFrameCreateStatusBar(hwnd, pWindowInfo->hInstance);
+               }
+
+               /* Adjust window sizes accordingly. */
+               ODScrnAdjustWindows();
+
+               /* Update the status bar setting in the registry. */
+               RegCreateKey(HKEY_CURRENT_USER, "Software\\Pirie\\OpenDoors",
+                  &hOpenDoorsKey);
+               RegSetValueEx(hOpenDoorsKey, "StatusBarOn", 0, REG_DWORD,
+                  (LPBYTE)&pWindowInfo->bStatusBarOn,
+                  sizeof(pWindowInfo->bStatusBarOn));
+               RegCloseKey(hOpenDoorsKey);
+               break;
+            }
+
+            case ID_USER_ADDONEMINUTE:
+               /* If add one minute command has been chosen, then        */
+               /* increment the user's time, up to the maximum allowable */
+               /* time.                                                  */
+               if(od_control.user_timelimit < MAX_TIME)
+               {
+                  od_control.user_timelimit++;
+                  ODFrameUpdateTimeLeft(pWindowInfo);
+               }
+               break;
+
+            case ID_USER_ADDFIVEMINUTES:
+               /* If add five minutes command has been chosen, then */
+               /* adjust the user's time accordingly.               */
+               od_control.user_timelimit =
+                  MIN(od_control.user_timelimit + 5, MAX_TIME);
+               ODFrameUpdateTimeLeft(pWindowInfo);
+               break;
+
+            case ID_USER_SUBTRACTONEMINUTE:
+               /* If subtract one minute command has been chosen, then */
+               /* adjust the user's time accordingly.                  */
+               if(od_control.user_timelimit > MIN_TIME)
+               {
+                  od_control.user_timelimit--;
+                  ODFrameUpdateTimeLeft(pWindowInfo);
+               }
+               break;
+
+            case ID_USER_SUBTRACTFIVEMINUTES:
+               /* If the subtract five mintues command has been chosen, */
+               /* then adjust the user's time accordingly.              */
+               od_control.user_timelimit =
+                  MAX(od_control.user_timelimit - 5, MIN_TIME);
+               ODFrameUpdateTimeLeft(pWindowInfo);
+               break;
+
+            case ID_USER_INACTIVITYTIMER:
+               /* If the user inactivity timer command has been chosen, */
+               /* then toggle the timer on or off.                      */
+               od_control.od_disable_inactivity =
+                  !od_control.od_disable_inactivity;
+               CheckMenuItem(GetMenu(hwnd), ID_USER_INACTIVITYTIMER,
+                  MF_BYCOMMAND | (od_control.od_disable_inactivity ?
+                  MF_UNCHECKED : MF_CHECKED));
+               break;
+
+            case ID_TIME_EDIT:
+            {
+               /* If the user's time remaining has been directly edited, */
+               /* then adjust the time limit accordingly.                */
+               if(HIWORD(wParam) == EN_CHANGE)
+               {
+                  char szTimeText[40];
+                  GetWindowText((HWND)lParam, szTimeText, sizeof(szTimeText));
+                  od_control.user_timelimit = atoi(szTimeText);
+
+                  /* Do not allow the time limit to fall outside of the */
+                  /* valid range.                                       */
+                  od_control.user_timelimit =
+                     MAX(MIN_TIME, od_control.user_timelimit);
+                  od_control.user_timelimit =
+                     MIN(MAX_TIME, od_control.user_timelimit);
+
+                  /* Update the position of the up-down control. */
+                  SendMessage(pWindowInfo->hwndTimeUpDown, UDM_SETPOS, 0,
+                     (LPARAM)MAKELONG(od_control.user_timelimit, 0));
+               }
+            }
+
+            default:
+               return(TRUE);
+         }
+         return(FALSE);
+
+      case WM_NOTIFY:
+         /* A control parent notification message has been sent. */
+         switch(((LPNMHDR)lParam)->code)
+         {
+            case TTN_NEEDTEXT:
+            {
+               /* This is the message from the tool tip control, requesting */
+               /* the appropriate string to display for the current toolbar */
+               /* item.                                                     */
+               LPTOOLTIPTEXT lpToolTipText = (LPTOOLTIPTEXT)lParam;
+               switch(lpToolTipText->hdr.idFrom)
+               {
+                  case ID_DOOR_EXIT:
+                     lpToolTipText->lpszText = "Exit To BBS";
+                     break;
+                  case ID_DOOR_CHATMODE:
+                     lpToolTipText->lpszText = "Chat Mode";
+                     break;
+                  case ID_DOOR_USERKEYBOARDOFF:
+                     lpToolTipText->lpszText = "User Keyboard Off";
+                     break;
+                  case ID_DOOR_SYSOPNEXT:
+                     lpToolTipText->lpszText = "Sysop Next";
+                     break;
+                  case ID_DOOR_HANGUP:
+                     lpToolTipText->lpszText = "Hangup";
+                     break;
+                  case ID_DOOR_LOCKOUT:
+                     lpToolTipText->lpszText = "Lockout";
+                     break;
+               }
+               break;
+            }
+         }
+         break;
+
+      case WM_VSCROLL:
+         /* A scrolling action has taken place. */
+
+         /* If it is the time limit up-down control that has scrolled. */
+         if((HWND)lParam == pWindowInfo->hwndTimeUpDown)
+         {
+            int nPos = HIWORD(wParam);
+
+            /* Adjust the user's time limit. */
+            od_control.user_timelimit = MAX(MIN(nPos, MAX_TIME), MIN_TIME);
+
+            /* Update the time left displayed in the edit box. */
+            ODFrameUpdateTimeLeft(pWindowInfo);
+         }
+         break;
+
+      case WM_SIZE:
+         /* The OpenDoors frame window has been resized, so its contents */
+         /* must now be resized accordingly.                             */
+
+         /* Pass the message on to the status bar window, so that it will */
+         /* automatically adjust its own position and overall size.       */
+         SendMessage(GetDlgItem(hwnd, ID_STATUSBAR), WM_SIZE, wParam, lParam);
+
+         /* Now, adjust the size of each part of the status bar. */
+         ODFrameSizeStatusBar(GetDlgItem(hwnd, ID_STATUSBAR));
+
+         /* Pass the message on to the toolbar, so that it will resize */
+         /* iteself.                                                   */
+         SendMessage(GetDlgItem(hwnd, ID_TOOLBAR), WM_SIZE, wParam, lParam);
+         break;
+
+      case WM_MENUSELECT:
+         /* If the user has selected an item on the menu, then we should */
+         /* update the status bar accordingly.                           */
+         if(HIWORD(wParam) == 0xFFFF)
+         {
+            /* If menu is being exited, then turn off the status bar simple */
+            /* mode.                                                        */
+            HWND hwndStatusBar = GetDlgItem(hwnd, ID_STATUSBAR);
+
+            SendMessage(hwndStatusBar, SB_SIMPLE, (WPARAM)FALSE, 0L);
+         }
+         else
+         {
+            char szCommandString[160] = "";
+            HWND hwndStatusBar = GetDlgItem(hwnd, ID_STATUSBAR);
+
+            /* A new menu item is being selected. */
+
+            /* If this item is on the system menu, then provide the strings */
+            /* for any of those menu items.                                 */
+            if(HIWORD(wParam) & MF_SYSMENU)
+            {
+               switch(LOWORD(wParam))
+               {
+                  case SC_SIZE:
+                     strcpy(szCommandString,
+                        "Resizes this window.");
+                     break;
+                  case SC_MOVE:
+                     strcpy(szCommandString,
+                        "Moves this window.");
+                     break;
+                  case SC_MINIMIZE:
+                     strcpy(szCommandString,
+                        "Collapses this window to an icon.");
+                     break;
+                  case SC_MAXIMIZE:
+                     strcpy(szCommandString,
+                        "Expands this window to fill the screen.");
+                     break;
+                  case SC_CLOSE:
+                     strcpy(szCommandString,
+                        "Closes this window, and returns the user to the BBS.");
+                     break;
+                  case SC_RESTORE:
+                     strcpy(szCommandString,
+                        "Restores this window to normal size.");
+                     break;
+                  case SC_TASKLIST:
+                     strcpy(szCommandString,
+                        "");
+                     break;
+               }
+            }
+            else
+            {
+               /* If this item is on the window menu provided by OpenDoors, */
+               /* then load the status bar string for this command ID.      */
+               LoadString(pWindowInfo->hInstance, LOWORD(wParam),
+                  szCommandString, sizeof(szCommandString));
+            }
+
+            /* Switch the status bar into simple (single-paned) mode. */
+            SendMessage(hwndStatusBar, SB_SIMPLE, (WPARAM)TRUE, 0L);
+
+            /* Set the text for the status bar. */
+            SendMessage(hwndStatusBar, SB_SETTEXT, (WPARAM)255 | SBT_NOBORDERS,
+               (LPARAM)szCommandString);
+         }
+         return(DefWindowProc(hwnd, uMsg, wParam, lParam));
+
+      case WM_SHOW_MESSAGE:
+         if(pWindowInfo->hwndMessageWindow == NULL)
+         {
+            pWindowInfo->pszCurrentMessage = (char *)lParam;
+            pWindowInfo->nCurrentMessageFlags = (int)wParam;
+
+            /* Create the message window. */
+            DialogBoxParam(pWindowInfo->hInstance,
+               MAKEINTRESOURCE(IDD_MESSAGE), hwnd, ODFrameMessageDlgProc,
+               (LPARAM)pWindowInfo);
+         }
+         break;
+
+      case WM_REMOVE_MESSAGE:
+         if(pWindowInfo->hwndMessageWindow != NULL)
+         {
+            PostMessage(pWindowInfo->hwndMessageWindow, WM_COMMAND,
+               MAKELONG(IDOK, 0), 0L);
+            pWindowInfo->hwndMessageWindow = NULL;
+         }
+         break;
+
+      default:
+         /* Pass messages that we don't explicitly handle on to the */
+         /* default window proc.                                    */
+         return(DefWindowProc(hwnd, uMsg, wParam, lParam));
+   }
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameUpdateCmdUI()
+ *
+ * Updates the enabled and checked state of OpenDoors commands that may change.
+ *
+ * Parameters: None.
+ *
+ *     Return: void.
+ */
+void ODFrameUpdateCmdUI(void)
+{
+   HWND hwndFrame = hwndCurrentFrame;
+   HMENU hMenu = GetMenu(hwndFrame);
+   HWND hwndToolbar = GetDlgItem(hwndFrame, ID_TOOLBAR);
+   tODFrameWindowInfo *pWindowInfo;
+
+   if(hwndFrame == NULL) return;
+
+   /* Obtain window information structure. */
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndFrame, GWL_USERDATA);
+   if(pWindowInfo == NULL) return;
+
+   /* Check or uncheck the toolbar and status bar menu items. */
+   CheckMenuItem(hMenu, ID_VIEW_TOOL_BAR, MF_BYCOMMAND |
+      (pWindowInfo->bToolbarOn ? MF_CHECKED : MF_UNCHECKED));
+   CheckMenuItem(hMenu, ID_VIEW_STAT_BAR, MF_BYCOMMAND |
+      (pWindowInfo->bStatusBarOn ? MF_CHECKED : MF_UNCHECKED));
+
+   /* Check or uncheck the inactivity timer menu item. */
+   CheckMenuItem(hMenu, ID_USER_INACTIVITYTIMER, MF_BYCOMMAND |
+      (od_control.od_disable_inactivity ? MF_UNCHECKED : MF_CHECKED));
+
+   /* Check or uncheck the sysop next menu item and toolbar button. */
+   CheckMenuItem(hMenu, ID_DOOR_SYSOPNEXT, MF_BYCOMMAND |
+      (od_control.sysop_next ? MF_CHECKED : MF_UNCHECKED));
+   SendMessage(hwndToolbar, TB_CHECKBUTTON,
+      ID_DOOR_SYSOPNEXT, MAKELONG(od_control.sysop_next, 0));
+
+   /* Check or uncheck the keyboard off menu item and toolbar button. */
+   CheckMenuItem(hMenu, ID_DOOR_USERKEYBOARDOFF, MF_BYCOMMAND |
+      (od_control.od_user_keyboard_on ? MF_UNCHECKED : MF_CHECKED));
+   SendMessage(hwndToolbar, TB_CHECKBUTTON,
+      ID_DOOR_USERKEYBOARDOFF,
+      MAKELONG(!od_control.od_user_keyboard_on, 0));
+
+   /* Update the chat mode menu item and toolbar button. */
+   CheckMenuItem(hMenu, ID_DOOR_CHATMODE, MF_BYCOMMAND |
+      (od_control.od_chat_active ? MF_CHECKED : MF_UNCHECKED));
+   SendMessage(hwndToolbar, TB_CHECKBUTTON, ID_DOOR_CHATMODE,
+      MAKELONG(od_control.od_chat_active, 0));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameUpdateTimeDisplay()
+ *
+ * Updates the remaining time online that is displayed anywhere by the frame
+ * window. Uses ODFrameUpdateTimeLeft().
+ *
+ * Parameters: None.
+ *
+ *     Return: void.
+ */
+void ODFrameUpdateTimeDisplay(void)
+{
+   tODFrameWindowInfo *pWindowInfo;
+
+   /* If there is no current frame window, then return without doing */
+   /* anything.                                                      */
+   if(hwndCurrentFrame == NULL) return;
+
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndCurrentFrame,
+      GWL_USERDATA);
+   ASSERT(pWindowInfo != NULL);
+
+   ODFrameUpdateTimeLeft(pWindowInfo);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameUpdateWantChat()
+ *
+ * Updates the state of the flashing wants-chat indicator on the frame window.
+ *
+ * Parameters: None.
+ *
+ *     Return: void.
+ */
+void ODFrameUpdateWantChat(void)
+{
+   tODFrameWindowInfo *pWindowInfo;
+
+   /* If there is no current frame window, then return without doing */
+   /* anything.                                                      */
+   if(hwndCurrentFrame == NULL) return;
+
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndCurrentFrame,
+      GWL_USERDATA);
+   ASSERT(pWindowInfo != NULL);
+
+   /* If the status bar is on, then update the text displayed in the */
+   /* status bar's main pane.                                        */
+   if(pWindowInfo->bStatusBarOn)
+   {
+      ODFrameSetMainStatusText(pWindowInfo->hwndStatusBar);
+   }
+
+   /* Toggle the state of the wants-chat indicator, of needed. */
+   if(pWindowInfo->bWantsChatIndicator && !od_control.user_wantchat)
+   {
+      /* Restore original window text. */
+      SetWindowText(hwndCurrentFrame, od_control.od_prog_name);
+
+      /* Restore the window flash to its original state. */
+      FlashWindow(hwndCurrentFrame, FALSE);
+
+      /* Destroy the Windows timer. */
+      KillTimer(hwndCurrentFrame, 1);
+
+      /* Record that wants chat indicator is now off. */
+      pWindowInfo->bWantsChatIndicator = FALSE;
+   }
+   else if (!pWindowInfo->bWantsChatIndicator && od_control.user_wantchat)
+   {
+      /* Set window title to include the wants chat indicator. */
+      char szNewWindowTitle[sizeof(od_control.od_prog_name) + 20];
+      sprintf(szNewWindowTitle, "%s - User Wants Chat",
+         od_control.od_prog_name);
+      SetWindowText(hwndCurrentFrame, szNewWindowTitle);
+
+      /* Start the flashing the window. */
+      SetTimer(hwndCurrentFrame, 1,
+         GetCaretBlinkTime(), NULL);
+
+      /* Record that wants chat indicator is now on. */
+      pWindowInfo->bWantsChatIndicator = TRUE;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameDestroyWindow()                              *** PRIVATE FUNCTION ***
+ *
+ * Destroys the OpenDoors frame window and its children.
+ *
+ * Parameters: hwndFrame   - Handle to the window previously created by
+ *                           ODFrameCreateWindow().
+ *
+ *     Return: void.
+ */
+static void ODFrameDestroyWindow(HWND hwndFrame)
+{
+   tODFrameWindowInfo *pWindowInfo;
+
+   ASSERT(hwndFrame != NULL);
+
+   /* Obtain a pointer to the frame window information structure. */
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndFrame, GWL_USERDATA);
+
+   /* At this point, deallocate the accelerator table. */
+   if(pWindowInfo->hacclFrameCommands != NULL)
+   {
+      DestroyAcceleratorTable(pWindowInfo->hacclFrameCommands);
+   }
+
+   /* Destroying the main frame window will automatically cause its */
+   /* children to be destroyed.                                     */
+   DestroyWindow(hwndFrame);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameToolbarProc()                                *** PRIVATE FUNCTION ***
+ *
+ * The toolbar window proceedure.
+ *
+ * Parameters: hwnd   - Handle to the toolbar window.
+ *
+ *             uMsg   - Specifies the message.
+ *
+ *             wParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *             lParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *     Return: The return value is the result of the message processing and
+ *             depends on the message.
+ */
+LRESULT CALLBACK ODFrameToolbarProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   switch(uMsg)
+   {
+      /* Forward needed message to the main frame window proceedure. */
+      case WM_VSCROLL:
+      case WM_COMMAND:
+         SendMessage(GetParent(hwnd), uMsg, wParam, lParam);
+         break;
+   }
+
+   /* Pass all messages on to the default toolbar window proceedure. */
+   return(CallWindowProc(pfnDefToolbarProc, hwnd, uMsg, wParam, lParam));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameUpdateTimeLeft()                             *** PRIVATE FUNCTION ***
+ *
+ * Updates the displayed time remaining from od_control.user_timelimit.
+ *
+ * Parameters: pWindowInfo - Pointer to frame window information structure.
+ *
+ *     Return: void.
+ */
+static void ODFrameUpdateTimeLeft(tODFrameWindowInfo *pWindowInfo)
+{
+   char szTimeLeft[12];
+   RECT rcWindow;
+
+   if(pWindowInfo->hwndTimeEdit == NULL)
+   {
+      /* If the time limit edit control does not exist (i.e., if the       */
+      /* toolbar is not currently on), then there is nothing for us to do. */
+      return;
+   }
+
+   /* Generate the string to be displayed in the edit control. */
+   sprintf(szTimeLeft, "%d min.", od_control.user_timelimit);
+
+   /* Set the edit control's text to the new string. */
+   SetWindowText(pWindowInfo->hwndTimeEdit, szTimeLeft);
+
+   /* Force edit control to be redrawn. (Except for rightmost pixel */
+   /* column.)                                                      */
+   GetWindowRect(pWindowInfo->hwndTimeEdit, &rcWindow);
+   rcWindow.right--;
+   InvalidateRect(pWindowInfo->hwndTimeEdit, &rcWindow, TRUE);
+
+   /* Set the position of the up-down control to match. */
+   SendMessage(pWindowInfo->hwndTimeUpDown, UDM_SETPOS, 0,
+      (LPARAM)MAKELONG(od_control.user_timelimit, 0));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameTimeEditProc()                               *** PRIVATE FUNCTION ***
+ *
+ * The time edit window proceedure. Relays mouse messages from the edit box
+ * to the tooltip control, and then passes all messages on to the standard
+ * edit box window proceedure.
+ *
+ * Parameters: hwnd   - Handle to the time edit window.
+ *
+ *             uMsg   - Specifies the message.
+ *
+ *             wParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *             lParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *     Return: The return value is the result of the message processing and
+ *             depends on the message.
+ */
+LRESULT CALLBACK ODFrameTimeEditProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   switch(uMsg)
+   {
+      case WM_MOUSEMOVE:
+      case WM_LBUTTONDOWN:
+      case WM_LBUTTONUP:
+      {
+         MSG msg;
+         HWND hwndToolTip;
+
+         /* Setup message structure. */
+         msg.lParam = lParam;
+         msg.wParam = wParam;
+         msg.message = uMsg;
+         msg.hwnd = hwnd;
+
+         /* Obtain handle to the tooltip window. */
+         hwndToolTip = (HWND)SendMessage(GetParent(hwnd), TB_GETTOOLTIPS,
+            0, 0);
+
+         /* Relay the message to the tooltip window. */
+         SendMessage(hwndToolTip, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
+
+         break;
+      }
+   }
+
+   /* Pass all messages on to the default edit box window proceedure. */
+   return(CallWindowProc(pfnDefEditProc, hwnd, uMsg, wParam, lParam));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameAboutDlgProc()
+ *
+ * DialogProc for the OpenDoors default Help About dialog box.
+ *
+ * Parameters: hwndDlg  - Window handle to the dialog box.
+ *
+ *             uMsg     - Message ID.
+ *
+ *             wParam   - First message parameter.
+ *
+ *             lParam   - Second message parameter.
+ *
+ *     Return: TRUE if message is processed, FALSE otherwise.
+ */
+BOOL CALLBACK ODFrameAboutDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   switch(uMsg)
+   {
+      case WM_INITDIALOG:
+         /* At dialog box creation time, update the text in the about      */
+         /* box with any information provided by the OpenDoors programmer. */
+
+         /* If a program name has been provided, then display it. */
+         if(strcmp(od_control.od_prog_name, OD_VER_SHORTNAME) != 0)
+         {
+            SetWindowText(GetDlgItem(hwndDlg, IDC_DOORNAME),
+               od_control.od_prog_name);
+         }
+
+         /* If copyright information has been provided, then display it. */
+         if(strlen(od_control.od_prog_copyright) > 0)
+         {
+            SetWindowText(GetDlgItem(hwndDlg, IDC_COPYRIGHT),
+               od_control.od_prog_copyright);
+         }
+
+         /* If program version information has been provided, then display */
+         /* it.                                                            */
+         if(strlen(od_control.od_prog_version) > 0)
+         {
+            SetWindowText(GetDlgItem(hwndDlg, IDC_VERSION),
+               od_control.od_prog_version);
+         }
+
+         /* Center the about dialog box in the area occupied by the */
+         /* main frame window.                                      */
+         ODFrameCenterWindowInParent(hwndDlg);
+
+         return(TRUE);
+
+      case WM_COMMAND:
+         /* If a command has been chosen. */
+         switch(LOWORD(wParam))
+         {
+            case IDCANCEL:
+            case IDOK:
+               /* If the OK button has been pressed, then close the dialog. */
+               EndDialog(hwndDlg, IDOK);
+               break;
+         }
+         return(TRUE);
+
+      default:
+         /* Otherwise, indicate that this message has not been processed. */
+         return(FALSE);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameCenterWindowInParent()                       *** PRIVATE FUNCTION ***
+ *
+ * Repositions the specified window so that it is centered in its parent.
+ *
+ * Parameters: hwndChild - The window to reposition.
+ *
+ *     Return: void.
+ */
+void ODFrameCenterWindowInParent(HWND hwndChild)
+{
+   HWND hwndParent;
+   RECT rcParent;
+   RECT rcChild;
+   INT nChildWidth;
+   INT nChildHeight;
+   INT nParentWidth;
+   INT nParentHeight;
+
+   ASSERT(hwndChild != NULL);
+
+   /* Obtain a handle to the parent window. */
+   hwndParent = GetParent(hwndChild);
+   ASSERT(hwndParent != NULL);
+
+   /* Obtain the bounding boxes of both windows. */
+   GetWindowRect(hwndChild, &rcChild);
+   GetWindowRect(hwndParent, &rcParent);
+   
+   /* Determine the height and width of both windows. */
+   nChildWidth = rcChild.right - rcChild.left;
+   nChildHeight = rcChild.bottom - rcChild.top;
+   nParentWidth = rcParent.right - rcParent.left;
+   nParentHeight = rcParent.bottom - rcParent.top;
+
+   /* Move the child to the center of the parent. */
+   SetWindowPos(hwndChild, NULL,
+      rcParent.left + (nParentWidth - nChildWidth) / 2,
+      rcParent.top + (nParentHeight - nChildHeight) / 2,
+      0, 0, SWP_NOSIZE | SWP_NOZORDER);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameMessageLoop()                                *** PRIVATE FUNCTION ***
+ *
+ * Message loop for OpenDoors local UI thread (frame window handling).
+ *
+ * Parameters: hInstance   - Handle to current instance.
+ *
+ *             hwndFrame   - Handle to the frame window.
+ *
+ *     Return: void.
+ */
+static void ODFrameMessageLoop(HANDLE hInstance, HWND hwndFrame)
+{
+   MSG msg;
+
+   ASSERT(hInstance != NULL);
+   ASSERT(hwndFrame != NULL);
+
+   /* Loop, fetching, translating and dispatching messages for any windows */
+   /* created by this thread. (GetMessage() blocks when no messages are    */
+   /* available.)                                                          */
+   while(GetMessage(&msg, NULL, 0, 0))
+   {
+      if(!ODFrameTranslateAccelerator(hwndFrame, &msg))
+      {
+         TranslateMessage(&msg);
+         DispatchMessage(&msg);
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameTranslateAccelerator()
+ *
+ * Translates WM_KEYDOWN or WM_SYSKEYDOWN messages to frame window commands,
+ * if needed, based on the frame window's accelerator table.
+ *
+ * Parameters: hwndFrame  - Handle to the OpenDoors main frame window.
+ *
+ *             pMsg       - Pointer to the message that may require
+ *                          translation.
+ *
+ *     Return: TRUE if message was translated, FALSE if not.
+ */
+BOOL ODFrameTranslateAccelerator(HWND hwndFrame, LPMSG pMsg)
+{
+   tODFrameWindowInfo *pWindowInfo;
+
+   ASSERT(hwndFrame != NULL);
+   ASSERT(pMsg != NULL);
+
+   /* Obtain a pointer to the frame window information structure. */
+   pWindowInfo = (tODFrameWindowInfo *)GetWindowLong(hwndFrame, GWL_USERDATA);
+   ASSERT(pWindowInfo != NULL);
+
+   /* Perform accelerator translation, based on the frame window's */
+   /* accelerator table, sending any resulting WM_COMMAND messages */
+   /* to the frame window.                                         */
+   return(TranslateAccelerator(hwndFrame, pWindowInfo->hacclFrameCommands,
+      pMsg) != 0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameThreadProc()                                 *** PRIVATE FUNCTION ***
+ *
+ * Function that execute the OpenDoors frame window thread.
+ *
+ * Parameters: pParam   - The thread parameter, which must be the handle to the
+ *                        current application instance.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+DWORD OD_THREAD_FUNC ODFrameThreadProc(void *pParam)
+{
+   HWND hwndFrame;
+   HANDLE hInstance = (HANDLE)pParam;
+
+   /* Create the frame window. */
+   hwndFrame = ODFrameCreateWindow(hInstance);
+
+   if(hwndFrame == NULL)
+   {
+      return(FALSE);
+   }
+
+   /* Store a pointer to the frame window. */
+   hwndCurrentFrame = hwndFrame;
+
+   /* Loop, processing messages for the frame window. */
+   ODFrameMessageLoop(hInstance, hwndFrame);
+
+   /* Destroy the frame window. */
+   ODFrameDestroyWindow(hwndFrame);
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameStart()
+ *
+ * Function that starts up the frame window.
+ *
+ * Parameters: hInstance     - Handle to the current application instance.
+ *
+ *             phFrameThread - Pointer to the frame thread handle.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODFrameStart(HANDLE hInstance, tODThreadHandle *phFrameThread)
+{
+   return(ODThreadCreate(phFrameThread, ODFrameThreadProc,
+      (void *)hInstance));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFrameMessageDlgProc()
+ *
+ * Dialog proceedure for the OpenDoors message window.
+ *
+ * Parameters: hwndDlg  - Window handle to the dialog box.
+ *
+ *             uMsg     - Message ID.
+ *
+ *             wParam   - First message parameter.
+ *
+ *             lParam   - Second message parameter.
+ *
+ *     Return: TRUE if message is processed, FALSE otherwise.
+ */
+BOOL CALLBACK ODFrameMessageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   switch(uMsg)
+   {
+      case WM_INITDIALOG:
+      {
+         tODFrameWindowInfo *pWindowInfo;
+
+         pWindowInfo = (tODFrameWindowInfo *)lParam;
+
+         ASSERT(pWindowInfo != NULL);
+
+         pWindowInfo->hwndMessageWindow = hwndDlg;
+         
+         /* Set the message window title. */
+         SetWindowText(hwndDlg, od_control.od_prog_name);
+
+         /* Change the text displayed in the message window. */
+         SetWindowText(GetDlgItem(hwndDlg, IDC_MESSAGE_TEXT1),
+            (char *)pWindowInfo->pszCurrentMessage);
+
+         /* Center window in parent window. */
+         ODFrameCenterWindowInParent(hwndDlg);
+
+         return(FALSE);
+      }
+
+      case WM_COMMAND:
+         /* If a command has been chosen. */
+         switch(LOWORD(wParam))
+         {
+            case IDOK:
+               /* If the OK button has been pressed, then close the dialog. */
+               EndDialog(hwndDlg, IDOK);
+               break;
+         }
+         return(TRUE);
+
+      default:
+         /* Indicate that this message has not been processed. */
+         return(FALSE);
+   }
+
+   return(TRUE);
+}
+
+
+#endif /* ODPLAT_WIN32 */

+ 58 - 0
odoors/ODFrame.h

@@ -0,0 +1,58 @@
+/* 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: ODFrame.h
+ *
+ * Description: Defines the public interface to the OpenDoors frame window.
+ *              This file is only applicable when building the Win32 version
+ *              of OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Aug 20, 1995  6.00  BP   Created.
+ *              Jan 20, 1996  6.00  BP   Made ODFrameCenter...() shared.
+ *              Feb 17, 1996  6.00  BP   Add ...Accelerator() return value.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODFRAME
+#define _INC_ODFRAME
+
+#include "ODPlat.h"
+#include "ODGen.h"
+
+#ifdef ODPLAT_WIN32
+
+/* Public frame window functions. */
+tODResult ODFrameStart(HANDLE hInstance, tODThreadHandle *phFrameThread);
+INT ODFrameGetUsedClientAtTop(HWND hwndFrame);
+INT ODFrameGetUsedClientAtBottom(HWND hwndFrame);
+BOOL ODFrameTranslateAccelerator(HWND hwndFrame, LPMSG pMsg);
+void ODFrameUpdateCmdUI(void);
+void ODFrameUpdateTimeDisplay(void);
+void ODFrameUpdateWantChat(void);
+void ODFrameCenterWindowInParent(HWND hwndChild);
+
+/* User defined messages that are handled by the frame window. */
+#define WM_SHOW_MESSAGE             (WM_USER + 1)
+#define WM_REMOVE_MESSAGE           (WM_USER + 2)
+
+#endif /* ODPLAT_WIN32 */
+
+#endif /* _INC_ODFRAME */

+ 199 - 0
odoors/ODGen.h

@@ -0,0 +1,199 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODGen.h
+ *
+ * Description: Contains general definitions used throughout OpenDoors,
+ *              including: - version information manifest constants
+ *                         - debugging macros
+ *                         - compiler-dependent definitions
+ *                         - internally used macros
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   Created.
+ *              Oct 20, 1994  6.00  BP   Added DIM macro.
+ *              Dec 31, 1994  6.00  BP   Remove USEINLINE option.
+ *              Dec 12, 1995  6.00  BP   Moved ODPLAT_??? to OpenDoor.h.
+ *              Dec 19, 1995  6.00  BP   Implement ASSERT() for Win32.
+ *              Jan 23, 1996  6.00  BP   Added OD_TEXTMODE.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 24, 1996  6.00  BP   Turn off OD_DIAGNOSTICS.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 03, 1996  6.10  BP   Moved ODFAR to OpenDoor.h.
+ *              Oct 19, 2001  6.20  RS   Incremented version for socket support.
+ */
+
+#ifndef _INC_ODGEN
+#define _INC_ODGEN
+
+
+/* PLATFORM-SPECIFIC DEFINITIONS. */
+
+/* DLL specific defintions. */
+#ifdef OD_DLL
+#ifdef ODPLAT_WIN32
+#define OD_DLL_NAME "ODOORS62"
+#endif /* ODPLAT_WIN32 */
+#endif /* OD_DLL */
+
+/* Mutlithreading specific definitions. */
+#ifdef ODPLAT_WIN32
+#define OD_MULTITHREADED
+#endif /* ODPLAT_WIN32 */
+
+/* Text mode specific definitions. */
+#if defined(ODPLAT_DOS) || defined(ODPLAT_NIX)
+#define OD_TEXTMODE
+#endif /* ODPLAT_DOS */
+
+/* DOS specific definitions. */
+#ifdef ODPLAT_DOS
+
+/* Keyword to flag ISR functions. */
+#define INTERRUPT interrupt
+
+/* Inline assembly keyword varies from compiler to compiler. */
+#ifdef _MSC_VER
+#define ASM __asm
+#else
+#define ASM asm
+#endif
+
+/* Memory model information. */
+#ifdef __TINY__
+#define SMALLDATA
+#define SMALLCODE
+#endif
+#ifdef __SMALL__
+#define SMALLDATA
+#define SMALLCODE
+#endif
+#ifdef __COMPACT__
+#define LARGEDATA
+#define SMALLCODE
+#endif
+#ifdef __MEDIUM__
+#define SMALLDATA
+#define LARGECODE
+#endif
+#ifdef __LARGE__
+#define LARGEDATA
+#define LARGECODE
+#endif
+#ifdef __HUGE__
+#define LARGEDATA
+#define LARGECODE
+#endif
+#endif /* ODPLAT_DOS */
+
+
+/* VERSION INFORMATION CONSTANTS. */
+#define OD_VER_SHORTNAME   "OpenDoors"
+#define OD_VER_STATUSLINE  "  OpenDoors 6.24 - (C) Copyright 1991-2001" \
+                           " by Brian Pirie                      "
+#define OD_VER_UNREG_STAT  "  OpenDoors 6.24  *WARNING* Unregistered Version" \
+                           " - Limit 1 month trial period! "
+
+#ifdef ODPLAT_DOS
+#define OD_VER_SIGNON      "[OpenDoors 6.24/DOS - " \
+                           "(C) Copyright 1991-2001 by Brian Pirie]\n\r"
+#define OD_VER_FULLNAME    "OpenDoors 6.24/DOS"
+#endif /* ODPLAT_DOS */
+#ifdef ODPLAT_WIN32
+#define OD_VER_SIGNON      "[OpenDoors 6.24/Win32 - " \
+                           "(C) Copyright 1991-2001 by Brian Pirie]\n\r"
+#define OD_VER_FULLNAME    "OpenDoors 6.24/Win32"
+#endif /* ODPLAT_WIN32 */
+#ifdef ODPLAT_NIX
+#define OD_VER_SIGNON      "[OpenDoors 6.24/*nix - " \
+                           "(C) Copyright 1991-2001 by Brian Pirie]\n\r"
+#define OD_VER_FULLNAME    "OpenDoors 6.24/*nix"
+#endif /* ODPLAT_NIX */
+
+
+/* COMPILER DEPENDENT DEFINITIONS. */
+
+/* Some compilers don't like const keyword on parameters. */
+#define CONST const
+
+
+/* DEBUG MACROS. */
+
+/* OD_DEBUG is defined for debug version of the library. */
+/* #define OD_DEBUG */
+
+/* OD_DIAGNOSTICS is defined to enable od_internal_debug. */
+/* #define OD_DIAGNOSTICS */
+
+/* ASSERTion macro - terminates if test condition fails. */
+#ifdef OD_DEBUG
+#define __STR(x) __VAL(x)
+#define __VAL(x) #x
+#ifdef ODPLAT_WIN32
+#define ASSERT(x) if(!(x)) { MessageBox(NULL, __FILE__ ":" \
+   __STR(__LINE__) "\n" #x,  OD_VER_FULLNAME " - Test condition failed", \
+   MB_ICONSTOP | MB_OK); exit(1); }
+#else /* !ODPLAT_WIN32 */
+#define ASSERT(x) if(!(x)) { puts(OD_VER_FULLNAME \
+   " - Test condition failed:\n" __FILE__ ":" __STR(__LINE__) "\n" #x); \
+   exit(1); }
+#endif /* !ODPLAT_WIN32 */
+#else /* !OD_DEBUG */
+#define ASSERT(x)
+#endif /* !OD_DEBUG */
+
+/* TRACE() macro - used to generate debug output. */
+#ifdef OD_TRACE
+#include <stdio.h>
+#define TRACE_API 1
+#define TRACE(x, y) printf("[%s]", y);
+#else
+#define TRACE(x, y)
+#endif
+
+
+/* SCREEN SIZE. */
+#define OD_SCREEN_WIDTH    80
+#define OD_SCREEN_HEIGHT   25
+
+
+/* INTERNALLY USED MACROS. */
+
+/* MIN() and MAX() macros. Note that expressions passed to macros may be */
+/* evaluated more than once. For this reason, it is best to only pass    */
+/* constants or variables to these macros.                               */
+#ifndef MIN
+#define MIN(x, y) ((x) > (y)) ? (y) : (x)
+#endif /* !MIN */
+#ifndef MAX
+#define MAX(x, y) ((x) > (y)) ? (x) : (y)
+#endif /* !MAX */
+
+/* DIM() macro. Returns the number of elements in an array. */
+#ifndef DIM
+#define DIM(x) (sizeof(x) / sizeof(*x))
+#endif /* !DIM */
+
+/* UNUSED() macro. Used to flag that a function parameter is intentionally */
+/* not used, thus preventing a compile-time warning.                       */
+#define UNUSED(x) ((void)(x))
+
+#endif /* !_INC_ODGEN */

+ 511 - 0
odoors/ODGetIn.c

@@ -0,0 +1,511 @@
+/* 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: ODGetIn.c
+ *
+ * Description: Implements the od_get_input() function, which obtains the
+ *              next input event of any type, optionally performing
+ *              translation.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Jan 04, 1996  6.00  BP   Created.
+ *              Jan 30, 1996  6.00  BP   Tweaked TREAT_ESC_AS_ANSI_TIMEOUT.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
+ *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
+ *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 19, 1996  6.00  BP   Switched to table implementation.
+ *              Feb 25, 1996  6.00  BP   Added new control sequences to table.
+ *              Feb 27, 1996  6.00  BP   Added od_max_key_latency.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ *              Apr 11, 2005  6.23  SH   Fix hang forver on ESC press and latency timeout.
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stddef.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#include "ODInQue.h"
+#include "ODCore.h"
+#include "ODKrnl.h"
+
+/* Control sequence table definitions. */
+typedef struct
+{
+   char *pszSequence;
+   char chExtendedKey;
+   BOOL bIsControlKey;
+} tODKeySequence;
+
+tODKeySequence aKeySequences[] =
+{
+   /* VT-52 control sequences. */
+   {"\033A", OD_KEY_UP, FALSE},
+   {"\033B", OD_KEY_DOWN, FALSE},
+   {"\033C", OD_KEY_RIGHT, FALSE},
+   {"\033D", OD_KEY_LEFT, FALSE},
+   {"\033H", OD_KEY_HOME, FALSE},
+   {"\033K", OD_KEY_END, FALSE},
+   {"\033P", OD_KEY_F1, FALSE},
+   {"\033Q", OD_KEY_F2, FALSE},
+   {"\033?w", OD_KEY_F3, FALSE},
+   {"\033?x", OD_KEY_F4, FALSE},
+   {"\033?t", OD_KEY_F5, FALSE},
+   {"\033?u", OD_KEY_F6, FALSE},
+   {"\033?q", OD_KEY_F7, FALSE},
+   {"\033?r", OD_KEY_F8, FALSE},
+   {"\033?p", OD_KEY_F9, FALSE},
+
+   /* Control sequences common to VT-100/VT-102/VT-220/VT-320/ANSI. */
+   {"\033[A", OD_KEY_UP, FALSE},
+   {"\033[B", OD_KEY_DOWN, FALSE},
+   {"\033[C", OD_KEY_RIGHT, FALSE},
+   {"\033[D", OD_KEY_LEFT, FALSE},
+   {"\033[V", OD_KEY_PGUP, FALSE},
+   {"\033[U", OD_KEY_PGDN, FALSE},
+   {"\033[H", OD_KEY_HOME, FALSE},
+   {"\033[K", OD_KEY_END, FALSE},
+   {"\033OP", OD_KEY_F1, FALSE},
+   {"\033OQ", OD_KEY_F2, FALSE},
+   {"\033OR", OD_KEY_F3, FALSE},
+   {"\033OS", OD_KEY_F4, FALSE},
+
+   /* VT-220/VT-320 specific control sequences. */
+   {"\033[1~",  OD_KEY_HOME, FALSE}, /* Windows XP telnet.exe.  Is actually FIND */
+   {"\033[2~",  OD_KEY_INSERT, FALSE},
+   {"\033[3~",  OD_KEY_DELETE, FALSE},
+   {"\033[4~",  OD_KEY_END, FALSE},  /* Windows XP telnet.exe.  Is actually SELECT */
+   {"\033[5~",  OD_KEY_PGUP, FALSE},
+   {"\033[6~",  OD_KEY_PGDN, FALSE},
+   {"\033[17~", OD_KEY_F6, FALSE},
+   {"\033[18~", OD_KEY_F7, FALSE},
+   {"\033[19~", OD_KEY_F8, FALSE},
+   {"\033[20~", OD_KEY_F9, FALSE},
+   {"\033[21~", OD_KEY_F10, FALSE},
+   {"\033[23~", OD_KEY_F11, FALSE},
+   {"\033[24~", OD_KEY_F12, FALSE},
+
+   /* ANSI-specific control sequences. */
+   {"\033[L", OD_KEY_HOME, FALSE},
+   {"\033Ow", OD_KEY_F3, FALSE},
+   {"\033Ox", OD_KEY_F4, FALSE},
+   {"\033Ot", OD_KEY_F5, FALSE},
+   {"\033Ou", OD_KEY_F6, FALSE},
+   {"\033Oq", OD_KEY_F7, FALSE},
+   {"\033Or", OD_KEY_F8, FALSE},
+   {"\033Op", OD_KEY_F9, FALSE},
+
+   /* PROCOMM-specific control sequences (non-keypad alternatives). */
+   {"\033OA", OD_KEY_UP, FALSE},
+   {"\033OB", OD_KEY_DOWN, FALSE},
+   {"\033OC", OD_KEY_RIGHT, FALSE},
+   {"\033OD", OD_KEY_LEFT, FALSE},
+   {"\033OH", OD_KEY_HOME, FALSE},
+   {"\033OK", OD_KEY_END, FALSE},
+
+   /* Other standard control sequences. */
+   {"\x16\t", OD_KEY_INSERT, TRUE},
+
+   /* OpenDoors-specific alternatives. */
+   {"\x7f", OD_KEY_DELETE, FALSE},
+   {"\x5", OD_KEY_UP, TRUE},
+   {"\x18", OD_KEY_DOWN, TRUE},
+   {"\x13", OD_KEY_LEFT, TRUE},
+   {"\x4", OD_KEY_RIGHT, TRUE},
+   {"\x7", OD_KEY_DELETE, TRUE},
+   {"\x16", OD_KEY_INSERT, TRUE},
+};
+
+/* Constant that indicates no match has been found. */
+#define NO_MATCH DIM(aKeySequences)
+
+/* Configurable constants. */
+
+/* The time, in milliseconds, to wait for a second character in a control */
+/* sequence, before deciding that none is going to come.                  */
+#define MAX_CHARACTER_LATENCY 250
+
+/* Size of inbound sequence buffer. */
+#define SEQUENCE_BUFFER_SIZE 10
+
+/* Current control sequence received state. */
+static char szCurrentSequence[SEQUENCE_BUFFER_SIZE] = "";
+#if 0	// Unused...
+static tODTimer SequenceFailTimer;
+static BOOL bSequenceFromRemote;
+static int nMatchedSequence = NO_MATCH;
+static BOOL bTimerActive = FALSE;
+#endif
+static BOOL bDoorwaySequence = FALSE;
+static BOOL bDoorwaySequencePending = FALSE;
+
+/* Local private function prototypes. */
+static int ODGetCodeIfLongest(WORD wFlags);
+static int ODHaveStartOfSequence(WORD wFlags);
+static int ODLongestFullCode(WORD wFlags);
+static void ODShiftSeq(int chars);
+
+/* ----------------------------------------------------------------------------
+ * od_get_input()
+ *
+ * Obtains the next input event of any type, optionally performing
+ * translation on input events.
+ *
+ * Parameters: pInputEvent  - Pointer to a tODInputEvent structure, which
+ *                            will be filled by information on the next input
+ *                            event, if any is obtained.
+ *
+ *             TimeToWait   - Number of milliseconds to wait for input to be
+ *                            available. A value of 0 causes od_get_input()
+ *                            to return immediately if no input is waiting,
+ *                            while a value of OD_NO_TIMEOUT causes the
+ *                            function to never return unless input has been
+ *                            obtained.
+ *
+ *             wFlags       - Flags which customize od_get_input()'s behaviour.
+ *
+ *     Return: TRUE if an input event was obtained, FALSE if not.
+ */
+ODAPIDEF BOOL ODCALL od_get_input(tODInputEvent *pInputEvent,
+   tODMilliSec TimeToWait, WORD wFlags)
+{
+   tODInputEvent LastInputEvent;
+   int nSequence;
+   int nSequenceLen;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_get_input()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check for parameter validity. */
+   if(pInputEvent == NULL)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Call the OpenDoors kernel, if applicable */
+   CALL_KERNEL_IF_NEEDED();
+
+   /* If you don't know better, it's a remote char */
+   /* Local chars are always correctly received (no ANSI sequences) */
+   LastInputEvent.bFromRemote = TRUE;
+
+   if((!bDoorwaySequence) && bDoorwaySequencePending && (!szCurrentSequence[0])) {
+      bDoorwaySequencePending=FALSE;
+      bDoorwaySequence=TRUE;
+   }
+
+   /* If no pending input string, wait for the first keystroke */
+   if((!szCurrentSequence[0]) && (!bDoorwaySequence)) {
+      if(ODInQueueGetNextEvent(hODInputQueue, &LastInputEvent, TimeToWait)
+            != kODRCSuccess) {
+         OD_API_EXIT();
+         return(FALSE);
+      }
+
+      /* If you have a *local* char, send it immediately */
+      if((!LastInputEvent.bFromRemote) && (LastInputEvent.chKeyPress != 0)
+#if 0
+            && (LastInputEvent.EventType == EVENT_EXTENDED_KEY)) {
+#else
+            ) {
+#endif
+         memcpy(pInputEvent, &LastInputEvent, sizeof(tODInputEvent));
+         OD_API_EXIT();
+         return(TRUE);
+      }
+
+      /* IF the received char is a NULL, this is the start of a doorway char */
+      if(!LastInputEvent.chKeyPress)
+         bDoorwaySequence = TRUE;
+      else {
+         szCurrentSequence[0]=LastInputEvent.chKeyPress;
+         szCurrentSequence[1]=0;
+      }
+   }
+
+   nSequenceLen=strlen(szCurrentSequence);
+
+   CALL_KERNEL_IF_NEEDED();
+
+   /* If you don't have the start of a sequence, and it's not doorway mode, you have
+    * a char.  It's that simple.
+    */
+   if((!bDoorwaySequence) && (!ODHaveStartOfSequence(wFlags))) {
+      pInputEvent->chKeyPress = szCurrentSequence[0];
+      pInputEvent->EventType = EVENT_CHARACTER;
+      pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+      /* Shift the sequence buffer over one */
+      ODShiftSeq(1);
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* Now... if the current sequence IS the longest valid one, return it
+    * immediately.  If it's sequence leftovers, it HAS to be a remote key
+    * since local chars are #1 always doorway mode and #2 have no delay
+    * betwixt them.
+    */
+   if((nSequence = ODGetCodeIfLongest(wFlags)) != NO_MATCH) {
+      pInputEvent->chKeyPress = aKeySequences[nSequence].chExtendedKey;
+      pInputEvent->EventType = EVENT_EXTENDED_KEY;
+      pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+      /* Shift the sequence buffer... being sure to copy the terminator */
+      ODShiftSeq(strlen(aKeySequences[nSequence].pszSequence));
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* Now, continue adding chars, waiting at MOST MAX_CHARACTER_LATENCY between them */
+   CALL_KERNEL_IF_NEEDED();
+   while((!bDoorwaySequencePending)
+            && (ODInQueueGetNextEvent(hODInputQueue, &LastInputEvent, MAX_CHARACTER_LATENCY)
+            == kODRCSuccess)) {
+      /* If you are looking for a doorway sequence, any char completes it (honest!) */
+      /* Further, thanks to some lack of planning, it's EXACTLY THE SAME as the char,
+       * only it's extended.
+       */
+      if(bDoorwaySequence) {
+         memcpy(pInputEvent, &LastInputEvent, sizeof(tODInputEvent));
+         pInputEvent->EventType = EVENT_EXTENDED_KEY;
+         bDoorwaySequence=FALSE;
+         OD_API_EXIT();
+         return(TRUE);
+      }
+
+      /* If we get a 0, we *WILL BE* looking for a doorway sequence.  But NOT
+       * until the current sequence buffer is drained!
+       */
+      if(LastInputEvent.chKeyPress == 0) {
+         bDoorwaySequencePending=TRUE;
+         break;
+      }
+
+      /* If you have a *local* char, send it immediately */
+      if((!LastInputEvent.bFromRemote) && (LastInputEvent.chKeyPress != 0)
+#if 0
+            && (LastInputEvent.EventType == EVENT_EXTENDED_KEY)) {
+#else
+            ) {
+#endif
+         memcpy(pInputEvent, &LastInputEvent, sizeof(tODInputEvent));
+         OD_API_EXIT();
+         return(TRUE);
+      }
+
+      /* Put this char into the sequence buffer */
+      szCurrentSequence[nSequenceLen++]=LastInputEvent.chKeyPress;
+      szCurrentSequence[nSequenceLen]=0;
+
+      /* When you have the longest possible sequence, you ARE done */
+      if((nSequence = ODGetCodeIfLongest(wFlags)) != NO_MATCH) {
+         pInputEvent->chKeyPress = aKeySequences[nSequence].chExtendedKey;
+         pInputEvent->EventType = EVENT_EXTENDED_KEY;
+         pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+         /* Shift the sequence buffer... being sure to copy the terminator */
+         ODShiftSeq(strlen(aKeySequences[nSequence].pszSequence));
+         OD_API_EXIT();
+         return(TRUE);
+      }
+      CALL_KERNEL_IF_NEEDED();
+   }
+
+   /* If we were looking for a doorway sequence, tough, we didn't get it. */
+   if(bDoorwaySequence) {
+      pInputEvent->chKeyPress = 0;
+      pInputEvent->EventType = EVENT_CHARACTER;
+      pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+      bDoorwaySequence=FALSE;
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* Now, if we have any kind of sequence, we'll settle for it. */
+   if((nSequence = ODLongestFullCode(wFlags)) != NO_MATCH) {
+      pInputEvent->chKeyPress = aKeySequences[nSequence].chExtendedKey;
+      pInputEvent->EventType = EVENT_EXTENDED_KEY;
+      pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+      /* Shift the sequence buffer... being sure to copy the terminator */
+      ODShiftSeq(strlen(aKeySequences[nSequence].pszSequence));
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* If we don't have a complete sequence, send a single char */
+   pInputEvent->chKeyPress = szCurrentSequence[0];
+   if(szCurrentSequence[0]) {
+      pInputEvent->EventType = EVENT_CHARACTER;
+      pInputEvent->bFromRemote = LastInputEvent.bFromRemote;
+      /* Shift the sequence buffer over one */
+      ODShiftSeq(1);
+   }
+   OD_API_EXIT();
+   /* Return false if something broke */
+   return(pInputEvent->chKeyPress);
+}
+
+/* ----------------------------------------------------------------------------
+ * ODLongestFullCode()                                 *** PRIVATE FUNCTION ***
+ *
+ * Return the index of the longest full code that matches the start of the
+ * sequence buffer
+ *
+ * Parameters: wFlags from od_get_input()
+ *
+ *     Return: void
+ */
+static int ODLongestFullCode(WORD wFlags)
+{
+   int CurrLen=0;
+   int seqlen;
+   int i;
+   int retval=NO_MATCH;;
+
+   if(wFlags & GETIN_RAW)
+      return(NO_MATCH);
+   for(i = 0; i < DIM(aKeySequences); ++i) {
+      if((wFlags & GETIN_RAWCTRL) && aKeySequences[i].bIsControlKey) {
+         continue;
+      }
+      seqlen=strlen(aKeySequences[i].pszSequence);
+      if(seqlen>CurrLen) {
+         if(strncmp(aKeySequences[i].pszSequence, szCurrentSequence, seqlen)==0) {
+            retval=i;
+            CurrLen=seqlen;
+         }
+      }
+   }
+
+   return(retval);
+}
+
+/* ----------------------------------------------------------------------------
+ * ODHaveStartOfSequence()                             *** PRIVATE FUNCTION ***
+ *
+ * If the current sequence buffer is the start of a valid sequence, return TRUE
+ *
+ * Parameters: wFlags from od_get_input()
+ *
+ *     Return: void
+ */
+static int ODHaveStartOfSequence(WORD wFlags)
+{
+   int seqlen1;
+   int seqlen2;
+   int i;
+
+   if(wFlags & GETIN_RAW)
+      return(FALSE);
+   seqlen1=strlen(szCurrentSequence);
+   for(i = 0; i < DIM(aKeySequences); ++i) {
+      if((wFlags & GETIN_RAWCTRL) && aKeySequences[i].bIsControlKey) {
+         continue;
+      }
+      seqlen2=strlen(aKeySequences[i].pszSequence);
+      if(seqlen1<seqlen2)
+         seqlen2=seqlen1;
+      if(strncmp(aKeySequences[i].pszSequence, szCurrentSequence, seqlen2)==0) {
+         return(TRUE);
+      }
+   }
+
+   return(FALSE);
+}
+
+/* ----------------------------------------------------------------------------
+ * ODGetCodeIfLongest()                                *** PRIVATE FUNCTION ***
+ *
+ * Returns the index of the entry in the sequence buffer only if there
+ * are no longer sequences that could start the same way.
+ *
+ * Parameters: wFlags from od_get_input()
+ *
+ *     Return: void
+ */
+static int ODGetCodeIfLongest(WORD wFlags)
+{
+   int CurrLen=0;
+   int seqlen1;
+   int seqlen2;
+   int i;
+   int retval=NO_MATCH;;
+
+   if(wFlags & GETIN_RAW)
+      return(NO_MATCH);
+   seqlen1=strlen(szCurrentSequence);
+   for(i = 0; i < DIM(aKeySequences); ++i) {
+      if((wFlags & GETIN_RAWCTRL) && aKeySequences[i].bIsControlKey) {
+         continue;
+      }
+      seqlen2=strlen(aKeySequences[i].pszSequence);
+      if(seqlen2>CurrLen) {
+         if(seqlen2<=seqlen1) {	/* The sequence would be completed in buffer */
+            if(strncmp(aKeySequences[i].pszSequence, szCurrentSequence, seqlen2)==0) {
+               retval=i;
+               CurrLen=seqlen2;
+            }
+         }
+         else {		/* Possible partial sequence */
+            if(strncmp(aKeySequences[i].pszSequence, szCurrentSequence, seqlen1)==0) {
+               return(NO_MATCH);
+            }
+         }
+      }
+   }
+
+   return(retval);
+}
+
+/* ----------------------------------------------------------------------------
+ * ODShiftSeq()                                        *** PRIVATE FUNCTION ***
+ *
+ * Shifts szCurrentSequence left the specified number of chars
+ *
+ * Parameters: int chars to shift
+ *
+ *     Return: void
+ */
+static void ODShiftSeq(int chars)
+{
+   char *in;
+   char *out;
+
+   if(!chars)
+      return;
+   out=szCurrentSequence;
+   in=out+chars;
+
+   if(in>strchr(szCurrentSequence,0))
+      return;
+   while(*in) {
+      *(out++)=*(in++);
+   }
+   *out=*in;
+}
+

+ 247 - 0
odoors/ODGraph.c

@@ -0,0 +1,247 @@
+/* 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: ODGraph.c
+ *
+ * Description: Implements special ANSI/AVATAR control functions.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              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 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 16, 1995  6.00  BP   Moved local vars here from odcore.c.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 13, 1996  6.10  BP   Added od_get_cursor().
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODKrnl.h"
+
+
+/* Local private variables. */
+static char szANSIClearLine[3] = {27, '[', 'K'};
+static char szAvatarClearLine[2] = {22, 7};
+
+
+/* ----------------------------------------------------------------------------
+ * od_clr_line()
+ *
+ * Clears the contents of the current line, from the current cursor position
+ * to the end of the line. This function affects both local and remote
+ * screens.
+ *
+ * Parameters: None.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_clr_line(void)
+{
+   char *pchLine;
+   INT nCharsLeft;
+   INT nCount;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_clr_line()");
+
+   /* Ensure that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Obtain the current cursor position. */
+   ODScrnGetTextInfo(&ODTextInfo);
+
+   /* Calculate the number of columns that are to be erased. */
+   nCharsLeft = 80 - ODTextInfo.curx;
+
+   /* If either ANSI or AVATAR mode is available, then we first */
+   /* clear the line on the local screen without affecting the  */
+   /* remote screen.                                            */
+   if(od_control.user_avatar || od_control.user_ansi)
+   {
+      pchLine = (char *)szODWorkString;
+      for(nCount = 0; nCount <= nCharsLeft; ++nCount) *pchLine++ = ' ';
+      *pchLine = '\0';
+      ODScrnEnableScrolling(0);
+      ODScrnDisplayString(szODWorkString);
+      ODScrnEnableScrolling(1);
+      ODScrnSetCursorPos(ODTextInfo.curx, ODTextInfo.cury);
+   }
+
+   /* If AVATAR mode is active. */
+   if(od_control.user_avatar)
+   {
+      /* Transmit the two-character AVATAR clear to end of line sequence. */
+      od_disp(szAvatarClearLine, 2, FALSE);
+   }
+
+   /* If ANSI mode is active. */
+   else if(od_control.user_ansi)
+   {
+      /* Transmit the three-character ANSI clear to end of line sequence. */
+      od_disp(szANSIClearLine, 3, FALSE);
+   }
+
+   /* If we are operating in plain-ASCII mode. */
+   else
+   {
+      /* Generate a sequence of space characters followed by backspace */
+      /* characters.                                                   */
+      pchLine = (char *)szODWorkString;
+      for(nCount = 0; nCount < nCharsLeft; ++nCount) *pchLine++ = ' ';
+      for(nCount = 0; nCount < nCharsLeft; ++nCount) *pchLine++ = 8;
+      *pchLine='\0';
+
+      /* Send this sequence to both the local and remote screens. */
+      od_disp(szODWorkString, strlen(szODWorkString), TRUE);
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_cursor()
+ *
+ * Moves the position of the cursor on both local and remote screens. This
+ * function is available in all display modes other than plain-ASCII.
+ *
+ * Parameters: nRow     - 1-based index of the row to position the cursor in.
+ *
+ *             nColumn  - Index of the column to position the cursor in.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_set_cursor(INT nRow, INT nColumn)
+{
+   static char szControlSequence[40];
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_set_cursor()");
+
+   /* Ensure that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check validity of parameters. */
+   if(nRow < 1 || nColumn < 1)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      return;
+   }
+
+   /* If AVATAR mode is on. */
+   if(od_control.user_avatar)
+   {
+      /* Position the local cursor. */
+      ODScrnSetCursorPos((BYTE)nColumn, (BYTE)nRow);
+
+      /* Generate the AVATAR control sequence to position the remote cursor. */
+      szControlSequence[0] = 22;
+      szControlSequence[1] = 8;
+      szControlSequence[2] = nRow;
+      szControlSequence[3] = nColumn;
+
+      /* Transmit the AVATAR control sequence to the remote terminal. */
+      od_disp(szControlSequence, 4, FALSE);
+   }
+
+   /* If ANSI mode is on. */
+   else if(od_control.user_ansi)
+   {
+      /* Generate the ANSI control sequence to position the remote cursor. */
+      sprintf(szControlSequence, "x[%d;%dH", nRow, nColumn);
+      szControlSequence[0] = 27;
+
+      /* Transmit the ANSI control seequence to the remote terminal. */
+      od_disp(szControlSequence, strlen(szControlSequence), FALSE);
+
+      /* Position the cursor on the local screen. */
+      ODScrnSetCursorPos((BYTE)nColumn, (BYTE)nRow);
+   }
+   else
+   {
+      /* If neither ANSI nor AVATAR modes are available, indicate this */
+      /* in the error code in od_control.                              */
+      od_control.od_error = ERR_NOGRAPHICS;
+   }
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_get_cursor()
+ *
+ * Returns our best estimate of the current position of the cursor on the
+ * remote screen.
+ *
+ * Parameters: pnRow    - 1-based index of the row to position the cursor in.
+ *
+ *             pnColumn - Index of the column to position the cursor in.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_get_cursor(INT *pnRow, INT *pnColumn)
+{
+   tODScrnTextInfo TextInfo;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_get_cursor()");
+
+   /* Ensure that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check for parameter validity. */
+   if(pnRow == NULL && pnColumn == NULL)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Obtain current state of local screen. */
+   ODScrnGetTextInfo(&TextInfo);
+
+   /* Set the caller's parameters to the current row and column, if each */
+   /* of these parameters were supplied.                                 */
+   if(pnRow != NULL) *pnRow = (INT)TextInfo.cury;
+   if(pnColumn != NULL) *pnColumn = (INT)TextInfo.curx;
+
+   OD_API_EXIT();
+}

+ 450 - 0
odoors/ODInEx.h

@@ -0,0 +1,450 @@
+/* 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: ODInEx.h
+ *
+ * Description: OpenDoors initialization and shutdown operations
+ *              (od_init() and od_exit()), including drop file I/O.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 22, 1995  6.00  BP   Created.
+ *              Nov 23, 1995  6.00  BP   32-bit portability.
+ *              Dec 03, 1995  6.00  BP   Win32 port.
+ *              Jan 19, 1996  6.00  BP   Don't use atexit() under Win32.
+ *              Jan 19, 1996  6.00  BP   Make ODInitError() a shared function.
+ *              Jan 20, 1996  6.00  BP   Prompt for user name if force_local.
+ *              Feb 02, 1996  6.00  BP   Added RA 2.50 EXITINFO.BBS support.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 20, 1996  6.00  BP   Added bParsedCmdLine.
+ *              Feb 21, 1996  6.00  BP   Don't override command line options.
+ *              Feb 21, 1996  6.00  BP   Force single-byte structure alignment.
+ *              Feb 23, 1996  6.00  BP   Make DTR disable code shared.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODINEX
+#define _INC_ODINEX
+
+#include "ODPlat.h"
+
+/* Drop file structures. */
+
+/* Force byte alignment, if possible */
+#ifdef __TURBOC__
+#if(__TURBOC__ >= 0x295)
+#pragma option -a-
+#endif /* __TURBOC__ >= 0x295 */
+#endif /* __TURBOC__ */
+#ifdef _MSC_VER
+#pragma pack(1)
+#endif /* _MSC_VER */
+
+typedef struct
+{
+   WORD baud;
+   DWORD num_calls;
+   char last_caller[36];
+   char sLastHandle[36];               /* New to RA 2.50 */
+   char extra1[92];
+   char start_date[9];
+   WORD busyperhour[24];
+   WORD busyperday[7];
+   char name[36];
+   char location[26];
+   char organisation[51];
+   char address[3][51];
+   char handle[36];
+   char comment[81];
+   DWORD password_crc;
+   char dataphone[16];
+   char homephone[16];
+   char lasttime[6];
+   char lastdate[9];
+   BYTE attrib;
+   BYTE attrib2;
+   char flags[4];
+   DWORD credit;
+   DWORD pending;
+   WORD posted;
+   WORD sec;
+   DWORD lastread;
+   DWORD nocalls;
+   DWORD ups;
+   DWORD downs;
+   DWORD upk;
+   DWORD downk;
+   DWORD todayk;
+   INT16 elapsed;
+   WORD screenlen;
+   char lastpwdchange;
+   WORD group;
+   WORD combinedrecord[200];
+   char firstcall[9];
+   char birthday[9];
+   char subdate[9];
+   BYTE screenwidth;
+   BYTE language;
+   BYTE dateformat;
+   char forwardto[36];
+   WORD msgarea;
+   WORD filearea;
+   BYTE default_protocol;
+   WORD file_group;
+   BYTE last_dob_check;
+   BYTE sex;
+   DWORD xirecord;
+   WORD msg_group;
+   BYTE btAttribute3;                  /* New to RA 2.50. */
+   char sPassword[16];                 /* New to RA 2.50. */
+   BYTE extra2[31];
+   char status;
+   char starttime[6];
+   char errorlevel;
+   char days;
+   char forced;
+   char lasttimerun[9];
+   char netmailentered;
+   char echomailentered;
+   char logintime[6];
+   char logindate[9];
+   INT16 timelimit;
+   DWORD loginsec;
+   WORD userrecord;
+   WORD readthru;
+   WORD numberpages;
+   WORD downloadlimit;
+   char timeofcreation[6];
+   DWORD logonpasswordcrc;
+   BYTE wantchat;
+   INT16 deducted_time;
+   char menustack[50][9];
+   BYTE menustackpointer;
+   char extra3[200];
+   BYTE error_free;
+   BYTE sysop_next;
+   char emsi_session;
+   char emsi_crtdef[41];
+   char emsi_protocols[41];
+   char emsi_capabilities[41];
+   char emsi_requests[41];
+   char emsi_software[41];
+   BYTE hold_attr1;
+   BYTE hold_attr2;
+   BYTE hold_len;
+   char page_reason[81];
+   BYTE status_line;
+   char last_cost_menu[9];
+   WORD menu_cost_per_min;
+   BYTE has_avatar;
+   BYTE has_rip;
+   BYTE btRIPVersion;                  /* New to RA 2.50. */
+   BYTE btExtraSpace[85];
+} tRA2ExitInfoRecord;
+
+
+typedef struct
+{
+   WORD baud;
+   DWORD num_calls;
+   char last_caller[36];
+   char extraspace[128];
+   char start_date[9];
+   WORD busyperhour[24];
+   WORD busyperday[7];
+   char uname[36];
+   char uloc[26];
+   char password[16];
+   char dataphone[13];
+   char homephone[13];
+   char lasttime[6];
+   char lastdate[9];
+   BYTE attrib;
+   BYTE flags[4];
+   WORD credit;
+   WORD pending;
+   WORD posted;
+   WORD lastread;
+   WORD sec;
+   WORD nocalls;
+   WORD ups;
+   WORD downs;
+   WORD upk;
+   WORD downk;
+   WORD todayk;
+   WORD elapsed;
+   WORD screenlen;
+   BYTE lastpwdchange;
+   BYTE attrib2;
+   BYTE group;
+   WORD xirecord;
+   char extra2[3];
+   char status;
+   char starttime[6];
+   char errorlevel;
+   char days;
+   char forced;
+   char lasttimerun[9];
+   char netmailentered;
+   char echomailentered;
+   char logintime[6];
+   char logindate[9];
+   WORD timelimit;
+   DWORD loginsec;
+   DWORD net_credit;
+   WORD userrecord;
+   WORD readthru;
+   WORD numberpages;
+   WORD downloadlimint;
+   union
+   {
+      struct
+      {
+         char timeofcreation[6];
+         char logonpassword[16];
+         char wantchat;
+      } ra;
+      struct
+      {
+         char qwantchat;
+         char gosublevel;
+         char menustack[20][9];
+         char menu[9];
+         BYTE screenclear;
+         BYTE moreprompts;
+         BYTE graphicsmode;
+         BYTE externedit;
+         INT16 screenlength;
+         BYTE mnpconnect;
+         char chatreason[49];
+         BYTE externlogoff;
+         BYTE ansicapable;
+         BYTE ripactive;
+         BYTE extraspace[199];
+      } qbbs;
+   } bbs;
+} tExitInfoRecord;
+
+typedef struct
+{
+   INT16 deducted_time;
+   char menustack[50][9];
+   char menustackpointer;
+   char userhandle[36];
+   char comment[81];
+   char firstcall[9];
+   char combinedrecord[25];
+   char birthday[9];
+   char subdate[9];
+   BYTE screenwidth;
+   BYTE msgarea;
+   BYTE filearea;
+   BYTE language;
+   BYTE dateformat;
+   char forwardto[36];
+   char extra_space[43];
+   char error_free;
+   char sysop_next;
+   char emsi_session;
+   char emsi_crtdef[41];
+   char emsi_protocols[41];
+   char emsi_capabilities[41];
+   char emsi_requests[41];
+   char emsi_software[41];
+   char hold_attr1;
+   char hold_attr2;
+   char hold_len;
+   char extr_space[100];
+} tExtendedExitInfo;
+
+struct _pcbsys
+{
+   char display[2];       /* "-1" = On, " 0" = Off */
+   char printer[2];
+   char pagebell[2];
+   char calleralarm[2];
+   char sysopflag;        /* ' ', 'N'=sysop next, 'X'=exit to dos */
+   char errorcorrection[2];
+   char graphicsmode;     /* 'Y'=Yes, 'N'=No, '7'=7E1 */
+   char nodechat;         /* 'A'=available, 'U'=unavailable */
+   char dteportspeed[5];
+   char connectspeed[5];  /* "Local"=local mode */
+   WORD recordnum;
+   char firstname[15];
+   char password[15];
+   INT16 logontimeval;    /* minutes since midnight */
+   INT16 todayused;       /* -ve # of minutes */
+   char logontime[5];
+   INT16 timeallowed;
+   WORD kallowed;
+   char conference;
+   char joined[5];
+   char scanned[5];
+   INT16 conferenceaddtime;
+   INT16 creditminutes;
+   char languageext[4];
+   char fullname[25];
+   INT16 minutesremaining;
+   char node;             /* ' ' if no network */
+   char eventtime[5];
+   char eventactive[2];
+   char slideevent[2];
+   DWORD memmessage;
+   char comport;          /* 0=none, 1-8 */
+   char reserved1[2];
+   char useansi;          /* 1 = yes, 0 = no */
+   char lasteventdate[8];
+   WORD lasteventminute;
+   char dosexit;
+   char eventupcoming;
+   char stopuploads;
+   WORD conferencearea;
+};
+
+struct _userssyshdr
+{
+   WORD     Version;           /* PCBoard version number (i.e. 145) */
+   DWORD    RecNo;             /* Record number from USER's file */
+   WORD     SizeOfRec;         /* Size of "fixed" user record */
+   WORD     NumOfAreas;        /* Number of conference areas (Main=1) */
+   WORD     NumOfBitFields;    /* Number of Bit Map fields for conferences */
+   WORD     SizeOfBitFields;   /* Size of each Bit Map field */
+   char     AppName[15];       /* Name of the Third Party Application */
+   WORD     AppVersion;        /* Version number for the application */
+   WORD     AppSizeOfRec;      /* Size of a "fixed length" record (if any) */
+   WORD     AppSizeOfConfRec;  /* Size of each conference record (if any) */
+   DWORD    AppRecOffset;      /* Offset of AppRec into USERS.INF record */
+   char     Updated;           /* TRUE if USERS.SYS has been updated */
+};
+
+struct _pcbflags
+{
+   int Dirty    :1;            /* Dirty Flag (meaning file has been updated) */
+   int MsgClear :1;            /* User's choice for screen clear after messages */
+   int HasMail  :1;            /* Indicates if NEW mail has been left for user */
+   int Reserved :5;
+};
+
+struct _pcbdate
+{
+   int Day   :5;               /* 5 bit integer representing the Day */
+   int Month :4;               /* 4 bit integer representing the Month */
+   int Year  :7;               /* 7 bit integer representing the Year MINUS 80 */
+};
+
+struct _userssysrec
+{
+   char     Name[26];          /* Name (NULL terminated) */
+   char     City[25];          /* City (NULL terminated) */
+   char     Password[13];      /* Password (NULL terminated) */
+   char     BusDataPhone[14];  /* Business or Data Phone (NULL terminated) */
+   char     HomeVoicePhone[14];/* Home or Voice Phone (NULL terminated) */
+   WORD     LastDateOn;        /* Julian date for the Last Date On */
+   char     LastTimeOn[6];     /* Last Time On (NULL Terminated) */
+   char     ExpertMode;        /* 1=Expert, 0=Novice */
+   char     Protocol;          /* Protocol (A thru Z) */
+   struct _pcbflags PackedFlags;
+   struct _pcbdate DateLastDirRead;
+   INT16    SecurityLevel;     /* Security Level */
+   WORD     NumTimesOn;        /* Number of times the caller has connected */
+   char     PageLen;           /* Page Length when display data on the screen */
+   WORD     NumUploads;        /* Total number of FILES uploaded */
+   WORD NumDownloads;      /* Total number of FILES downloaded */
+   DWORD    DailyDnldBytes;    /* Number of BYTES downloaded so far today */
+   char     UserComment[31];   /* Comment field #1 (NULL terminated) */
+   char     SysopComment[31];  /* Comment field #1 (NULL terminated) */
+   INT16    ElapsedTimeOn;     /* Number of minutes online */
+   WORD     RegExpDate;        /* Julian date for Registration Expiration Date */
+   INT16    ExpSecurityLevel;  /* Expired Security Level */
+   WORD     LastConference;    /* Number of the conference the caller was in */
+   DWORD    TotDnldBytes;      /* Total number of BYTES downloaded */
+   DWORD    TotUpldBytes;      /* Total number of BYTES uploaded */
+   char     DeleteFlag;        /* 1=delete this record, 0=keep */
+   DWORD    RecNum;            /* Record Number in USERS.INF file */
+   char     Reserved[9];       /* Bytes 391-399 from the USERS file */
+   DWORD    MsgsRead;          /* Number of messages the user has read in PCB */
+   DWORD    MsgsLeft;          /* Number of messages the user has left in PCB */
+};
+
+/* Restore original structure alignment, if possible. */
+#ifdef _MSC_VER
+#pragma pack()
+#endif /* _MSC_VER */
+
+
+/* od_init() and od_exit() global helper functons. */
+#ifndef ODPLAT_WIN32
+void ODAtExitCallback(void);
+#endif /* !ODPLAT_WIN32 */
+INT ODWriteExitInfoPrimitive(FILE *pfDropFile, INT nCount);
+BOOL ODReadExitInfoPrimitive(FILE *pfDropFile, INT nCount);
+INT ODSearchForDropFile(char **papszFileNames, INT nNumFileNames,
+   char *pszFound, char *pszDirectory);
+void ODInitError(char *pszErrorText);
+#ifdef ODPLAT_WIN32
+BOOL CALLBACK ODInitLoginDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+void ODInExDisableDTR(void);
+#endif /* ODPLAT_WIN32 */
+
+
+/* Global variables. */
+extern WORD wODNodeNumber;
+extern BOOL bIsCoSysop;
+extern BOOL bIsSysop;
+extern char *apszDropFileInfo[25];
+extern BYTE btExitReason;
+extern DWORD dwForcedBPS;
+extern INT nForcedPort;
+extern DWORD dwFileBPS;
+extern char szDropFilePath[120];
+extern char szExitinfoBBSPath[120];
+extern INT16 nInitialElapsed;
+extern char *szOriginalDir;
+extern BYTE btDoorSYSLock;
+extern time_t nStartupUnixTime;
+extern INT16 nInitialRemaining;
+extern BOOL bSysopNameSet;
+extern char szForcedSysopName[40];
+extern BOOL bSystemNameSet;
+extern char szForcedSystemName[40];
+extern BOOL bUserFull;
+extern BOOL bCalledFromConfig;
+extern tRA2ExitInfoRecord *pRA2ExitInfoRecord;
+extern tExitInfoRecord *pExitInfoRecord;
+extern tExtendedExitInfo *pExtendedExitInfo;
+extern struct _pcbsys *pPCBoardSysRecord;
+extern struct _userssyshdr *pUserSysHeader;
+extern struct _userssysrec *pUserSysRecord;
+extern BOOL bPreOrExit;
+extern BOOL bRAStatus;
+extern BOOL bPromptForUserName;
+extern BOOL bParsedCmdLine;
+extern WORD wPreSetInfo;
+#ifdef ODPLAT_WIN32
+extern tODThreadHandle hFrameThread;
+#endif /* ODPLAT_WIN32 */
+
+
+/* wPreSetInfo flags. */
+#define PRESET_BPS         0x0001
+#define PRESET_PORT        0x0002
+#define PRESET_REQUIRED    (PRESET_BPS | PRESET_PORT)
+
+
+#endif /* _INC_ODINEX */

+ 2536 - 0
odoors/ODInEx1.c

@@ -0,0 +1,2536 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODInEx1.c
+ *
+ * Description: Performs OpenDoors initialization and shutdown operations
+ *              (od_init() and od_exit()), including drop file I/O. This
+ *              module is broken into two files, ODInEx1.c and ODInEx2.c.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Oct 19, 1994  6.00  BP   Default paging hours 8:00-22:00.
+ *              Oct 21, 1994  6.00  BP   Further isolated com routines.
+ *              Oct 29, 1994  6.00  BP   Properly read dorinfo?.def BBS name.
+ *              Oct 31, 1994  6.00  BP   Only use dorinfo?.def /w exitinfo.bbs.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Dec 31, 1994  6.00  BP   Remove call to _mt_init().
+ *              Dec 31, 1994  6.00  BP   Add call to ODPlatInit().
+ *              Jul 30, 1995  6.00  BP   Split up od_init().
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 14, 1995  6.00  BP   New default for od_colour_char is 0.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Nov 23, 1995  6.00  BP   Moved Pascal conversion to odutil.c.
+ *              Dec 21, 1995  6.00  BP   Add ability to use already open port.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Dec 30, 1995  6.00  BP   Only use comm idle func under DOS.
+ *              Jan 01, 1996  6.00  BP   Added od_disable_dtr.
+ *              Jan 01, 1996  6.00  BP   Raise DTR after opening serial port.
+ *              Jan 02, 1996  6.00  BP   Use printf() in ODInitError().
+ *              Jan 03, 1996  6.00  BP   Display connect speed with %lu.
+ *              Jan 19, 1996  6.00  BP   Don't use atexit() under Win32.
+ *              Jan 19, 1996  6.00  BP   Make ODInitError() a shared function.
+ *              Jan 20, 1996  6.00  BP   Prompt for user name if force_local.
+ *              Jan 23, 1996  6.00  BP   Added od_exiting and OD_TEXTMODE.
+ *              Jan 31, 1996  6.00  BP   Added DIS_NAME_PROMPT.
+ *              Jan 31, 1996  6.00  BP   Support new SFDOORS.DAT format.
+ *              Feb 02, 1996  6.00  BP   Added RA 2.50 EXITINFO.BBS support.
+ *              Feb 06, 1996  6.00  BP   Added od_silent_mode.
+ *              Feb 08, 1996  6.00  BP   Recognize SFSYSOP.DAT.
+ *              Feb 09, 1996  6.00  BP   Correctly translate RA 2.x sex field.
+ *              Feb 09, 1996  6.00  BP   Made default outbound buffer 3072.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 20, 1996  6.00  BP   Added bParsedCmdLine.
+ *              Feb 21, 1996  6.00  BP   Don't override command line options.
+ *              Feb 21, 1996  6.00  BP   Change od_always_clear default to on.
+ *              Feb 23, 1996  6.00  BP   Changed default DTR disable string.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 06, 1996  6.10  BP   Added TRIBBS.SYS support.
+ *              Mar 06, 1996  6.10  BP   Initial support for Door32 interface.
+ *              Mar 13, 1996  6.10  BP   Added od_local_win_col.
+ *              Mar 17, 1996  6.10  BP   Reset text color after local login.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Apr 08, 1996  6.10  BP   Store local login name in user_handle.
+ *              Jan 13, 1997  6.10  BP   Fixes for Door32 support.
+ *              Oct 19, 2001  6.20  RS   Added door32.sys and socket support.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#ifdef ODPLAT_NIX
+#include <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#endif
+#include "ODStr.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODInQue.h"
+#include "ODKrnl.h"
+#include "ODInEx.h"
+#include "ODUtil.h"
+#ifdef ODPLAT_WIN32
+#include "ODFrame.h"
+#include "ODRes.h"
+#endif /* ODPLAT_WIN32 */
+
+/* Default size of local/remote combined keyboard buffer. */
+#define DEFAULT_EVENT_QUEUE_SIZE    128
+
+
+/* Local private helper functions. */
+static void ODInitReadExitInfo(void);
+static void ODInitPartTwo(void);
+static BOOL ODInitReadSFDoorsDAT(void);
+static void ODInitPartTwo(void);
+
+
+/* Private variables. */
+static BYTE btRAStatusToSet = 0;
+#ifndef ODPLAT_WIN32
+static BOOL bPreset = TRUE;
+#endif /* !ODPLAT_WIN32 */
+static char szIFTemp[256];
+
+/* Configuration file keywords. */
+static char *apszConfigText[] =
+{
+   "Node",
+   "BBSDir",
+   "DoorDir",
+   "LogFileName",
+   "DisableLogging",
+   "SundayPagingHours",
+   "MondayPagingHours",
+   "TuesdayPagingHours",
+   "WednesdayPagingHours",
+   "ThursdayPagingHours",
+   "FridayPagingHours",
+   "SaturdayPagingHours",
+   "MaximumDoorTime",
+   "SysopName",
+   "SystemName",
+   "SwappingDisable",
+   "SwappingDir",
+   "SwappingNoEMS",
+   "LockedBPS",
+   "SerialPort",
+   "CustomFileName",
+   "CustomFileLine",
+   "InactivityTimeout",
+   "PageDuration",
+   "ChatUserColour",
+   "ChatSysopColour",
+   "FileListTitleColour",
+   "FileListNameColour",
+   "FileListSizeColour",
+   "FileListDescriptionColour",
+   "FileListOfflineColour",
+   "Personality",
+   "NoFossil",
+   "PortAddress",
+   "PortIRQ",
+   "ReceiveBuffer",
+   "TransmitBuffer",
+   "PagePromptColour",
+   "LocalMode",
+   "PopupMenuTitleColour",
+   "PopupMenuBorderColour",
+   "PopupMenuTextColour",
+   "PopupMenuKeyColour",
+   "PopupMenuHighlightColour",
+   "PopupMenuHighKeyColour",
+   "NoFIFO",
+   "FIFOTriggerSize",
+   "DisableDTR",
+   "NoDTRDisable",
+};
+
+/* Custom drop file keywords. */
+static char *apszConfigLines[] =
+{
+   "Ignore",
+   "ComPort",
+   "FossilPort",
+   "ModemBPS",
+   "LocalMode",
+   "UserName",
+   "UserFirstName",
+   "UserLastName",
+   "Alias",
+   "HoursLeft",
+   "MinutesLeft",
+   "SecondsLeft",
+   "ANSI",
+   "AVATAR",
+   "PagePausing",
+   "ScreenLength",
+   "ScreenClearing",
+   "Security",
+   "City",
+   "Node",
+   "SysopName",
+   "SysopFirstName",
+   "SysopLastName",
+   "SystemName",
+   "RIP",
+};
+
+/* Logfile messages. */
+static char *apszLogMessages[] = 
+{
+   "Carrier lost, exiting door",
+   "System operator terminating call, exiting door",
+   "User's time limit expired, exiting door",
+   "User keyboard inactivity time limit exceeded, exiting door",
+   "System operator returning user to BBS, exiting door",
+   "Exiting door with errorlevel %d",
+   "Invoking operating system shell",
+   "Returning from operating system shell",
+   "User paging system operator",
+   "Entering sysop chat mode",
+   "Terminating sysop chat mode",
+   "%s entering door",
+   "Reason for chat: %s",
+   "Exiting door",
+};
+
+/* Color name strings. */
+static char *apszColorNames[] =
+{
+   "BLACK",
+   "BLUE",
+   "GREEN",
+   "CYAN",
+   "RED",
+   "MAGENTA",
+   "YELLOW",
+   "WHITE",
+   "BROWN",
+   "GREY",
+   "BRIGHT",
+   "FLASHING",
+};
+
+/* Array of door information (drop) file names to search for. */
+static char *apszDropFileNames[] = 
+{
+   "exitinfo.bbs",
+   "dorinfo1.def",
+   "chain.txt",
+   "sfdoors.dat",
+   "door.sys",
+   "callinfo.bbs",
+   "sfmain.dat",
+   "sffile.dat",
+   "sfmess.dat",
+   "sfsysop.dat",
+   "tribbs.sys",
+   "door32.sys",
+};
+
+/* Array of door information (drop) file numbers 
+ * (corresponding to apszDropFileNames) 
+ */
+enum {
+   FOUND_EXITINFO_BBS,
+   FOUND_DORINFO1_DEF,
+   FOUND_CHAIN_TXT,
+   FOUND_SFDOORS_DAT,
+   FOUND_DOOR_SYS,
+   FOUND_CALLINFO_BBS,
+   FOUND_SFMAIN_DAT,
+   FOUND_SFFILE_DAT,
+   FOUND_SFMESS_DAT,
+   FOUND_SFSYSOP_DAT,
+   FOUND_TRIBBS_SYS,
+   FOUND_DOOR32_SYS,
+};
+
+#define FOUND_NONE -1
+
+
+/* Global variables. */
+WORD wODNodeNumber = 65535U;
+BOOL bIsCoSysop;
+BOOL bIsSysop;
+char *apszDropFileInfo[25];
+BYTE btExitReason = 0;
+DWORD dwForcedBPS = 1;
+INT nForcedPort = -1;
+DWORD dwFileBPS;
+char szDropFilePath[120];
+char szExitinfoBBSPath[120];
+INT16 nInitialElapsed;
+char *szOriginalDir = NULL;
+BYTE btDoorSYSLock = 0;
+time_t nStartupUnixTime;
+INT16 nInitialRemaining;
+BOOL bSysopNameSet = FALSE;
+char szForcedSysopName[40];
+BOOL bSystemNameSet = FALSE;
+char szForcedSystemName[40];
+BOOL bUserFull = FALSE;
+BOOL bCalledFromConfig = FALSE;
+tRA2ExitInfoRecord *pRA2ExitInfoRecord = NULL;
+tExitInfoRecord *pExitInfoRecord = NULL;
+tExtendedExitInfo *pExtendedExitInfo = NULL;
+struct _pcbsys *pPCBoardSysRecord = NULL;
+struct _userssyshdr *pUserSysHeader = NULL;
+struct _userssysrec *pUserSysRecord = NULL;
+BOOL bPreOrExit = FALSE;
+BOOL bRAStatus;
+BOOL bPromptForUserName = FALSE;
+BOOL bParsedCmdLine = FALSE;
+WORD wPreSetInfo = 0;
+#ifdef ODPLAT_WIN32
+tODThreadHandle hFrameThread;
+#endif /* ODPLAT_WIN32 */
+
+
+static void strcopydate(char *dest, char *src) {
+	if (strlen(src) == 9) {
+		strcpy(dest, src);
+	} else if (strlen(src) == 11) {
+		dest[0] = src[0];
+		dest[1] = src[1];
+		dest[2] = src[2];
+		dest[3] = src[3];
+		dest[4] = src[4];
+		dest[5] = src[5];
+		dest[6] = src[9];
+		dest[7] = src[10];
+		dest[8] = '\0';
+	} else {
+		strcpy(dest, "01-01-71");
+	}
+}
+
+/* ----------------------------------------------------------------------------
+ * od_init()
+ *
+ * Starts up OpenDoors. Initializes various members of od_control, reads the
+ * BBS door information (drop file), initializes the serial port and carries
+ * out other operations that must be done at initialization time. May be
+ * explicitly called by the user, or called as a result of the first call to
+ * some other OpenDoors API function.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_init(void)
+{
+   BYTE btCount;
+   FILE *pfDropFile=NULL;
+   char *pointer;
+   INT nFound = FOUND_NONE;
+#ifdef _WIN32
+   float forcefloats;
+
+   forcefloats=1.1;
+#endif
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_init()");
+
+   /* If a callback function is active, then don't do anything. */
+   if(bIsCallbackActive) return;
+
+   /* If we are not being called from within ODConfigInit(). */
+   if(!bCalledFromConfig)
+   {
+      /* If OpenDoors has already been initialized, then return without */
+      /* doing anything.                                                */
+      if(bODInitialized) return;
+
+      /* Otherwise, set the initialized flag so that od_init() won't be */
+      /* run again.                                                     */
+      bODInitialized = TRUE;
+
+      /* Initialize program name string. */
+      if(od_control.od_prog_name[0] == '\0')
+      {
+         strcpy(od_control.od_prog_name, OD_VER_SHORTNAME);
+      }
+
+      /* Initialize color name strings in od_control. */
+      for(btCount = 0; btCount < DIM(apszColorNames); ++btCount)
+      {
+         if(!*od_control.od_color_names[btCount])
+         {
+            strcpy(od_control.od_color_names[btCount],
+               apszColorNames[btCount]);
+         }
+      }
+
+      /* Initialize custom drop file strings in od_control. */
+      for(btCount = 0; btCount < LINES_SIZE; ++btCount)
+      {
+         if(!*od_control.od_cfg_lines[btCount])
+         {
+            strcpy(od_control.od_cfg_lines[btCount], apszConfigLines[btCount]);
+         }
+      }
+
+      /* Initialize configuration keyword strings in od_control. */
+      for(btCount = 0; btCount < TEXT_SIZE; ++btCount)
+      {
+         if(!*od_control.od_cfg_text[btCount])
+         {
+            strcpy(od_control.od_cfg_text[btCount], apszConfigText[btCount]);
+         }
+      }
+
+      /* Enable multiple personality system if it has been installed. */
+#ifdef OD_TEXTMODE
+      if(od_control.od_mps != NULL)
+      {
+         (*(OD_COMPONENT_CALLBACK *)od_control.od_mps)();
+      }
+#endif /* !OD_TEXTMODE */
+
+      /* If baud rate has been set in od_control, then remember the forced */
+      /* rate for later use.                                               */
+      if(od_control.baud != 0)
+      {
+         dwForcedBPS = od_control.baud;
+      }
+
+      /* If the serial port number has already been set in od_control, then */
+      /* remember the forced port number for later use.                     */
+      if(od_control.port != 0)
+      {
+         nForcedPort = od_control.port;
+      }
+
+      /* If the configuration file system has been installed, then allow   */
+      /* the configuration file initialization routine take over           */
+      /* initialization from here. Once it has read the configuration file */
+      /* it will call od_init() again, at which time od_init() will carry  */
+      /* on from this point in the code.                                  */
+      if(od_control.config_file != NULL)
+      {
+         (*(OD_COMPONENT_CALLBACK *)od_control.config_file)();
+         return;
+      }
+   }
+
+   /* Initialize the OpenDoors platform-specific utility functions. */
+   ODPlatInit();
+
+   /* Store the time of door startup. */
+   time(&nStartupUnixTime);
+
+   /* Allocate the set of strings that are used to store certain parts */
+   /* of drop files for rewriting at od_exit() time.                   */
+   for(btCount = 0; btCount < DIM(apszDropFileInfo); ++btCount)
+   {
+      if((apszDropFileInfo[btCount] = (char *)malloc(81)) == NULL)
+      {
+malloc_error:
+         ODInitError("Insufficient memory available to start up program.");
+         exit(od_control.od_errorlevel[1]);
+      }
+   }
+
+   /* Determine the current node number. */ 
+   if((pointer=getenv("TASK")) != NULL)
+   {
+      od_control.od_node = atoi(pointer);
+   }
+   else if((pointer=getenv("SBBSNNUM")) != NULL)
+   {
+      od_control.od_node = atoi(pointer);
+   }
+   else if(wODNodeNumber != 65535U)
+   {
+      od_control.od_node = wODNodeNumber;
+   }
+   else if(od_control.od_node == 0)
+   {
+      od_control.od_node = 1;
+   }
+
+   /* If a custom drop file format is not being used, then set certain   */
+   /* od_control members to their default values. In the case where      */
+   /* a custom drop file format is being used, these values will already */
+   /* have been set, and so we don't want to touch them.                 */
+   if(od_control.od_info_type != CUSTOM)
+   {
+      od_control.user_avatar = FALSE;
+      od_control.user_rip = FALSE;
+      od_control.user_attribute = 0x06;
+      od_control.user_screen_length = 23;
+      od_control.user_screenwidth = 80;
+      od_control.od_page_pausing = TRUE;
+      od_control.od_page_len = 15;
+   }
+   else
+   {
+      /* When a custom drop file is being used, there are certain      */
+      /* variables that we must initialize if they weren't already set */
+      /* when the custom drop file was read.                           */
+      if(od_control.user_timelimit == 0) od_control.user_timelimit = 60;
+#ifdef ODPLAT_NIX
+      if(od_control.port == -1) od_control.baud = 1L;
+#else
+      if(od_control.port == -1) od_control.baud = 0L;
+#endif
+   }
+
+   /* Setup inbound local/remote buffer. */
+   if(ODInQueueAlloc(&hODInputQueue, od_control.od_in_buf_size == 0 ?
+      DEFAULT_EVENT_QUEUE_SIZE : od_control.od_in_buf_size) != kODRCSuccess)
+   {
+      goto malloc_error;
+   }
+
+   /* Enable user's keyboard by default. */
+   od_control.od_user_keyboard_on = TRUE;
+
+   /* If door information (drop) file reading has been disabled, then */
+   /* don't attempt to read any drop file.                            */
+   if(od_control.od_disable & DIS_INFOFILE)
+   {
+      od_control.od_info_type = NO_DOOR_FILE;
+   }
+
+   /* Otherwise, if the local mode override has been explicitly asked for, */
+   /* setup od_control for default local mode operation.                   */
+   else if(od_control.od_force_local)
+   {
+force_local:
+      /* No door information file is being used. */
+      od_control.od_info_type = NO_DOOR_FILE;
+
+      /* Operate in local mode. */
+#ifdef ODPLAT_NIX
+      od_control.baud = 1L;
+#else
+      od_control.baud = 0L;
+#endif
+
+      if(!bParsedCmdLine)
+      {
+         /* Enable ANSI mode. */
+         od_control.user_ansi = TRUE;
+
+         /* Default to 60 minutes of time available. */
+         od_control.user_timelimit = 60;
+      }
+
+      /* Choose the appropriate system name. */
+      if(bSystemNameSet)
+      {
+         strcpy(od_control.user_location, szForcedSystemName);
+      }
+      else if(od_control.system_name[0] != '\0')
+      {
+         strcpy(od_control.user_location, od_control.system_name);
+      }
+      else
+      {
+         strcpy(od_control.user_location, "Unknown Location");
+      }
+   }
+
+   /* If drop file reading isn't disable, if a custom format drop file */
+   /* hasn't already been read and automatic local mode hasn't been    */
+   /* specified, then attempt to find and read a standard drop file.   */
+   else if(od_control.od_info_type != CUSTOM)
+   {
+      /* Generate the name of the dorinfox.def file for this node. */
+      if(od_control.od_node > 35)
+      {
+         apszDropFileNames[1] = (char *)"dorinfo1.def";
+      }
+      else if(od_control.od_node > 9)
+      {
+         sprintf(szIFTemp, "dorinfo%c.def", od_control.od_node + 55);
+         apszDropFileNames[1] = (char *)szIFTemp;
+      }
+      else
+      {
+         sprintf(szIFTemp, "dorinfo%d.def", od_control.od_node);
+         apszDropFileNames[1] = (char *)szIFTemp;
+      }
+
+      nFound = FOUND_NONE;
+
+      if(!ODFileAccessMode(od_control.info_path, 4))
+      {
+         /* Check for a DORINFOx.DEF filename. */
+         if(ODStringHasTail(od_control.info_path, ".def") &&
+            strlen(od_control.info_path) >= strlen(apszDropFileNames[2]) &&
+            strnicmp((char *)&od_control.info_path +
+            (strlen(od_control.info_path) - 12), "dorinfo", 7) == 0)
+         {
+            nFound = FOUND_DORINFO1_DEF;
+            strcpy(szDropFilePath, od_control.info_path);
+         }
+         else
+         {
+            /* Check filenames other than DORINFOx.DEF */
+            for(btCount = 0; btCount < DIM(apszDropFileNames); ++btCount)
+            {
+               if(ODStringHasTail(od_control.info_path,
+                  apszDropFileNames[btCount]))
+               {
+                  strcpy(szDropFilePath, od_control.info_path);
+                  nFound = btCount;
+                  break;
+               }
+            }
+         }
+      }
+
+      /* Search for a door information file. */
+      if(nFound == FOUND_NONE)
+      {
+         nFound = ODSearchForDropFile((char **)&apszDropFileNames,
+            DIM(apszDropFileNames), (char *)&szDropFilePath,
+            (char *)&szExitinfoBBSPath);
+      }
+
+      if(nFound == FOUND_EXITINFO_BBS)
+      {
+         od_control.od_info_type = NO_DOOR_FILE;
+         ODInitReadExitInfo();
+         if(od_control.od_info_type == NO_DOOR_FILE)
+         {
+            goto DropFileFail;
+         }
+
+         ODMakeFilename(szODWorkString, szExitinfoBBSPath, "dorinfo1.def",
+            sizeof(szExitinfoBBSPath));
+         if((pfDropFile = fopen(szODWorkString, "r")) == NULL)
+         {
+            goto DropFileFail;
+         }
+
+         goto read_dorinfox;
+      }
+
+      else if(nFound==FOUND_DORINFO1_DEF)
+      {
+          /* Open DORINFO?.DEF. */
+          if((pfDropFile = fopen(szDropFilePath, "r")) == NULL) goto DropFileFail;
+
+          /* Set door type to DORINFO. */
+          od_control.od_info_type = DORINFO1;
+
+read_dorinfox:
+          /* If not able to read first line. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+
+          if(szIFTemp[strlen(szIFTemp) - 1] == '\n')
+          {
+            szIFTemp[strlen(szIFTemp) - 1] = '\0';
+          }
+          if(szIFTemp[strlen(szIFTemp) - 1] == '\r')
+          {
+            szIFTemp[strlen(szIFTemp) - 1] = '\0';
+          }
+          strncpy(od_control.system_name, szIFTemp, 39);
+          od_control.system_name[39] = '\0';
+
+          /* get sysop name from DORINFO1.DEF */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          strncpy(od_control.sysop_name, szIFTemp, 19);
+
+                                          /* get sysop's last name */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          if(strlen(szIFTemp))
+          {
+             strcat(od_control.sysop_name," ");
+             strncat(od_control.sysop_name,szIFTemp,19);
+          }
+                                   /* get com port that modem is connected to */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.port=szIFTemp[3]-'1';
+                                          /* determine BPS rate of connection */
+          if(fgets((char *)apszDropFileInfo[0],255,pfDropFile)==NULL) goto DropFileFail;
+#ifdef ODPLAT_NIX
+          od_control.baud= (od_control.port == -1) ? 1 : atol((char *)apszDropFileInfo[0]);
+#else
+          od_control.baud= (od_control.port == -1) ? 0 : atol((char *)apszDropFileInfo[0]);
+#endif
+
+          if(fgets((char *)apszDropFileInfo[1],80,pfDropFile)==NULL) goto DropFileFail;
+
+                                          /* get user's first name */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          strncpy(od_control.user_name,szIFTemp,17);
+                                          /* get user's last name */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          if(strlen(szIFTemp))
+          {
+             strcat(od_control.user_name," ");
+             strncat(od_control.user_name,szIFTemp,17);
+          }
+                                          /* get user's location */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          strncpy(od_control.user_location,szIFTemp,25);
+                                          /* get ANSI mode settings */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(szIFTemp[0]=='0') od_control.user_ansi=FALSE;
+          else od_control.user_ansi=TRUE;
+                                          /* get user security level */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_security=atoi(szIFTemp);
+                                          /* get time left in door */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_timelimit=atoi(szIFTemp);
+          fclose(pfDropFile);
+       }
+                                       /* Read CHAIN.TXT */
+       else if(nFound==FOUND_CHAIN_TXT)
+       {
+          if((pfDropFile=fopen(szDropFilePath,"r"))==NULL) goto DropFileFail;
+
+          od_control.od_info_type=CHAINTXT;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_num=atoi(szIFTemp);
+
+          if(fgets((char *)&od_control.user_handle,35,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(od_control.user_handle);
+
+          if(fgets((char *)&od_control.user_name,35,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(od_control.user_name);
+
+          if(fgets((char *)&od_control.user_callsign,12,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(od_control.user_callsign);
+
+          if(fgets((char *)apszDropFileInfo[0],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_sex=szIFTemp[0];
+
+          if(fgets((char *)apszDropFileInfo[1],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          strncpy(od_control.user_lastdate,szIFTemp,8);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_screenwidth=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_screen_length=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_security=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          bIsSysop=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          bIsCoSysop=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_ansi=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;    /* non-zero if remote */
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_timelimit=atoi(szIFTemp);
+          od_control.user_timelimit/=60;
+
+          if(fgets((char *)apszDropFileInfo[3],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets((char *)apszDropFileInfo[4],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets((char *)apszDropFileInfo[5],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(strcmp(szIFTemp,"KB")==0)
+          {
+#ifdef ODPLAT_NIX
+             od_control.baud=1;
+#else
+             od_control.baud=0;
+#endif
+          }
+          else
+          {
+             od_control.baud=atol(szIFTemp);
+          }
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.port=atoi(szIFTemp)-1;
+
+          if(fgets((char *)apszDropFileInfo[6],80,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets((char *)&od_control.user_password,15,pfDropFile)==NULL) goto DropFileFail;
+          ODStringToName(od_control.user_password);
+
+          if(fgets((char *)apszDropFileInfo[2],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[7],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[8],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[9],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[10],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[11],80,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets((char *)apszDropFileInfo[12],80,pfDropFile)==NULL) goto DropFileFail;
+
+          fclose(pfDropFile);
+       }
+
+       else if(nFound == FOUND_SFDOORS_DAT 
+			 || nFound == FOUND_SFMAIN_DAT 
+			 || nFound == FOUND_SFFILE_DAT 
+			 || nFound == FOUND_SFMESS_DAT
+          || nFound == FOUND_SFSYSOP_DAT)
+       {
+          od_control.od_info_type = SFDOORSDAT;
+
+          if(!ODInitReadSFDoorsDAT())
+          {
+             goto DropFileFail;
+          }
+       }
+
+       else if(nFound==FOUND_DOOR_SYS)
+       {
+          if((pfDropFile=fopen(szDropFilePath,"r"))==NULL) goto DropFileFail;
+
+          /* Read line 1. */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(szIFTemp[0]=='C' && szIFTemp[1]=='O' && szIFTemp[2]=='M' && szIFTemp[4]==':')
+          {                            /* GAP style DOOR.SYS */
+             od_control.od_info_type=DOORSYS_GAP;
+
+             od_control.port=szIFTemp[3]-'1';
+
+             /* Check for COM0:STDIO */
+             if(!strncmp(szIFTemp,"COM0:STDIO",10))
+                od_control.od_com_method=COM_STDIO;
+
+             /* Check for COM0:SOCKET### */
+             if(!strncmp(szIFTemp,"COM0:SOCKET",11)) {
+                od_control.od_com_method=COM_SOCKET;
+		od_control.od_use_socket = TRUE;
+                od_control.od_open_handle=atoi(szIFTemp+11);
+             }
+
+             /* Check for COM0:HANDLE### */
+             if(!strncmp(szIFTemp,"COM0:HANDLE",11)) {
+                od_control.od_com_method=COM_WIN32;
+                od_control.od_open_handle=atoi(szIFTemp+11);
+             }
+
+             /* Read line 2. */
+             if(fgets((char *)apszDropFileInfo[0], 80, pfDropFile) == NULL)
+             {
+               goto DropFileFail;
+             }
+             od_control.od_connect_speed = atol(apszDropFileInfo[0]);
+
+             /* Read line 3. */
+             if(fgets((char *)apszDropFileInfo[1],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 4. */
+             if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+             od_control.od_node = atoi(szIFTemp);
+
+             /* Read line 5. */
+             if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+             strupr(szIFTemp);
+             if(strchr(szIFTemp, 'N') != NULL)
+             {
+                btDoorSYSLock = 1;
+                od_control.baud = atol(apszDropFileInfo[0]);
+             }
+             else if(strchr(szIFTemp, 'Y') != NULL)
+             {
+                btDoorSYSLock = 2;
+                od_control.baud = 19200;
+             }
+             else
+             {
+                od_control.baud = atol(szIFTemp);
+             }
+
+#ifdef ODPLAT_NIX
+             if(od_control.port == -1) od_control.baud = 1L;
+#else
+             if(od_control.port == -1) od_control.baud = 0L;
+#endif
+
+             /* Read line 6. */
+             if(fgets((char *)apszDropFileInfo[3],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 7. */
+             if(fgets((char *)apszDropFileInfo[4],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 8. */
+             if(fgets((char *)apszDropFileInfo[5],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 9. */
+             if(fgets((char *)apszDropFileInfo[22],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 10. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[35]='\0';
+             ODStringToName(szIFTemp);
+             strcpy(od_control.user_name,szIFTemp);
+
+             /* Read line 11. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[25]='\0';
+             if(szIFTemp[strlen(szIFTemp)-1]=='\n') szIFTemp[strlen(szIFTemp)-1]='\0';
+             if(szIFTemp[strlen(szIFTemp)-1]=='\r') szIFTemp[strlen(szIFTemp)-1]='\0';
+             strcpy(od_control.user_location,szIFTemp);
+
+             /* Read line 12. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[15]='\0';
+             ODStringToName(szIFTemp);
+             strcpy(od_control.user_homephone,szIFTemp);
+
+             /* Read line 13. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[15]='\0';
+             ODStringToName(szIFTemp);
+             strcpy(od_control.user_dataphone,szIFTemp);
+
+             /* Read line 14. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[15]='\0';
+             if(szIFTemp[strlen(szIFTemp)-1]=='\n') szIFTemp[strlen(szIFTemp)-1]='\0';
+             if(szIFTemp[strlen(szIFTemp)-1]=='\r') szIFTemp[strlen(szIFTemp)-1]='\0';
+             strcpy(od_control.user_password,szIFTemp);
+
+             /* Read line 15. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_security=atoi(szIFTemp);
+
+             /* Read line 16. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_numcalls=atoi(szIFTemp);
+
+             /* Read line 17. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[15]='\0';
+             ODStringToName(szIFTemp);
+             strcopydate(od_control.user_lastdate,szIFTemp);
+
+             /* Read line 18. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 19. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_timelimit=atoi(szIFTemp);
+
+             /* Read line 20. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             strupr(szIFTemp);
+             if(!strcmp(szIFTemp,"RIP"))
+             {
+                od_control.user_rip=TRUE;
+                od_control.user_ansi=TRUE;
+             }
+             else if(szIFTemp[0]=='G')
+             {
+                od_control.user_rip=FALSE;
+                od_control.user_ansi=TRUE;
+             }
+             else
+             {
+                od_control.user_rip=FALSE;
+                od_control.user_ansi=FALSE;
+             }
+
+             /* Read line 21. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_screen_length=atoi(szIFTemp);
+
+             /* Read line 22. */
+             if(fgets((char *)apszDropFileInfo[8],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 23. */
+             if(fgets((char *)apszDropFileInfo[9],80,pfDropFile)==NULL) goto DropFileFail;
+             if(apszDropFileInfo[9][strlen(apszDropFileInfo[9])-1]!='\n')
+             {
+                int ch;
+                apszDropFileInfo[9][strlen(apszDropFileInfo[9])-1]='\n';
+                do
+                {
+                   ch = fgetc(pfDropFile);
+                } while(ch != '\n' && ch != EOF);
+             }
+
+             /* Read line 24. */
+again:
+             if(fgets((char *)apszDropFileInfo[10],80,pfDropFile)==NULL) goto DropFileFail;
+             if(strchr(apszDropFileInfo[10],',')!=NULL) goto again;
+
+             /* Read line 25. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             szIFTemp[15]='\0';
+             ODStringToName(szIFTemp);
+             strcopydate(od_control.user_subdate,szIFTemp);
+
+             /* Read line 26. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_num=atoi(szIFTemp);
+
+             /* Read line 27. */
+             if(fgets((char *)apszDropFileInfo[6],80,pfDropFile)==NULL) goto DropFileFail;
+
+             /* Read line 28. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_uploads=atoi(szIFTemp);
+
+             /* Read line 29. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_downloads=atoi(szIFTemp);
+
+             /* Read line 30. */
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_todayk=atoi(szIFTemp);
+
+             /* Read line 31. */
+             if(fgets((char *)apszDropFileInfo[21],80,pfDropFile)==NULL) goto DropFileFail;
+
+
+                                        /* Beginning of extending DOOR.SYS data */
+             /* Read line 32. */
+             fgets((char *)apszDropFileInfo[7],80,pfDropFile);
+             /* Read line 33. */
+             fgets((char *)apszDropFileInfo[11],80,pfDropFile);
+             /* Read line 34. */
+             fgets((char *)apszDropFileInfo[12],80,pfDropFile);
+             /* Read line 35. */
+             fgets((char *)apszDropFileInfo[13],80,pfDropFile);
+             /* Read line 36. */
+             if(fgets((char *)apszDropFileInfo[14],80,pfDropFile)!=NULL)
+             {
+                /* Read line 37. */
+                strcopydate(od_control.user_birthday,apszDropFileInfo[7]);
+
+                /* Read line 38. */
+                strncpy(od_control.sysop_name,apszDropFileInfo[13],39);
+                od_control.sysop_name[39]='\0';
+                ODStringToName(od_control.sysop_name);
+
+                /* Read line 39. */
+                strncpy(od_control.user_handle,apszDropFileInfo[14],35);
+                od_control.user_handle[35]='\0';
+                ODStringToName(od_control.user_handle);
+
+                /* Read line 40. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                strncpy(od_control.event_starttime, szIFTemp, 5);
+                od_control.event_starttime[5] = '\0';
+
+                /* Read line 41. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                if(szIFTemp[0]=='y' || szIFTemp[0]=='Y')
+                   od_control.user_error_free=TRUE;
+                else
+                   od_control.user_error_free=FALSE;
+
+                /* Read line 42. */
+                if(fgets((char *)apszDropFileInfo[7],80,pfDropFile)==NULL) goto finished;
+                /* Read line 43. */
+                if(fgets((char *)apszDropFileInfo[13],80,pfDropFile)==NULL) goto finished;
+                /* Read line 44. */
+                if(fgets((char *)apszDropFileInfo[14],80,pfDropFile)==NULL) goto finished;
+                /* Read line 45. */
+                if(fgets((char *)apszDropFileInfo[15],80,pfDropFile)==NULL) goto finished;
+                /* Read line 46. */
+                if(fgets((char *)apszDropFileInfo[16],80,pfDropFile)==NULL) goto finished;
+
+                /* Read line 47. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                strncpy(od_control.user_logintime,szIFTemp,5);
+                od_control.user_logintime[5]='\0';
+
+                /* Read line 48. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                strncpy(od_control.user_lasttime,szIFTemp,5);
+                od_control.user_lasttime[5]='\0';
+
+                /* Read line 49. */
+                if(fgets((char *)apszDropFileInfo[18],80,pfDropFile)==NULL) goto finished;
+
+                /* Read line 50. */
+                if(fgets((char *)apszDropFileInfo[19],80,pfDropFile)==NULL) goto finished;
+
+                /* Read line 51. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                od_control.user_upk=atoi(szIFTemp);
+
+                /* Read line 52. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                od_control.user_downk=atoi(szIFTemp);
+
+                /* Read line 53. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                strncpy(od_control.user_comment,szIFTemp,79);
+                od_control.user_comment[79]='\0';
+                if(od_control.user_comment[strlen(od_control.user_comment)-1]=='\n')
+                   od_control.user_comment[strlen(od_control.user_comment)-1]='\0';
+                if(od_control.user_comment[strlen(od_control.user_comment)-1]=='\r')
+                   od_control.user_comment[strlen(od_control.user_comment)-1]='\0';
+
+                /* Read line 54. */
+                if(fgets((char *)apszDropFileInfo[20],80,pfDropFile)==NULL) goto finished;
+
+                /* Read line 55. */
+                if(fgets(szIFTemp,255,pfDropFile)==NULL) goto finished;
+                od_control.user_messages=atoi(szIFTemp);
+
+
+                od_control.od_info_type=DOORSYS_WILDCAT;
+             }
+          }
+
+          else                            /* DoorWay style DOOR.SYS */
+          {
+             od_control.od_info_type=DOORSYS_DRWY;
+
+             szIFTemp[35]='\0';
+             ODStringToName(szIFTemp);
+             strcpy(od_control.user_name,szIFTemp);
+
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.port=szIFTemp[0]-'1';
+
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             if(od_control.port==-1)
+             {
+#ifdef ODPLAT_NIX
+                od_control.baud=1L;
+#else
+                od_control.baud=0L;
+#endif
+             }
+             else
+             {
+                od_control.baud=atol(szIFTemp);
+             }
+
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             od_control.user_timelimit=atoi(szIFTemp);
+
+             if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+             if(szIFTemp[0]=='G')
+             {
+                od_control.user_ansi=TRUE;
+             }
+             else
+             {
+                od_control.user_ansi=FALSE;
+             }
+          }
+finished:
+          fclose(pfDropFile);
+       }
+
+       else if(nFound==FOUND_CALLINFO_BBS)
+       {
+          if((pfDropFile=fopen(szDropFilePath,"r"))==NULL) goto DropFileFail;
+
+          od_control.od_info_type=CALLINFO;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          szIFTemp[35]='\0';
+          ODStringToName(szIFTemp);
+          strcpy(od_control.user_name,szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          szIFTemp[25]='\0';
+          ODStringToName(szIFTemp);
+          strcpy(od_control.user_location,szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_security=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_timelimit=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(szIFTemp[0]=='M')
+          {
+             od_control.user_ansi=FALSE;
+          }
+          else
+          {
+             od_control.user_ansi=TRUE;
+          }
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          szIFTemp[15]='\0';
+          ODStringToName(szIFTemp);
+          strcpy(od_control.user_password,szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          szIFTemp[15]='\0';
+          ODStringToName(szIFTemp);
+          strcpy(od_control.user_homephone,szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_screen_length=atoi(szIFTemp);
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.port=szIFTemp[3]-'1';
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.baud=atol(szIFTemp);
+
+          fclose(pfDropFile);
+       }
+       else if(nFound == FOUND_TRIBBS_SYS)
+       {
+          /* We have found reading a TRIBBS.SYS file. */
+
+          /* Attempt to open the file. */
+          if((pfDropFile = fopen(szDropFilePath, "r")) == NULL)
+          {
+             goto DropFileFail;
+          }
+
+          /* Record the drop file type being used. */
+          od_control.od_info_type = TRIBBSSYS;
+
+          /* Read line 1: User's number. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_num = atoi(szIFTemp);
+
+          /* Read line 2: User's name. */
+          if(fgets((char *)&od_control.user_name, 35, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+          ODStringToName(od_control.user_name);
+
+          /* Read line 3: User's password. */
+          if(fgets((char *)&od_control.user_password, 15, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+
+          /* Read line 4: User's security level. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_security = atoi(szIFTemp);
+
+          /* Read line 5: "Y" for expert mode, "N" for novice mode. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_expert = (szIFTemp[0] == 'Y');
+
+          /* Read line 6: "Y" for ANSI, "N" for TTY. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_ansi = (szIFTemp[0] == 'Y');
+
+          /* Read line 7: Minutes remaining for this call. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_timelimit = atoi(szIFTemp);
+
+          /* Read line 8: User's phone number. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          szIFTemp[15] = '\0';
+          ODStringToName(szIFTemp);
+          strcpy(od_control.user_homephone, szIFTemp);
+
+          /* Read line 9: User's location. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          ODStringToName(szIFTemp);
+          strncpy(od_control.user_location, szIFTemp, 25);
+
+          /* Read line 10: User's birthday. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          strncpy(od_control.user_birthday, szIFTemp, 8);
+          od_control.user_birthday[8] = '\0';
+          od_control.user_birthday[2] = '-';
+          od_control.user_birthday[5] = '-';
+
+          /* Read line 11: Node number. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.od_node = atoi(szIFTemp);
+
+          /* Read line 12: Serial port. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.port = atoi(szIFTemp) - 1;
+
+          /* Read line 13: Baud rate. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.od_connect_speed =
+             (od_control.port == -1) ? 0 : atol(szIFTemp);
+
+          /* Read line 14: Locked baud rate or 0 for unlocked port. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.baud = atol(szIFTemp);
+          if(od_control.baud == 0)
+          {
+             od_control.baud = od_control.od_connect_speed;
+          }
+
+          /* Read line 15: "Y" for RTS/CTS handshaking, "N" for none. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.od_com_flow_control = (szIFTemp[0] == 'Y')
+             ? COM_RTSCTS_FLOW : COM_NO_FLOW;
+
+          /* Read line 16: "Y" for error free connection, "N" otherwise. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_error_free = (szIFTemp[0] == 'Y');
+
+          /* Read line 17: BBS name. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          ODStringCopy(od_control.system_name, szIFTemp, 40);
+          ODStringToName(od_control.system_name);
+
+          /* Read line 18: Sysop's name. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          ODStringCopy(od_control.sysop_name, szIFTemp, 40);
+          ODStringToName(od_control.sysop_name);
+
+          /* Read line 19: User's alias. */
+          if(fgets((char *)&od_control.user_handle, 35, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+          ODStringToName(od_control.user_handle);
+
+          /* Read line 20: "Y" for RIP mode, "N" for no RIP mode. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_rip = (szIFTemp[0] == 'Y');
+
+          /* Now, close the TRIBBS.SYS file. */
+          fclose(pfDropFile);
+       }
+       else if(nFound == FOUND_DOOR32_SYS)
+       {
+          /* We have found a DOOR32.SYS file. */
+
+          /* Attempt to open the file. */
+          if((pfDropFile = fopen(szDropFilePath, "r")) == NULL)
+          {
+             goto DropFileFail;
+          }
+
+          /* Record the drop file type being used. */
+          od_control.od_info_type = DOOR32SYS;
+
+          /* Read line 1: Comm type. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          switch(atoi(szIFTemp)) {
+				case 0: /* local */
+					od_control.od_force_local = TRUE;	/* Is this the right way? */
+					break;
+				case 1: /* serial */
+					break;
+				case 2: /* telnet */
+					od_control.od_use_socket = TRUE;
+					break;
+			 }
+			 /* Read line 2: Comm or Socket handle. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.od_open_handle	= atoi(szIFTemp);
+
+			 /* Read line 3: Baud rate */
+          if(fgets((char *)apszDropFileInfo[0],255,pfDropFile)==NULL) goto DropFileFail;
+#ifdef ODPLAT_NIX
+          od_control.baud= (od_control.port == -1) ? 1 : atol((char *)apszDropFileInfo[0]);
+#else
+          od_control.baud= (od_control.port == -1) ? 0 : atol((char *)apszDropFileInfo[0]);
+#endif
+
+			 /* Read line 4: BBS Software name and version - unused. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+
+          /* Read line 5: User's number. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_num = atoi(szIFTemp);
+
+          /* Read line 6: User's real name. */
+          if(fgets((char *)&od_control.user_name, 35, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+          ODStringToName(od_control.user_name);
+
+          /* Read line 7: User's alias. */
+          if(fgets((char *)&od_control.user_handle, 35, pfDropFile) == NULL)
+          {
+             goto DropFileFail;
+          }
+          ODStringToName(od_control.user_handle);
+
+          /* Read line 8: User's security level */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+          od_control.user_security=atoi(szIFTemp);
+
+          /* Read line 9: Minutes remaining for this call. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.user_timelimit = atoi(szIFTemp);
+
+          /* Read line 10: User's Terminal type  */
+          if(fgets(szIFTemp,255,pfDropFile)==NULL) goto DropFileFail;
+    		 od_control.user_ansi=TRUE;
+			 switch(atoi(szIFTemp)) {
+					case 0: /* ASCII */
+						od_control.user_ansi=FALSE;
+						break;
+					case 1: /* ANSI */
+						od_control.user_ansi=TRUE;
+						break;
+					case 2: /* AVATAR */
+						od_control.user_avatar=TRUE;
+						break;
+					case 3: /* RIP */
+						od_control.user_rip=TRUE;
+						break;
+			 }
+
+          /* Read line 11: Current Node number. */
+          if(fgets(szIFTemp, 255, pfDropFile) == NULL) goto DropFileFail;
+          od_control.od_node = atoi(szIFTemp);
+
+          /* Now, close the DOOR32.SYS file. */
+          fclose(pfDropFile);
+       }
+       else
+       {
+DropFileFail:
+			 if(pfDropFile!=NULL)	/* Let's not leave the file open */
+				 fclose(pfDropFile);
+
+          od_control.od_info_type = NO_DOOR_FILE;
+
+          if(od_control.od_no_file_func != NULL)
+          {
+             (*od_control.od_no_file_func)();
+          }
+
+          /* Check whether force local has been turned on by the no */
+          /* file function.                                         */
+          if(od_control.od_force_local)
+          {
+             goto force_local;
+          }
+
+          /* Exit with failure if the no file function has not read a */
+          /* door information file itself.                            */
+          if(od_control.od_info_type == NO_DOOR_FILE
+            && (wPreSetInfo & PRESET_REQUIRED) != PRESET_REQUIRED)
+          {
+             ODInitError("Unable to read door information (drop) file.");
+             exit(od_control.od_errorlevel[1]);
+          }
+       }
+   }
+
+   ODInitPartTwo();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInitReadSFDoorsDAT()                              *** PRIVATE FUNCTION ***
+ *
+ * Reads an SFDOORS.DAT format drop file.
+ *
+ * Parameters: none
+ *
+ *     Return: TRUE on success or FALSE on failure.
+ */
+static BOOL ODInitReadSFDoorsDAT(void)
+{
+   FILE *pfDropFile;
+
+   if((pfDropFile=fopen(szDropFilePath,"r"))==NULL) return(FALSE);
+
+   /* Line 1: User number. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_num=atoi(szIFTemp);
+
+   /* Line 2: User name. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   szIFTemp[35]='\0';
+   ODStringToName(szIFTemp);
+   strcpy(od_control.user_name,szIFTemp);
+
+   /* Line 3: User password. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   szIFTemp[15]='\0';
+   ODStringToName(szIFTemp);
+   strcpy(od_control.user_password,szIFTemp);
+
+   /* Line 4: Unused. */
+   if(fgets((char *)apszDropFileInfo[0],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 5: Modem <-> Serial port bps rate. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.baud=atol(szIFTemp);
+
+   /* Line 6: Serial port number. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.port=atoi(szIFTemp)-1;
+
+   /* Line 7: User's time remaining. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_timelimit=atoi(szIFTemp);
+
+   /* Line 8: Unused. */
+   if(fgets((char *)apszDropFileInfo[13],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 9: Unused. */
+   if(fgets((char *)apszDropFileInfo[14],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 10: User's ANSI mode setting. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   strupr(szIFTemp);
+   od_control.user_ansi=(szIFTemp[0]=='T');
+
+   /* Line 11: User's security level. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_security=atoi(szIFTemp);
+
+   /* Line 12: User's upload count. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_uploads=atoi(szIFTemp);
+
+   /* Line 13: User's download count. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_downloads=atoi(szIFTemp);
+
+   /* Line 14: Unused. */
+   if(fgets((char *)apszDropFileInfo[1],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 15: User's login time. */
+   if(fgets((char *)apszDropFileInfo[2],255,pfDropFile)==NULL) return(FALSE);
+   sprintf(od_control.user_logintime, "%02d:%02d",
+      atoi((char *)apszDropFileInfo[2]) % 60,
+      atoi((char *)apszDropFileInfo[2]) / 60);
+
+   /* Line 16: Unused. */
+   if(fgets((char *)apszDropFileInfo[3],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 17: Sysop next flag. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   strupr(szIFTemp);
+   od_control.sysop_next=(szIFTemp[0]=='T');
+
+   /* Line 18: Unused. */
+   if(fgets((char *)apszDropFileInfo[4],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 19: Unused. */
+   if(fgets((char *)apszDropFileInfo[5],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 20: Unused. */
+   if(fgets((char *)apszDropFileInfo[6],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 21: Error free connection flag. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   strupr(szIFTemp);
+   od_control.user_error_free=(szIFTemp[0]=='T');
+
+   /* Line 22: Current message area. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_msg_area=atoi(szIFTemp);
+
+   /* Line 23: Current file area. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_file_area=atoi(szIFTemp);
+
+   /* Line 24: Current node number. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.od_node=atoi(szIFTemp);
+
+   /* Line 25: Unused. */
+   if(fgets((char *)apszDropFileInfo[10],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 26: Unused. */
+   if(fgets((char *)apszDropFileInfo[11],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 27: Unused. */
+   if(fgets((char *)apszDropFileInfo[12],80,pfDropFile)==NULL) return(FALSE);
+
+   /* Line 28: Kilobytes downloaded today. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_todayk=atoi(szIFTemp);
+
+   /* Line 29: Kilobytes uploaded in total. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_upk=atoi(szIFTemp);
+
+   /* Line 30: Kilobytes downloaded in total. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   od_control.user_downk=atoi(szIFTemp);
+
+   /* Line 31: User's home phone number. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   szIFTemp[15]='\0';
+   ODStringToName(szIFTemp);
+   strcpy(od_control.user_homephone,szIFTemp);
+
+   /* Line 32: User's home location. */
+   if(fgets(szIFTemp,255,pfDropFile)==NULL) return(FALSE);
+   szIFTemp[25]='\0';
+   ODStringToName(szIFTemp);
+   strcpy(od_control.user_location,szIFTemp);
+
+   /* Line 33: Unused. */
+   if(fgets((char *)apszDropFileInfo[15],80,pfDropFile)==NULL)
+   {
+      apszDropFileInfo[15][0]='\0';
+   }
+
+   /* Line 34: RIP mode. */
+   if(fgets(szIFTemp,255,pfDropFile) != NULL)
+   {
+      strupr(szIFTemp);
+      od_control.user_rip = (szIFTemp[0] == 'T');
+   }
+
+   /* line 35: User wants chat. */
+   if(fgets(szIFTemp,255,pfDropFile) != NULL)
+   {
+      strupr(szIFTemp);
+      od_control.user_wantchat = (szIFTemp[0] == 'T');
+   }
+
+   /* Line 36: Unused. */
+   if(fgets((char *)apszDropFileInfo[17],80,pfDropFile)==NULL)
+   {
+      apszDropFileInfo[17][0]='\0';
+   }
+
+   /* Line 37: IRQ number. */
+   if(fgets(szIFTemp,255,pfDropFile) != NULL)
+   {
+      od_control.od_com_irq = atoi(szIFTemp);
+   }
+
+   /* Line 38: Serial port address. */
+   if(fgets(szIFTemp,255,pfDropFile) != NULL)
+   {
+      od_control.od_com_address = atoi(szIFTemp);
+   }
+
+   /* Line 39: Unused. */
+   if(fgets((char *)apszDropFileInfo[18],80,pfDropFile)==NULL)
+   {
+      apszDropFileInfo[18][0]='\0';
+   }
+
+   fclose(pfDropFile);
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInitReadExitInfo()                                *** PRIVATE FUNCTION ***
+ *
+ * Reads an EXITINFO.BBS format drop file.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODInitReadExitInfo(void)
+{
+   DWORD dwFileSize;
+   FILE *pfDropFile;
+   BYTE btCount;
+
+   od_control.od_extended_info=FALSE;
+   od_control.od_ra_info=FALSE;
+
+   /* Try to open EXITINFO.BBS. */
+   ODMakeFilename(szODWorkString, szExitinfoBBSPath, "exitinfo.bbs",
+      sizeof(szExitinfoBBSPath));
+   if((pfDropFile = fopen(szODWorkString, "rb")) != NULL)
+   {
+      dwFileSize = ODFileSize(pfDropFile);
+
+      if(dwFileSize >= 2363)
+      {
+         if((pRA2ExitInfoRecord=malloc(sizeof(tRA2ExitInfoRecord)))!=NULL)
+         {
+            if(fread(pRA2ExitInfoRecord,1,2363,pfDropFile)==2363)
+            {
+               od_control.od_ra_info=TRUE;
+               od_control.od_extended_info=TRUE;
+               od_control.od_info_type=RA2EXITINFO;
+
+               od_control.baud=(long)pRA2ExitInfoRecord->baud;
+               od_control.system_calls=pRA2ExitInfoRecord->num_calls;
+               ODStringPascalToC(od_control.system_last_caller,pRA2ExitInfoRecord->last_caller,35);
+               ODStringPascalToC(od_control.system_last_handle,pRA2ExitInfoRecord->sLastHandle,35);
+               ODStringPascalToC(od_control.timelog_start_date,pRA2ExitInfoRecord->start_date,8);
+               memcpy(&od_control.timelog_busyperhour,&pRA2ExitInfoRecord->busyperhour,62);
+               ODStringPascalToC(od_control.user_name,pRA2ExitInfoRecord->name,35);
+               ODStringPascalToC(od_control.user_location,pRA2ExitInfoRecord->location,25);
+               ODStringPascalToC(od_control.user_org,pRA2ExitInfoRecord->organisation,50);
+               for(btCount=0;btCount<3;++btCount)
+                  ODStringPascalToC(od_control.user_address[btCount],pRA2ExitInfoRecord->address[btCount],50);
+               ODStringPascalToC(od_control.user_handle,pRA2ExitInfoRecord->handle,35);
+               ODStringPascalToC(od_control.user_comment,pRA2ExitInfoRecord->comment,80);
+               od_control.user_pwd_crc=pRA2ExitInfoRecord->password_crc;
+               ODStringPascalToC(od_control.user_dataphone,pRA2ExitInfoRecord->dataphone,15);
+               ODStringPascalToC(od_control.user_homephone,pRA2ExitInfoRecord->homephone,15);
+               ODStringPascalToC(od_control.user_lasttime,pRA2ExitInfoRecord->lasttime,5);
+               ODStringPascalToC(od_control.user_lastdate,pRA2ExitInfoRecord->lastdate,8);
+               od_control.user_attribute=pRA2ExitInfoRecord->attrib;
+               od_control.user_attrib2=pRA2ExitInfoRecord->attrib2;
+               memcpy(&od_control.user_flags,&pRA2ExitInfoRecord->flags,14);
+               od_control.user_security=pRA2ExitInfoRecord->sec;
+               od_control.user_lastread=pRA2ExitInfoRecord->lastread;
+               memcpy(&od_control.user_numcalls,&pRA2ExitInfoRecord->nocalls,29);
+               od_control.user_group=pRA2ExitInfoRecord->group;
+               memcpy(&od_control.user_combinedrecord,&pRA2ExitInfoRecord->combinedrecord,200);
+               ODStringPascalToC(od_control.user_firstcall,pRA2ExitInfoRecord->firstcall,8);
+               ODStringPascalToC(od_control.user_birthday,pRA2ExitInfoRecord->birthday,8);
+               ODStringPascalToC(od_control.user_subdate,pRA2ExitInfoRecord->subdate,8);
+               od_control.user_screenwidth=pRA2ExitInfoRecord->screenwidth;
+               od_control.user_language=pRA2ExitInfoRecord->language;
+               od_control.user_date_format=pRA2ExitInfoRecord->dateformat;
+               ODStringPascalToC(od_control.user_forward_to,pRA2ExitInfoRecord->forwardto,35);
+               memcpy(&od_control.user_msg_area,&pRA2ExitInfoRecord->msgarea,15);
+               od_control.user_sex = (pRA2ExitInfoRecord->sex == 1) ? 'M' : 'F';
+               od_control.user_attrib3=pRA2ExitInfoRecord->btAttribute3;
+               ODStringPascalToC(od_control.user_password,pRA2ExitInfoRecord->sPassword,15);
+               od_control.event_status=pRA2ExitInfoRecord->status;
+               ODStringPascalToC(od_control.event_starttime,pRA2ExitInfoRecord->starttime,5);
+               memcpy(&od_control.event_errorlevel,&pRA2ExitInfoRecord->errorlevel,3);
+               ODStringPascalToC(od_control.event_last_run,pRA2ExitInfoRecord->lasttimerun,8);
+               memcpy(&od_control.user_netmailentered,&pRA2ExitInfoRecord->netmailentered,2);
+               ODStringPascalToC(od_control.user_logintime,pRA2ExitInfoRecord->logintime,5);
+               ODStringPascalToC(od_control.user_logindate,pRA2ExitInfoRecord->logindate,8);
+               memcpy(&od_control.user_timelimit,&pRA2ExitInfoRecord->timelimit,6);
+               memcpy(&od_control.user_num,&pRA2ExitInfoRecord->userrecord,8);
+               ODStringPascalToC(od_control.user_timeofcreation,pRA2ExitInfoRecord->timeofcreation,5);
+               od_control.user_logon_pwd_crc=pRA2ExitInfoRecord->logonpasswordcrc;
+               od_control.user_wantchat=pRA2ExitInfoRecord->wantchat;
+               od_control.user_deducted_time=pRA2ExitInfoRecord->deducted_time;
+               for(btCount=0;btCount<50;++btCount)
+                  ODStringPascalToC(od_control.user_menustack[btCount],pRA2ExitInfoRecord->menustack[btCount],8);
+               od_control.user_menustackpointer=pRA2ExitInfoRecord->menustackpointer;
+               memcpy(&od_control.user_error_free,&pRA2ExitInfoRecord->error_free,3);
+               ODStringPascalToC(od_control.user_emsi_crtdef,pRA2ExitInfoRecord->emsi_crtdef,40);
+               ODStringPascalToC(od_control.user_emsi_protocols,pRA2ExitInfoRecord->emsi_protocols,40);
+               ODStringPascalToC(od_control.user_emsi_capabilities,pRA2ExitInfoRecord->emsi_capabilities,40);
+               ODStringPascalToC(od_control.user_emsi_requests,pRA2ExitInfoRecord->emsi_requests,40);
+               ODStringPascalToC(od_control.user_emsi_software,pRA2ExitInfoRecord->emsi_software,40);
+               memcpy(&od_control.user_hold_attr1,&pRA2ExitInfoRecord->hold_attr1,3);
+               ODStringPascalToC(od_control.user_reasonforchat,pRA2ExitInfoRecord->page_reason,77);
+               btRAStatusToSet = pRA2ExitInfoRecord->status_line-1;
+               ODStringPascalToC(od_control.user_last_cost_menu,pRA2ExitInfoRecord->last_cost_menu,8);
+               od_control.user_menu_cost=pRA2ExitInfoRecord->menu_cost_per_min;
+               od_control.user_rip=pRA2ExitInfoRecord->has_rip;
+               od_control.user_rip_ver=pRA2ExitInfoRecord->btRIPVersion;
+
+               od_control.user_ansi=od_control.user_attribute&8;
+               od_control.user_avatar=od_control.user_attrib2&2;
+            }
+
+            else
+            {
+               free(pRA2ExitInfoRecord);
+            }
+         }
+      }
+
+      else if(dwFileSize>=1493)
+      {
+         if(ODReadExitInfoPrimitive(pfDropFile,476))
+         {
+            if((pExtendedExitInfo=malloc(sizeof(tExtendedExitInfo)))!=NULL)
+            {
+               if(fread(pExtendedExitInfo,1,sizeof(tExtendedExitInfo), pfDropFile)==sizeof(tExtendedExitInfo))
+               {                 /* transfer info into od_control struct */
+                  ODStringPascalToC(od_control.user_timeofcreation,pExitInfoRecord->bbs.ra.timeofcreation,5);
+                  ODStringPascalToC(od_control.user_logonpassword,pExitInfoRecord->bbs.ra.logonpassword,15);
+                  od_control.user_wantchat=pExitInfoRecord->bbs.ra.wantchat;
+
+                  od_control.user_deducted_time=pExtendedExitInfo->deducted_time;
+                  for(btCount=0;btCount<50;++btCount)
+                  {
+                     ODStringPascalToC(od_control.user_menustack[btCount],pExtendedExitInfo->menustack[btCount],8);
+                  }
+                  od_control.user_menustackpointer=pExtendedExitInfo->menustackpointer;
+                  ODStringPascalToC(od_control.user_handle,pExtendedExitInfo->userhandle,35);
+                  ODStringPascalToC(od_control.user_comment,pExtendedExitInfo->comment,80);
+                  ODStringPascalToC(od_control.user_firstcall,pExtendedExitInfo->firstcall,8);
+                  memcpy(od_control.user_combinedrecord,pExtendedExitInfo->combinedrecord,25);
+                  ODStringPascalToC(od_control.user_birthday,pExtendedExitInfo->birthday,8);
+                  ODStringPascalToC(od_control.user_subdate,pExtendedExitInfo->subdate,8);
+                  od_control.user_screenwidth=pExtendedExitInfo->screenwidth;
+                  od_control.user_msg_area=pExtendedExitInfo->msgarea;
+                  od_control.user_file_area=pExtendedExitInfo->filearea;
+                  od_control.user_language=pExtendedExitInfo->language;
+                  od_control.user_date_format=pExtendedExitInfo->dateformat;
+                  ODStringPascalToC(od_control.user_forward_to,pExtendedExitInfo->forwardto,35);
+                  memcpy(&od_control.user_error_free,&pExtendedExitInfo->error_free,3);
+                  ODStringPascalToC(od_control.user_emsi_crtdef,pExtendedExitInfo->emsi_crtdef,40);
+                  ODStringPascalToC(od_control.user_emsi_protocols,pExtendedExitInfo->emsi_protocols,40);
+                  ODStringPascalToC(od_control.user_emsi_capabilities,pExtendedExitInfo->emsi_capabilities,40);
+                  ODStringPascalToC(od_control.user_emsi_requests,pExtendedExitInfo->emsi_requests,40);
+                  ODStringPascalToC(od_control.user_emsi_software,pExtendedExitInfo->emsi_software,40);
+                  memcpy(&od_control.user_hold_attr1,&pExtendedExitInfo->hold_attr1,3);
+
+                  od_control.od_ra_info=TRUE;
+                  od_control.od_extended_info=TRUE;
+                  od_control.od_info_type=RA1EXITINFO;
+               }
+            }
+         }
+      }
+
+      else if(dwFileSize>476)
+      {
+         if(dwFileSize > sizeof(tExitInfoRecord))
+         {
+            dwFileSize = sizeof(tExitInfoRecord);
+         }
+
+         if(ODReadExitInfoPrimitive(pfDropFile,(int)dwFileSize))
+         {
+            od_control.user_wantchat=pExitInfoRecord->bbs.qbbs.qwantchat;
+            for(btCount=0;btCount<pExitInfoRecord->bbs.qbbs.gosublevel;++btCount)
+            {
+               ODStringPascalToC(od_control.user_menustack[btCount],pExitInfoRecord->bbs.qbbs.menustack[btCount],8);
+            }
+            od_control.user_menustackpointer=pExitInfoRecord->bbs.qbbs.gosublevel;
+            ODStringPascalToC(od_control.user_menustack[od_control.user_menustackpointer],pExitInfoRecord->bbs.qbbs.menu,8);
+
+            od_control.od_extended_info=TRUE;
+            od_control.od_info_type=QBBS275EXITINFO;
+            nInitialElapsed=pExitInfoRecord->elapsed;
+
+            od_control.user_rip = pExitInfoRecord->bbs.qbbs.ripactive;
+         }
+      }
+
+      else if(dwFileSize>=452)
+      {
+         if(ODReadExitInfoPrimitive(pfDropFile,(int)dwFileSize))
+         {
+            ODStringPascalToC(od_control.user_timeofcreation,pExitInfoRecord->bbs.ra.timeofcreation,5);
+            ODStringPascalToC(od_control.user_logonpassword,pExitInfoRecord->bbs.ra.logonpassword,15);
+            od_control.user_wantchat=pExitInfoRecord->bbs.ra.wantchat;
+
+            od_control.od_extended_info=TRUE;
+            od_control.od_info_type=EXITINFO;
+         }
+      }
+
+      od_control.od_page_pausing=od_control.user_attribute&0x04;
+
+      fclose(pfDropFile);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInitPartTwo()                                     *** PRIVATE FUNCTION ***
+ *
+ * Called by od_init() to carry on with second stage of OpenDoors
+ * initialization. The sole reason that this function exists is because some
+ * compilers were unable to compile this file with a huge od_init() function
+ * that had this code in the main od_init() body.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODInitPartTwo(void)
+{
+   BYTE btCount;
+#ifdef ODPLAT_NIX
+   struct termios term;
+   struct passwd  *uinfo;
+#endif
+
+   /* Initialize any colors that haven't already been set. */
+   if(!od_control.od_list_title_col) od_control.od_list_title_col = 0x0f;
+   if(!od_control.od_continue_col) od_control.od_continue_col = 0x0f;
+   if(!od_control.od_list_name_col) od_control.od_list_name_col = 0x0e;
+   if(!od_control.od_list_size_col) od_control.od_list_size_col = 0x0d;
+   if(!od_control.od_list_comment_col) od_control.od_list_comment_col = 0x03;
+   if(!od_control.od_list_offline_col) od_control.od_list_offline_col = 0x0c;
+   if(!od_control.od_local_win_col) od_control.od_local_win_col = 0x19;
+   if(!od_control.od_menu_title_col) od_control.od_menu_title_col = 0x74;
+   if(!od_control.od_menu_border_col) od_control.od_menu_border_col = 0x70;
+   if(!od_control.od_menu_text_col) od_control.od_menu_text_col = 0x70;
+   if(!od_control.od_menu_key_col) od_control.od_menu_key_col = 0x7f;
+   if(!od_control.od_menu_highkey_col) od_control.od_menu_highkey_col = 0x0f;
+   if(!od_control.od_menu_highlight_col)
+   {
+      od_control.od_menu_highlight_col = 0x07;
+   }
+
+   /* Color sequence delimiter characters. */
+   od_control.od_color_char = '\0';
+   od_control.od_color_delimiter = '`';
+
+   /* Enable sysop paging between the hours of 8:00am and 10:00pm. */
+   od_control.od_okaytopage = PAGE_USE_HOURS;
+   od_control.od_pagestartmin = 480;
+   od_control.od_pageendmin = 1320;
+
+   /* Default maximum user inactivity is 200 seconds. */
+   od_control.od_inactivity = 200;
+
+   /* Screen clearing is on by default. */
+   od_control.od_always_clear = TRUE;
+   
+   od_control.od_inactive_warning = 10;
+   od_control.od_cur_attrib = -1;
+
+   /* Enable screen clearing on door exit. */
+   od_control.od_clear_on_exit = TRUE;
+
+   /* RA/QBBS control codes are now disabled by default. */
+   od_control.od_no_ra_codes = TRUE;
+
+   if(od_control.od_chat_color1 == 0) od_control.od_chat_color1 = 0x0c;
+   if(od_control.od_chat_color2 == 0) od_control.od_chat_color2 = 0x0f;
+
+   /* Set default messages and prompts. */
+   od_control.od_before_shell = "\n\rPlease wait a moment...\n\r";
+   od_control.od_after_shell = "\n\r...Thanks for waiting\n\r\n\r";
+   od_control.od_help_text = "  Alt: [C]hat [H]angup [L]ockout [J]Dos [K]eyboard-Off [D]rop to BBS            ";
+   od_control.od_before_chat = "\n\rThe system operator has placed you in chat mode to talk with you:\n\r\n\r";
+   od_control.od_after_chat = "\n\rChat mode ended.\n\r\n\r";
+   od_control.od_inactivity_timeout = "\n\rMaximum user inactivity time has elapsed, please call again.\n\r\n\r";
+   od_control.od_inactivity_warning = "\n\rWARNING: Inactivity timeout in 10 seconds, press a key now to remain online.\n\r\n\r";
+   od_control.od_time_warning = "\n\rWARNING: You only have %d minute(s) remaining for this session.\n\r\n\r";
+   od_control.od_time_left = "%d mins   ";
+   od_control.od_sysop_next = "[SN] ";
+   od_control.od_no_keyboard = "[Keyboard]";
+   od_control.od_want_chat = "[Want-Chat]";
+   od_control.od_no_time = "\n\rSorry, you have used up of your time for this session.\n\r\n\r";
+   od_control.od_no_sysop = "\n\rSorry, the system operator is not available at this time.\n\r";
+   od_control.od_press_key = "Press [Enter] to continue";
+   od_control.od_chat_reason = "               Why would you like to chat? (Blank line to cancel)\n\r";
+   od_control.od_paging = "\n\rPaging system operator for chat";
+   od_control.od_no_response = " No response.\n\r\n\r";
+   od_control.od_status_line[0] = "                                                                     [Node:     ";
+   od_control.od_status_line[1] = "%s of %s at %lu BPS";
+   od_control.od_status_line[2] = "Security:        Time:                                               [F9]=Help ";
+   od_control.od_month[0] = "Jan";
+   od_control.od_month[1] = "Feb";
+   od_control.od_month[2] = "Mar";
+   od_control.od_month[3] = "Apr";
+   od_control.od_month[4] = "May";
+   od_control.od_month[5] = "Jun";
+   od_control.od_month[6] = "Jul";
+   od_control.od_month[7] = "Aug";
+   od_control.od_month[8] = "Sep";
+   od_control.od_month[9] = "Oct";
+   od_control.od_month[10] = "Nov";
+   od_control.od_month[11] = "Dec";
+   od_control.od_day[0] = "Sun";
+   od_control.od_day[1] = "Mon";
+   od_control.od_day[2] = "Tue";
+   od_control.od_day[3] = "Wed";
+   od_control.od_day[4] = "Thu";
+   od_control.od_day[5] = "Fri";
+   od_control.od_day[6] = "Sat";
+   od_control.od_offline = "[OFFLINE] ";
+   od_control.od_continue = "Continue? [Y/n/=]";
+   od_control.od_continue_yes = 'y';
+   od_control.od_continue_no = 'n';
+   od_control.od_continue_nonstop = '=';
+   od_control.od_help_text2 = OD_VER_STATUSLINE;
+   od_control.od_sending_rip = "Sending RIP file: ";
+   od_control.od_hanging_up = "Ending call...";
+   od_control.od_exiting = "Program is exiting...";
+   if(strlen(od_control.od_disable_dtr) == 0)
+   {
+      strcpy(od_control.od_disable_dtr, "~+++~  AT&D0|  ATO|");
+   }
+
+   nInitialRemaining = od_control.user_timelimit;
+
+   if(od_control.od_maxtime > 0 && od_control.od_maxtime <= 1440)
+   {
+      if(od_control.user_timelimit > (INT)od_control.od_maxtime)
+      {
+         od_control.od_maxtime_deduction
+            = od_control.user_timelimit-od_control.od_maxtime;
+         od_control.user_timelimit
+            = od_control.od_maxtime;
+      }
+   }
+
+   /* If sysop name is unkown, then use the word "Sysop". */
+   if(strlen(od_control.sysop_name) == 0)
+   {
+      strcpy(od_control.sysop_name, "Sysop");
+   }
+
+   /* If in foced local mode and user  name has not yet been set. */
+   if(od_control.od_force_local && od_control.user_name[0] == '\0')
+   {
+      /* If name prompting is not disabled, then turn on flag to prompt for */
+      /* user's name.                                                       */
+      if(!(od_control.od_disable & DIS_NAME_PROMPT))
+      {
+         bPromptForUserName = TRUE;
+      }
+
+      /* Use sysop's name as default user name. */
+      if(bSysopNameSet)
+      {
+         strcpy(od_control.user_name, szForcedSysopName);
+      }
+      else
+      {
+         strcpy(od_control.user_name, od_control.sysop_name);
+      }
+   }
+
+   dwFileBPS = od_control.baud;
+
+   /* Determine desired BPS rate. */
+   if(dwForcedBPS != 1)
+   {
+      if(od_control.od_disable & DIS_LOCAL_OVERRIDE || od_control.baud != 0)
+      {
+         od_control.baud = dwForcedBPS;
+      }
+   }
+
+   /* If we are not operating in local mode, then setup for serial I/O. */
+#ifndef ODPLAT_NIX
+   if(od_control.baud != 0)
+#endif
+   {
+      tComMethod ComMethod;
+
+      /* Attempt to allocate an OpenDoors port object. */
+      if(ODComAlloc(&hSerialPort) != kODRCSuccess)
+      {
+malloc_error:
+         ODInitError("Insufficient memory available to start up program.");
+         exit(od_control.od_errorlevel[1]);
+      }
+
+      /* Set socket I/O method, if specified by user. */
+      if(od_control.od_use_socket)
+      {
+         ODComSetPreferredMethod(hSerialPort, kComMethodSocket);
+      }
+
+#if defined ODPLAT_WIN32 || defined ODPLAT_NIX
+      /* Check whether a handle has been provided by the caller. */
+      if(od_control.od_open_handle != 0)
+      {
+         if(ODComOpenFromExistingHandle(hSerialPort, od_control.od_open_handle)
+            != kODRCSuccess)
+         {
+            ODInitError("Unable to use provided serial port handle.");
+            exit(od_control.od_errorlevel[1]);
+         }
+      }
+      else
+#endif /* ODPLAT_WIN32 */
+      {
+         /* Set flow control type. */
+         switch(od_control.od_com_flow_control)
+         {
+            case COM_NO_FLOW:
+               ODComSetFlowControl(hSerialPort, FLOW_NONE);
+               break;
+            case COM_RTSCTS_FLOW:
+               ODComSetFlowControl(hSerialPort, FLOW_RTSCTS);
+               break;
+            case COM_DEFAULT_FLOW:
+               /* Don't say anything to the serial I/O module about flow */
+               /* control. */
+               break;
+            default:
+               /* We should never get here. */
+               ASSERT(FALSE);
+         }
+
+         /* Determine desired port. */
+         if(nForcedPort != -1) od_control.port=nForcedPort;
+
+         /* Set port number. */
+         ODComSetPort(hSerialPort, (BYTE)od_control.port);
+
+         /* If serial port address has been explicitly set, then set user's */
+         /* port address in serial port object.                             */
+         if(od_control.od_com_address != 0)
+         {
+            ODComSetPortAddress(hSerialPort, od_control.od_com_address);
+         }
+
+         /* If serial port IRQ line number has been explicitly set, then */
+         /* set user's setting in serial port object.                    */
+         if(od_control.od_com_irq >= 1 && od_control.od_com_irq < 15)
+         {
+            ODComSetIRQ(hSerialPort, od_control.od_com_irq);
+         }
+
+         /* Set BPS rate, if this is not disabled. */
+         if(!(od_control.od_disable&DIS_BPS_SETTING))
+         {
+            ODComSetSpeed(hSerialPort, od_control.baud);
+         }
+         else
+         {
+            ODComSetSpeed(hSerialPort, SPEED_UNSPECIFIED);
+         }
+
+         /* Set serial I/O method, if specified by user. */
+         if(od_control.od_no_fossil)
+         {
+            ODComSetPreferredMethod(hSerialPort, kComMethodUART);
+         }
+
+         /* Establish default buffer sizes */
+         if(od_control.od_com_rx_buf == 0)
+            od_control.od_com_rx_buf = 256;
+         if(od_control.od_com_tx_buf == 0)
+            od_control.od_com_tx_buf = 3072;
+
+         /* Set buffer sizes for serial port. */
+         ODComSetRXBuf(hSerialPort, od_control.od_com_rx_buf);
+         ODComSetTXBuf(hSerialPort, od_control.od_com_tx_buf);
+
+         /* Set FIFO enabled/disabled and trigger size */
+         if(od_control.od_com_no_fifo)
+         {
+            /* Disable UART FIFO buffers. */
+            ODComSetFIFO(hSerialPort, FIFO_DISABLE);
+         }
+         else
+         {
+            /* Enable FIFO, setting size of FIFO trigger. */
+            switch(od_control.od_com_fifo_trigger)
+            {
+               case 1:
+                  ODComSetFIFO(hSerialPort, FIFO_ENABLE | FIFO_TRIGGER_1);
+                  break;
+               case 4:
+                  ODComSetFIFO(hSerialPort, FIFO_ENABLE | FIFO_TRIGGER_4);
+                  break;
+               case 8:
+                  ODComSetFIFO(hSerialPort, FIFO_ENABLE | FIFO_TRIGGER_8);
+                  break;
+               case 14:
+                  ODComSetFIFO(hSerialPort, FIFO_ENABLE | FIFO_TRIGGER_14);
+                  break;
+               default:
+                  od_control.od_com_fifo_trigger = 4;
+                  ODComSetFIFO(hSerialPort, FIFO_ENABLE | FIFO_TRIGGER_4);
+            }
+         }
+
+#ifdef ODPLAT_DOS
+         /* Set od_kernel() to be idle function. */
+         ODComSetIdleFunction(hSerialPort, od_kernel);
+#endif /* ODPLAT_DOS */
+
+         /* Open serial port. */
+         switch(ODComOpen(hSerialPort))
+         {
+            case kODRCSuccess:
+               /* Serial port open succeeded, so proceed with OpenDoors */
+               /* initialization.                                       */
+               break;
+            case kODRCNoMemory:
+               goto malloc_error;
+            case kODRCNoPortAddress:
+               ODInitError("Serial port address is unknown, cannot continue.");
+               exit(od_control.od_errorlevel[1]);
+               break;
+            case kODRCNoUART:
+               ODInitError("No UART at specified port address, cannot continue.\n");
+               exit(od_control.od_errorlevel[1]);
+               break;
+            default:
+               ODInitError("Unable to access serial port, cannot continue.\n");
+               exit(od_control.od_errorlevel[1]);
+               break;
+         }
+      }
+
+      /* Raise DTR signal. */
+      ODComSetDTR(hSerialPort, TRUE);
+
+      /* Get serial I/O method actually being used. */
+      ODComGetMethod(hSerialPort, (tComMethod *)&ComMethod);
+      switch(ComMethod)
+      {
+         case kComMethodFOSSIL:
+            od_control.od_com_method = COM_FOSSIL;
+            break;
+         case kComMethodUART:
+            od_control.od_com_method = COM_INTERNAL;
+            break;
+         case kComMethodWin32:
+            od_control.od_com_method = COM_WIN32;
+            break;
+         case kComMethodDoor32:
+            od_control.od_com_method = COM_DOOR32;
+            break;
+			case kComMethodSocket:
+				od_control.od_com_method = COM_SOCKET;	/* Why are using doubling up constants here? */
+				break;
+		 case kComMethodStdIO:
+		    od_control.od_com_method = COM_STDIO;
+			break;
+         default:
+            ODInitError("No method of accessing serial port, cannot continue.\n");
+            exit(od_control.od_errorlevel[1]);
+            break;
+      }
+   }
+
+   /* If we are operating in local mode, then disable silent mode. */
+   if(od_control.baud == 0)
+   {
+      od_control.od_silent_mode = FALSE;
+   }
+
+   /* Setup local screen. */
+   ODScrnInitialize();
+#ifdef OD_TEXTMODE
+   ODScrnSetBoundary(1, 1, 80, 23);
+#else /* !OD_TEXTMODE */
+   ODScrnSetBoundary(1, 1, 80, 25);
+#endif /* !OD_TEXTMODE */
+
+#ifndef ODPLAT_WIN32
+   if(bPreset)
+   {
+      atexit(ODAtExitCallback);
+      bPreset = FALSE;
+   }
+#endif /* !ODPLAT_WIN32 */
+
+   /* Setup remote terminal for ANSI graphics if operating in RIP mode. */
+   if(od_control.user_rip)
+   {
+      od_clr_scr();
+   }
+
+#ifdef ODPLAT_WIN32
+   /* Startup the OpenDoors frame window, if we are not operating in silent */
+   /* mode.                                                                 */
+   if(!od_control.od_silent_mode)
+   {
+#ifdef OD_DLL
+      ODFrameStart(GetModuleHandle(OD_DLL_NAME), &hFrameThread);
+#else /* !OD_DLL */
+      ODFrameStart(GetModuleHandle(NULL), &hFrameThread);
+#endif /* !OD_DLL */
+   }
+#endif /* ODPLAT_WIN32 */
+
+   /* Initialize the OpenDoors kernel. */
+   ODKrnlInitialize();
+
+#ifndef ODPLAT_WIN32
+#ifdef ODPLAT_NIX
+   if(bPromptForUserName)
+   {
+      od_control.od_com_method=COM_STDIO;
+      od_control.baud=19200;
+      gethostname(od_control.system_name,sizeof(od_control.system_name));
+      od_control.system_name[sizeof(od_control.system_name)-1]=0;
+      if (isatty(STDIN_FILENO))  {
+        tcgetattr(STDIN_FILENO,&term);
+   	  od_control.baud=cfgetispeed(&term);
+        if(!od_control.baud)
+   	    od_control.baud=cfgetispeed(&term);
+        if(!od_control.baud)
+   		 od_control.baud=19200;
+      }
+      uinfo=getpwuid(getuid());
+      ODStringCopy(od_control.user_handle, uinfo->pw_name,sizeof(od_control.user_handle));
+      ODStringCopy(od_control.user_name, uinfo->pw_gecos,sizeof(od_control.user_name));
+   }
+#else
+   if(bPromptForUserName)
+   {
+      void *pWindow = ODScrnCreateWindow(10, 8, 70, 15,
+         od_control.od_local_win_col, od_control.od_prog_name,
+         od_control.od_local_win_col);
+
+      if(pWindow != NULL)
+      {
+         ODScrnSetCursorPos(12, 9);
+         ODScrnDisplayString("This program has been started in local mode,");
+         ODScrnSetCursorPos(12, 10);
+         ODScrnDisplayString("independently of a BBS system. When operating in this");
+         ODScrnSetCursorPos(12, 11);
+         ODScrnDisplayString("mode, you may specify what name you should be known to");
+         ODScrnSetCursorPos(12, 12);
+         ODScrnDisplayString("the program by.");
+         ODScrnSetCursorPos(12, 14);
+         ODScrnDisplayString("Your name:");
+         ODScrnLocalInput(23, 14, od_control.user_name, 35);
+         ODStringCopy(od_control.user_handle, od_control.user_name,
+            sizeof(od_control.user_name));
+         ODScrnDestroyWindow(pWindow);
+         ODScrnSetCursorPos(1, 1);
+         ODScrnSetAttribute(0x07);
+      }
+   }
+#endif /* !ODPLAT_NIX */
+#endif /* !ODPLAT_WIN32 */
+
+#ifdef OD_TEXTMODE
+   /* Setup sysop status line/function key personality. */
+   if(pfSetPersonality == NULL)
+   {
+no_default:
+      if (od_control.od_default_personality == NULL)
+      {
+         pfCurrentPersonality = pdef_opendoors;
+      }
+      else
+      {
+         pfCurrentPersonality = od_control.od_default_personality;
+      }
+      (*pfCurrentPersonality)(20);
+      if(bRAStatus)
+      {
+         od_set_statusline(btRAStatusToSet);
+      }
+      else
+      {
+         od_set_statusline(0);
+      }
+   }
+   else
+   {
+      if(!((*pfSetPersonality)(szDesiredPersonality)))
+      {
+         goto no_default;
+      }
+   }
+#endif /* OD_TEXTMODE */
+
+   /* If connect speed has not been set yet, then set it to the */
+   /* serial port speed.                                        */
+   if(od_control.od_connect_speed == 0)
+   {
+      od_control.od_connect_speed = od_control.baud;
+   }
+
+   /* Initialize the array of characters to use for drawing boxes, */
+   /* window and menu boarders, etc.                               */
+   od_control.od_box_chars[0] = 218U;
+   od_control.od_box_chars[1] = 196U;
+   od_control.od_box_chars[2] = 191U;
+   od_control.od_box_chars[3] = 179U;
+   od_control.od_box_chars[4] = 192U;
+   od_control.od_box_chars[5] = 217U;
+
+   /* Enable pausing and stoping of listing. */
+   od_control.od_list_stop = TRUE;
+   od_control.od_list_pause = TRUE;
+
+   /* Initialize array of logfile messages. */
+   for(btCount = 0; btCount < DIM(apszLogMessages); ++btCount)
+   {
+      if(od_control.od_logfile_messages[btCount] == NULL)
+      {
+         od_control.od_logfile_messages[btCount]
+            = apszLogMessages[btCount];
+      }
+   }
+
+   /* Set log file name to default, if none has been specified already. */
+   if(strlen(od_control.od_logfile_name) == 0)
+   {
+      strcpy(od_control.od_logfile_name, "DOOR.LOG");
+   }
+
+   /* If OpenDoors log file system is installed, then start it up. */
+   if(od_control.od_logfile != NULL)
+   {
+       (*(OD_COMPONENT_CALLBACK *)od_control.od_logfile)();
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInitError()
+ *
+ * Called to display an error message when OpenDoors initialization fails,
+ * forcing the program to exit.
+ *
+ * Parameters: pszErrorText - Pointer to the error message explaining the
+ *                            reason for failure.
+ *
+ *     Return: void
+ */
+void ODInitError(char *pszErrorText)
+{
+#ifdef ODPLAT_DOS
+   printf("%s: %s\n", od_control.od_prog_name, pszErrorText);
+   if(bParsedCmdLine)
+   {
+      printf("Use the -HELP command line option for help, or -LOCAL for local mode.\n");
+   }
+#endif
+#ifdef ODPLAT_WIN32
+   char *pszMessage;
+   if(!bParsedCmdLine ||
+      (pszMessage = malloc(strlen(pszErrorText) + 80)) == NULL)
+   {
+      MessageBox(NULL, pszErrorText, od_control.od_prog_name,
+         MB_ICONSTOP | MB_OK | MB_TASKMODAL);
+   }
+   else
+   {
+      sprintf(pszMessage,
+         "%s\nUse the -HELP command line option for help, or -LOCAL for local mode.",
+         pszErrorText);
+      MessageBox(NULL, pszMessage, od_control.od_prog_name,
+         MB_ICONSTOP | MB_OK | MB_TASKMODAL);
+      free(pszMessage);
+   }
+#endif
+#ifdef ODPLAT_NIX
+   fwrite(pszErrorText,strlen(pszErrorText),1,stderr);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInitLoginDlgProc()
+ *
+ * Dialog box proceedure for the local mode login dialog box.
+ *
+ * Parameters: hwndDlg  - Window handle to the dialog box.
+ *
+ *             uMsg     - Message ID.
+ *
+ *             wParam   - First message parameter.
+ *
+ *             lParam   - Second message parameter.
+ *
+ *     Return: TRUE if message is processed, FALSE otherwise.
+ */
+#ifdef ODPLAT_WIN32
+BOOL CALLBACK ODInitLoginDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   switch(uMsg)
+   {
+      case WM_INITDIALOG:
+         ODFrameCenterWindowInParent(hwndDlg);
+
+         /* Set the title of the dialog box to the name of this program. */
+         SetWindowText(hwndDlg, od_control.od_prog_name);
+
+         /* The initial text in the user name dialog box should be the */
+         /* default user name.                                         */
+         SetWindowText(GetDlgItem(hwndDlg, IDC_USER_NAME),
+            od_control.user_name);
+
+         /* Limit the number of characters that may be entered as the */
+         /* user's name to the maximum size of the string.            */
+         SendMessage(GetDlgItem(hwndDlg, IDC_USER_NAME), EM_LIMITTEXT,
+            sizeof(od_control.user_name), 0L);
+
+         return(TRUE);
+
+      case WM_COMMAND:
+         /* If a command has been chosen. */
+         switch(LOWORD(wParam))
+         {
+            case IDOK:
+               /* If the OK button has been pressed, obtain the entered */
+               /* user name.                                            */
+               GetWindowText(GetDlgItem(hwndDlg, IDC_USER_NAME),
+                  od_control.user_name, sizeof(od_control.user_name));
+               ODStringCopy(od_control.user_handle, od_control.user_name,
+                  sizeof(od_control.user_name));
+
+               /* Now close the dialog. */
+               EndDialog(hwndDlg, IDOK);
+               break;
+
+            case IDCANCEL:
+               /* If the Cancel button has benn pressed, close the dialog. */
+               EndDialog(hwndDlg, IDCANCEL);
+               break;
+         }
+         return(TRUE);
+
+      default:
+         /* Otherwise, indicate that this message has not been processed. */
+         return(FALSE);
+   }
+}
+#endif /* ODPLAT_WIN32 */

+ 1434 - 0
odoors/ODInEx2.c

@@ -0,0 +1,1434 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: ODInEx2.c
+ *
+ * Description: Performs OpenDoors initialization and shutdown operations
+ *              (od_init() and od_exit()), including drop file I/O. This
+ *              module is broken into two files, ODInEx1.c and ODInEx2.c.
+ *
+ *   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.
+ *              Oct 29, 1994  6.00  BP   New EXITINFO.BBS timelimit writing.
+ *              Nov 01, 1994  6.00  BP   New directory access functions.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Dec 13, 1994  6.00  BP   Remove include of dir.h.
+ *              Dec 31, 1994  6.00  BP   Add DIR_ATTRIB_ARCH in file search.
+ *              Dec 31, 1994  6.00  BP   Move _mt_init to new func in odplat.c
+ *              Jan 01, 1995  6.00  BP   _waitdrain() -> ODWaitDrain().
+ *              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 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 01, 1996  6.00  BP   Added od_disable_dtr, DIS_DTR_DISABLE.
+ *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
+ *              Jan 19, 1996  6.00  BP   Don't use atexit() under Win32.
+ *              Jan 21, 1996  6.00  BP   Try DTR disable sequence twice.
+ *              Jan 21, 1996  6.00  BP   Use ODScrnShowMessage().
+ *              Jan 23, 1996  6.00  BP   Added od_exiting.
+ *              Jan 23, 1996  6.00  BP   Use ODProcessExit() instead of exit().
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
+ *              Jan 31, 1996  6.00  BP   Support new SFDOORS.DAT format.
+ *              Feb 02, 1996  6.00  BP   Added RA 2.50 EXITINFO.BBS support.
+ *              Feb 09, 1996  6.00  BP   Correctly translate RA 2.x sex field.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 23, 1996  6.00  BP   Make DTR disable code shared.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 06, 1996  6.10  BP   Added TRIBBS.SYS support.
+ *              Mar 27, 1996  6.10  BP   Added WCNODEID to
+ *              Jan 13, 1997  6.10  BP   Fixes for Door32 support.
+ *              Oct 19, 2001  6.20  RS   Added TCP/IP socket (telnet) support.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODCom.h"
+#include "ODPlat.h"
+#include "ODTypes.h"
+#include "ODScrn.h"
+#include "ODInQue.h"
+#include "ODKrnl.h"
+#include "ODInEx.h"
+#include "ODUtil.h"
+
+
+/* Time difference leeway for door information files to be considered to */
+/* have been written during the same exit (door execution session).      */
+#define DROPFILE_TIME_LEEWAY 10
+
+/* Maximum length of modem response string. */
+#define MAX_RESPONSE_LEN 40
+
+/* Maximum time to wait for modem response string, in milliseconds. */
+#define RESPONSE_TIMEOUT 2000
+
+
+/* Environment variables that specify directories where drop files may be */
+/* found.                                                                 */
+static char *apszEnvVarNames[] =
+{
+   "RA",
+   "QUICK",
+   "PCB",
+   "BBS",
+   "WCNODEID",
+   "SBBSNODE",
+};
+#define NUM_DIR_ENV_VARS DIM(apszEnvVarNames)
+
+
+/* Local helper functions. */
+static INT ODSearchInDir(char **papszFileNames, INT nNumFileNames,
+   char *pszFound, char *pszDirectory);
+
+/* Currently, the following functions are only used in the Win32 version. */
+#ifdef ODPLAT_WIN32
+static BOOL ODSendModemCommand(char *pszCommand, int nRetries);
+static BOOL ODSendModemCommandOnce(char *pszCommand);
+static BOOL ODWaitForString(char *pszResponse, tODMilliSec ResponseTimeout);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef OD_DIAGNOSTICS
+static char szDebugWorkString[500] = "";
+#endif /* OD_DIAGNOSTICS */
+
+
+
+/* ----------------------------------------------------------------------------
+ * od_exit()
+ *
+ * Shuts down OpenDoors operations. Normally, the program is exited as soon
+ * as OpenDoors is shutdown.
+ *
+ * Parameters: nErrorLevel - Result code to exit program with.
+ *
+ *             bTermCall   - TRUE to disconnect the user before exiting,
+ *                           FALSE to leave the user connected.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_exit(INT nErrorLevel, BOOL bTermCall)
+{
+   BYTE btCount;
+   FILE *pfDropFile;
+   time_t nMaxTime;
+   time_t nDoorEndTime;
+   void *pWindow = NULL;
+   DWORD dwActiveMinutes;
+   static BOOL bExiting = FALSE;
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_exit()");
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Starting up od_exit()", "OpenDoors Diagnostics",
+         MB_OK);
+   }
+#endif
+
+   /* If this is a recursive od_exit() call, then ignore it. */
+   if(bExiting)
+   {
+      return;
+   }
+   bExiting = TRUE;
+
+   /* If user called od_exit() before doing anything else, then we first */
+   /* initialize OpenDoors in order to shutdown and exit.                */
+   if(!bODInitialized) od_init();
+
+   /* Update remaining time. */
+   od_control.user_timelimit += od_control.od_maxtime_deduction;
+
+   /* Calculate deducted time */
+   time(&nDoorEndTime);
+   ODDWordDivide(&dwActiveMinutes, NULL, nDoorEndTime - nStartupUnixTime, 60L);
+   od_control.user_time_used += ((nInitialRemaining
+      - od_control.user_timelimit) - (int)dwActiveMinutes);
+
+   /* Reset to original bps rate that was stored in drop file */
+   od_control.baud = dwFileBPS;
+
+   /* If function hook is defined. */
+   if(od_control.od_before_exit != NULL)
+   {
+      /* Then call it. */
+      (*od_control.od_before_exit)();
+   }
+
+   if(bTermCall && od_control.od_hanging_up != NULL)
+   {
+      pWindow = ODScrnShowMessage(od_control.od_hanging_up, 0);
+   }
+   else if(!bTermCall)
+   {
+      pWindow = ODScrnShowMessage(od_control.od_exiting, 0);
+   }
+
+   if(szOriginalDir != NULL)
+   {
+      ODDirChangeCurrent(szOriginalDir);
+      free(szOriginalDir);
+      szOriginalDir=NULL;
+   }
+
+   if(od_control.od_extended_info)     /* Update EXITINFO.BBS, if applicable */
+   {
+      ODMakeFilename(szExitinfoBBSPath, szExitinfoBBSPath, "EXITINFO.BBS",
+         sizeof(szExitinfoBBSPath));
+      if((pfDropFile = fopen(szExitinfoBBSPath, "r+b")) != NULL)
+      {
+         switch(od_control.od_info_type)
+         {
+            case RA2EXITINFO:
+               pRA2ExitInfoRecord->baud = (unsigned int)od_control.baud;
+               pRA2ExitInfoRecord->num_calls = od_control.system_calls;
+               ODStringCToPascal(pRA2ExitInfoRecord->last_caller,35,od_control.system_last_caller);
+               ODStringCToPascal(pRA2ExitInfoRecord->sLastHandle,35,od_control.system_last_handle);
+               ODStringCToPascal(pRA2ExitInfoRecord->start_date,8,od_control.timelog_start_date);
+               memcpy(&pRA2ExitInfoRecord->busyperhour,&od_control.timelog_busyperhour,62);
+               ODStringCToPascal(pRA2ExitInfoRecord->name,35,od_control.user_name);
+               ODStringCToPascal(pRA2ExitInfoRecord->location,25,od_control.user_location);
+               ODStringCToPascal(pRA2ExitInfoRecord->organisation,50,od_control.user_org);
+               for(btCount=0;btCount<3;++btCount)
+                  ODStringCToPascal(pRA2ExitInfoRecord->address[btCount],50,od_control.user_address[btCount]);
+               ODStringCToPascal(pRA2ExitInfoRecord->handle,35,od_control.user_handle);
+               ODStringCToPascal(pRA2ExitInfoRecord->comment,80,od_control.user_comment);
+               pRA2ExitInfoRecord->password_crc=od_control.user_pwd_crc;
+               ODStringCToPascal(pRA2ExitInfoRecord->dataphone,15,od_control.user_dataphone);
+               ODStringCToPascal(pRA2ExitInfoRecord->homephone,15,od_control.user_homephone);
+               ODStringCToPascal(pRA2ExitInfoRecord->lasttime,5,od_control.user_lasttime);
+               ODStringCToPascal(pRA2ExitInfoRecord->lastdate,8,od_control.user_lastdate);
+               pRA2ExitInfoRecord->attrib=od_control.user_attribute;
+               pRA2ExitInfoRecord->attrib2=od_control.user_attrib2;
+               memcpy(&pRA2ExitInfoRecord->flags,&od_control.user_flags,14);
+               pRA2ExitInfoRecord->sec=od_control.user_security;
+               pRA2ExitInfoRecord->lastread=od_control.user_lastread;
+               memcpy(&pRA2ExitInfoRecord->nocalls,&od_control.user_numcalls,29);
+               pRA2ExitInfoRecord->group=od_control.user_group;
+               memcpy(&pRA2ExitInfoRecord->combinedrecord,&od_control.user_combinedrecord,200);
+               ODStringCToPascal(pRA2ExitInfoRecord->firstcall,8,od_control.user_firstcall);
+               ODStringCToPascal(pRA2ExitInfoRecord->birthday,8,od_control.user_birthday);
+               ODStringCToPascal(pRA2ExitInfoRecord->subdate,8,od_control.user_subdate);
+               pRA2ExitInfoRecord->screenwidth=od_control.user_screenwidth;
+               pRA2ExitInfoRecord->language=od_control.user_language;
+               pRA2ExitInfoRecord->dateformat=od_control.user_date_format;
+               ODStringCToPascal(pRA2ExitInfoRecord->forwardto,35,od_control.user_forward_to);
+               memcpy(&pRA2ExitInfoRecord->msgarea,&od_control.user_msg_area,15);
+               pRA2ExitInfoRecord->sex = (od_control.user_sex == 'M') ? 1 : 2;
+               pRA2ExitInfoRecord->btAttribute3=od_control.user_attrib3;
+               ODStringCToPascal(pRA2ExitInfoRecord->sPassword,15,od_control.user_password);
+               pRA2ExitInfoRecord->status=od_control.event_status;
+               ODStringCToPascal(pRA2ExitInfoRecord->starttime,5,od_control.event_starttime);
+               memcpy(&pRA2ExitInfoRecord->errorlevel,&od_control.event_errorlevel,3);
+               ODStringCToPascal(pRA2ExitInfoRecord->lasttimerun,8,od_control.event_last_run);
+               memcpy(&pRA2ExitInfoRecord->netmailentered,&od_control.user_netmailentered,2);
+               ODStringCToPascal(pRA2ExitInfoRecord->logintime,5,od_control.user_logintime);
+               ODStringCToPascal(pRA2ExitInfoRecord->logindate,8,od_control.user_logindate);
+               memcpy(&pRA2ExitInfoRecord->timelimit,&od_control.user_timelimit,6);
+               memcpy(&pRA2ExitInfoRecord->userrecord,&od_control.user_num,8);
+               ODStringCToPascal(pRA2ExitInfoRecord->timeofcreation,5,od_control.user_timeofcreation);
+               pRA2ExitInfoRecord->logonpasswordcrc=od_control.user_logon_pwd_crc;
+               pRA2ExitInfoRecord->wantchat=od_control.user_wantchat;
+               pRA2ExitInfoRecord->deducted_time=od_control.user_deducted_time;
+               for(btCount=0;btCount<50;++btCount)
+                  ODStringCToPascal(pRA2ExitInfoRecord->menustack[btCount],8,od_control.user_menustack[btCount]);
+               pRA2ExitInfoRecord->menustackpointer=od_control.user_menustackpointer;
+               memcpy(&pRA2ExitInfoRecord->error_free,&od_control.user_error_free,3);
+               ODStringCToPascal(pRA2ExitInfoRecord->emsi_crtdef,40,od_control.user_emsi_crtdef);
+               ODStringCToPascal(pRA2ExitInfoRecord->emsi_protocols,40,od_control.user_emsi_protocols);
+               ODStringCToPascal(pRA2ExitInfoRecord->emsi_capabilities,40,od_control.user_emsi_capabilities);
+               ODStringCToPascal(pRA2ExitInfoRecord->emsi_requests,40,od_control.user_emsi_requests);
+               ODStringCToPascal(pRA2ExitInfoRecord->emsi_software,40,od_control.user_emsi_software);
+               memcpy(&pRA2ExitInfoRecord->hold_attr1,&od_control.user_hold_attr1,3);
+               ODStringCToPascal(pRA2ExitInfoRecord->page_reason,77,od_control.user_reasonforchat);
+               if(bRAStatus)
+               {
+                  pRA2ExitInfoRecord->status_line = btCurrentStatusLine + 1;
+               }
+
+               ODStringCToPascal(pRA2ExitInfoRecord->last_cost_menu,9,od_control.user_last_cost_menu);
+               pRA2ExitInfoRecord->menu_cost_per_min=od_control.user_menu_cost;
+               pRA2ExitInfoRecord->has_rip=od_control.user_rip;
+               pRA2ExitInfoRecord->btRIPVersion=od_control.user_rip_ver;
+
+               fwrite(pRA2ExitInfoRecord,1,sizeof(tRA2ExitInfoRecord),pfDropFile);
+               free(pRA2ExitInfoRecord);
+               break;
+
+            case EXITINFO:
+               ODStringCToPascal(pExitInfoRecord->bbs.ra.timeofcreation,5,od_control.user_timeofcreation);
+               ODStringCToPascal(pExitInfoRecord->bbs.ra.logonpassword,15,od_control.user_logonpassword);
+               pExitInfoRecord->bbs.ra.wantchat=od_control.user_wantchat;
+
+               ODWriteExitInfoPrimitive(pfDropFile,476);
+               break;
+
+
+            case RA1EXITINFO:
+               pExtendedExitInfo->deducted_time=od_control.user_deducted_time;
+
+               for(btCount=0;btCount<50;++btCount)
+               {
+                  ODStringCToPascal(pExtendedExitInfo->menustack[btCount],8,od_control.user_menustack[btCount]);
+               }
+
+               pExtendedExitInfo->menustackpointer=od_control.user_menustackpointer;
+               ODStringCToPascal(pExtendedExitInfo->userhandle,35,od_control.user_handle);
+               ODStringCToPascal(pExtendedExitInfo->comment,80,od_control.user_comment);
+               ODStringCToPascal(pExtendedExitInfo->firstcall,8,od_control.user_firstcall);
+               memcpy(pExtendedExitInfo->combinedrecord,od_control.user_combinedrecord,25);
+               ODStringCToPascal(pExtendedExitInfo->birthday,8,od_control.user_birthday);
+               ODStringCToPascal(pExtendedExitInfo->subdate,8,od_control.user_subdate);
+               pExtendedExitInfo->screenwidth=od_control.user_screenwidth;
+               pExtendedExitInfo->msgarea = (BYTE)od_control.user_msg_area;
+               pExtendedExitInfo->filearea = (BYTE)od_control.user_file_area;
+               pExtendedExitInfo->language=od_control.user_language;
+               pExtendedExitInfo->dateformat=od_control.user_date_format;
+               ODStringCToPascal(pExtendedExitInfo->forwardto,35,od_control.user_forward_to);
+               memcpy(&pExtendedExitInfo->error_free,&od_control.user_error_free,3);
+               ODStringCToPascal(pExtendedExitInfo->emsi_crtdef,40,od_control.user_emsi_crtdef);
+               ODStringCToPascal(pExtendedExitInfo->emsi_protocols,40,od_control.user_emsi_protocols);
+               ODStringCToPascal(pExtendedExitInfo->emsi_capabilities,40,od_control.user_emsi_capabilities);
+               ODStringCToPascal(pExtendedExitInfo->emsi_requests,40,od_control.user_emsi_requests);
+               ODStringCToPascal(pExtendedExitInfo->emsi_software,40,od_control.user_emsi_software);
+               memcpy(&pExtendedExitInfo->hold_attr1,&od_control.user_hold_attr1,3);
+
+               ODStringCToPascal(pExitInfoRecord->bbs.ra.timeofcreation,5,od_control.user_timeofcreation);
+               ODStringCToPascal(pExitInfoRecord->bbs.ra.logonpassword,15,od_control.user_logonpassword);
+               pExitInfoRecord->bbs.ra.wantchat=od_control.user_wantchat;
+
+               ODWriteExitInfoPrimitive(pfDropFile,476);
+               fwrite(pExtendedExitInfo,1,1017,pfDropFile);
+               free(pExtendedExitInfo);
+               break;
+
+
+            case QBBS275EXITINFO:
+               pExitInfoRecord->elapsed=nInitialElapsed;
+               pExitInfoRecord->bbs.qbbs.qwantchat=od_control.user_wantchat;
+               pExitInfoRecord->bbs.qbbs.gosublevel=od_control.user_menustackpointer;
+               for(btCount=0;btCount<pExitInfoRecord->bbs.qbbs.gosublevel;++btCount)
+               {
+                  ODStringCToPascal(pExitInfoRecord->bbs.qbbs.menustack[btCount],8,od_control.user_menustack[btCount]);
+               }
+               ODStringCToPascal(pExitInfoRecord->bbs.qbbs.menu,8,od_control.user_menustack[od_control.user_menustackpointer]);
+               pExitInfoRecord->bbs.qbbs.externlogoff = bTermCall ? 1 : 0;
+               pExitInfoRecord->bbs.qbbs.ripactive = od_control.user_rip ? 1 : 0;
+
+               ODWriteExitInfoPrimitive(pfDropFile,644);
+         }
+
+         fclose(pfDropFile);
+      }
+   }
+
+
+   switch(od_control.od_info_type)
+   {
+      case DOORSYS_GAP:
+      case DOORSYS_WILDCAT:
+         pfDropFile=fopen(szDropFilePath,"w");
+         if(od_control.baud==0L)
+         {
+            fprintf(pfDropFile,"COM0:\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"COM%d:\n",od_control.port+1);
+         }
+         fprintf(pfDropFile,"%s",apszDropFileInfo[0]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[1]);
+         fprintf(pfDropFile,"%u\n",od_control.od_node);
+         switch(btDoorSYSLock)
+         {
+            case 0:
+               fprintf(pfDropFile,"%lu\n",od_control.baud);
+               break;
+            case 1:
+               fprintf(pfDropFile,"N\n");
+               break;
+            case 2:
+               fprintf(pfDropFile,"Y\n");
+         }
+         fprintf(pfDropFile,"%s",apszDropFileInfo[3]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[4]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[5]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[22]);
+         strupr(od_control.user_name);
+         fprintf(pfDropFile,"%s\n",od_control.user_name);
+         fprintf(pfDropFile,"%s\n",od_control.user_location);
+         fprintf(pfDropFile,"%s\n",od_control.user_homephone);
+         fprintf(pfDropFile,"%s\n",od_control.user_dataphone);
+         fprintf(pfDropFile,"%s\n",od_control.user_password);
+         fprintf(pfDropFile,"%u\n",od_control.user_security);
+         fprintf(pfDropFile,"%d\n",od_control.user_numcalls);
+         fprintf(pfDropFile,"%s\n",od_control.user_lastdate);
+         fprintf(pfDropFile,"%u\n",(signed int)od_control.user_timelimit*60);
+         fprintf(pfDropFile,"%d\n",od_control.user_timelimit);
+         if(od_control.user_rip)
+         {
+            fprintf(pfDropFile,"RIP\n");
+         }
+         else if(od_control.user_ansi)
+         {
+            fprintf(pfDropFile,"GR\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"NG\n");
+         }
+         fprintf(pfDropFile,"%d\n",od_control.user_screen_length);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[8]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[9]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[10]);
+         fprintf(pfDropFile,"%s\n",od_control.user_subdate);
+         fprintf(pfDropFile,"%u\n",od_control.user_num);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[6]);
+         fprintf(pfDropFile,"%u\n",od_control.user_uploads);
+         fprintf(pfDropFile,"%u\n",od_control.user_downloads);
+         fprintf(pfDropFile,"%u\n",od_control.user_todayk);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[21]);
+
+
+         if(od_control.od_info_type==DOORSYS_WILDCAT)
+         {
+            fprintf(pfDropFile,"%s\n",od_control.user_birthday);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[11]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[12]);
+            fprintf(pfDropFile,"%s\n",od_control.sysop_name);
+            strupr(od_control.user_handle);
+            fprintf(pfDropFile,"%s\n",od_control.user_handle);
+            fprintf(pfDropFile,"%s\n",od_control.event_starttime);
+            if(od_control.user_error_free)
+               fprintf(pfDropFile,"Y\n");
+            else
+               fprintf(pfDropFile,"N\n");
+            fprintf(pfDropFile,"%s",apszDropFileInfo[7]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[13]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[14]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[15]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[16]);
+            fprintf(pfDropFile,"%s\n",od_control.user_logintime);
+            fprintf(pfDropFile,"%s\n",od_control.user_lasttime);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[18]);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[19]);
+            fprintf(pfDropFile,"%u\n",od_control.user_upk);
+            fprintf(pfDropFile,"%u\n",od_control.user_downk);
+            fprintf(pfDropFile,"%s\n",od_control.user_comment);
+            fprintf(pfDropFile,"%s",apszDropFileInfo[20]);
+            fprintf(pfDropFile,"%u\n",od_control.user_messages);
+         }
+
+         fclose(pfDropFile);
+         break;
+
+
+      case DOORSYS_DRWY:
+         pfDropFile=fopen(szDropFilePath,"w");
+         fprintf(pfDropFile,"%s\n",od_control.user_name);
+
+         if(od_control.baud==0L)
+         {
+            fprintf(pfDropFile,"-1\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"%d\n",od_control.port+1);
+         }
+
+         fprintf(pfDropFile,"%lu\n",od_control.baud);
+
+         fprintf(pfDropFile,"%d\n",od_control.user_timelimit);
+
+         if(od_control.user_ansi)
+         {
+            fprintf(pfDropFile,"G\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"M\n");
+         }
+
+         fclose(pfDropFile);
+         break;
+
+
+      case SFDOORSDAT:
+         pfDropFile=fopen(szDropFilePath,"w");
+
+         fprintf(pfDropFile,"%u\n",od_control.user_num);
+         fprintf(pfDropFile,"%s\n",od_control.user_name);
+         fprintf(pfDropFile,"%s\n",od_control.user_password);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[0]);
+         fprintf(pfDropFile,"%lu\n",od_control.baud);
+         fprintf(pfDropFile,"%d\n",od_control.port+1);
+         fprintf(pfDropFile,"%d\n",od_control.user_timelimit);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[13]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[14]);
+         if(od_control.user_ansi)
+         {
+            fprintf(pfDropFile,"TRUE\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"FALSE\n");
+         }
+         fprintf(pfDropFile,"%u\n",od_control.user_security);
+         fprintf(pfDropFile,"%u\n",od_control.user_uploads);
+         fprintf(pfDropFile,"%u\n",od_control.user_downloads);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[1]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[2]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[3]);
+         if(od_control.sysop_next)
+         {
+            fprintf(pfDropFile,"TRUE\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"FALSE\n");
+         }
+         fprintf(pfDropFile,"%s",apszDropFileInfo[4]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[5]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[6]);
+         if(od_control.user_error_free)
+         {
+            fprintf(pfDropFile,"TRUE\n");
+         }
+         else
+         {
+            fprintf(pfDropFile,"FALSE\n");
+         }
+
+         fprintf(pfDropFile,"%u\n",od_control.user_msg_area);
+         fprintf(pfDropFile,"%u\n",od_control.user_file_area);
+         fprintf(pfDropFile,"%u\n",od_control.od_node);
+
+         fprintf(pfDropFile,"%s",apszDropFileInfo[10]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[11]);
+         fprintf(pfDropFile,"%s",apszDropFileInfo[12]);
+         fprintf(pfDropFile,"%u\n",od_control.user_todayk);
+         fprintf(pfDropFile,"%u\n",od_control.user_upk);
+         fprintf(pfDropFile,"%u\n",od_control.user_downk);
+         fprintf(pfDropFile,"%s\n",od_control.user_homephone);
+         fprintf(pfDropFile,"%s\n",od_control.user_location);
+         if(apszDropFileInfo[15][0]!='\0')
+         {
+            fprintf(pfDropFile, "%s", apszDropFileInfo[15]);
+            fprintf(pfDropFile, od_control.user_rip ? "TRUE\n" : "FALSE\n");
+            fprintf(pfDropFile, od_control.user_wantchat ? "TRUE\n"
+               : "FALSE\n");
+            fprintf(pfDropFile, "%s", apszDropFileInfo[17]);
+            fprintf(pfDropFile, "%d\n", od_control.od_com_irq);
+            fprintf(pfDropFile, "%d\n", od_control.od_com_address);
+            fprintf(pfDropFile, "%s", apszDropFileInfo[18]);
+         }
+         fclose(pfDropFile);
+         break;
+
+
+        case CHAINTXT:
+           pfDropFile=fopen(szDropFilePath,"w");
+           fprintf(pfDropFile,"%d\n",od_control.user_num);
+           fprintf(pfDropFile,"%s\n",od_control.user_handle);
+           fprintf(pfDropFile,"%s\n",od_control.user_name);
+           fprintf(pfDropFile,"%s\n",od_control.user_callsign);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[0]);
+           fprintf(pfDropFile,"%c\n",od_control.user_sex);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[1]);
+           fprintf(pfDropFile,"%s\n",od_control.user_lastdate);
+           fprintf(pfDropFile,"%d\n",od_control.user_screenwidth);
+           fprintf(pfDropFile,"%d\n",od_control.user_screen_length);
+           fprintf(pfDropFile,"%d\n",od_control.user_security);
+           fprintf(pfDropFile,"%d\n",bIsSysop);
+           fprintf(pfDropFile,"%d\n",bIsCoSysop);
+           fprintf(pfDropFile,"%d\n",od_control.user_ansi);
+           if(od_control.baud==0L)
+           {
+              fprintf(pfDropFile,"0\n");
+           }
+           else
+           {
+              fprintf(pfDropFile,"1\n");
+           }
+           fprintf(pfDropFile,"    %d.00\n",od_control.user_timelimit*60);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[3]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[4]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[5]);
+           if(od_control.baud==0L)
+           {
+              fprintf(pfDropFile,"KB\n");
+           }
+           else
+           {
+              fprintf(pfDropFile,"%lu\n",od_control.baud);
+           }
+           fprintf(pfDropFile,"%d\n",od_control.port+1);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[6]);
+           fprintf(pfDropFile,"%s\n",od_control.user_password);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[2]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[7]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[8]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[9]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[10]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[11]);
+           fprintf(pfDropFile,"%s",apszDropFileInfo[12]);
+           fclose(pfDropFile);
+           break;
+        case TRIBBSSYS:
+           pfDropFile = fopen(szDropFilePath, "w");
+           fprintf(pfDropFile, "%u\n", od_control.user_num);
+           fprintf(pfDropFile, "%s\n", od_control.user_name);
+           fprintf(pfDropFile, "%s\n", od_control.user_password);
+           fprintf(pfDropFile, "%u\n", od_control.user_security);
+           fprintf(pfDropFile, "%c\n", od_control.user_expert ? 'Y' : 'N');
+           fprintf(pfDropFile, "%c\n", od_control.user_ansi ? 'Y' : 'N');
+           fprintf(pfDropFile, "%d\n", od_control.user_timelimit);
+           fprintf(pfDropFile, "%s\n", od_control.user_homephone);
+           fprintf(pfDropFile, "%s\n", od_control.user_location);
+          od_control.user_birthday[2] = '/';
+          od_control.user_birthday[5] = '/';
+           fprintf(pfDropFile, "%s\n", od_control.user_birthday);
+           fprintf(pfDropFile, "%d\n", od_control.od_node);
+           fprintf(pfDropFile, "%d\n", od_control.port + 1);
+           fprintf(pfDropFile, "%lu\n", od_control.od_connect_speed);
+           fprintf(pfDropFile, "%lu\n", od_control.baud);
+           fprintf(pfDropFile, "%c\n", (od_control.od_com_flow_control
+              == COM_RTSCTS_FLOW) ? 'Y' : 'N');
+           fprintf(pfDropFile, "%c\n", od_control.user_error_free ? 'Y' : 'N');
+           fprintf(pfDropFile, "%s\n", od_control.system_name);
+           fprintf(pfDropFile, "%s\n", od_control.sysop_name);
+           fprintf(pfDropFile, "%s\n", od_control.user_handle);
+           fprintf(pfDropFile, "%c\n", od_control.user_rip ? 'Y' : 'N');
+           fclose(pfDropFile);
+           break;
+   }
+
+   /* Deallocate temorary strings. */
+   for(btCount=0;btCount<25;++btCount)
+   {
+      free(apszDropFileInfo[btCount]);
+   }
+
+   /* If logfile system is active. */
+   if(pfLogClose != NULL)
+   {
+      /* Then close the logfile. */
+      (*pfLogClose)(nErrorLevel);
+   }
+
+   /* Disconnect the remote user if required. */
+   if(od_control.baud && bTermCall)
+   {
+      BOOL bCarrier;
+
+      /* Wait up to ten seconds for bufffer to drain. */
+      ODWaitDrain(10000);
+
+      /* Wait up to five seconds for no carrier */
+      ODComSetDTR(hSerialPort, FALSE);
+      nMaxTime = time(NULL) + 5L;
+
+      do
+      {
+         ODComCarrier(hSerialPort, &bCarrier);
+      } while(bCarrier && time(NULL) <= nMaxTime);
+
+      /* Raise DTR signal again. */
+      ODComSetDTR(hSerialPort, TRUE);
+   }
+
+   /* In Win32 version, disable DTR before closing serial port, if */
+   /* required.                                                    */
+#ifdef ODPLAT_WIN32
+   /* If we are operating in remote mode, and we should not hangup on the */
+   /* caller ...                                                          */
+   if(!bTermCall && od_control.baud)
+   {
+      ODInExDisableDTR();
+   }
+#endif /* ODPLAT_WIN32 */
+
+   /* Remove the message that indicates we are in the process of exiting */
+   /* or hanging up.                                                     */
+   ODScrnRemoveMessage(pWindow);
+
+#ifndef ODPLAT_WIN32
+   /* Reset output area boundary to the entire screen. */
+   ODScrnSetBoundary(1,1,80,25);
+
+   /* Reset text color. */
+   ODScrnSetAttribute(0x07);
+
+   /* Clear screen if neccesary. */   
+   if(od_control.od_clear_on_exit)
+   {
+      ODScrnClear();
+   }
+   else
+   {
+      ODScrnSetCursorPos(1, 1);
+   }
+#endif /* !ODPLAT_WIN32 */
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Terminating kernel threads", "OpenDoors Diagnostics",
+         MB_OK);
+   }
+#endif
+   /* Shutdown the OpenDoors kernel. */
+   ODKrnlShutdown();
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Shutting down local screen", "OpenDoors Diagnostics",
+         MB_OK);
+   }
+#endif
+   /* Shutdown OpenDoors local screen module. */
+   ODScrnShutdown();
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Performing any final serial port deallocation",
+         "OpenDoors Diagnostics", MB_OK);
+   }
+#endif
+   /* If not operating in local mode, then deallocate serial port resources. */
+   if(od_control.baud != 0)
+   {
+      /* Close serial port. */
+      ODComClose(hSerialPort);
+
+      /* Deallocate serial port object. */
+      ODComFree(hSerialPort);
+   }
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Deallocating common queue", "OpenDoors Diagnostics",
+         MB_OK);
+   }
+#endif
+   /* Deallocate input buffer. */
+   ODInQueueFree(hODInputQueue);
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Going to inactive mode", "OpenDoors Diagnostics",
+         MB_OK);
+   }
+#endif
+   /* OpenDoors is no longer active. */
+   bODInitialized = FALSE;
+
+   /* od_exit() is no longer active. */
+   bExiting = FALSE;
+
+   /* If the client does not want a call to od_exit() to shutdown the */
+   /* application, but just to shutdown OpenDoors, then return now.   */
+   if(od_control.od_noexit) return;
+
+   /* If exit() has already been called, then do not call it again. */
+   if(bPreOrExit) return;
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+   {
+      MessageBox(NULL, "Terminating process", "OpenDoors Diagnostics", MB_OK);
+   }
+#endif
+   /* Exit with appropriate errorlevel. */
+   ODProcessExit(nErrorLevel);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSearchForDropFile()
+ *
+ * Searches for a door information (drop) file, given a list of possible drop
+ * file names. Searches for the drop file first in the directory specified
+ * by od_control.info_path. If a directory was specified in the configuration
+ * file, this is where that directory name would be stored. This function will
+ * then proceed to search the current directory and any directories specified
+ * by recognized environment variables, until either a drop file is found, or
+ * until all possibilities are exhaused.
+ *
+ * If a directory contains more than one supported dropfile, the choice of
+ * drop files is narrowed to the most recently written file, and any files
+ * written in the ten seconds before that file was written. Of these files,
+ * the file with the highest priority (based on its position in the list of
+ * possible drop file names) is selected. This heuristic attempts to ignore
+ * any "old" drop files that may still be hanging around from another
+ * program or another login session, while still choosing the file with the
+ * most information.
+ *
+ * Parameters: papszFileNames - Array of possible drop file names.
+ *
+ *             nNumFilesNames - The number of names in papszFileNames.
+ *
+ *             pszFound       - If a drop file was found, this string
+ *                              will be changed to point to the filename
+ *                              of the file that was found.
+ *
+ *             pszDirectory   - If a drop file was found, this string
+ *                              will be changed to contain the name of
+ *                              the directory in which the file was found.
+ *
+ *     Return: Index in the array of the file that was found, or -1 if no
+ *             potential drop file was found.
+ */
+INT ODSearchForDropFile(char **papszFileNames, INT nNumFileNames,
+   char *pszFound, char *pszDirectory)
+{
+   BYTE btCount;
+   char *pszEnvVarSetting;
+   INT nResult;
+
+   ASSERT(papszFileNames != NULL);
+   ASSERT(nNumFileNames > 0);
+   ASSERT(pszFound != NULL);
+
+   /* First, look for the drop file(s) in the directory specified by */
+   /* od_control.info_path.                                          */
+   if(strlen(od_control.info_path) != 0)
+   {
+      if((nResult = ODSearchInDir(papszFileNames, nNumFileNames, pszFound,
+         od_control.info_path)) != -1)
+      {
+         if(pszDirectory != NULL) strcpy(pszDirectory, od_control.info_path);
+         return(nResult);
+      }
+   }
+
+   /* Next, look for the drop file(s) in the current directory. */
+   if((nResult = ODSearchInDir(papszFileNames, nNumFileNames, pszFound,
+      "."DIRSEP_STR)) != -1)
+   {
+      if(pszDirectory != NULL) strcpy(pszDirectory, "."DIRSEP_STR);
+      return(nResult);
+   }
+
+   /* Look through array of environment variables, checking whether any of */
+   /* them specify the name of a directory in which a drop file can be     */
+   /* found.                                                               */
+   ASSERT(DIM(apszEnvVarNames) == NUM_DIR_ENV_VARS);
+   for(btCount = 0; btCount < NUM_DIR_ENV_VARS; ++btCount)
+   {
+      if((pszEnvVarSetting = (char *)getenv(apszEnvVarNames[btCount])) != NULL)
+      {
+         if((nResult = ODSearchInDir(papszFileNames, nNumFileNames, pszFound,
+            pszEnvVarSetting)) != -1)
+         {
+            if(pszDirectory != NULL) strcpy(pszDirectory,pszEnvVarSetting);
+            return(nResult);
+         }
+      }
+   }
+
+   return(-1);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSearchInDir()                                     *** PRIVATE FUNCTION ***
+ *
+ * Private helper function used by ODSearchForDropFile(). Searches for a drop
+ * file in a single specified directory. The heuristic for selecting a drop
+ * file, if more than one exists, is described in the header for the
+ * ODSearchForDropFile() function.
+ *
+ * Parameters: papszFileNames - Array of possible drop file names.
+ *
+ *             nNumFilesNames - The number of names in papszFileNames.
+ *
+ *             pszFound       - If a drop file was found, this string
+ *                              will be changed to point to the filename
+ *                              of the file that was found.
+ *
+ *             pszDirectory   - Name of the directory to search in.
+ *
+ *     Return: Index in the array of the file that was found, or -1 if no
+ *             potential drop file was found.
+ */
+static INT ODSearchInDir(char **papszFileNames, INT nNumFileNames,
+   char *pszFound, char *pszDirectory)
+{
+   BYTE btCount;
+   char szFullName[80];
+   INT nFound = -1;
+   tODDirHandle hDir;
+   tODDirEntry DirEntry;
+   time_t LatestTime = 0;
+
+   ASSERT(papszFileNames != NULL);
+   ASSERT(nNumFileNames > 0);
+   ASSERT(pszFound != NULL);
+   ASSERT(pszDirectory != NULL);
+
+   for(btCount=0; btCount < nNumFileNames; ++btCount)
+   {
+      /* Do not consider DORINFO1.DEF if a DORINFOx.DEF for this node has */
+      /* been found. */
+      if(btCount == 2 && nFound == 1)
+      {
+         continue;
+      }
+
+      ASSERT(papszFileNames[btCount] != NULL);
+
+      ODMakeFilename(szFullName, pszDirectory, (char *)papszFileNames[btCount],
+         sizeof(szFullName));
+
+      /* Attempt to open directory. */
+      if(ODDirOpen(szFullName, DIR_ATTRIB_NORMAL | DIR_ATTRIB_ARCH, &hDir)
+         == kODRCSuccess)
+      {
+         /* Read the first matching entry in the directory. */
+         ODDirRead(hDir, &DirEntry);
+
+         if(nFound == -1
+            || DirEntry.LastWriteTime > LatestTime + DROPFILE_TIME_LEEWAY)
+         {
+            if(!ODFileAccessMode(szFullName, 4))
+            {
+               nFound=btCount;
+               LatestTime = DirEntry.LastWriteTime;
+            }
+         }
+
+         /* Close the open directory. */
+         ODDirClose(hDir);
+      }
+   }
+
+   if(nFound != -1)
+   {
+      ODMakeFilename(pszFound, pszDirectory, (char *)papszFileNames[nFound],
+         160);
+   }
+
+   return(nFound);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODReadExitInfoPrimitive()
+ *
+ * Reads the core a of pre-RA2 style EXITINFO.BBS file.
+ *
+ * Parameters: pfDropFile - Pointer to already open EXITINFO.BBS file.
+ *
+ *             nCount     - Specifies the number of bytes to read.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+BOOL ODReadExitInfoPrimitive(FILE *pfDropFile, INT nCount)
+{
+   if((pExitInfoRecord=malloc(sizeof(tExitInfoRecord)))==NULL) return(FALSE);
+
+   if(fread(pExitInfoRecord,1,nCount,pfDropFile)!=(size_t)nCount)
+   {
+      return(FALSE);
+   }
+
+                                          /* now we read all the data from the */
+                                          /* EXITINFO structure to the OpenDoors */
+                                          /* control structure. This may look */
+                                          /* a bit messy, but it gets the job */
+                                          /* done, and allows the programmer */
+                                          /* to access all the strings in C */
+                                          /* format instead of Pascal */
+   od_control.baud=pExitInfoRecord->baud;
+   od_control.system_calls=pExitInfoRecord->num_calls;
+   ODStringPascalToC(od_control.system_last_caller,pExitInfoRecord->last_caller,35);
+   ODStringPascalToC(od_control.timelog_start_date,pExitInfoRecord->start_date,8);
+   memcpy(&od_control.timelog_busyperhour,&pExitInfoRecord->busyperhour,62);
+   ODStringPascalToC(od_control.user_name,pExitInfoRecord->uname,35);
+   ODStringPascalToC(od_control.user_location,pExitInfoRecord->uloc,25);
+   ODStringPascalToC(od_control.user_password,pExitInfoRecord->password,15);
+   ODStringPascalToC(od_control.user_dataphone,pExitInfoRecord->dataphone,12);
+   ODStringPascalToC(od_control.user_homephone,pExitInfoRecord->homephone,12);
+   ODStringPascalToC(od_control.user_lasttime,pExitInfoRecord->lasttime,5);
+   ODStringPascalToC(od_control.user_lastdate,pExitInfoRecord->lastdate,8);
+   memcpy(&od_control.user_attribute,&pExitInfoRecord->attrib,5);
+   od_control.user_net_credit=pExitInfoRecord->credit;
+   od_control.user_pending=pExitInfoRecord->pending;
+   od_control.user_messages=pExitInfoRecord->posted;
+   od_control.user_lastread=pExitInfoRecord->lastread;
+   od_control.user_security=pExitInfoRecord->sec;
+   od_control.user_numcalls=pExitInfoRecord->nocalls;
+   od_control.user_uploads=pExitInfoRecord->ups;
+   od_control.user_downloads=pExitInfoRecord->downs;
+   od_control.user_upk=pExitInfoRecord->upk;
+   od_control.user_downk=pExitInfoRecord->downk;
+   od_control.user_todayk=pExitInfoRecord->todayk;
+   memcpy(&od_control.user_time_used,&pExitInfoRecord->elapsed,6);
+   od_control.user_group=pExitInfoRecord->group;
+   od_control.user_xi_record=pExitInfoRecord->xirecord;
+   od_control.event_status=pExitInfoRecord->status;
+   ODStringPascalToC(od_control.event_starttime,pExitInfoRecord->starttime,5);
+   memcpy(&od_control.event_errorlevel,&pExitInfoRecord->errorlevel,3);
+   ODStringPascalToC(od_control.event_last_run,pExitInfoRecord->lasttimerun,8);
+   memcpy(&od_control.user_netmailentered,&pExitInfoRecord->netmailentered,2);
+   ODStringPascalToC(od_control.user_logintime,pExitInfoRecord->logintime,5);
+   ODStringPascalToC(od_control.user_logindate,pExitInfoRecord->logindate,8);
+
+   /* Note that the timelimit field is skipped here. This value has already */
+   /* been read from the DORINFOx.DEF file, and is not consistently written */
+   /* to the EXITINFO.BBS file by various BBS packages.                     */
+
+   memcpy(&od_control.user_loginsec,&pExitInfoRecord->loginsec,16);
+   od_control.user_ansi=od_control.user_attribute&8;
+   od_control.user_avatar=od_control.user_attrib2&2;
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODWriteExitInfoPrimitive()
+ *
+ * Writes the core a of pre-RA2 style EXITINFO.BBS file.
+ *
+ * Parameters: pfDropFile - Pointer to already open EXITINFO.BBS file.
+ *
+ *             nCount     - Number of bytes to be written.
+ *
+ *     Return: Number of bytes actually written.
+ */
+INT ODWriteExitInfoPrimitive(FILE *pfDropFile, INT nCount)
+{
+   INT nToReturn;
+   DWORD dwActiveMinutes;
+   INT nUserTimeLost;
+   INT nTimeSubtractedBySysop;
+   time_t nCurrentUnixTime;
+
+   pExitInfoRecord->num_calls=od_control.system_calls;
+   ODStringCToPascal(pExitInfoRecord->last_caller,35,od_control.system_last_caller);
+   ODStringCToPascal(pExitInfoRecord->start_date,8,od_control.timelog_start_date);
+   memcpy(&pExitInfoRecord->busyperhour,&od_control.timelog_busyperhour,31);
+   ODStringCToPascal(pExitInfoRecord->uname,35,od_control.user_name);
+   ODStringCToPascal(pExitInfoRecord->uloc,25,od_control.user_location);
+   ODStringCToPascal(pExitInfoRecord->password,15,od_control.user_password);
+   ODStringCToPascal(pExitInfoRecord->dataphone,12,od_control.user_dataphone);
+   ODStringCToPascal(pExitInfoRecord->homephone,12,od_control.user_homephone);
+   ODStringCToPascal(pExitInfoRecord->lasttime,5,od_control.user_lasttime);
+   ODStringCToPascal(pExitInfoRecord->lastdate,8,od_control.user_lastdate);
+   memcpy(&pExitInfoRecord->attrib,&od_control.user_attribute,5);
+   pExitInfoRecord->credit=(WORD)od_control.user_net_credit;
+   pExitInfoRecord->pending=(WORD)od_control.user_pending;
+   pExitInfoRecord->posted=(WORD)od_control.user_messages;
+   pExitInfoRecord->lastread=(WORD)od_control.user_lastread;
+   pExitInfoRecord->sec=(WORD)od_control.user_security;
+   pExitInfoRecord->nocalls=(WORD)od_control.user_numcalls;
+   pExitInfoRecord->ups=(WORD)od_control.user_uploads;
+   pExitInfoRecord->downs=(WORD)od_control.user_downloads;
+   pExitInfoRecord->upk=(WORD)od_control.user_upk;
+   pExitInfoRecord->downk=(WORD)od_control.user_downk;
+   pExitInfoRecord->todayk=(WORD)od_control.user_todayk;
+   memcpy(&pExitInfoRecord->elapsed,&od_control.user_time_used,6);
+   pExitInfoRecord->group = (BYTE)od_control.user_group;
+   pExitInfoRecord->xirecord=(WORD)od_control.user_xi_record;
+   pExitInfoRecord->status=od_control.event_status;
+   pExitInfoRecord->status=od_control.event_status;
+   ODStringCToPascal(pExitInfoRecord->starttime,5,od_control.event_starttime);
+   memcpy(&pExitInfoRecord->errorlevel,&od_control.event_errorlevel,3);
+   ODStringCToPascal(pExitInfoRecord->lasttimerun,8,od_control.event_last_run);
+   memcpy(&pExitInfoRecord->netmailentered,&od_control.user_netmailentered,2);
+   ODStringCToPascal(pExitInfoRecord->logintime,5,od_control.user_logintime);
+   ODStringCToPascal(pExitInfoRecord->logindate,8,od_control.user_logindate);
+
+   /* Calculate new time limit based on how time was adjusted during door's */
+   /* execution.                                                            */
+   time(&nCurrentUnixTime);
+   ODDWordDivide(&dwActiveMinutes, NULL, nCurrentUnixTime-nStartupUnixTime, 60L);
+   nUserTimeLost = (nInitialRemaining - od_control.user_timelimit);
+   nTimeSubtractedBySysop = nUserTimeLost - (int)dwActiveMinutes;
+   pExitInfoRecord->timelimit -= nTimeSubtractedBySysop;
+
+   memcpy(&pExitInfoRecord->loginsec,&od_control.user_loginsec,16);
+
+   nToReturn=(fwrite(pExitInfoRecord,1,nCount,pfDropFile) == (size_t)nCount);
+   free(pExitInfoRecord);
+   return(nToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODAtExitCallback()
+ *
+ * OpenDoors sets up the C library to call back this function when the program
+ * is about to exit. OpenDoors uses this function to attempt to trap the
+ * condition where the programmer exits the program without explicitly calling
+ * od_exit(). If the program is about to exit, and OpenDoors is still active,
+ * then od_exit() is called.
+ *
+ * It is not recommended that the programmer using OpenDoors rely on this
+ * mechanism, because:
+ *
+ *    1. It doesn't seem to be supported by all compilers.
+ *
+ *    2. It doesn't permit OpenDoors to determine the actual error level
+ *       that the program is exiting with in order to report this information
+ *       in the log file (if enabled).
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+#ifndef ODPLAT_WIN32
+void ODAtExitCallback(void)
+{
+   if(bODInitialized)
+   {
+      bPreOrExit = TRUE;
+      if(od_control.od_errorlevel[0])
+      {
+         od_exit(od_control.od_errorlevel[7],FALSE);
+      }
+      else
+      {
+         od_exit(6,FALSE);
+      }
+   }
+}
+#endif /* !ODPLAT_WIN32 */
+
+
+/* Currently, these functions are only used in the Win32 version. */
+#ifdef ODPLAT_WIN32
+
+
+/* ----------------------------------------------------------------------------
+ * ODSendModemCommand()                                *** PRIVATE FUNCTION ***
+ *
+ * Sends a sequence of commands to the modem, waiting for the specified
+ * response between each command. The command sequence is retried the specified
+ * number of times.
+ *
+ * Parameters: pszCommand - Command string to send to the modem, along with
+ *                          response strings. Each of these are separated by
+ *                          a space character. A pipe character ('|') denotes a
+ *                          CR, and a tilde character ('~') denotes a one
+ *                          second pause.
+ *
+ *             nRetries   - Number of times to retry command sequence.
+ *
+ *     Return: TRUE on success, or FALSE if some expected response string was
+ *             not received from the modem after modem response timeout period.
+ */
+static BOOL ODSendModemCommand(char *pszCommand, int nRetries)
+{
+   ASSERT(pszCommand != NULL);
+   ASSERT(nRetries >= 1);
+
+   while(nRetries--)
+   {
+      if(ODSendModemCommandOnce(pszCommand))
+      {
+         return(TRUE);
+      }
+   }
+
+   return(FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSendModemCommandOnce()                            *** PRIVATE FUNCTION ***
+ *
+ * Sends a series of commands to the modem, waiting for the specified response
+ * between each command.
+ *
+ * Parameters: pszCommand - Command string to send to the modem, along with
+ *                          response strings. Each of these are separated by
+ *                          a space character. A pipe character ('|') denotes a
+ *                          CR, and a tilde character ('~') denotes a one
+ *                          second pause.
+ *
+ *     Return: TRUE on success, or FALSE if some expected response string was
+ *             not received from the modem after modem response timeout period.
+ */
+static BOOL ODSendModemCommandOnce(char *pszCommand)
+{
+   char *pchCurrent;
+   char szResponse[MAX_RESPONSE_LEN + 1];
+   int nResponsePos;
+   BOOL bSendingCommand = TRUE;
+
+   ASSERT(pszCommand != NULL);
+
+   /* We must be operating in remote mode. */
+   ASSERT(od_control.baud != 0);
+
+   /* Loop through each character in the string. */
+   for(pchCurrent = pszCommand; *pchCurrent != '\0'; ++pchCurrent)
+   {
+      /* What we do with this character depends upon whether we are */
+      /* currently sending a command, or waiting for a response.    */
+      if(bSendingCommand)
+      {
+         switch(*pchCurrent)
+         {
+            case ' ':
+               /* A space character denotes that we should toggle between */
+               /* sending a command and receiving a response.             */
+               bSendingCommand = FALSE;
+
+               /* Start at the beginning of the empty response string. */
+               nResponsePos = 0;
+               szResponse[0] = '\0';
+               break;
+
+            case '|':
+               /* A pipe character denotes that a carriage return should be */
+               /* send to the modem.                                        */
+               ODComSendByte(hSerialPort, '\r');
+#ifdef OD_DIAGNOSTICS
+               strcat(szDebugWorkString, "\n");
+#endif /* OD_DIAGNOSTICS */
+               break;
+
+            case '~':
+               /* A tilde character denotes a 1 second pause. */
+               od_sleep(1000);
+               break;
+
+            default:
+               /* Otherwise, send this character as is. */
+               ODComSendByte(hSerialPort, *pchCurrent);
+#ifdef OD_DIAGNOSTICS
+               {
+                  char szAppend[2];
+                  szAppend[0] = *pchCurrent;
+                  szAppend[1] = 0;
+                  strcat(szDebugWorkString, szAppend);
+               }
+#endif /* OD_DIAGNOSTICS */
+         }
+
+         od_sleep(200);
+      }
+      else
+      {
+         /* We are currently building a string that we should wait for. */
+         switch(*pchCurrent)
+         {
+            case ' ':
+               /* A space character denotes that we should toggle between */
+               /* sending a command and receiving a response.             */
+
+               /* Wait until the response string we have built is received. */
+               if(!ODWaitForString(szResponse, RESPONSE_TIMEOUT))
+               {
+                  /* If string was not received, then return now. */
+                  return(FALSE);
+               }
+
+               /* Switch to sending command mode. */
+               bSendingCommand = TRUE;
+               break;
+
+            case '~':
+               /* Pauses are ignored in response strings. */
+               break;
+
+            default:
+               /* Otherwise, add this character to the response string. */
+               if(nResponsePos < MAX_RESPONSE_LEN)
+               {
+                  szResponse[nResponsePos] = *pchCurrent;
+                  ++nResponsePos;
+                  szResponse[nResponsePos] = '\0';
+               }
+         }
+      }
+   }
+
+   /* Return with success. */
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODWaitForString()                                   *** PRIVATE FUNCTION ***
+ *
+ * Waits for the specified string to be received from the modem, for up to
+ * the specified length of time.
+ *
+ * Parameters: pszResponse     - Pointer to the string to wait for.
+ *
+ *             ResponseTimeout - The maximum time, in milliseconds, to wait.
+ *
+ *     Return: TRUE on success, or FALSE if some expected response string was
+ *             not received from the modem after modem response timeout period.
+ */
+static BOOL ODWaitForString(char *pszResponse, tODMilliSec ResponseTimeout)
+{
+   tODTimer Timer;
+   char szReceived[MAX_RESPONSE_LEN + 1] = "\0";
+   tODInputEvent InputEvent;
+
+   ASSERT(pszResponse != NULL);
+   ASSERT(ResponseTimeout > 0);
+
+   /* We must be operating in remote mode. */
+   ASSERT(od_control.baud != 0);
+
+   /* If response string is empty, then we don't wait for anything. */
+   if(strlen(pszResponse) == 0) return(TRUE);
+
+#ifdef OD_DIAGNOSTICS
+   strcat(szDebugWorkString, "[");
+#endif /* OD_DIAGNOSTICS */
+
+   ODTimerStart(&Timer, ResponseTimeout);
+   while(!ODTimerElapsed(&Timer))
+   {
+      if(ODInQueueGetNextEvent(hODInputQueue, &InputEvent,
+         ODTimerLeft(&Timer)) == kODRCSuccess)
+      {
+         if(InputEvent.bFromRemote && InputEvent.EventType == EVENT_CHARACTER)
+         {
+#ifdef OD_DIAGNOSTICS
+            {
+               char szAppend[2];
+               szAppend[0] = InputEvent.chKeyPress;
+               szAppend[1] = 0;
+               strcat(szDebugWorkString, szAppend);
+            }
+#endif /* OD_DIAGNOSTICS */
+
+            /* Add the received character to the received string. */
+            if(strlen(szReceived) == MAX_RESPONSE_LEN)
+            {
+               memmove(szReceived, szReceived + 1, MAX_RESPONSE_LEN);
+            }
+            szReceived[strlen(szReceived) + 1] = '\0';
+            szReceived[strlen(szReceived)] = InputEvent.chKeyPress;
+
+            /* If the sequence has been received, then return with success. */
+            if(strstr(szReceived, pszResponse) != NULL)
+            {
+#ifdef OD_DIAGNOSTICS
+               strcat(szDebugWorkString, "]");
+#endif /* OD_DIAGNOSTICS */
+               return(TRUE);
+            }
+         }
+      }
+      else
+      {
+         /* When no characters are waiting, allow other processes to run. */
+         od_sleep(0);
+      }
+   }
+
+#ifdef OD_DIAGNOSTICS
+   strcat(szDebugWorkString, "]");
+#endif OD_DIAGNOSTICS
+
+   /* Indicate that string was not received in the time alotted. */
+   return(FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInExDisableDTR()
+ *
+ * Disables DTR response by the modem, if required.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+void ODInExDisableDTR(void)
+{
+   BOOL bCarrier;
+
+   /* If we are using the Door32 interface, then do not disable DTR. */
+   if(od_control.od_com_method == COM_DOOR32 || od_control.od_com_method == COM_SOCKET)
+   {
+      return;
+   }
+
+   /* Check that carrier detect signal is still present. */
+   ODComCarrier(hSerialPort, &bCarrier);
+   if(bCarrier)
+   {
+      /* Only disable DTR response if OpenDoors opened the serial port, */
+      /* and DTR disabling has not been explicitly turned off.          */
+      if(od_control.od_open_handle == 0
+         && !(od_control.od_disable & DIS_DTR_DISABLE))
+      {
+         if(!ODSendModemCommand(od_control.od_disable_dtr, 2))
+         {
+#ifdef OD_DIAGNOSTICS
+            if(od_control.od_internal_debug)
+            {
+               MessageBox(NULL, szDebugWorkString, "DTR Disable FAILED!",
+                  MB_OK);
+               szDebugWorkString[0] = '\0';
+            }
+#endif /* OD_DIAGNOSTICS */
+         }
+         else
+         {
+#ifdef OD_DIAGNOSTICS
+            if(od_control.od_internal_debug)
+            {
+               MessageBox(NULL, szDebugWorkString, "DTR Disable Succeeded!",
+                  MB_OK);
+               szDebugWorkString[0] = '\0';
+            }
+#endif /* OD_DIAGNOSTICS */
+         }
+      }
+   }
+}
+
+#endif /* ODPLAT_WIN32 */

+ 443 - 0
odoors/ODInQue.c

@@ -0,0 +1,443 @@
+/* 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: ODInQue.h
+ *
+ * Description: OpenDoors input queue management. This input queue is where
+ *              all input events (e.g. keystrokes) from both local and remote
+ *              systems are combined into a single stream.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 16, 1995  6.00  BP   Created.
+ *              Nov 17, 1995  6.00  BP   Added multithreading support.
+ *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add semaphore timeout.
+ *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#include "ODGen.h"
+#include "ODInQue.h"
+#include "ODPlat.h"
+#include "ODKrnl.h"
+
+
+/* Input queue handle structure. */
+typedef struct
+{
+   tODInputEvent *paEvents;
+   INT nQueueEntries;
+   INT nInIndex;
+   INT nOutIndex;
+   time_t nLastActivityTime;
+#ifdef OD_MULTITHREADED
+   tODSemaphoreHandle hItemCountSemaphore;
+   tODSemaphoreHandle hAddEventSemaphore;
+#endif /* OD_MULTITHREADED */
+} tInputQueueInfo;
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueAlloc()
+ *
+ * Allocates a new input queue.
+ *
+ * Parameters: phInQueue         - Pointer to location where a handle to the
+ *                                 newly allocated input queue should be
+ *                                 stored.
+ *
+ *             nInitialQueueSize - The minimum number of events that the
+ *                                 input queue should be able to hold.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODInQueueAlloc(tODInQueueHandle *phInQueue, INT nInitialQueueSize)
+{
+   tInputQueueInfo *pInputQueueInfo = NULL;
+   tODInputEvent *pInputQueue = NULL;
+   tODResult Result = kODRCNoMemory;
+
+   ASSERT(phInQueue != NULL);
+
+   if(phInQueue == NULL) return(kODRCInvalidCall);
+
+   /* Attempt to allocate a serial port information structure. */
+   pInputQueueInfo = malloc(sizeof(tInputQueueInfo));
+
+   /* If memory allocation failed, return with failure. */
+   if(pInputQueueInfo == NULL) goto CleanUp;
+
+   /* Initialize semaphore handles to NULL. */
+#ifdef OD_MULTITHREADED
+   pInputQueueInfo->hItemCountSemaphore = NULL;
+   pInputQueueInfo->hAddEventSemaphore = NULL;
+#endif /* OD_MULTITHREADED */
+   
+   /* Attempt to allocate space for the queue itself. */
+   pInputQueue = calloc(nInitialQueueSize, sizeof(tODInputEvent));
+   if(pInputQueue == NULL) goto CleanUp;
+
+   /* Create semaphores if this is a multithreaded platform. */
+#ifdef OD_MULTITHREADED
+   if(ODSemaphoreAlloc(&pInputQueueInfo->hItemCountSemaphore, 0,
+      nInitialQueueSize) != kODRCSuccess)
+   {
+      goto CleanUp;
+   }
+
+   if(ODSemaphoreAlloc(&pInputQueueInfo->hAddEventSemaphore, 1, 1)
+      != kODRCSuccess)
+   {
+      goto CleanUp;
+   }
+#endif /* OD_MULTITHREADED */
+
+   /* Initialize input queue information structure. */
+   pInputQueueInfo->paEvents = pInputQueue;
+   pInputQueueInfo->nQueueEntries = nInitialQueueSize;
+   pInputQueueInfo->nInIndex = 0;
+   pInputQueueInfo->nOutIndex = 0;
+
+   /* Convert intut queue information structure pointer to a handle. */
+   *phInQueue = ODPTR2HANDLE(pInputQueueInfo, tInputQueueInfo);
+
+   /* Reset the time of the last activity. */
+   ODInQueueResetLastActivity(*phInQueue);
+
+   Result = kODRCSuccess;
+
+CleanUp:
+   if(Result != kODRCSuccess)
+   {
+#ifdef OD_MULTITHREADED
+      if(pInputQueueInfo != NULL
+         && pInputQueueInfo->hItemCountSemaphore != NULL)
+      {
+         ODSemaphoreFree(pInputQueueInfo->hItemCountSemaphore);
+      }
+
+      if(pInputQueueInfo != NULL
+         && pInputQueueInfo->hAddEventSemaphore != NULL)
+      {
+         ODSemaphoreFree(pInputQueueInfo->hAddEventSemaphore);
+      }
+#endif /* OD_MULTITHREADED */
+
+      if(pInputQueue != NULL) free(pInputQueue);
+      if(pInputQueueInfo != NULL) free(pInputQueueInfo);
+      *phInQueue = ODPTR2HANDLE(NULL, tInputQueueInfo);
+   }
+
+   /* Return with the appropriate result code. */
+   return(Result);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueFree()
+ *
+ * Destroys an input queue that was previously created by ODInQueueAlloc().
+ *
+ * Parameters: hInQueue - Handle to the input queue to destroy.
+ *
+ *     Return: void
+ */
+void ODInQueueFree(tODInQueueHandle hInQueue)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+
+   ASSERT(pInputQueueInfo != NULL);
+
+   /* Deallocate semaphores, if appropriate. */
+#ifdef OD_MULTITHREADED
+   ASSERT(pInputQueueInfo->hItemCountSemaphore != NULL);
+   ODSemaphoreFree(pInputQueueInfo->hItemCountSemaphore);
+#endif /* OD_MULTITHREADED */
+
+   /* Deallocate the input queue itself. */
+   ASSERT(pInputQueueInfo->paEvents != NULL);
+   free(pInputQueueInfo->paEvents);
+
+   /* Deallocate port information structure. */
+   free(pInputQueueInfo);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueWaiting()
+ *
+ * Determines whether or not an event is currently waiting in the input queue.
+ *
+ * Parameters: hInQueue - Handle to the input queue to check.
+ *
+ *     Return: TRUE if there is one or more waiting events, or FALSE if the
+ *             queue is empty.
+ */
+BOOL ODInQueueWaiting(tODInQueueHandle hInQueue)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+   BOOL bEventWaiting;
+
+   ASSERT(pInputQueueInfo != NULL);
+
+   /* There is data waiting in the queue if the in index is not equal to */
+   /* the out index.                                                     */
+   bEventWaiting = (pInputQueueInfo->nInIndex != pInputQueueInfo->nOutIndex);
+
+   return(bEventWaiting);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueAddEvent()
+ *
+ * Adds a new event to the input queue.
+ *
+ * Parameters: hInQueue  - Handle to the input queue to add an event to.
+ *
+ *             pEvent    - Pointer to the event structure to obtain the
+ *                         event information from.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODInQueueAddEvent(tODInQueueHandle hInQueue,
+   tODInputEvent *pEvent)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+   INT nNextInPos;
+
+   ASSERT(pInputQueueInfo != NULL);
+   ASSERT(pEvent != NULL);
+   if(pInputQueueInfo == NULL || pEvent == NULL) return(kODRCInvalidCall);
+
+   /* Serialize access to add event function. */
+#ifdef OD_MULTITHREADED
+   ODSemaphoreDown(pInputQueueInfo->hAddEventSemaphore, OD_NO_TIMEOUT);
+#endif /* OD_MULTITHREADED */
+
+   /* Reset the time of the last activity. */
+   ODInQueueResetLastActivity(hInQueue);
+
+   /* Determine what the next in index would be after this addition to the */
+   /* queue.                                                               */
+   nNextInPos = (pInputQueueInfo->nInIndex + 1)
+      % pInputQueueInfo->nQueueEntries;
+
+   /* If the queue is full, then return an out of space error. */
+   if(nNextInPos == pInputQueueInfo->nOutIndex)
+   {
+      /* Allow further access to input queue. */
+#ifdef OD_MULTITHREADED
+      ODSemaphoreUp(pInputQueueInfo->hAddEventSemaphore, 1);
+#endif /* OD_MULTITHREADED */
+
+      return(kODRCNoMemory);
+   }
+
+   /* Otherwise, add the new event to the input queue. */
+   memcpy(&pInputQueueInfo->paEvents[pInputQueueInfo->nInIndex], pEvent,
+      sizeof(tODInputEvent));
+
+   /* Update queue in index. */
+   pInputQueueInfo->nInIndex = nNextInPos;
+
+   /* Increment queue items count semaphore. */
+#ifdef OD_MULTITHREADED
+   ODSemaphoreUp(pInputQueueInfo->hItemCountSemaphore, 1);
+#endif /* OD_MULTITHREADED */
+
+   /* Allow further access to add event function. */
+#ifdef OD_MULTITHREADED
+   ODSemaphoreUp(pInputQueueInfo->hAddEventSemaphore, 1);
+#endif /* OD_MULTITHREADED */
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueGetNextEvent()
+ *
+ * Obtains the next event from the input queue. If no events are currently
+ * waiting in the input queue, this function blocks until an item is added
+ * to the queue, or the maximum wait time is reached.
+ *
+ * Parameters: hInQueue - Handle to the input queue to obtain the next event
+ *                        from.
+ *
+ *             pEvent   - Pointer to structure to store input event information
+ *                        in.
+ *
+ *             Timeout  - Maximum time, in milliseconds, to wait for next input
+ *                        event. A value of OD_NO_TIMEOUT causes this function
+ *                        to only return when an input event is obtained.
+ *
+ *     Return: kODRCSuccess on succes, or kODRCTimeout if the maximum wait time
+ *             is exceeded.
+ */
+tODResult ODInQueueGetNextEvent(tODInQueueHandle hInQueue,
+   tODInputEvent *pEvent, tODMilliSec Timeout)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+
+   ASSERT(pInputQueueInfo != NULL);
+   ASSERT(pEvent != NULL);
+
+#ifdef OD_MULTITHREADED
+
+   /* In multithreaded implementations, we wait for there to be an item in  */
+   /* the queue by decrementing the queue size semaphore. This will cause   */
+   /* this thread to be blocked until an event is added to the queue, if it */
+   /* is currently empty.                                                   */
+
+   if(ODSemaphoreDown(pInputQueueInfo->hItemCountSemaphore, Timeout)==kODRCTimeout)
+      return(kODRCTimeout);
+
+#else /* !OD_MULTITHREADED */
+
+   /* In non-multithreaded implementations, we check queue in and out     */
+   /* indicies to determine whether there are any events waiting in the   */
+   /* queue. If the queue is empty we loop, calling od_kernel() to check  */
+   /* for new events and od_yeild() to give more time to other processors */
+   /* if there is nothing for us to do, until an event is added to the    */
+   /* queue.                                                              */
+   if(pInputQueueInfo->nInIndex == pInputQueueInfo->nOutIndex)
+   {
+      tODTimer Timer;
+
+      /* If a timeout has been specified, then start timer to keep track */
+      /* of how long we have been waiting.                               */
+      if(Timeout != 0 && Timeout != OD_NO_TIMEOUT)
+      {
+         ODTimerStart(&Timer, Timeout);
+      }
+
+      /* As soon as we see that there is nothing in the queue, we do an */
+      /* od_kernel() call to check for new input.                       */
+      CALL_KERNEL_IF_NEEDED();
+
+      /* As long as we don't have new input, we loop, yielding to other */
+      /* processes, and then giving od_kernel() a chance to run.        */
+      while(pInputQueueInfo->nInIndex == pInputQueueInfo->nOutIndex)
+      {
+         /* If a timeout has been specified, then ensure that the maximum */
+         /* wait time has not elapsed.                                    */
+         if(Timeout != 0 && Timeout != OD_NO_TIMEOUT
+            && ODTimerElapsed(&Timer))
+         {
+            return(kODRCTimeout);
+         }
+
+         /* Yield the processor to other tasks. */
+         od_sleep(0);
+
+         /* Call od_kernel(). */
+         CALL_KERNEL_IF_NEEDED();
+      }
+   }
+
+#endif /* !OD_MULTITHREADED */
+
+   /* Copy next input event from the queue into the caller's structure. */
+   memcpy(pEvent, &pInputQueueInfo->paEvents[pInputQueueInfo->nOutIndex],
+      sizeof(tODInputEvent));
+
+   /* Move out pointer to the next queue item, wrapping back to the start */
+   /* of the queue if needed.                                             */
+   pInputQueueInfo->nOutIndex
+      = (pInputQueueInfo->nOutIndex + 1) % pInputQueueInfo->nQueueEntries;
+
+   /* Now, return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueEmpty()
+ *
+ * Removes all events from the input queue.
+ *
+ * Parameters: hInQueue - Handle to the input queue to be emptied.
+ *
+ *     Return: void
+ */
+void ODInQueueEmpty(tODInQueueHandle hInQueue)
+{
+   tODInputEvent InputEvent;
+
+   ASSERT(hInQueue != NULL);
+
+   /* Remove all items from the queue. */
+   while(ODInQueueWaiting(hInQueue))
+   {
+      ODInQueueGetNextEvent(hInQueue, &InputEvent, OD_NO_TIMEOUT);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueGetLastActivity()
+ *
+ * Returns the time of the last input activity. This is the latest of the time
+ * that the queue was created, the time of the last call to
+ * ODInQueueAddEvent() on this input queue, and the time of the last call to
+ * ODInQueueResetLastActivity() on this input queue.
+ *
+ * Parameters: hInQueue - Handle to the input queue.
+ *
+ *     Return: void
+ */
+time_t ODInQueueGetLastActivity(tODInQueueHandle hInQueue)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+
+   ASSERT(pInputQueueInfo != NULL);
+
+   /* Returns the last activity time. */
+   return(pInputQueueInfo->nLastActivityTime);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODInQueueResetLastActivity()
+ *
+ * Resets the time of the last input activity to the current time.
+ *
+ * Parameters: hInQueue - Handle to the input queue.
+ *
+ *     Return: void
+ */
+void ODInQueueResetLastActivity(tODInQueueHandle hInQueue)
+{
+   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
+
+   ASSERT(pInputQueueInfo != NULL);
+
+   /* Resets the last activity time to the current time. */
+   pInputQueueInfo->nLastActivityTime = time(NULL);
+}

+ 57 - 0
odoors/ODInQue.h

@@ -0,0 +1,57 @@
+/* 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: ODInQue.h
+ *
+ * Description: OpenDoors input queue management. This input queue is where
+ *              all input events (e.g. keystrokes) from both local and remote
+ *              systems are combined into a single stream.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 16, 1995  6.00  BP   Created.
+ *              Jan 04, 1996  6.00  BP   Moved event type defs to OpenDoor.h.
+ *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
+ *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODINQUE
+#define _INC_ODINQUE
+
+#include <time.h>
+
+#include "ODTypes.h"
+
+/* OpenDoors input queue handle. */
+typedef tODHandle tODInQueueHandle;
+
+/* Input queue functions. */
+tODResult ODInQueueAlloc(tODInQueueHandle *phInQueue,  INT nInitialQueueSize);
+void ODInQueueFree(tODInQueueHandle hInQueue);
+BOOL ODInQueueWaiting(tODInQueueHandle hInQueue);
+tODResult ODInQueueAddEvent(tODInQueueHandle hInQueue,
+   tODInputEvent *pEvent);
+tODResult ODInQueueGetNextEvent(tODInQueueHandle hInQueue,
+   tODInputEvent *pEvent, tODMilliSec Timeout);
+void ODInQueueEmpty(tODInQueueHandle hInQueue);
+time_t ODInQueueGetLastActivity(tODInQueueHandle hInQueue);
+void ODInQueueResetLastActivity(tODInQueueHandle hInQueue);
+
+#endif /* _INC_ODINQUE */

BIN
odoors/ODInfo.ico


+ 1675 - 0
odoors/ODKrnl.c

@@ -0,0 +1,1675 @@
+/* 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: ODKrnl.c
+ *
+ * Description: Contains the OpenDoors kernel, which is responsible for many
+ *              of the core functions which continue regardless of what the
+ *              client program is doing. The implementation of this file is
+ *              central to the OpenDoors architecture. The functionality
+ *              implemented by the OpenDoors kernel includes (but is not
+ *              limited to):
+ *
+ *                     - Obtaining and  input from the user, through the modem
+ *                       and possibly the local keyboard.
+ *                     - Monitoring maximum time and inactivity time limits.
+ *                     - Responding to loss of carrier.
+ *                     - Forcing the status line to be updated regularily,
+ *                       on platforms that it exists.
+ *                     - Implementing the system operator <-> remote user chat
+ *                       mode.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Jan 01, 1995  6.00  BP   Split off from odcore.c
+ *              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   32-bit portability.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Nov 21, 1995  6.00  BP   Ported to Win32.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 13, 1995  6.00  BP   Moved chat mode code to ODKrnl.h.
+ *              Dec 24, 1995  6.00  BP   od_chat_active = TRUE on chat start.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
+ *              Jan 12, 1996  6.00  BP   Added bOnlyShiftArrow.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add semaphore timeout.
+ *              Feb 06, 1996  6.00  BP   Added od_silent_mode.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 23, 1996  6.00  BP   Only create active semapore once.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 06, 1996  6.10  BP   Prevent TC generated N_SCOPY@ call.
+ *              Mar 13, 1996  6.10  BP   bOnlyShiftArrow -> nArrowUseCount.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Oct 22, 2001  6.21  RS   Lowered thread priorities to normal.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <limits.h>
+
+#include "OpenDoor.h"
+#ifdef ODPLAT_NIX
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODCom.h"
+#include "ODKrnl.h"
+#include "ODScrn.h"
+#include "ODInQue.h"
+#include "ODInEx.h"
+#ifdef ODPLAT_WIN32
+#include "ODFrame.h"
+#endif /* ODPLAT_WIN32 */
+
+
+/* Multithreading performance tuning. */
+#define REMOTE_INPUT_THREAD_PRIORITY      OD_PRIORITY_NORMAL /* was ABOVE_NORMAL */
+#define NO_CARRIER_THREAD_PRIORITY        OD_PRIORITY_NORMAL /* was ABOVE_NORMAL */
+#define NO_CARRIER_THREAD_SLEEP_TIME      6000
+#define TIME_UPDATE_THREAD_PRIORITY       OD_PRIORITY_NORMAL
+#define TIME_UPDATE_THREAD_SLEEP_TIME     3000
+
+/* Misc performance tuning. */
+#define STATUS_UPDATE_PERIOD        3L
+#define CHAT_YIELD_PERIOD           25L
+
+/* Pending command identifiers. */
+#define KERNEL_FUNC_CHATTOGGLE      0x0001
+
+/* Private function prototypes. */
+static void ODKrnlHandleReceivedChar(char chReceived, BOOL bFromRemote);
+static void ODKrnlTimeUpdate(void);
+static void ODKrnlChatCleanup(void);
+static void ODKrnlChatMode(void);
+#ifdef ODPLAT_NIX
+#ifdef USE_KERNEL_SIGNAL
+static void sig_run_kernel(int sig);
+static void sig_get_char(int sig);
+static void sig_no_carrier(int sig);
+#endif
+#endif
+
+/* Functions specific to the multithreaded implementation of the kernel. */
+#ifdef OD_MULTITHREADED
+/* Thread proceedures. */
+DWORD OD_THREAD_FUNC ODKrnlRemoteInputThread(void *pParam);
+DWORD OD_THREAD_FUNC ODKrnlNoCarrierThread(void *pParam);
+DWORD OD_THREAD_FUNC ODKrnlTimeUpdateThread(void *pParam);
+DWORD OD_THREAD_FUNC ODKrnlChatThread(void *pParam);
+
+/* Helper functions. */
+static void ODKrnlWaitForExclusiveControl(void);
+static void ODKrnlGiveUpExclusiveControl(void);
+#endif /* OD_MULTITHREADED */
+
+/* Local working variables. */
+#ifdef OD_MULTITHREADED
+static tODThreadHandle hRemoteInputThread = NULL;
+static tODThreadHandle hNoCarrierThread = NULL;
+static tODThreadHandle hTimeUpdateThread = NULL;
+static tODThreadHandle hClientThread = NULL;
+static tODThreadHandle hChatThread = NULL;
+static BOOL bHaveExclusiveControl;
+static BOOL bChatActivatedInternally;
+#endif /* OD_MULTITHREADED */
+static BOOL bKernelActive = FALSE;
+static BOOL bWarnedAboutInactivity = FALSE;
+static INT16 nLastInactivitySetting = 0;
+static time_t nNextStatusUpdateTime;
+static INT nKrnlFuncPending;
+static BOOL bLastStatusSetting;
+static INT16 nChatOriginalAttrib;
+
+/* Global kernel-related variables. */
+tODTimer RunKernelTimer;
+time_t nNextTimeDeductTime;
+char chLastControlKey = '\0';
+INT nArrowUseCount = 0;
+BOOL bForceStatusUpdate = FALSE;
+BOOL bIsShell;
+#ifdef OD_MULTITHREADED
+tODSemaphoreHandle hODActiveSemaphore = NULL;
+#endif /* OD_MULTITHREADED */
+
+
+
+/* ========================================================================= */
+/* Core of the OpenDoors Kernel.                                             */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlInitialize()
+ *
+ * Initializes kernel activities. In multithreaded versions of OpenDoors, this
+ * is the function that starts the various kernel threads.
+ *
+ * Parameters: kODRCSuccess on success, or an error code on failure.
+ *
+ *     Return: void
+ */
+tODResult ODKrnlInitialize(void)
+{
+#ifdef ODPLAT_NIX
+   sigset_t		block;
+#ifdef USE_KERNEL_SIGNAL
+   struct sigaction act;
+   struct itimerval itv;
+#endif
+#endif
+
+   tODResult Result = kODRCSuccess;
+   
+#ifdef ODPLAT_NIX
+#ifdef USE_KERNEL_SIGNAL
+   /* HUP Detection */
+   act.sa_handler=sig_no_carrier;
+   /* If two HUP signals are recieved, die on the second */
+   act.sa_flags=SA_RESETHAND|SA_RESTART;
+   sigemptyset(&(act.sa_mask));
+   sigaction(SIGHUP,&act,NULL);
+
+   /* Run kernel on SIGALRM (Every .01 seconds) */
+   act.sa_handler=sig_run_kernel;
+   act.sa_flags=SA_RESTART;
+   sigemptyset(&(act.sa_mask));
+   sigaction(SIGALRM,&act,NULL);
+   itv.it_interval.tv_sec=0;
+   itv.it_interval.tv_usec=10000;
+   itv.it_value.tv_sec=0;
+   itv.it_value.tv_usec=10000;
+   setitimer(ITIMER_REAL,&itv,NULL);
+
+   /* Make stdin signal driven. */
+//   act.sa_handler=sig_get_char;
+//   act.sa_flags=0;
+//   sigemptyset(&(act.sa_mask));
+//   sigaction(SIGIO,&act,NULL);
+//
+//   /* Have SIGIO signals delivered to this process */
+//   fcntl(0,F_SETOWN,getpid());
+//   
+//   /* Enable SIGIO when read possible on stdin */
+//   fcntl(0,F_SETFL,fcntl(0,F_GETFL)|O_ASYNC); 
+
+   /* Make sure SIGHUP, SIGALRM, and SIGIO are unblocked */
+   sigemptyset(&block);
+   sigaddset(&block,SIGHUP);
+   sigaddset(&block,SIGALRM);
+#if 0
+   sigaddset(&block,SIGIO);
+#endif
+   sigprocmask(SIG_UNBLOCK,&block,NULL);
+#else	/* Using ODComCarrier... don't catch HUP signal */
+   sigemptyset(&block);
+   sigaddset(&block,SIGHUP);
+   sigprocmask(SIG_BLOCK,&block,NULL);
+#endif
+#endif
+
+   /* Initialize time of next status update and next time deduction. */
+   nNextStatusUpdateTime = time(NULL) + STATUS_UPDATE_PERIOD;
+   nNextTimeDeductTime = time(NULL) + 60L;
+   bLastStatusSetting = od_control.od_status_on = TRUE;
+
+   /* Initially, no kernel functions are pending. */
+   nKrnlFuncPending = 0;
+
+   /* Initially, the kernel is not active. */
+   bKernelActive = FALSE;
+
+#ifdef OD_MULTITHREADED
+   /* Initially, we do not have exclusive control of the application. */
+   bHaveExclusiveControl = FALSE;
+
+   /* Obtain a handle to the client thread. */
+   hClientThread = ODThreadGetCurrent();
+
+   /* Create OpenDoors activation semaphore. */
+   if(hODActiveSemaphore == NULL)
+   {
+      Result = ODSemaphoreAlloc(&hODActiveSemaphore, 0, INT_MAX);
+      if(Result != kODRCSuccess) return(Result);
+   }
+
+   /* Start the remote input thread if we are not operating in local mode. */
+   if(od_control.baud != 0)
+   {
+      Result = ODThreadCreate(&hRemoteInputThread, ODKrnlRemoteInputThread,
+         NULL);
+      if(Result != kODRCSuccess) return(Result);
+      ODThreadSetPriority(hRemoteInputThread, REMOTE_INPUT_THREAD_PRIORITY);
+   }
+
+   /* Start the carrier detection thread if we are not operating in local */
+   /* mode.                                                               */
+   if(od_control.baud != 0)
+   {
+      Result = ODThreadCreate(&hNoCarrierThread, ODKrnlNoCarrierThread, NULL);
+      if(Result != kODRCSuccess) return(Result);
+      ODThreadSetPriority(hNoCarrierThread, NO_CARRIER_THREAD_PRIORITY);
+   }
+
+   /* Start the time update thread. */
+   Result = ODThreadCreate(&hTimeUpdateThread, ODKrnlTimeUpdateThread, 0);
+   if(Result != kODRCSuccess) return(Result);
+   ODThreadSetPriority(hTimeUpdateThread, TIME_UPDATE_THREAD_PRIORITY);
+#endif /* OD_MULTITHREADED */
+
+   /* Return with success. */
+   return(Result);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlShutdown()
+ *
+ * Shuts down kernel activities.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODKrnlShutdown(void)
+{
+   if(bKernelActive) return;
+
+#ifdef OD_MULTITHREADED
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+      MessageBox(NULL, "Terminating remote input thread", "OpenDoors Diagnostics", MB_OK);
+#endif
+   /* Shutdown the remote input thread, if it exists. */
+   if(hRemoteInputThread != NULL) ODThreadTerminate(hRemoteInputThread);
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+      MessageBox(NULL, "Terminating carrier detection", "OpenDoors Diagnostics", MB_OK);
+#endif
+   /* Shutdown the carrier detection thread, if it exists. */
+   if(hNoCarrierThread != NULL) ODThreadTerminate(hNoCarrierThread);
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+      MessageBox(NULL, "Terminating time update thread", "OpenDoors Diagnostics", MB_OK);
+#endif
+   /* Shutdown the time update thread, if it exists. */
+   if(hTimeUpdateThread != NULL) ODThreadTerminate(hTimeUpdateThread);
+
+#if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32)
+   if(od_control.od_internal_debug)
+      MessageBox(NULL, "Releasing activation semaphore", "OpenDoors Diagnostics", MB_OK);
+#endif
+#endif /* OD_MULTITHREADED */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_kernel()
+ *
+ * Carries out any kernel tasks that must be performed through regular,
+ * explicit calls to this function,
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_kernel(void)
+{
+#ifndef OD_MULTITHREADED
+   char ch;
+#ifdef ODPLAT_DOS
+   WORD wKey;
+   BYTE btShiftStatus;
+   char *pszShellName;
+#endif
+   BOOL bCarrier;
+#endif /* OD_MULTITHREADED */
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_kernel()");
+
+   /* Initialize OpenDoors if not already done. */
+   if(!bODInitialized) od_init();
+
+   /* If this is an attempt at a re-entrant call to od_kernel() from another */
+   /* function called by a currently active od_kernel(), then return without */
+   /* doing anything.                                                        */
+   if(bKernelActive) return;
+
+   OD_API_ENTRY();
+
+   /* Note that kernel is active to prevent recursive calls to the kernel. */
+   bKernelActive = TRUE;
+
+   /* Call od_ker_exec function if required. */
+   if(od_control.od_ker_exec != NULL)
+   {
+      (*od_control.od_ker_exec)();
+   }
+
+   /* The remainder of od_kernel() only applies to non-multithreaded */
+   /* versions of OpenDoors.                                         */
+#ifndef OD_MULTITHREADED
+   /* If not operating in local mode, then perform remote-mode specific */
+   /* activies.                                                         */
+   if(od_control.baud != 0)
+   {
+#ifndef USE_KERNEL_SIGNAL
+      /* If carrier detection is enabled, then shutdown OpenDoors if */
+      /* the carrier detect signal is no longer high.                */
+      if(!(od_control.od_disable&DIS_CARRIERDETECT))
+      {
+         ODComCarrier(hSerialPort, &bCarrier);
+         if(!bCarrier)
+         {
+            ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER);
+         }
+      }
+#endif
+
+      /* Loop, obtaining any new characters from the serial port and */
+      /* adding them to the common local/remote input queue.         */
+      while(ODComGetByte(hSerialPort, &ch, FALSE) == kODRCSuccess)
+      {
+         ODKrnlHandleReceivedChar(ch, TRUE);
+      }
+   }
+
+#ifdef ODPLAT_DOS
+check_keyboard_again:
+    if(nKrnlFuncPending && !bShellChatActive)
+    {
+       if(nKrnlFuncPending & KERNEL_FUNC_CHATTOGGLE)
+       {
+          nKrnlFuncPending &=~ KERNEL_FUNC_CHATTOGGLE;
+          goto chat_pressed;
+       }
+    }
+
+   /* Don't check local keyboard if sysop DIS_SYSOP_KEYS is set, or if we */
+   /* are operatingin silent mode.                                        */
+   if(od_control.od_disable & DIS_SYSOP_KEYS
+      || od_control.od_silent_mode)
+   {
+      goto after_key_check;
+   }
+
+   ASM    mov ah, 1
+   ASM    push si
+   ASM    push di
+   ASM    int 0x16
+   ASM    jnz key_waiting
+   ASM    pop di
+   ASM    pop si
+   ASM    jmp after_key_check
+key_waiting:
+   ASM    mov ah, 0
+   ASM    int 0x16
+   ASM    mov wKey, ax
+   ASM    mov ah, 2
+   ASM    int 0x16
+   ASM    mov btShiftStatus, al
+   ASM    pop di
+   ASM    pop si
+
+      if(nArrowUseCount > 0 && (wKey == 0x4800 || wKey == 0x5000)
+         && !(btShiftStatus & 2))
+      {
+         /* Pass key on to od_local_input, if it is defined. */
+         if(od_control.od_local_input != NULL)
+         {
+            (*od_control.od_local_input)(wKey);
+         }
+
+         /* Add this key to the local/remote input queue. */
+         ODKrnlHandleLocalKey(wKey);
+      }
+
+      /* If hangup key is pressed. */
+      else if(wKey == od_control.key_hangup)
+      {
+         ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP);
+      }
+
+      /* If drop to BBS key is pressed. */
+      else if(wKey == od_control.key_drop2bbs)
+      {
+         ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_DROPTOBBS);
+      }
+
+      else if(wKey == od_control.key_dosshell)
+      {
+         if(!bShellChatActive)
+         {
+            if(pfLogWrite != NULL)
+               (*pfLogWrite)(6);
+
+            /* If function hook is defined. */
+            if(od_control.od_cbefore_shell != NULL)
+            {
+               /* Then call it. */
+               bShellChatActive = TRUE;
+               (*od_control.od_cbefore_shell)();
+               bShellChatActive = FALSE;
+            }
+
+            if(od_control.od_before_shell != NULL)
+               od_disp_str(od_control.od_before_shell);
+
+            if((pszShellName = (char *)getenv("COMSPEC")) == NULL)
+            {
+               pszShellName = (char *)"COMMAND.COM";
+            }
+            bIsShell = TRUE;
+            od_spawnvpe(P_WAIT, pszShellName, NULL, NULL);
+            bIsShell = FALSE;
+
+            if(od_control.od_after_shell != NULL)
+               od_disp_str(od_control.od_after_shell);
+
+            /* If a function hook is defined. */
+            if(od_control.od_cafter_shell != NULL)
+            {
+               /* Then call it. */
+               bShellChatActive = TRUE;
+               (*od_control.od_cafter_shell)();
+               bShellChatActive = FALSE;
+            }
+
+            if(pfLogWrite != NULL)
+               (*pfLogWrite)(7);
+         }
+      }
+
+      /* If toggle chat mode key is pressed. */
+      else if(wKey == od_control.key_chat)
+      {
+chat_pressed:
+         if(!bShellChatActive || od_control.od_chat_active)
+         {
+            /* If chat mode is active. */
+            if(od_control.od_chat_active)
+            {
+               /* Signal exit of chat mode. */
+               ODKrnlEndChatMode();
+            }
+
+            /* If chat mode is off. */
+            else
+            {
+               /* Enable second call to kernel. */
+               bKernelActive = FALSE;
+
+               /* Enter chat mode. */
+               ODKrnlChatMode();
+
+               /* Disable second call to kernel. */
+               bKernelActive = TRUE;
+            }
+         }
+         else
+         {
+            if(nKrnlFuncPending & KERNEL_FUNC_CHATTOGGLE)
+            {
+               nKrnlFuncPending &= ~KERNEL_FUNC_CHATTOGGLE;
+            }
+            else
+            {
+               nKrnlFuncPending |= KERNEL_FUNC_CHATTOGGLE;
+            }
+         }
+      }
+
+      /* If sysop next key is pressed. */
+      else if(wKey == od_control.key_sysopnext)
+      {
+         /* Toggle sysop next setting. */
+         od_control.sysop_next = !od_control.sysop_next;
+
+         /* Update status line. */
+         goto statup;
+      }
+
+      /* If ESCape key is pressed and we are in chat mode. */
+      else if((wKey&0xff) == 27 && od_control.od_chat_active)
+      {
+         /* Signal exit from chat mode. */
+         od_control.od_chat_active = FALSE;
+      }
+
+      /* If lockout user key is pressed. */
+      else if(wKey == od_control.key_lockout)
+      {
+         /* Set the user's access security level to 0. */
+         od_control.user_security = 0;
+
+         /* Shutdown OpenDoors. */
+         ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP);
+      }
+
+
+      /* If toggle keyboard off key is pressed. */
+      else if(wKey == od_control.key_keyboardoff)
+      {
+         /* Toggle user keyboard settings. */
+         od_control.od_user_keyboard_on =! od_control.od_user_keyboard_on;
+
+         /* Update status line. */
+         goto statup;
+      }
+
+      /* If increase time key is pressed. */
+      else if(wKey == od_control.key_moretime)
+      {
+         /* If time limit is less than maximum possible time limit. */
+         if(od_control.user_timelimit < 1440)
+         {
+             /* Increase time left online. */
+            ++od_control.user_timelimit;
+         }
+
+         /* Update status line. */
+         goto statup;
+      }
+
+      /* If decrease time key is pressed. */
+      else if(wKey == od_control.key_lesstime)
+      {
+         /* Never let user's time limit be set to a negative value. */
+         if(od_control.user_timelimit > 0)
+         {
+            /* Decrease user's timelimit. */
+            --od_control.user_timelimit;
+         }
+
+         /* Update the status line. */
+         goto statup;
+      }
+
+      else
+      {
+         for(ch = 0; ch < 9; ++ch)
+         {
+            if(wKey == od_control.key_status[ch])
+            {
+               if(btCurrentStatusLine != ch && od_control.od_status_on)
+               {
+                  od_set_statusline(ch);
+               }
+               goto check_keyboard_again;
+            }
+         }
+
+         /* Look for user-defined hotkeys. */
+         for(ch=0; ch<od_control.od_num_keys; ++ch)
+         {
+            /* If it matches. */
+            if(wKey == (WORD)od_control.od_hot_key[ch])
+            {
+               /* Record keypress. */
+               od_control.od_last_hot = wKey;
+
+               /* Notify the current personality. */
+               (*pfCurrentPersonality)(21);
+
+               /* Check for a hotkey function. */
+               if(od_control.od_hot_function[ch] != NULL)
+               {
+                  /* Call it if it exists. */
+                  (*od_control.od_hot_function[ch])();
+               }
+
+               /* Stop searching. */
+               break;
+            }
+         }
+
+         /* If no hotkeys found. */
+         if(ch >= od_control.od_num_keys)
+         {
+            /* Pass key on to od_local_input, if it is defined. */
+            if(od_control.od_local_input != NULL)
+            {
+               (*od_control.od_local_input)(wKey);
+            }
+
+            /* Add this key to the local/remote input queue. */
+            ODKrnlHandleLocalKey(wKey);
+         }
+      }
+   goto check_keyboard_again;
+
+after_key_check:
+
+   /* If status line has been turned on since last call to kernel. */
+   if(bLastStatusSetting != od_control.od_status_on)
+   {
+      /* Generate the status line. */
+      od_set_statusline(0);
+   }
+
+   bLastStatusSetting = od_control.od_status_on;
+
+   if(od_control.od_update_status_now)
+   {
+      od_set_statusline(btCurrentStatusLine);
+      od_control.od_update_status_now = FALSE;
+   }
+
+   /* Update status line when needed. */
+   if(nNextStatusUpdateTime < time(NULL) || bForceStatusUpdate)
+   {
+statup:
+      nNextStatusUpdateTime = time(NULL) + STATUS_UPDATE_PERIOD;
+
+      /* Turn off status line update force flag */
+      bForceStatusUpdate = FALSE;
+
+      if(od_control.od_status_on && btCurrentStatusLine != 8)
+      {
+         /* Store console settings. */
+         ODStoreTextInfo();
+
+         /* Enable writes to whole screen. */
+         ODScrnSetBoundary(1, 1, 80, 25);
+         ODScrnEnableCaret(FALSE);
+         (*pfCurrentPersonality)((BYTE)(10 + btCurrentStatusLine));
+         ODRestoreTextInfo();
+         ODScrnEnableCaret(TRUE);
+      }
+   }
+#endif
+
+   ODKrnlTimeUpdate();
+
+   ODTimerStart(&RunKernelTimer, 250);
+
+   OD_API_EXIT();
+
+   bKernelActive = FALSE;
+#endif /* !OD_MULTITHREADED */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlHandleLocalKey()
+ *
+ * Called when a key is pressed on the local keyboard that should be placed
+ * in the common local/remote input queue. This function is not called for
+ * sysop function keys.
+ *
+ * Parameters: wKeyCode
+ *
+ *     Return: void
+ */
+void ODKrnlHandleLocalKey(WORD wKeyCode)
+{
+   /* If local keyboard input by sysop has not been disabled. */
+   if(!(od_control.od_disable & DIS_LOCAL_INPUT))
+   {
+      if((wKeyCode & 0xff) == 0)
+      {
+         ODKrnlHandleReceivedChar('\0', FALSE);
+         ODKrnlHandleReceivedChar((char)(wKeyCode >> 8), FALSE);
+      }
+      else
+      {
+         ODKrnlHandleReceivedChar((char)wKeyCode, FALSE);
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlHandleReceivedChar()                          *** PRIVATE FUNCTION ***
+ *
+ * Called when a character is received from the local or remote system.
+ *
+ * Parameters: chReceived  - Character that should be handled.
+ *
+ *             bFromRemote - TRUE if this character was received from the
+ *                           remote system, FALSE if it originated from the
+ *                           local console.
+ *
+ *     Return: void
+ */
+static void ODKrnlHandleReceivedChar(char chReceived, BOOL bFromRemote)
+{
+   tODInputEvent InputEvent;
+
+   /* If we are operating in remote mode, and remote user keyboard has been */
+   /* disabled by the sysop, then return, ignoring this character.          */
+   if(bFromRemote && !od_control.od_user_keyboard_on)
+   {
+      return;
+   }
+
+   /* Add this input event to the local/remote common input queue. */
+   InputEvent.EventType = EVENT_CHARACTER;
+   InputEvent.bFromRemote = bFromRemote;
+   InputEvent.chKeyPress = chReceived;
+   ODInQueueAddEvent(hODInputQueue, &InputEvent);
+
+   /* Update last control key information. */
+   switch(chReceived)
+   {
+      case 's':
+      case 'S':
+      case 3:
+      case 11:
+      case 0x18:
+         chLastControlKey = 's';
+         break;
+      case 'p':
+      case 'P':
+         chLastControlKey = 'p';
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlTimeUpdate()                                  *** PRIVATE FUNCTION ***
+ *
+ * Performs regular updating of time remaining online, inactivity time, and
+ * forces OpenDoors to exit if a time limit has been exceeded.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+static void ODKrnlTimeUpdate(void)
+{
+   time_t CurrentTime;
+   static char szTemp[80];
+
+   /* Obtain the current time. */
+   CurrentTime = time(NULL);
+
+   /* If inactivity setting has changed. */
+   if(nLastInactivitySetting != od_control.od_inactivity)
+   {
+      /* If it was previously disabled. */
+      if(nLastInactivitySetting == 0)
+      {
+         /* Prevent immediate timeout. */
+         ODInQueueResetLastActivity(hODInputQueue);
+      }
+
+      /* Store current value. */
+      nLastInactivitySetting = od_control.od_inactivity;
+   }
+
+   /* Check user keyboard inactivity. */
+   if((ODInQueueGetLastActivity(hODInputQueue) + od_control.od_inactivity)
+      < CurrentTime)
+   {
+      /* If timeout, display message. */
+      if(od_control.od_inactivity != 0 && !od_control.od_disable_inactivity)
+      {
+         if(od_control.od_time_msg_func == NULL)
+         {
+            od_disp_str(od_control.od_inactivity_timeout);
+         }
+         else
+         {
+            (*od_control.od_time_msg_func)(od_control.od_inactivity_timeout);
+         }
+
+         /* End connection. */
+         ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_INACTIVITY);
+      }
+   }
+
+   /* If less than 5s left of inactivity. */
+   else if(ODInQueueGetLastActivity(hODInputQueue) + od_control.od_inactivity
+      < CurrentTime + od_control.od_inactive_warning)
+   {
+      if(!bWarnedAboutInactivity && od_control.od_inactivity != 0
+         && !od_control.od_disable_inactivity)
+      {
+         /* Warn the user. */
+         if(od_control.od_time_msg_func == NULL)
+         {
+            od_disp_str(od_control.od_inactivity_warning);
+         }
+         else
+         {
+            (*od_control.od_time_msg_func)(od_control.od_inactivity_warning);
+         }
+         /* Don't warn the user a second time. */
+         bWarnedAboutInactivity = TRUE;
+      }
+   }
+   else
+   {
+      /* Re-enable inactivity warning. */
+      bWarnedAboutInactivity = FALSE;
+   }
+
+   /* If chat mode is active. */
+   if(od_control.od_chat_active)
+   {
+      /* Prevent the user's time from being drained. */
+      nNextTimeDeductTime = time(NULL) + 60;
+   }
+
+   /* If 1 minute has passed since last time update. */
+   if(CurrentTime >= nNextTimeDeductTime)
+   {
+      /* Next time update should occur 60 seconds after this one was */
+      /* scheduled.                                                  */
+      nNextTimeDeductTime += 60;
+
+      /* Force status line to be updated immediately. */
+      bForceStatusUpdate = TRUE;
+
+      /* Decrement time left. */
+      --od_control.user_timelimit;
+
+      /* If the user's time limit is close to expiring, then notify */
+      /* the user.                                                  */
+      if(od_control.user_timelimit <= 3 &&
+         od_control.user_timelimit > 0 &&
+         !(od_control.od_disable & DIS_TIMEOUT))
+      {
+         /* If less than 3 mins left, tell user. */
+         sprintf(szTemp, od_control.od_time_warning,
+            od_control.user_timelimit);
+         if(od_control.od_time_msg_func == NULL)
+         {
+            od_disp_str(szTemp);
+         }
+         else
+         {
+            (*od_control.od_time_msg_func)(szTemp);
+         }
+      }
+
+#ifdef ODPLAT_WIN32
+      ODFrameUpdateTimeDisplay();
+#endif /* ODPLAT_WIN32 */
+   }
+
+   /* If user has no time left. */
+   if(od_control.user_timelimit <= 0
+      && !(od_control.od_disable & DIS_TIMEOUT))
+   {
+      /* Notify the user. */
+      if(od_control.od_time_msg_func == NULL)
+      {
+         od_disp_str(od_control.od_no_time);
+      }
+      else
+      {
+         (*od_control.od_time_msg_func)(od_control.od_no_time);
+      }
+
+      /* Force OpenDoors to shutdown. */
+      ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_TIMEOUT);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlForceOpenDoorsShutdown()
+ *
+ * Called to force the application to exit due to some event in OpenDoors,
+ * such as loss of carrier, user inactivity timeout, the hangup command
+ * being chosen by the system operator, etc. The only time when OpenDoors
+ * is shutdown without going through this function should be as a result of
+ * an explicit call to od_exit() by the client application.
+ *
+ * Parameters: btReasonForShutdown - An OpenDoors exit reason code.
+ *
+ *     Return: Never returns.
+ */
+void ODKrnlForceOpenDoorsShutdown(BYTE btReasonForShutdown)
+{
+   BOOL bHangup;
+
+#ifdef OD_MULTITHREADED
+   /* First, wait until an OpenDoors API is active. This way, we won't  */
+   /* interrupt any client application operations that may leave the    */
+   /* system in an unstable state (for instance, interrupting some file */
+   /* I/O operations).                                                  */
+   ODKrnlWaitForExclusiveControl();
+#endif /* OD_MULTITHREADED */
+
+   bKernelActive = TRUE;
+
+   /* Determine whether we should hangup on the user before exiting. */
+   if(btReasonForShutdown == ERRORLEVEL_HANGUP
+      || btReasonForShutdown == ERRORLEVEL_INACTIVITY)
+   {
+      bHangup = TRUE;
+   }
+   else
+   {
+      bHangup = FALSE;
+   }
+
+   /* Record exit reason in global variable. */
+   btExitReason = btReasonForShutdown - 1;
+
+   /* Use the client-defined errorlevel, if any. */
+   if(od_control.od_errorlevel[0])
+   {
+      od_exit(od_control.od_errorlevel[btReasonForShutdown], bHangup);
+   }
+
+   /* Otherwise, use the default OpenDoors errorlevel. */
+   else
+   {
+      od_exit(btReasonForShutdown - 1, bHangup);
+   }
+}
+
+
+/* ========================================================================= */
+/* OpenDoors Kernel multithreaded implementation.                            */
+/* ========================================================================= */
+
+#ifdef OD_MULTITHREADED
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlRemoteInputThread()                           *** PRIVATE FUNCTION ***
+ *
+ * Code for the remote input thread. This thread executes an infinite loop,
+ * blocking until a character is received from the remote system, and then
+ * adding this character to the common local/remote input queue. This thread
+ * should be given higher than normal priority.
+ *
+ * In non-multithreaded versions of OpenDoors, the task of checking for new
+ * characters from the remote system and adding them to the common input
+ * queue is performed on each call to od_kernel().
+ *
+ * Parameters: As dictated for any thread function.
+ *
+ *     Return: As dictated for any thread function.
+ */
+DWORD OD_THREAD_FUNC ODKrnlRemoteInputThread(void *pParam)
+{
+   char chReceived;
+
+   /* We keep looping until someone else terminates this thread. */
+   for(;;)
+   {
+      /* Get next character from the modem, blocking if no character */
+      /* is waiting.                                                 */
+      ODComGetByte(hSerialPort, &chReceived, TRUE);
+
+      /* Handle this received character, adding it to the local/remote */
+      /* common input queue, if appropriate.                           */
+      ODKrnlHandleReceivedChar(chReceived, TRUE);
+   }
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlNoCarrierThread()                             *** PRIVATE FUNCTION ***
+ *
+ * Thread which performs carrier detection. Normally, this thread doesn't
+ * execute at all, but instead blocks waiting for a no carrier serial port
+ * event. Only when the carrier detect signal goes low does this thread
+ * execute, performing its one purpose in live - to trigger an OpenDoors
+ * shutdown. This thread should be given higher than normal priority.
+ *
+ * This thread should only be created when OpenDoors is operating in remote
+ * mode.
+ *
+ * In non-multithreaded versions of OpenDoors, this task is performed by
+ * od_kernel().
+ *
+ * Parameters: As dictated for any thread function.
+ *
+ *     Return: As dictated for any thread function.
+ */
+DWORD OD_THREAD_FUNC ODKrnlNoCarrierThread(void *pParam)
+{
+   /* Block until the carrier detect signal goes low with carrier */
+   /* detection enabled.                                          */
+   for(;;)
+   {
+      /* Wait for carrier detect signal to go low. */
+      ODComWaitEvent(hSerialPort, kNoCarrier);
+
+      /* If carrier detection has not been disabled, then we have found */
+      /* a condition where OpenDoors should exit.                       */
+      if(!(od_control.od_disable&DIS_CARRIERDETECT)) break;
+
+      /* If we have no carrier but carrier detection is currently   */
+      /* disabled, then we sleep for a while before checking again. */
+      /* This isn't a very elegant implementation, and perhaps a    */
+      /* better approach will be used for future versions.          */
+      od_sleep(NO_CARRIER_THREAD_SLEEP_TIME);
+   }
+
+   /* Force OpenDoors to exit. */
+   ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER);
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlTimeUpdateThread()                            *** PRIVATE FUNCTION ***
+ *
+ * Thread which performs time limit updating and checking. This thread executes
+ * an infinite loop, sleeping for several seconds, waking up to perform time
+ * limit updating, and then going back to sleep. This thead should typically
+ * operate at normal priority.
+ *
+ * In non-multithreaded versions of OpenDoors, this task is performed by
+ * od_kernel().
+ *
+ * Parameters: As dictated for any thread function.
+ *
+ *     Return: As dictated for any thread function.
+ */
+DWORD OD_THREAD_FUNC ODKrnlTimeUpdateThread(void *pParam)
+{
+   /* We keep looping until someone else terminates this thread. */
+   for(;;)
+   {
+      /* Sleep until it is time to do the next update. */
+      od_sleep(TIME_UPDATE_THREAD_SLEEP_TIME);
+
+      /* Now, perform time update. */
+      ODKrnlTimeUpdate();
+   }
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlWaitForExclusiveControl()                     *** PRIVATE FUNCTION ***
+ *
+ * Claims exclusive control of the application by the OpenDoors kernel. This is
+ * required to ensure that the client application is not busy when the
+ * OpenDoors kernel interrupts other operations for one reason or another
+ * (for example, to start chat mode or to force the program to exit).
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+static void ODKrnlWaitForExclusiveControl(void)
+{
+   /* If we already have exclusive control, then don't do anything. */
+   if(bHaveExclusiveControl) return;
+
+   /* Wait until an OpenDoors API is active. */
+   ODSemaphoreDown(hODActiveSemaphore, OD_NO_TIMEOUT);
+
+   /* Now, suspend the client thread. */
+   ASSERT(hClientThread != NULL);
+   ODThreadSuspend(hClientThread);
+
+   /* Record that we now have exclusive control. */
+   bHaveExclusiveControl = TRUE;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlGiveUpExclusiveControl()                      *** PRIVATE FUNCTION ***
+ *
+ * Relinguishes exclusive control of the application by the OpenDoors kernel.
+ * A call to this function should only take place after a previous call to
+ * ODKrnlWaitForExclusiveControl().
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+static void ODKrnlGiveUpExclusiveControl(void)
+{
+   /* If we don't have exclusive control, then this call doesn't do */
+   /* anything.                                                     */
+   if(!bHaveExclusiveControl) return;
+
+   /* First, restart the client thread. */
+   ASSERT(hClientThread != NULL);
+   ODThreadResume(hClientThread);
+
+   /* Now, allow currently active OpenDoors API to return control */
+   /* to the client application.                                  */
+   ODSemaphoreUp(hODActiveSemaphore, 1);
+
+   /* Note that we no longer have exclusive control. */
+   bHaveExclusiveControl = FALSE;
+}
+
+#endif /* OD_MULTITHREADED */
+
+
+
+/* ========================================================================= */
+/* OpenDoors chat mode.                                                      */
+/* ========================================================================= */
+
+BOOL bChatted;
+BOOL bSysopColor;
+
+#ifdef OD_MULTITHREADED
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlChatThread()                                  *** PRIVATE FUNCTION ***
+ *
+ * Thread which implements sysop <-> remote user chat mode.
+ *
+ * Parameters: As dictated for any thread function.
+ *
+ *     Return: As dictated for any thread function.
+ */
+DWORD OD_THREAD_FUNC ODKrnlChatThread(void *pParam)
+{
+   BOOL bTriggeredInsideOpenDoors = bChatActivatedInternally;
+
+   /* The chat thread doesn't start up chat mode until the kernel has */
+   /* exclusive control of the client application.                    */
+   if(bTriggeredInsideOpenDoors)
+   {
+      ODKrnlWaitForExclusiveControl();
+   }
+
+   /* Now, execute the chat mode loop. */
+   ODKrnlChatMode();
+
+   /* If we get here, then we are responsible for relinguishing exclusive */
+   /* control of the application.                                         */
+   if(bTriggeredInsideOpenDoors)
+   {
+      ODKrnlGiveUpExclusiveControl();
+   }
+
+   /* Exit the chat thread. */
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlStartChatThread()
+ *
+ * Starts the chat mode thread.
+ *
+ * Parameters: bTriggeredInternally - TRUE if chat mode has been triggered
+ *                                    inside OpenDoors, or FALSE if it has
+ *                                    been triggered by a call to od_chat().
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODKrnlStartChatThread(BOOL bTriggeredInternally)
+{
+   tODResult Result;
+
+   bChatActivatedInternally = bTriggeredInternally;
+
+   Result = ODThreadCreate(&hChatThread, ODKrnlChatThread, NULL);
+   if(Result != kODRCSuccess)
+   {
+      return(Result);
+   }
+
+   /* If chat mode command has been chosen, then toggle chat */
+   /* mode on or off.                                        */
+   od_control.od_chat_active = TRUE;
+
+#ifdef ODPLAT_WIN32
+   /* Update the enabled and checked state of commands. */
+   ODFrameUpdateCmdUI();
+#endif /* ODPLAT_WIN32 */
+
+   return(kODRCSuccess);
+}
+
+
+#endif /* OD_MULTITHREADED */
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlEndChatMode()
+ *
+ * Forces chat mode to exit.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+void ODKrnlEndChatMode(void)
+{
+#ifdef OD_MULTITHREADED
+
+   /* Shutdown the chat thread. */
+   ODThreadTerminate(hChatThread);
+
+   /* Perform post-chat cleanup operations. */
+   ODKrnlChatCleanup();
+
+#else /* !OD_MULTITHREADED */
+
+   /* Turn off chat mode. */
+   od_control.od_chat_active = FALSE;
+
+#endif /* !OD_MULTITHREADED */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_chat()
+ *
+ * Allows the client application to activate the line-by-line default chat
+ * mode provided by OpenDoors, allowing the local sysop and remote user to
+ * communicate with one another in real time.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_chat(void)
+{
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_chat()");
+
+   /* Set the main chat active flag in od_control. */
+   od_control.od_chat_active = TRUE;
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+#ifdef OD_MULTITHREADED
+
+   /* In multithreaded versions of OpenDoors, od_chat() causes the chat */
+   /* mode thread to be started, which in turn implements chat mode.    */
+   /* od_chat() only returns when this thread exits.                    */
+   if(ODKrnlStartChatThread(FALSE) != kODRCSuccess)
+   {
+      od_control.od_error = ERR_GENERALFAILURE;
+      OD_API_EXIT();
+   }
+
+   /* Now, wait for the chat thread to exit. */
+   ODThreadWaitForExit(hChatThread);
+
+   /* Now, note that the chat thread no longer exists. */
+   hChatThread = NULL;
+
+#else /* !OD_MULTITHREADED */
+
+   /* In non-multithreaded versions, a call to od_chat() maps directly to a */
+   /* call to ODKrnlChatMode(), which implements chat mode.                 */
+   ODKrnlChatMode();
+
+#endif /* !OD_MULTITHREADED */
+
+   OD_API_EXIT();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlChatMode()                                    *** PRIVATE FUNCTION ***
+ *
+ * Implements the OpenDoors chat mode.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+static void ODKrnlChatMode(void)
+{
+   BYTE chKeyPressed;
+   char szCurrentWord[79];
+   BYTE btWordLength = 0;
+   BYTE btCurrentColumn = 0;
+   char *pchCurrent;
+   BYTE btCount;
+#ifndef OD_MULTITHREADED
+   tODTimer Timer;
+#endif /* !OD_MULTITHREADED */
+
+   /* Empty current word string. */
+   szCurrentWord[0] = '\0';
+
+   /* Save current display color attribute. */
+   nChatOriginalAttrib = od_control.od_cur_attrib;
+
+   /* Record that sysop has entered chat mode. */
+   bChatted = TRUE;
+
+   /* Turn off "user wants to chat" indicator, and force the status line. */
+   /* to be updated.                                                      */
+   od_control.user_wantchat = FALSE;
+#ifdef ODPLAT_WIN32
+   ODFrameUpdateWantChat();
+#endif /* ODPLAT_WIN32 */
+
+   bForceStatusUpdate = TRUE;
+   CALL_KERNEL_IF_NEEDED();
+
+   /* Note that chat mode is now active. */
+   od_control.od_chat_active = TRUE;
+
+   /* If a pre-chat function hook has been defined, then call it. */
+   if(od_control.od_cbefore_chat!=NULL)
+   {
+      bShellChatActive = TRUE;
+      (*od_control.od_cbefore_chat)();
+      bShellChatActive = FALSE;
+
+      /* If chat has been deactivated, then return right away */
+      if(!od_control.od_chat_active) goto cleanup;
+   }
+
+   /* Display a message indicating that the sysop has entered chat mode. */
+   od_set_attrib(od_control.od_chat_color1);
+   if(od_control.od_before_chat != NULL)
+      od_disp_str(od_control.od_before_chat);
+
+   /* Currently set to sysop color. */
+   bSysopColor = TRUE;
+
+   /* If the logfile system is hooked up, then write a log entry */
+   /* indicating that the sysop has entered chat mode.           */
+   if(pfLogWrite != NULL)
+   {
+      (*pfLogWrite)(9);
+   }
+
+#ifndef OD_MULTITHREADED
+   /* Start a timer that will elapse after 25 milliseconds. */
+   ODTimerStart(&Timer, CHAT_YIELD_PERIOD);
+#endif /* !OD_MULTITHREADED */
+
+   /* Loop while sysop chat mode is stilil on. */
+   while(od_control.od_chat_active)
+   {
+      /* Obtain the next key from the user. */
+#ifdef OD_MULTITHREADED
+      chKeyPressed = od_get_key(TRUE);
+#else /* !OD_MULTITHREADED */
+      chKeyPressed = od_get_key(FALSE);
+#endif /* !OD_MULTITHREADED */
+
+      /* If color not set correctly. */
+      if((od_control.od_last_input && !bSysopColor)
+         || (!od_control.od_last_input && bSysopColor))
+      {
+         /* If sysop was last person to type. */
+         if(od_control.od_last_input)
+         {
+            /* Switch to sysop text color. */
+            od_set_attrib(od_control.od_chat_color1);
+         }
+         else
+         {
+            /* Otherwise, switch to the user text color. */
+            od_set_attrib(od_control.od_chat_color2);
+         }
+
+         /* Record current color setting. */
+         bSysopColor = od_control.od_last_input;
+      }
+
+      /* If this is a displayable character. */
+      if(chKeyPressed >= 32)
+      {
+         /* Display the character that was typed. */
+         od_putch(chKeyPressed);
+
+         /* If the user pressed spacebar, then this is the end of the */
+         /* previous word. */
+         if(chKeyPressed == 32)
+         {
+            btWordLength = 0;
+            szCurrentWord[0] = 0;
+         }
+
+         /* Add this character to the current word, if we haven't exceeded */
+         /* the maximum word length.                                       */
+         else if(btWordLength < 70)
+         {
+            szCurrentWord[btWordLength++] = chKeyPressed;
+            szCurrentWord[btWordLength] = '\0';
+         }
+
+         /* If we are not yet at the end of the line, then increment the */
+         /* current column number.                                       */
+         if(btCurrentColumn < 75)
+         {
+            ++btCurrentColumn;
+         }
+
+         /* If we are at the end of the line. */
+         else
+         {
+            /* If the current word should be wrapped to the next line. */
+            if(btWordLength < 70 && btWordLength > 0)
+            {
+               /* Generate a string to erase the word from the current line. */
+               pchCurrent = (char *)szODWorkString;
+               for(btCount = 0; btCount < btWordLength; ++btCount)
+               {
+                  *(pchCurrent++) = 8;
+               }
+
+               for(btCount = 0; btCount < btWordLength; ++btCount)
+               {
+                  *(pchCurrent++) = ' ';
+               }
+
+               *pchCurrent = '\0';
+
+               /* Display the string to erase the old word. */
+               od_disp_str(szODWorkString);
+
+               /* Move to the next line. */               
+               od_disp_str("\n\r");
+
+               /* Redisplay the word on the next line. */
+               od_disp_str(szCurrentWord);
+
+               /* Update current column number. */               
+               btCurrentColumn = btWordLength;
+            }
+
+            /* If we have reached the end of the line, but word wrap should */
+            /* not be performed.                                            */
+            else
+            {
+               /* Move to the next line. */
+               od_disp_str("\n\r");
+
+               /* Update the current column number. */
+               btCurrentColumn = 0;
+            }
+
+            /* Reset the current word information. */
+            btWordLength = 0;
+            szCurrentWord[0] = 0;
+         }
+      }
+
+      /* If the backspace key was pressed. */
+      else if(chKeyPressed == 8)
+      {
+         /* Send backspace sequence. */
+         od_disp_str(szBackspaceWithDelete);
+
+         /* If we are in the middle of a word, then we must remove the */
+         /* last character of the word.                                */         
+         if(btWordLength > 0)
+         {
+            szCurrentWord[--btWordLength] = '\0';
+         }
+
+         /* Update the current column number. */
+         if(btCurrentColumn > 0) --btCurrentColumn;
+      }
+
+      /* If the enter key was pressed. */
+      else if(chKeyPressed == 13)
+      {
+         /* Send carriage return / line feed sequence. */
+         od_disp_str("\n\r");
+
+         /* Reset the current word contents. */
+         btWordLength = 0;
+         szCurrentWord[0] = 0;
+
+         /* Update the current column number. */
+         btCurrentColumn = 0;
+      }
+
+      /* If the sysop pressed the escape key. */
+      else if(chKeyPressed == 27 && od_control.od_last_input)
+      {
+         /* Exit chat mode. */
+         goto cleanup;
+      }
+
+#ifndef OD_MULTITHREADED
+      /* Give up processor after 25 milliseconds elapsed. */
+      else if(ODTimerElapsed(&Timer))
+      {
+         od_sleep(0);
+
+         /* Restart the timer, so that it will elapse after another */
+         /* 25 milliseconds.                                        */
+         ODTimerStart(&Timer, CHAT_YIELD_PERIOD);
+      }
+#endif /* !OD_MULTITHREADED */
+   }
+
+cleanup:
+   ODKrnlChatCleanup();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODKrnlChatCleanup()                                 *** PRIVATE FUNCTION ***
+ *
+ * Performs post-chat operations, such as resetting the original display
+ * color, etc.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+static void ODKrnlChatCleanup(void)
+{
+   od_set_attrib(od_control.od_chat_color1);
+
+   /* Indicate that chat mode is exiting. */
+   if(od_control.od_after_chat != NULL)
+   {
+      od_disp_str(od_control.od_after_chat);
+   }
+
+   /* If an after chat function has been provided, then call it. */
+   if(od_control.od_cafter_chat != NULL)
+   {
+      bShellChatActive = TRUE;
+      (*od_control.od_cafter_chat)();
+      bShellChatActive = FALSE;
+   }
+
+   /* If the logfile system is hooked up, then write a line to the log */
+   /* indicating that chat mode has been exited.                       */
+   if(pfLogWrite != NULL)
+   {
+      (*pfLogWrite)(10);
+   }
+
+   /* Restore original display color attribute. */
+   od_set_attrib(nChatOriginalAttrib);
+
+   /* Record that chat mode is no longer active. */
+   od_control.od_chat_active = FALSE;
+
+#ifdef ODPLAT_WIN32
+   /* Update the enabled and checked state of commands. */
+   ODFrameUpdateCmdUI();
+#endif /* ODPLAT_WIN32 */
+
+#ifdef OD_MULTITHREADED
+   if(bChatActivatedInternally)
+   {
+      ODKrnlGiveUpExclusiveControl();
+   }
+#endif
+}
+
+#ifdef ODPLAT_NIX
+#ifdef USE_KERNEL_SIGNAL
+/* ----------------------------------------------------------------------------
+ * sig_run_kernel(sig)				   *** PRIVATE FUNCTION ***
+ *
+ * Runs od_kernel() on a SIGALRM
+ *
+ */
+static void sig_run_kernel(int sig)
+{
+   od_kernel();
+}
+
+/* ----------------------------------------------------------------------------
+ * sig_run_kernel(sig)				   *** PRIVATE FUNCTION ***
+ *
+ * Runs od_kernel() on a SIGALRM
+ *
+ */
+static void sig_get_char(int sig)
+{
+   static char ch;
+   /* Loop, obtaining any new characters from the serial port and */
+   /* adding them to the common local/remote input queue.         */
+   while(ODComGetByte(hSerialPort, &ch, FALSE) == kODRCSuccess)
+   {
+      ODKrnlHandleReceivedChar(ch, TRUE);
+   }
+}
+
+static void sig_no_carrier(int sig)
+{
+   if(od_control.baud != 0 && )
+   {
+      if(!(od_control.od_disable&DIS_CARRIERDETECT))
+      	ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER);
+   }
+}
+#endif
+#endif

+ 88 - 0
odoors/ODKrnl.h

@@ -0,0 +1,88 @@
+/* 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: ODKrnl.h
+ *
+ * Description: Contains the public definitions related to odkrnl.c
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Jan 01, 1995  6.00  BP   Split off from odcore.c and oddoor.h
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Jan 12, 1996  6.00  BP   Added bOnlyShiftArrow.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 13, 1996  6.10  BP   bOnlyShiftArrow -> nArrowUseCount.
+ */
+
+#ifndef _INC_ODKRNL
+#define _INC_ODKRNL
+
+#include "ODPlat.h"
+
+/* Global kernel-related variables. */
+extern tODTimer RunKernelTimer;
+extern time_t nNextTimeDeductTime;
+extern char chLastControlKey;
+extern INT nArrowUseCount;
+extern BOOL bForceStatusUpdate;
+extern BOOL bSysopColor;
+#ifdef OD_MULTITHREADED
+extern tODSemaphoreHandle hODActiveSemaphore;
+#endif /* OD_MULTITHREADED */
+
+/* Chat mode global variables. */
+extern BOOL bIsShell;
+extern BOOL bChatted;
+
+/* Kernel function prototypes. */
+tODResult ODKrnlInitialize(void);
+void ODKrnlShutdown(void);
+void ODKrnlHandleLocalKey(WORD wKeyCode);
+void ODKrnlEndChatMode(void);
+void ODKrnlForceOpenDoorsShutdown(BYTE btReasonForShutdown);
+#ifdef OD_MULTITHREADED
+tODResult ODKrnlStartChatThread(BOOL bTriggeredInternally);
+#endif /* OD_MULTITHREADED */
+
+/* Macro used to generate the appropriate code (if any) to call */
+/* the OpenDoors kernel from within OpenDoors code.             */
+#ifdef OD_MULTITHREADED
+#define CALL_KERNEL_IF_NEEDED()
+#else /* !OD_MULTITHREADED */
+#ifdef ODPLAT_NIX
+#ifdef USE_KERNEL_SIGNAL
+#define CALL_KERNEL_IF_NEEDED()
+#else
+#define CALL_KERNEL_IF_NEEDED()     od_kernel()
+#endif
+#else
+#define CALL_KERNEL_IF_NEEDED()     od_kernel()
+#endif /* !ODPLAT_NIX */
+#endif /* !OD_MULTITHREADED */
+
+/* Macro used to increment or decrement OpenDoors active semaphore. */
+#ifdef OD_MULTITHREADED
+#define OD_API_ENTRY()              ODSemaphoreUp(hODActiveSemaphore, 1);
+#define OD_API_EXIT()               ODSemaphoreDown(hODActiveSemaphore, 1);
+#else /* !OD_MULTITHREADED */
+#define OD_API_ENTRY()
+#define OD_API_EXIT()
+#endif /* !OD_MULTITHREADED */
+
+#endif /* _INC_ODKRNL */

+ 565 - 0
odoors/ODList.c

@@ -0,0 +1,565 @@
+/* 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: ODList.c
+ *
+ * Description: Implements the od_list_files() function for displaying
+ *              a FILES.BBS file.
+ *
+ *   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   Use new directory access functions.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              Nov 11, 1995  6.00  BP   Moved functions from odcore.c
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              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 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODCom.h"
+#include "ODPlat.h"
+#include "ODKrnl.h"
+#include "ODUtil.h"
+
+
+/* Filename component identifies. */
+#define WILDCARDS 0x01
+#define EXTENSION 0x02
+#define FILENAME  0x04
+#define DIRECTORY 0x08
+#define DRIVE     0x10
+
+
+/* Local private helper function prototypes. */
+static void ODListFilenameMerge(char *pszEntirePath, const char *pszDrive,
+   const char *pszDir, const char *pszName, const char *pszExtension);
+static char *ODListGetFirstWord(char *pszInStr, char *pszOutStr);
+static char *ODListGetRemainingWords(char *pszInStr);
+static INT ODListFilenameSplit(const char *pszEntirePath, char *pszDrive,
+   char *pszDir, char *pszName, char *pszExtension);
+
+
+/* ----------------------------------------------------------------------------
+ * od_list_files()
+ *
+ * Displays a list of files available for download, using an extended version
+ * of the standard FILES.BBS format index file.
+ *
+ * Parameters: pszFileSpec - Directory name where the FILES.BBS file can be
+ *                           found, or full path and filename of a FILES.BBS
+ *                           format index file.
+ *
+ *     Return: TRUE on success or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_list_files(char *pszFileSpec)
+{
+   BYTE btLineCount = 2;
+   BOOL bPausing;
+   static char szLine[513];
+   static char szFilename[80];
+   static char szDrive[3];
+   static char szDir[70];
+   static char szTemp1[9];
+   static char szTemp2[5];
+   static char szBaseName[9];
+   static char szExtension[5];
+   static char szDirectory[100];
+   INT nFilenameInfo;
+   FILE *pfFilesBBS;
+   static char *pszCurrent;
+   BOOL bIsDir;
+   BOOL bUseNextLine = TRUE;
+   tODDirHandle hDir;
+   tODDirEntry DirEntry;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_list_files()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Check user's page pausing setting. */
+   bPausing = od_control.od_page_pausing;
+
+   if(od_control.od_extended_info) bPausing = od_control.user_attribute & 0x04;
+
+   /* Parse directory parameter. */
+   if(pszFileSpec == NULL)
+   {
+      strcpy(szODWorkString, ".");
+      strcpy(szDirectory, "."DIRSEP_STR);
+   }
+   else if(*pszFileSpec == '\0')
+   {
+      strcpy(szODWorkString, ".");
+      strcpy(szDirectory, "."DIRSEP_STR);
+   }
+   else
+   {
+      strcpy(szODWorkString, pszFileSpec);
+      strcpy(szDirectory, pszFileSpec);
+      if(szODWorkString[strlen(szODWorkString) - 1] == DIRSEP)
+      {
+         szODWorkString[strlen(szODWorkString) - 1] = '\0';
+      }
+   }
+
+   /* Get directory information on path. */
+   if(ODDirOpen(szODWorkString, DIR_ATTRIB_ARCH | DIR_ATTRIB_RDONLY
+      | DIR_ATTRIB_DIREC, &hDir) != kODRCSuccess)
+   {
+      od_control.od_error = ERR_FILEOPEN;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   if(ODDirRead(hDir, &DirEntry) != kODRCSuccess)
+   {
+      ODDirClose(hDir);
+      od_control.od_error = ERR_FILEOPEN;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   ODDirClose(hDir);
+
+   /* If it is a directory. */
+   if(DirEntry.wAttributes & DIR_ATTRIB_DIREC)
+   {
+      /* Append FILES.BBS to directory name & open. */
+      bIsDir = TRUE;
+      ODMakeFilename(szODWorkString, szODWorkString, "FILES.BBS",
+         sizeof(szODWorkString));
+      if((pfFilesBBS = fopen(szODWorkString, "r")) == NULL)
+      {
+         od_control.od_error = ERR_FILEOPEN;
+         OD_API_EXIT();
+         return(FALSE);
+      }
+   }
+
+   /* If it is not a directory. */
+   else
+   {
+      bIsDir = FALSE;
+      if((pfFilesBBS = fopen(szODWorkString,"r")) == NULL)
+      {
+         od_control.od_error = ERR_FILEOPEN;
+         OD_API_EXIT();
+         return(FALSE);
+      }
+   }
+
+
+   /* Ignore previously pressed control keys. */
+   chLastControlKey = 0;
+
+
+   /* Loop until the end of the FILES.BBS file has been reached. */
+   for(;;)
+   {
+      if(fgets(szLine, 512, pfFilesBBS) == NULL) break;
+
+      if(!bUseNextLine)
+      {
+         if(szLine[strlen(szLine) - 1] == '\n')
+         {
+            bUseNextLine = TRUE;
+         }
+         continue;
+      }
+
+      if(szLine[strlen(szLine) - 1] == '\n')
+      {
+         szLine[strlen(szLine) - 1] = '\0';
+      }
+      else
+      {
+         bUseNextLine = FALSE;
+      }
+      if(szLine[strlen(szLine) - 1] == '\r')
+      {
+         szLine[strlen(szLine) - 1] = '\0';
+      }
+
+      if(chLastControlKey != 0)
+      {
+         switch(chLastControlKey)
+         {
+            case 's':
+               if(od_control.od_list_stop)
+               {
+                  if(od_control.baud)
+                  {
+                     ODComClearOutbound(hSerialPort);
+                  }
+                  od_clear_keybuffer();
+                  fclose(pfFilesBBS);
+                  OD_API_EXIT();
+                  return(TRUE);
+               }
+               break;
+
+            case 'p':
+               if(od_control.od_list_pause)
+               {
+                  od_clear_keybuffer();
+                  od_get_key(TRUE);
+               }
+         }
+         chLastControlKey = 0;
+      }
+
+      /* Determine whether or not this is a comment line. */
+      if(szLine[0] == ' ' || strlen(szLine) == 0)
+
+      {
+         /* If so, display the line in comment color. */
+         od_set_attrib(od_control.od_list_title_col);
+         od_disp_str(szLine);
+         od_disp_str("\n\r");
+         ++btLineCount;
+      }
+
+      /* If the line is not a comment. */
+      else                             
+      {
+         /* Extract the first word of the line, */
+         ODListGetFirstWord(szLine, szFilename);
+
+         /* And extract the filename. */
+         nFilenameInfo = ODListFilenameSplit(szFilename, szDrive, szDir,
+            szBaseName, szExtension);
+         if(!((nFilenameInfo & DRIVE) || (nFilenameInfo & DIRECTORY)))
+         {
+            if(bIsDir)
+            {
+               ODMakeFilename(szDirectory, szDirectory, szFilename,
+                  sizeof(szDirectory));
+               strcpy(szFilename, szDirectory);
+            }
+            else
+            {
+               ODListFilenameSplit(szDirectory, szDrive, szDir, szTemp1,
+                  szTemp2);
+               ODListFilenameMerge(szFilename, szDrive, szDir, szBaseName,
+                  szExtension);
+            }
+         }
+
+         /* Search for the filespec in directory. */
+         if(ODDirOpen(szFilename, DIR_ATTRIB_ARCH | DIR_ATTRIB_RDONLY, &hDir)
+            == kODRCSuccess)
+         {
+            /* Display information on every file that matches. */
+            while(ODDirRead(hDir, &DirEntry) == kODRCSuccess)
+            {
+               od_set_attrib(od_control.od_list_name_col);
+               od_printf("%-12.12s  ", DirEntry.szFileName);
+               od_set_attrib(od_control.od_list_size_col);
+               od_printf("%-6ld   ", DirEntry.dwFileSize);
+               od_set_attrib(od_control.od_list_comment_col);
+               pszCurrent = ODListGetRemainingWords(szLine);
+               if(strlen(pszCurrent) <= 56)
+               {
+                  od_disp_str(pszCurrent);
+                  od_disp_str("\n\r");
+               }
+               else
+               {
+                  od_printf("%-56.56s\n\r", pszCurrent);
+               }
+               ++btLineCount;
+            }
+
+            ODDirClose(hDir);
+         }
+
+         /* Otherwise, indicate that the file is "Offline". */
+         else
+         {
+            ODListFilenameMerge(szFilename, "", "", szBaseName, szExtension);
+            od_set_attrib(od_control.od_list_name_col);
+            od_printf("%-12.12s ", szFilename);
+            od_set_attrib(od_control.od_list_offline_col);
+            od_disp_str(od_control.od_offline);
+            od_set_attrib(od_control.od_list_comment_col);
+
+            od_printf("%-56.56s\n\r", ODListGetRemainingWords(szLine));
+            ++btLineCount;
+         }
+      }
+
+      /* Check for end of screen & page pausing. */
+      if(btLineCount >= od_control.user_screen_length && bPausing)
+      {
+         /* Provide page pausing at end of each screen. */
+         if(ODPagePrompt(&bPausing))
+         {
+            fclose(pfFilesBBS);
+            OD_API_EXIT();
+            return(TRUE);
+         }
+
+         /* Reset the line number counter. */
+         btLineCount = 2;
+      }
+   }
+
+   /* When finished, close the file. */
+   fclose(pfFilesBBS);
+
+   /*  Return with success. */
+   OD_API_EXIT();
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODListFilenameMerge()                               *** PRIVATE FUNCTION ***
+ *
+ * Builds a fully-qualified path name from the provided path component
+ * strings.
+ *
+ * Parameters: pszEntirePath - Pointer to the destination string where the
+ *                             generated path should be stored.
+ *
+ *             pszDrive      - Pointer to the drive string.
+ *
+ *             pszDir        - Pointer to the directory string.
+ *
+ *             pszName       - Pointer to the base filename string.
+ *
+ *             pszExtension  - Pointer to the extension name string.
+ *
+ *     Return: void
+ */
+static void ODListFilenameMerge(char *pszEntirePath, const char *pszDrive,
+   const char *pszDir, const char *pszName, const char *pszExtension)
+{
+   if(pszEntirePath == NULL) return;
+
+   pszEntirePath[0] = '\0';
+
+   if(pszDrive != NULL)
+   {
+      strcpy(pszEntirePath, pszDrive);
+   }
+   if(pszDir != NULL)
+   {
+      strcat(pszEntirePath, pszDir);
+   }
+   if(pszName != NULL)
+   {
+      strcat(pszEntirePath,pszName);
+   }
+   if(pszExtension != NULL)
+   {
+      strcat(pszEntirePath,pszExtension);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODListFilenameSplit()                               *** PRIVATE FUNCTION ***
+ *
+ * Splits the provided path string into drive, directory name, file base name
+ * and file extension components.
+ *
+ * Parameters: pszEntirePath - A string containing the path to split.
+ *
+ *             pszDrive      - A string where the drive letter should be stored.
+ *
+ *             pszDir        - A string where the directory name should be
+ *                             stored.
+ *
+ *             pszName       - A string where the base filename should be
+ *                             stored.
+ *
+ *             pszExtension  - A string where the filename extension should be
+ *                             stored.
+ *
+ *     Return: One or more flags indicating which components where found in the
+ *             provided path name.
+ */
+static INT ODListFilenameSplit(const char *pszEntirePath, char *pszDrive,
+   char *pszDir, char *pszName, char *pszExtension)
+{
+   char *pchCurrentPos;
+   char *pchStart;
+   BYTE btSize;
+   INT nToReturn;
+
+   ASSERT(pszEntirePath != NULL);
+   ASSERT(pszDrive != NULL);
+   ASSERT(pszDir != NULL);
+   ASSERT(pszName != NULL);
+   ASSERT(pszExtension != NULL);
+
+   pchStart = (char *)pszEntirePath;
+   nToReturn = 0;
+
+   if((pchCurrentPos = strrchr(pchStart,':')) == NULL)
+   {
+      pszDrive[0] = '\0';
+   }
+   else
+   {
+      btSize = (int)(pchCurrentPos - pchStart) + 1;
+      if(btSize > 2) btSize = 2;
+      strncpy(pszDrive, pchStart, btSize);
+      pszDrive[btSize] = '\0';
+      pchStart = pchCurrentPos + 1;
+      nToReturn |= DRIVE;
+   }
+
+   if((pchCurrentPos = strrchr(pchStart, DIRSEP))==NULL)
+   {
+      pszDir[0] = '\0';
+   }
+   else
+   {
+      btSize = (int)(pchCurrentPos - pchStart) + 1;
+      strncpy(pszDir,pchStart,btSize);
+      pszDir[btSize] = '\0';
+      pchStart = pchCurrentPos + 1;
+      nToReturn |= DIRECTORY;
+   }
+
+   if(strchr(pchStart,'*') != NULL || strchr(pchStart, '?') != NULL)
+   {
+      nToReturn |= WILDCARDS;
+   }
+
+   if((pchCurrentPos = strrchr(pchStart, '.')) == NULL)
+   {
+      if(pchStart =='\0')
+      {
+         pszExtension[0] = '\0';
+         pszName[0] = '\0';
+      }
+      else
+      {
+         pszExtension[0] = '\0';
+         btSize = strlen(pchStart);
+         if (btSize > 8) btSize = 0;
+         strncpy(pszName, pchStart, btSize);
+         pszName[btSize] = '\0';
+         nToReturn |= FILENAME;
+      }
+   }
+   else
+   {
+      nToReturn |= FILENAME;
+      nToReturn |= EXTENSION;
+
+      btSize = (int)(pchCurrentPos - pchStart);
+
+      if(btSize > 8) btSize = 8;
+
+      strncpy(pszName, pchStart, btSize);
+      pszName[btSize] = '\0';
+
+      btSize = strlen(pchCurrentPos);
+      if(btSize > 4) btSize = 4;
+      strncpy(pszExtension, pchCurrentPos, btSize);
+      pszExtension[btSize]='\0';
+   }
+
+   return(nToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODListGetFirstWord()                                *** PRIVATE FUNCTION ***
+ *
+ * Returns the first word in a string containing a series of words separated by
+ * one or more spaced.
+ *
+ * Parameters: pszInStr  - String to look in.
+ *
+ *             pszOutStr - Buffer to store result in. This buffer should be at
+ *                         least as long as the pszInStr string.
+ *
+ *     Return: Pointer to the pszOutStr that was passed in.
+ */
+static char *ODListGetFirstWord(char *pszInStr, char *pszOutStr)
+{
+   char *pchOut = (char *)pszOutStr;
+
+   ASSERT(pszInStr != NULL);
+   ASSERT(pszOutStr != NULL);
+
+   while(*pszInStr && *pszInStr != ' ')
+   {
+      *pchOut++ = *pszInStr++;
+   }
+   *pchOut = '\0';
+
+   return(pszOutStr);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODListGetRemainingWords()                           *** PRIVATE FUNCTION ***
+ *
+ * Obtains the remaining words in a string, after the first word. This function
+ * is a companion to ODListGetFirstWord(), which obtains just the first word
+ * in a string of many words.
+ *
+ * Parameters: pszInStr  - String to look at.
+ *
+ *     Return: A pointer to the position in a string of the second word.
+ */
+static char *ODListGetRemainingWords(char *pszInStr)
+{
+   char *pchStartOfRemaining = (char *)pszInStr;
+
+   /* Skip over the first word in the string. */
+   while(*pchStartOfRemaining && *pchStartOfRemaining != ' ')
+   {
+      ++pchStartOfRemaining;
+   }
+
+   /* Skip over any spaces after the first word. */
+   while(*pchStartOfRemaining && *pchStartOfRemaining == ' ')
+   {
+      ++pchStartOfRemaining;
+   }
+
+   /* Return pointer to the rest of the string. */
+   return((char *)pchStartOfRemaining);
+ }

+ 258 - 0
odoors/ODLog.c

@@ -0,0 +1,258 @@
+/* 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: ODLog.c
+ *
+ * Description: Implements the logfile subsystem.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODInEx.h"
+#include "ODKrnl.h"
+
+
+/* Private logfile file handle */
+static FILE *logfile_pointer;
+
+
+/* Private helper functions. */
+static BOOL ODLogWriteStandardMsg(INT nLogfileMessage);
+static void ODLogClose(INT nReason);
+
+
+/* ----------------------------------------------------------------------------
+ * ODLogEnable()
+ *
+ * This function is called from od_init() when the user explicitly includes the
+ * OpenDoors logfile option using the od_control.od_logfile setting.
+ *
+ * Parameters: None.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL ODLogEnable(void)
+{
+   /* At this time, this function simply maps to a call to od_log_open(). */
+   od_log_open();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_log_open()
+ *
+ * Called to begin logfile operations. This is when the first message is
+ * written to the logfile, indicating that the user is entering OpenDoors.
+ *
+ * Parameters: None.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_log_open()
+{
+   time_t nUnixTime;
+   struct tm *ptmTimeRecord;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_log_open()");
+
+   /* Initialize OpenDoors if not already done. */
+   if(!bODInitialized) od_init();
+
+   /* Don't open logfile if it has been disabled in config file, etc. */
+   if(od_control.od_logfile_disable) return(TRUE);
+
+   /* Open actual logfile. */
+   if((logfile_pointer=fopen(od_control.od_logfile_name, "a")) == NULL)
+   {
+      return(FALSE);
+   }
+
+   /* Get the current time. */
+   nUnixTime = time(NULL);
+   ptmTimeRecord = localtime(&nUnixTime);
+
+   /* Print logfile tear line. */
+   fprintf(logfile_pointer, "\n----------  %s %02d %s %02d, %s\n",
+      od_control.od_day[ptmTimeRecord->tm_wday],
+      ptmTimeRecord->tm_mday,
+      od_control.od_month[ptmTimeRecord->tm_mon],
+      ptmTimeRecord->tm_year,
+      od_program_name);
+
+   /* Print message of door start up. */
+   sprintf(szODWorkString, (char *)od_control.od_logfile_messages[11],
+      od_control.user_name);
+   od_log_write(szODWorkString);
+
+   /* Set internal function hooks to enable calling of logfile features */
+   /* from elsewhere in OpenDoors. */
+   pfLogWrite = ODLogWriteStandardMsg;
+   pfLogClose = ODLogClose;
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODLogWriteStandardMsg()                             *** PRIVATE FUNCTION ***
+ *
+ * Function called to write a standard message to the logfile.
+ *
+ * Parameters: nLogfileMessage   - Index of the standard message to write to
+ *                                 the logfile.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+static BOOL ODLogWriteStandardMsg(INT nLogfileMessage)
+{
+   if(nLogfileMessage < 0 || nLogfileMessage > 11)
+   {
+      return(FALSE);
+   }
+
+   od_log_write((char *)od_control.od_logfile_messages[nLogfileMessage]);
+
+   if(nLogfileMessage == 8)
+   {
+      sprintf(szODWorkString, od_control.od_logfile_messages[12],
+         od_control.user_reasonforchat);
+      szODWorkString[67] = '\0';
+      od_log_write(szODWorkString);
+   }
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_log_write()
+ *
+ * Called to write a message to the logfile.
+ *
+ * Parameters: pszMessage  - Pointer to a string containing the message text.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_log_write(char *pszMessage)
+{
+   char *pszFormat;
+   time_t nUnixTime;
+   struct tm *ptmTimeRecord;
+
+   /* Verify that OpenDoors has been initialized. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Stop if logfile has been disabled in config file, etc. */
+   if(od_control.od_logfile_disable)
+   {
+      OD_API_EXIT();
+      return(TRUE);
+   }
+
+   /* If logfile has not yet been opened, then open it. */
+   if(logfile_pointer==NULL)
+   {
+      if(!od_log_open())
+      {
+         OD_API_EXIT();
+         return(FALSE);
+      }
+   }
+
+   /* Get the current system time. */
+   nUnixTime=time(NULL);
+   ptmTimeRecord=localtime(&nUnixTime);
+
+   /* Determine which logfile format string to use. */
+   if(ptmTimeRecord->tm_hour<10)
+   {
+      pszFormat=(char *)">  %1.1d:%02d:%02d  %s\n";
+   }
+   else
+   {
+      pszFormat=(char *)"> %2.2d:%02d:%02d  %s\n";
+   }
+
+   /* Write a line to the logfile. */
+   fprintf(logfile_pointer, pszFormat, ptmTimeRecord->tm_hour,
+      ptmTimeRecord->tm_min, ptmTimeRecord->tm_sec, pszMessage);
+
+   OD_API_EXIT();
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODLogClose()                                        *** PRIVATE FUNCTION ***
+ *
+ * Writes final entry to the logfile when OpenDoors is exiting.
+ *
+ * Parameters: nReason  - Specifies the reason why OpenDoors is exiting.
+ *
+ *     Return: void
+ */
+static void ODLogClose(INT nReason)
+{
+   /* Stop if logfile has been disabled in the config file, etc. */
+   if(od_control.od_logfile_disable) return;
+
+   /* If logfile has not been opened, then abort. */
+   if(logfile_pointer==NULL) return;
+
+   if(bPreOrExit)
+   {
+      od_log_write((char *)od_control.od_logfile_messages[13]);
+   }
+   else if(btExitReason<=5 && btExitReason>=1)
+   {
+      od_log_write((char *)od_control.od_logfile_messages[btExitReason-1]);
+   }
+   else
+   {
+      sprintf(szODWorkString,(char *)od_control.od_logfile_messages[5],nReason);
+      od_log_write(szODWorkString);
+   }
+
+   /* Close the logfile. */
+   fclose(logfile_pointer);
+
+   /* Prevent further use of logfile without first re-opening it. */
+   pfLogWrite = NULL;
+   pfLogClose = NULL;
+   logfile_pointer = NULL;
+}

+ 255 - 0
odoors/ODMulti.c

@@ -0,0 +1,255 @@
+/* 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: ODMulti.c
+ *
+ * Description: Code for multiple personality system, which allows
+ *              a status line / function key personality to be dynamically
+ *              selected at run-time.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              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 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 23, 1996  6.00  BP   Disable MPS under Win32 version.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Sep 01, 1996  6.10  BP   Update output area on od_set_per...().
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <time.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODInEx.h"
+#include "ODKrnl.h"
+
+
+/* Maximum number of personalities that may be installed at once. */
+#define MAX_PERSONALITIES  12
+
+
+/* Information on installed personalities. */
+typedef struct
+{
+   char szName[33];
+   INT nStatusTopLine;
+   INT nStatusBottomLine;
+   OD_PERSONALITY_PROC *pfPersonalityFunction;
+} tPersonalityInfo;
+
+static tPersonalityInfo aPersonalityInfo[MAX_PERSONALITIES]=
+{
+   {"STANDARD", 1, 23, pdef_opendoors},
+   {"REMOTEACCESS", 1, 23, pdef_ra},
+   {"WILDCAT", 1, 23, pdef_wildcat},
+   {"PCBOARD", 1, 23, pdef_pcboard}
+};
+
+
+/* Private variables. */
+static INT nPersonalities = 5;
+static INT nCurrentPersonality = 255;
+
+
+/* ----------------------------------------------------------------------------
+ * ODMPSEnable()
+ *
+ * This function is called from within od_init() when the user enables the
+ * multiple personality system.
+ *
+ * Parameters: None.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL ODMPSEnable(void)
+{
+   pfSetPersonality = od_set_personality;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_set_personality()
+ *
+ * Sets the current personality to the one that is specified in pszName.
+ *
+ * Parameters: pszName - The name of the personality to switch to.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_set_personality(const char *pszName)
+{
+#ifdef OD_TEXTMODE
+   BYTE btNewPersonality;
+   char szNameToMatch[33];
+   tPersonalityInfo *pNewPersonalityInfo;
+#endif /* OD_TEXTMODE */
+
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_set_personality()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+   
+#ifdef OD_TEXTMODE
+   /* Check for valid parameters. */
+   if(strlen(pszName) == 0)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Build personality name to match. */
+   strncpy(szNameToMatch, pszName, 32);
+   szNameToMatch[32] = '\0';
+   strupr(szNameToMatch);
+
+   /* Loop through installed personalities, checking for a match. */
+   for(btNewPersonality = 0; btNewPersonality < nPersonalities;
+      ++btNewPersonality)
+   {
+      /* If the name of this personality matches the one we are looking for. */
+      if(strcmp(szNameToMatch, aPersonalityInfo[btNewPersonality].szName) == 0)
+      {
+         if(btNewPersonality != nCurrentPersonality)
+         {
+            /* Remove current status line from the screen .*/
+            od_set_statusline(8);
+
+            /* Initialize the new personality. */
+            if(nCurrentPersonality != 255)
+               (*(OD_PERSONALITY_CALLBACK *)pfCurrentPersonality)(22);
+            od_control.od_page_statusline = -1;
+            pNewPersonalityInfo = 
+               &aPersonalityInfo[nCurrentPersonality=btNewPersonality];
+            bRAStatus = TRUE;
+            (*(OD_PERSONALITY_CALLBACK *)pNewPersonalityInfo
+               ->pfPersonalityFunction)(20);
+            ODScrnSetBoundary(1, (BYTE)pNewPersonalityInfo->nStatusTopLine, 80,
+               (BYTE)pNewPersonalityInfo->nStatusBottomLine);
+            pfCurrentPersonality
+               = pNewPersonalityInfo->pfPersonalityFunction;
+            btCurrentStatusLine = 255;
+
+            /* Update output area. */
+            btOutputTop = (BYTE)pNewPersonalityInfo->nStatusTopLine;
+            btOutputBottom = (BYTE)pNewPersonalityInfo->nStatusBottomLine;
+
+            /* Draw the new statusline. */
+            od_set_statusline(0);
+         }
+
+         OD_API_EXIT();
+         return(TRUE);
+      }
+   }
+
+   OD_API_EXIT();
+   od_control.od_error = ERR_LIMIT;
+   return(FALSE);
+
+#else /* !OD_TEXTMODE */
+
+   /* The multiple personality system is not supported under this platform. */
+   od_control.od_error = ERR_UNSUPPORTED;
+
+   /* Return with failure. */
+   OD_API_EXIT();
+   return(FALSE);
+
+#endif /* !OD_TEXTMODE */
+}
+
+
+
+/* ----------------------------------------------------------------------------
+ * od_add_personality()
+ *
+ * Installs a new personality into the set of available personalities.
+ *
+ * Parameters: pszName        - Pointer to string containing the name of
+ *                              the new personality.
+ *
+ *             btOutputTop    - Index of the top line of the status bar.
+ *
+ *             btOutputBottom - Index of the bottom line of the status bar.
+ *
+ *             pfPerFunc      - Pointer to the callback function which
+ *                              implements this personality.
+ *
+ *     Return: TRUE on success or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_add_personality(const char *pszName, BYTE btOutputTop,
+   BYTE btOutputBottom, OD_PERSONALITY_PROC *pfPerFunc)
+{
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_add_personality()");
+
+#ifdef OD_TEXTMODE
+
+   /* Check that we haven't exceeded the limit on the total number of */
+   /* installed personalities.                                        */
+   if(nPersonalities == MAX_PERSONALITIES)
+   {
+      od_control.od_error = ERR_LIMIT;
+      return(FALSE);
+   }
+
+   /* Store information on this new personality. */
+   strncpy(aPersonalityInfo[nPersonalities].szName, pszName, 32);
+   aPersonalityInfo[nPersonalities].szName[32] = '\0';
+   strupr(aPersonalityInfo[nPersonalities].szName);
+   aPersonalityInfo[nPersonalities].nStatusTopLine = btOutputTop;
+   aPersonalityInfo[nPersonalities].nStatusBottomLine = btOutputBottom;
+   aPersonalityInfo[nPersonalities].pfPersonalityFunction = pfPerFunc;
+
+   /* Increment total number of personalities. */
+   ++nPersonalities;
+
+   /* Return with success. */
+   return(TRUE);
+
+#else /* !OD_TEXTMODE */
+
+   /* The multiple personality system is not supported under this platform. */
+   od_control.od_error = ERR_UNSUPPORTED;
+
+   /* Return with failure. */
+   return(FALSE);
+
+#endif /* !OD_TEXTMODE */
+}

+ 104 - 0
odoors/ODOORS62.TXT

@@ -0,0 +1,104 @@
+***************************************************************************
+* OpenDoors v6.24 C/C++ Door Development Kit for DOS/Win32/*nix Platforms *
+***************************************************************************
+
+$Id: ODOORS62.TXT,v 1.5 2006/12/07 02:06:14 rswindell Exp $
+
+August 10, 2003
+
+*nix (using StdIO only) update to Brian Pirie's OpenDoors Library 
+
+August 22, 2002
+
+Door32.sys and Socket update to Brian Pirie's OpenDoors Library 
+(http://www.pirieworld.ca/opendoors.html) 
+
+by Rob Swindell (http://www.synchro.net/)
+
+The current source code is always available via CVS at cvs.synchro.net.
+
+The latest and greatest Win32 ODoors62.dll can always be downloaded here:
+http://cvs.synchro.net/cgi-bin/cvsweb.cgi/~checkout~/src/odoors/ODoors62.dll
+
+==============================================================================
+
+This archive includes the source code to the OpenDoors library with 
+modifications made by me (Rob Swindell, aka Digital Man) to add support for 
+the Door32.sys drop file format and Win32 TCP/IP socket (Telnet) 
+communications.
+
+I made my modifications to the 6.1.1 release by Brian Pirie (ods611.zip) and
+used Microsoft Visual C++ v6.0. I also eliminated any warnings or errors
+detected by this compiler. The linker still warns of duplicate symbol 
+definition (od_control and od_printf), but I left those duplicate defintions
+in the .def file since they may have been necessary for another platform or
+compiler that I'm not using.
+
+This version also includes the ODEmu.c:od_send_file_section() modification 
+by Michael Dillon ([email protected] | http://members.darktech.org/gsvalore/).
+
+
+DOOR32.SYS
+----------
+The Door32.sys drop file format was created in June of 2000 to support the
+new wave of 32-bit Windows and *nix doors. The main feature of this
+drop file format is that it includes the currently open Win32 comm handle or 
+socket descriptor (whichever is appropriate for the current connection).
+
+I didn't add support for the Win32 comm handle in the door32.sys file
+(I run a telnet-only BBS and would have no way to test it). I can't
+imagine it would be very difficult to add; I'm just not sure how much demand
+there would be for such a feature today.
+
+I did however add support for the socket descriptor in the drop file (used for
+TCP/IP - Telnet connections) and this is currently the only "standard" drop 
+file format that is expected to include a socket descriptor (Synchronet's 
+XTRN.DAT drop file also includes a socket descriptor, but I don't really 
+consider that format to be a "standard").
+
+The Door32.sys drop file is currently supported by the following known BBS
+packages:
+
+    Software     Home Page                             Version
+    -----------+-------------------------------------+--------
+    Mystic BBS   www.mysticbbs.com                        1.07
+    EleBBS       www.elebbs.com                           0.08?
+    Synchronet   www.synchro.net                           3.x
+
+
+TCP SOCKET I/O
+--------------
+When using a Door32.sys drop file, the communications type (Local, Serial, or 
+Telnet) is automatically determined and I added support for (and tested) the 
+Telnet/socket communication method using Synchronet BBS Software v3.10 for 
+Win32 and HyperTerminal Private Edition v6.1.
+
+Since the Telnet protocol specifies that an end-of-line sequence (ENTER or 
+Carriage Return) is a CRLF (ASCII 13, 10), I had to modify od_get_key() to 
+ignore any line feed (ASCII 10, Ctrl-J) characters. Without this modification, 
+hitting enter in most Telnet clients would cause a "double return" to be sent 
+to the door. There may be a more desirable solution to the problem, but this 
+one seems to work for now.
+
+v6.21 fixes a bug in the socket disconnection ("carrier-loss") detection in 
+v6.20. I also lowered the input and carrier-detect thread priorities down to 
+"normal" which makes the doors appear to run faster and also make debugging
+continuous loop problems (like the one in v6.20) much easier.
+
+v6.22 changes:
+Another bug fixed in socket disconnection ("carrier-loss") detection.
+Added support for non-blocking sockets (e.g. EleBBS) - thanks to GSValore.
+Added support for the "-SOCKET" command-line option to specify the socket
+descriptor on the command-line.
+
+v6.23 changes:
+*nix support using stdio
+
+v6.24 chagnes:
+Fixed timeing bugs on *nix
+Fixed output truncated bug on *nix
+Fixed od_get_input() hang on ESC on Win32
+Fixed various other bugs.
+Added od_key_pending() function.
+
+/* End of ODOORS62.TXT */

+ 207 - 0
odoors/ODPCB.c

@@ -0,0 +1,207 @@
+/* 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: ODPCB.c
+ *
+ * Description: Implements the PC-Board personality.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Display connect speed with %lu.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODKrnl.h"
+#include "ODUtil.h"
+#include "ODStat.h"
+
+
+/* ----------------------------------------------------------------------------
+ * pdef_pcboard()
+ *
+ * Personality function for the PC-Board like status line / function key
+ * personality.
+ *
+ * Parameters: btOperation - Indicates personality operation to be performed.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL pdef_pcboard(BYTE btOperation)
+{
+   static char szTemp[81];
+   BYTE btInfoType = od_control.od_info_type;
+
+
+   switch(btOperation)
+   {
+      case PEROP_DISPLAY1:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("                                                      ALT-H=Help                                                                                                ");
+         ODScrnSetCursorPos(3, 24);
+         if(od_control.baud != 0)
+         {
+            ODScrnPrintf("(%lu) ", od_control.od_connect_speed);
+         }
+         else
+         {
+            ODScrnDisplayString("(Local) ");
+         }
+         sprintf(szTemp, "%s - %s", od_control.user_name,
+            od_control.user_location);
+         szTemp[42] = '\0';
+         strupr(szTemp);
+         ODScrnDisplayString(szTemp);
+         ODScrnSetCursorPos(1,25);
+         if(od_control.user_ansi || od_control.user_avatar
+            || od_control.user_rip)
+         {
+            ODScrnDisplayChar('G');
+         }
+         else
+         {
+            ODScrnDisplayChar('A');
+         }
+         if(btInfoType == RA1EXITINFO || btInfoType == RA2EXITINFO
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnPrintf(" (%s)",od_control.user_firstcall);
+         }
+         ODScrnSetCursorPos(15, 25);
+         ODScrnPrintf("Sec(0)=%u  ",od_control.user_security);
+         if(od_control.od_extended_info || btInfoType == DOORSYS_GAP
+            || btInfoType == CHAINTXT || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnPrintf("Times On=%u  ", od_control.user_numcalls);
+         }
+         if(od_control.od_extended_info || btInfoType == SFDOORSDAT
+            || btInfoType == DOORSYS_GAP || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnPrintf("Up:Dn=%lu:%lu", od_control.user_uploads,
+               od_control.user_downloads);
+         }
+         ODScrnSetCursorPos(70, 25);
+         ODScrnPrintf("%4d", od_control.user_timelimit);
+
+         od_control.key_status[0] = 0x0000;
+         od_control.key_status[1] = 0x2300;
+         break;
+
+      case PEROP_DISPLAY2:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("  Alt-> N=Next X=DOS F1/F2=Time                                                   2=LkOut 5=SHELL 8=HngUp 10=Chat                                               ");
+
+         od_control.key_status[0] = 0x2300;
+         od_control.key_status[1] = 0x0000;
+         break;
+
+      case PEROP_UPDATE1:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(70, 25);
+         ODScrnPrintf("%4d", od_control.user_timelimit);
+         break;
+
+      case PEROP_INITIALIZE:
+         od_control.key_hangup = 0x4200;
+         od_control.key_drop2bbs = 0x2d00;
+         od_control.key_dosshell = 0x3f00;
+         od_control.key_chat = 0x4400;
+         od_control.key_sysopnext = 0x3100;
+         od_control.key_lockout = 0x3c00;
+         od_control.key_status[0] = 0x0000;
+         od_control.key_status[1] = 0x2300;
+         od_control.key_status[2] = 0x0000;
+         od_control.key_status[3] = 0x0000;
+         od_control.key_status[4] = 0x0000;
+         od_control.key_status[5] = 0x0000;
+         od_control.key_status[6] = 0x0000;
+         od_control.key_status[7] = 0x0000;
+         od_control.key_status[8] = 0x0000;
+         od_control.key_keyboardoff = 0x2500;
+         od_control.key_moretime = 0x0000;
+         od_control.key_lesstime = 0x0000;
+         ODStatAddKey(0x6900);
+         ODStatAddKey(0x6800);
+         od_control.od_page_statusline = 0;
+         break;
+
+      case PEROP_CUSTOMKEY:
+         switch(od_control.od_last_hot)
+         {
+            /* Key to add five minutes. */
+            case 0x6900:
+               if(od_control.user_timelimit <= 1435 &&
+                  od_control.user_timelimit >= 5)
+               {
+                  od_control.user_timelimit += 5;
+                  bForceStatusUpdate = TRUE;
+                  CALL_KERNEL_IF_NEEDED();
+               }
+               else if(od_control.user_timelimit < 5)
+               {
+                  od_control.user_timelimit++;
+                  bForceStatusUpdate = TRUE;
+                  CALL_KERNEL_IF_NEEDED();
+               }
+               break;
+
+            /* Subtract five minutes from the user's remaining time. */
+            case 0x6800:
+               if(od_control.user_timelimit > 5)
+               {
+                  od_control.user_timelimit -= 5;
+                  bForceStatusUpdate = TRUE;
+                  CALL_KERNEL_IF_NEEDED();
+               }
+               else if(od_control.user_timelimit > 1)
+               {
+                  --od_control.user_timelimit;
+                  bForceStatusUpdate = TRUE;
+                  CALL_KERNEL_IF_NEEDED();
+               }
+               break;
+
+            default:
+               return;
+         }
+         od_control.od_last_hot = 0;
+         break;
+   }
+}

+ 1530 - 0
odoors/ODPlat.c

@@ -0,0 +1,1530 @@
+/* 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: ODPlat.c
+ *
+ * Description: Contains platform-specific utility functions. Non-platform
+ *              specific utility functions are implemented in odutil.c
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 14, 1994  6.00  BP   Created, with od_yield().
+ *              Nov 01, 1994  6.00  BP   Added new directory access functions.
+ *              Dec 09, 1994  6.00  BP   Eliminate access to old dir functions.
+ *              Dec 31, 1994  6.00  BP   Added timing, file delete functions.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Dec 31, 1994  6.00  BP   Added ODMultitasker and ODPlatInit()
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Added multithreading functions.
+ *              Nov 21, 1995  6.00  BP   Ported to Win32.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 13, 1995  6.00  BP   Added ODThreadWaitForExit().
+ *              Dec 13, 1995  6.00  BP   Added ODThreadGetCurrent().
+ *              Dec 19, 1995  6.00  BP   Fixed ODThreadGetCurrent() (Win32).
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 23, 1996  6.00  BP   Added ODProcessExit().
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 30, 1996  6.00  BP   Add semaphore timeout.
+ *              Jan 31, 1996  6.00  BP   Add ODTimerLeft(), rm ODTimerSleep().
+ *              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 calls N_LXMUL@ & N_LXDIV@.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "OpenDoor.h"
+#ifdef ODPLAT_NIX
+#include <sys/time.h>
+#include <glob.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+#include "ODGen.h"
+#include "ODCore.h"
+#include "ODPlat.h"
+#include "ODUtil.h"
+#include "ODSwap.h"
+#include "ODKrnl.h"
+
+#ifdef ODPLAT_WIN32
+#include "windows.h"
+#endif /* ODPLAT_WIN32 */
+
+
+/* Multitasker type, only availvable under DOS. */
+#ifdef ODPLAT_DOS
+tODMultitasker ODMultitasker = kMultitaskerNone;
+static void ODPlatYield(void);
+#endif /* ODPLAT_DOS */
+
+/* ----------------------------------------------------------------------------
+ * ODPlatInit()
+ *
+ * Performs any initialization required to use the utility functions supplied
+ * by this module.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODPlatInit(void)
+{
+#ifdef ODPLAT_DOS
+   /* If this is the DOS version of OpenDoors, then ODPlatInit() must */
+   /* determine what multitasker we are running under.                */
+
+   /* Check whether running under OS/2. */
+   ASM       mov ah, 0x30
+   ASM       int 0x21
+   ASM       cmp al, 0x0a
+   ASM       jl  NoOS2
+
+   /* If we get to this point, then OS/2 has been detected. */
+   ODMultitasker = kMultitaskerOS2;
+   return;
+
+NoOS2:
+   /* Check whether we are running under DesqView. */
+   ASM    mov cx, 0x4445
+   ASM    mov dx, 0x5351
+   ASM    mov ax, 0x2b01
+   ASM    int 0x21
+   ASM    cmp al, 0xff
+   ASM    je NoDesqView
+
+   /* If we get to this point, then DesqView has been detected. */
+   ODMultitasker = kMultitaskerDV;
+
+NoDesqView:
+   /* Check whether we are running under Windows. */
+   ASM    push di
+   ASM    push si
+   ASM    mov ax, 0x1600
+   ASM    int 0x2f
+   ASM    pop si
+   ASM    pop di
+   ASM    cmp al, 0x00
+   ASM    je NoWindows
+   ASM    cmp al, 0x80
+   ASM    je NoWindows
+
+    /* If we get to this point, then Windows has been detected. */
+   ODMultitasker = kMultitaskerWin;
+
+NoWindows:
+   ODMultitasker = kMultitaskerNone;
+#endif /* ODPLAT_DOS */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODPlatYield()                                       *** PRIVATE FUNCTION ***
+ *
+ * Yields control to other tasks when running as a DOS application under a
+ * multitasking system.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+#ifdef ODPLAT_DOS
+static void ODPlatYield(void)
+{
+   switch(ODMultitasker)
+   {
+      case kMultitaskerDV:
+         ASM  mov ax, 0x1000
+         ASM  int 0x15
+         break;
+
+      case kMultitaskerWin:
+         ASM  mov ax, 0x1680
+         ASM  int 0x2f
+         break;
+
+      case kMultitaskerOS2:
+      default:
+         ASM  int 0x28
+   }
+}
+#endif /* ODPLAT_DOS */
+
+
+/* ========================================================================= */
+/* Multithreading and synchronization support.                               */
+/* ========================================================================= */
+
+#ifdef OD_MULTITHREADED
+
+/* ----------------------------------------------------------------------------
+ * ODThreadCreate()
+ *
+ * Starts a new thread of concurrent execution.
+ *
+ * Parameters: phThread      - Pointer to the location where the handle to the
+ *                             new thread should be stored.
+ *
+ *             pfThreadProc  - Function to call to begin execution of the
+ *                             thread.
+ *
+ *             pThreadParam  - Parameter to pass to the thread function when
+ *                             it is called.
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODThreadCreate(tODThreadHandle *phThread,
+   ptODThreadProc *pfThreadProc, void *pThreadParam)
+{
+#ifdef ODPLAT_WIN32
+   DWORD dwThreadID;
+   HANDLE hNewThread;
+
+   ASSERT(phThread != NULL);
+   ASSERT(pfThreadProc != NULL);
+   
+   /* Attempt to create the new thread. */
+   hNewThread = CreateThread(NULL, 0, pfThreadProc, pThreadParam,
+      0, &dwThreadID);
+
+   /* Check for thread creation failure. */
+   if(hNewThread == NULL)
+   {
+      return(kODRCGeneralFailure);
+   }
+
+   /* Pass newly created thread's handle back to the caller. */
+   *phThread = hNewThread;
+
+   /* Return with success. */
+   return(kODRCSuccess);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadExit()
+ *
+ * Causes the calling thread to be terminated.
+ *
+ * Parameters: none
+ *
+ *     Return: Never returns!
+ */
+void ODThreadExit()
+{
+#ifdef ODPLAT_WIN32
+   ExitThread(0);
+#endif /* ODPLAT_WIN32 */
+
+   /* We should never get here. */
+   ASSERT(FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadTerminate()
+ *
+ * Terminates the specified thread. 
+ *
+ * Parameters: hThread - Handle to the thread to be terminated.
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODThreadTerminate(tODThreadHandle hThread)
+{
+   ASSERT(hThread != NULL);
+
+#ifdef ODPLAT_WIN32
+   return(TerminateThread(hThread, 0) ? kODRCSuccess : kODRCGeneralFailure);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadSuspend()
+ *
+ * Pauses execution of the specified thread, until the ODThreadResume()
+ * function is called.
+ *
+ * Parameters: hThread - Handle to the thread to be suspended.
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODThreadSuspend(tODThreadHandle hThread)
+{
+   ASSERT(hThread != NULL);
+
+#ifdef ODPLAT_WIN32
+   return(SuspendThread(hThread) == 0xFFFFFFFF ? kODRCGeneralFailure
+      : kODRCSuccess);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadResume()
+ *
+ * Continues execution of a thread previously paused by a call to
+ * ODThreadSuspend().
+ *
+ * Parameters: hThread - Handle to the thread to be resumed.
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODThreadResume(tODThreadHandle hThread)
+{
+   ASSERT(hThread != NULL);
+
+#ifdef ODPLAT_WIN32
+   return(ResumeThread(hThread) == 0xFFFFFFFF ? kODRCGeneralFailure
+      : kODRCSuccess);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadSetPriority()
+ *
+ * Changes the execution priority of a thread. Since the exact semantics of
+ * thread priorities are different for each platform, this function should
+ * be used carefully. The caller should assume that no thread will run if there
+ * exists a non-blocked thread with a higher priority.
+ *
+ * Parameters: hThread        - Handle to the thread to change the priority of.
+ *
+ *             ThreadPriority - New priority to assign to the thread.
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODThreadSetPriority(tODThreadHandle hThread,
+   tODThreadPriority ThreadPriority)
+{
+#ifdef ODPLAT_WIN32
+   int nWindowsThreadPriority;
+
+   ASSERT(hThread != NULL);
+
+   /* Determine the Windows thread priority to assign to the thread. */
+   switch(ThreadPriority)   
+   {
+      case OD_PRIORITY_LOWEST:
+         nWindowsThreadPriority = THREAD_PRIORITY_LOWEST;
+         break;
+      case OD_PRIORITY_BELOW_NORMAL:
+         nWindowsThreadPriority = THREAD_PRIORITY_BELOW_NORMAL;
+         break;
+      case OD_PRIORITY_NORMAL:
+         nWindowsThreadPriority = THREAD_PRIORITY_NORMAL;
+         break;
+      case OD_PRIORITY_ABOVE_NORMAL:
+         nWindowsThreadPriority = THREAD_PRIORITY_ABOVE_NORMAL;
+         break;
+      case OD_PRIORITY_HIGHEST:
+         nWindowsThreadPriority = THREAD_PRIORITY_HIGHEST;
+         break;
+      default:
+         ASSERT(FALSE);
+   }
+
+   /* Update the thread's priority. */
+   return(SetThreadPriority(hThread, nWindowsThreadPriority)
+      ? kODRCSuccess : kODRCGeneralFailure);
+
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadWaitForExit()
+ *
+ * Blocks until the specified thread is terminated.
+ *
+ * Parameters: hThread - Handle to the thread to wait for.
+ *
+ *     Return: void
+ */
+void ODThreadWaitForExit(tODThreadHandle hThread)
+{
+#ifdef ODPLAT_WIN32
+   WaitForSingleObject(hThread, INFINITE);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODThreadGetCurrent()
+ *
+ * Obtains a handle to the thread that called this function.
+ *
+ * Parameters: None.
+ *
+ *     Return: Handle to the current thread.
+ */
+tODThreadHandle ODThreadGetCurrent(void)
+{
+#ifdef ODPLAT_WIN32
+   HANDLE hDuplicate;
+   if(!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+      GetCurrentProcess(), &hDuplicate, 0, FALSE, DUPLICATE_SAME_ACCESS))
+   {
+      return(NULL);
+   }
+   return(hDuplicate);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSemaphoreAlloc()
+ *
+ * Allocates a semaphore synchronization object.
+ *
+ * Parameters: phSemaphore   - Pointer to location where the handle to the
+ *                             newly created semaphore should be stored.
+ *
+ *             nInitialCount - Initial value to assign to the semaphore.
+ *
+ *             nMaximumCount - Maximum value that the semaphore may have
+ *                             (if supported by the current platform).
+ *
+ *     Return: kOCRCSuccess on success, or an error code on failure.
+ */
+tODResult ODSemaphoreAlloc(tODSemaphoreHandle *phSemaphore, INT nInitialCount,
+   INT nMaximumCount)
+{
+   ASSERT(phSemaphore != NULL);
+   ASSERT(nInitialCount >= 0);
+   ASSERT(nMaximumCount >= nInitialCount);
+
+#ifdef ODPLAT_WIN32
+   *phSemaphore = CreateSemaphore(NULL, (LONG)nInitialCount,
+      (LONG)nMaximumCount, NULL);
+
+   return(*phSemaphore == NULL ? kODRCGeneralFailure : kODRCSuccess);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSemaphoreFree()
+ *
+ * Deallocates a semaphore that we previously created by ODSemaphoreAlloc().
+ *
+ * Parameters: hSemaphore - Handle to semaphore to deallocate.
+ *
+ *     Return: void
+ */
+void ODSemaphoreFree(tODSemaphoreHandle hSemaphore)
+{
+   ASSERT(hSemaphore != NULL);
+
+#ifdef ODPLAT_WIN32
+   DeleteObject(hSemaphore);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSemaphoreUp()
+ *
+ * Increments the count of the specified semaphore.
+ *
+ * Parameters: hSemaphore   - Semaphore to increment.
+ *
+ *             nIncrementBy - Amount to add to the semaphore's current value.
+ *
+ *     Return: void
+ */
+void ODSemaphoreUp(tODSemaphoreHandle hSemaphore, INT nIncrementBy)
+{
+   ASSERT(hSemaphore != NULL);
+   ASSERT(nIncrementBy > 0);
+
+#ifdef ODPLAT_WIN32
+   ReleaseSemaphore(hSemaphore, nIncrementBy, NULL);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODSemaphoreDown()
+ *
+ * Decrements the count of the specified semaphore. A semaphore may never have
+ * a value less than 0. Hence, an attempt to decrement the value of a
+ * semaphore below zero will cause the calling thread to be blocked until some
+ * other thread increments the semaphore.
+ *
+ * Parameters: hSemaphore   - Handle to the semaphore to decrement.
+ *
+ *             Timeout      - Maximum time to wait for the semaphore to be
+ *                            incremented, or OD_NO_TIMEOUT to prevent this
+ *                            function from returning before the semaphore is
+ *                            incremented.
+ *
+ *     Return: kODRCSuccess, or kODRCTimeout if the semaphore was not
+ *             decremented before Timeout milliseconds elapsed.
+ */
+tODResult ODSemaphoreDown(tODSemaphoreHandle hSemaphore, tODMilliSec Timeout)
+{
+   ASSERT(hSemaphore != NULL);
+
+#ifdef ODPLAT_WIN32
+   if(WaitForSingleObject(hSemaphore, Timeout) != WAIT_OBJECT_0)
+   {
+      return(kODRCTimeout);
+   }
+#endif /* ODPLAT_WIN32 */
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+#endif /* OD_MULTITHREADED */
+
+
+/* ----------------------------------------------------------------------------
+ * ODProcessExit()
+ *
+ * Ends the current process.
+ *
+ * Parameters: nExitCode - Exit code to return to the calling process.
+ *
+ *     Return: Never returns.
+ */
+void ODProcessExit(INT nExitCode)
+{
+#ifdef ODPLAT_WIN32
+   ExitProcess(nExitCode);
+#else /* !ODPLAT_WIN32 */
+   exit(nExitCode);
+#endif /* !ODPLAT_WIN32 */
+}
+
+
+/* ========================================================================= */
+/* Millisecond timer functions.                                              */
+/* ========================================================================= */
+
+#ifdef ODPLAT_DOS
+/* For the DOS platform, we need to know the number of milliseconds per */
+/* clock tick.                                                          */
+#define MILLISEC_PER_TICK 55           /* (approx. == 1000 / CLOCKS_PER_SEC) */
+#endif /* ODPLAT_DOS */
+
+/* ----------------------------------------------------------------------------
+ * ODTimerStart()
+ *
+ * Starts a timer for a specified number of milliseconds. Future calls to
+ * ODTimerElapsed() can be used to determine whether or not specified time
+ * has elapsed. Note that while this function accepts its parameter in
+ * milliseconds, it does not gurantee millisecond resolution. In fact, under
+ * DOS, this timer mechanism has just under a 55 millisecond resolution, with
+ * an average error of about 27 milliseconds.
+ *
+ * Parameters: pTimer   - Pointer to a tODTimer structure that will be later
+ *                        passed to ODTimerElapsed().
+ *
+ *             Duration - Number of milliseconds after which timer should
+ *                        elapse.
+ *
+ *     Return: void
+ */
+void ODTimerStart(tODTimer *pTimer, tODMilliSec Duration)
+{
+#ifdef ODPLAT_NIX
+   struct timeval tv;
+#endif
+   ASSERT(pTimer != NULL);
+   ASSERT(Duration >= 0);
+
+#ifdef ODPLAT_DOS
+   /* Store timer start time right away. */
+   pTimer->Start = clock();
+
+   /* Calculate duration of timer. */
+   ODDWordDivide((DWORD *)&pTimer->Duration, NULL, Duration,
+      MILLISEC_PER_TICK);
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   /* Store timer start time now. */
+   pTimer->Start = GetTickCount();
+   pTimer->Duration = Duration;
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   gettimeofday(&tv,NULL);
+   pTimer->Start=tv.tv_sec*1000+tv.tv_usec/1000;
+   pTimer->Duration = Duration;
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODTimerElapsed()
+ *
+ * Determines whether or not a timer set by ODTimerStart() has elapsed.
+ *
+ * Parameters: pTimer - Pointer to a tODTimer structure that was populated
+ *                      by ODTimerStart().
+ *
+ *     Return: TRUE if timer has elapsed, FALSE if it has not.
+ */
+BOOL ODTimerElapsed(tODTimer *pTimer)
+{
+   ASSERT(pTimer != NULL);
+
+#ifdef ODPLAT_DOS
+   return(clock() > pTimer->Start + pTimer->Duration
+      || clock() < pTimer->Start);
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   return(ODTimerLeft(pTimer)==0);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+	return(ODTimerLeft(pTimer)==0);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODTimerWaitForElapse()
+ *
+ * Sleeps until the specified timer elapses.
+ *
+ * Parameters: pTimer - Pointer to a tODTimer structure that was populated
+ *                      by ODTimerStart().
+ *
+ *     Return: void
+ */
+void ODTimerWaitForElapse(tODTimer *pTimer)
+{
+   ASSERT(pTimer != NULL);
+
+#ifdef ODPLAT_DOS
+
+   /* Under DOS, our timer resolution is low enough (only 18.2 ticks per    */
+   /* second), that we cannot accurately calculate the time to sleep for.   */
+   /* For this reason, we simply loop until the timer has elapsed, yielding */
+   /* control to other tasks if when the timer has not elapsed.             */
+
+   /* While timer has not elapsed. */
+   while(!ODTimerElapsed(pTimer))
+   {
+      /* Let other tasks run. */
+      od_sleep(0);
+   }
+
+#else /* !ODPLAT_DOS */
+   /* Under other platforms, timer resolution is high enough that we can */
+   /* ask the OS to block this thread for the amount of time required    */
+   /* for the timer to elapse.                                           */
+
+   od_sleep(ODTimerLeft(pTimer));
+#endif /* !ODPLAT_DOS */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODTimerLeft()
+ *
+ * Determines the number of milliseconds left before the timer elapses.
+ *
+ * Parameters: pTimer - Pointer to a tODTimer structure that was populated
+ *                      by ODTimerStart().
+ *
+ *     Return: Number of milliseconds before timer elapses, or 0 if the timer
+ *             has already elapsed.
+ */
+tODMilliSec ODTimerLeft(tODTimer *pTimer)
+{
+#ifdef ODPLAT_NIX
+   struct timeval tv;
+   time_t nowtick;
+#endif
+   ASSERT(pTimer != NULL);
+
+#ifdef ODPLAT_DOS
+   {
+      clock_t Now = clock();
+      clock_t Left;
+
+      /* If timer has elapsed, return 0. */
+      if(Now > pTimer->Start + pTimer->Duration
+         || Now < pTimer->Start)
+      {
+         return(0);
+      }
+
+      Left = pTimer->Start + pTimer->Duration - Now;
+
+      return(ODDWordMultiply(Left, MILLISEC_PER_TICK));
+   }
+#elif defined(ODPLAT_NIX)
+   gettimeofday(&tv,NULL);
+   nowtick=tv.tv_sec*1000+(tv.tv_usec/1000);
+   if(pTimer->Start+pTimer->Duration <= nowtick)
+      return(0);
+   return((tODMilliSec)(pTimer->Start + pTimer->Duration - nowtick));
+#else /* !ODPLAT_DOS */
+   {
+      tODMilliSec Now;
+
+#ifdef ODPLAT_WIN32      
+      Now = GetTickCount();
+#endif /* ODPLAT_WIN32 */
+
+      /* If timer has elapsed, return 0. */
+      if(Now > pTimer->Start + pTimer->Duration
+         || Now < pTimer->Start)
+      {
+         return(0);
+      }
+
+      return(pTimer->Start + pTimer->Duration - Now);
+   }
+#endif /* !ODPLAT_DOS */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_sleep()
+ *
+ * Sleeps for the specified number of milliseconds, being as friendly to other
+ * running tasks as possible. Under DOS, this function uses the ODTimerStart()/
+ * ODTimerElapsed() mechanism, and so its accuracy is limited by the accuracy
+ * of that mechanism.
+ *
+ * Parameters: Milliseconds - Number of milliseconds to sleep. A value of 0
+ *                            allows any other waiting processes to run for
+ *                            the rest of the current timeslice.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL od_sleep(tODMilliSec Milliseconds)
+{
+#ifdef ODPLAT_NIX
+   struct timeval tv;
+   struct timeval start;
+   time_t started;
+   time_t left
+#endif
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_sleep()");
+
+   /* Ensure that OpenDoors is initialized before proceeding. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+#ifdef ODPLAT_DOS
+   if(Milliseconds == 0)
+   {
+      ODPlatYield();
+   }
+   else
+   {
+      tODTimer SleepTimer;
+      ODTimerStart(&SleepTimer, Milliseconds);
+      while(!ODTimerElapsed(&SleepTimer))
+      {
+         /* Let other tasks run. */
+         ODPlatYield();
+      }
+   }
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   Sleep(Milliseconds);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   if(Milliseconds==0)  {
+      /* Prevent 100% CPU *only* no delay is actually required here */
+      tv.tv_sec=0;
+      tv.tv_usec=1000;
+      select(0,NULL,NULL,NULL,&tv);
+   }
+   else  {
+      gettimeofday(&start,NULL);
+	  started=start.tv_sec*1000+(start.tv_usec/1000);
+
+      while(1)  {
+	     /* This is timing sensitive and *MUST* wait for at least Milliseconds regardless of 100% CPU or signals */
+         gettimeofday(&tv,NULL);
+		 left=tv.tv_sec*1000+(tv.tv_usec/1000);
+		 left-=started;
+		 left=Milliseconds-left;
+         tv.tv_sec = left/1000;
+         tv.tv_usec = (left*1000)%1000000;
+         if(tv.tv_sec<0 || tv.tv_usec<0)
+            break;
+         if(!select(0,NULL,NULL,NULL,&tv))
+            break;
+      }
+   }
+#endif
+
+   OD_API_EXIT();
+}
+
+
+/* ========================================================================= */
+/* Directory access.                                                         */
+/* ========================================================================= */
+
+/* Structure for directories entries returned by DOS. */
+#ifdef ODPLAT_DOS
+typedef struct
+{
+   BYTE abtReserved[21];
+   BYTE btAttrib;
+   WORD wFileTime;
+   WORD wFileDate;
+   DWORD dwFileSize;
+   char szFileName[13];
+} tDOSDirEntry;
+#endif /* ODPLAT_DOS */
+
+
+/* Dir handle structure. */
+typedef struct
+{
+   BOOL bEOF;
+#ifdef ODPLAT_DOS
+   tDOSDirEntry FindBlock;
+#endif /* ODPLAT_DOS */
+#ifdef ODPLAT_WIN32
+   HANDLE hWindowsDir;
+   WIN32_FIND_DATA WindowsDirEntry;
+   int wAttributes;
+#endif /* ODPLAT_WIN32 */
+#ifdef ODPLAT_NIX
+   glob_t	g;
+   int		pos;
+   int		wAttributes;
+#endif
+} tODDirInfo;
+
+
+/* Directory access private function prototypes. */
+#if defined(ODPLAT_DOS) || defined(ODPLAT_WIN32)
+static time_t DOSToCTime(WORD wDate, WORD wTime);
+#endif
+#ifdef ODPLAT_DOS
+static INT ODDirDOSFindFirst(CONST char *pszPath, tDOSDirEntry *pBlock,
+   WORD wAttributes);
+static INT ODDirDOSFindNext(tDOSDirEntry *pBlock);
+#endif /* ODPLAT_DOS */
+#ifdef ODPLAT_WIN32
+static BOOL ODDirWinMatchesAttributes(tODDirInfo *pDirInfo);
+#endif /* ODPLAT_WIN32 */
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirOpen()
+ *
+ * Opens a directory for future access using ODDirRead(). On Success,
+ * ODDirOpen() provides a directory handle that represents a list of directory
+ * entries that match the specified path and attributes. When finished with
+ * the directory handle, the caller should release it using ODDirClose().
+ *
+ * Parameters: pszPath     - Directory with filename (wildcards are supported),
+ *                           for which matching files should be found. If there
+ *                           are no matching files, ODDirOpen() returns with
+ *                           kODRCNoMatch.
+ *
+ *             nAttributes - One or more of the DIR_ATTRIB_... constants,
+ *                           connected by the bitmap-OR (|) operator.
+ *
+ *             phDir       - Pointer to a tODDirHandle, into which ODDirOpen()
+ *                           will place a valid directory handle if and only
+ *                           if it returns kODRCSuccess.
+ *
+ *     Return: A tODResult indicating success or reason for failure.
+ */
+tODResult ODDirOpen(CONST char *pszPath, WORD wAttributes, tODDirHandle *phDir)
+{
+   tODDirInfo *pDirInfo;
+
+   ASSERT(pszPath != NULL);
+   ASSERT(phDir != NULL);
+
+   /* Attempt to allocate a directory information structure. */
+   if((pDirInfo = malloc(sizeof(tODDirInfo))) == NULL)
+   {
+      /* If unable to allocate enough memory, return this state to the */
+      /* caller. */
+      return(kODRCNoMemory);
+   }
+
+   /* Initialize directory information structure. */
+   pDirInfo->bEOF = FALSE;
+
+#ifdef ODPLAT_DOS
+   /* Read the first matching directory entry structure. */
+   if(ODDirDOSFindFirst(pszPath, &pDirInfo->FindBlock, wAttributes))
+   {
+      /* If unable to read directory entry, release directory information   */
+      /* structure, and return indicating that there are no matching files. */
+      free(pDirInfo);
+      return(kODRCNoMatch);
+   }
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   /* Store a copy of the attributes passed to open function. */
+   pDirInfo->wAttributes = wAttributes;
+
+   /* Attempt to read first directory entry. */
+   pDirInfo->hWindowsDir = FindFirstFile(pszPath, &pDirInfo->WindowsDirEntry);
+
+   if(pDirInfo->hWindowsDir == INVALID_HANDLE_VALUE)
+   {
+      /* If unable to read directory entry, release directory information   */
+      /* structure, and return indicating that there are no matching files. */
+      free(pDirInfo);
+      return(kODRCNoMatch);
+   }
+
+   /* If first file doesn't match specified attributes, then find one that */
+   /* does.                                                                */
+   /* Find next matching entry, if any. */
+   while(!ODDirWinMatchesAttributes(pDirInfo))
+   {
+      if(!FindNextFile(pDirInfo->hWindowsDir, &pDirInfo->WindowsDirEntry))
+      {
+         /* If unable to find matching directory entry, then release       */
+         /* structure, return indicating that there are no matching files. */
+         free(pDirInfo);
+         return(kODRCNoMatch);
+      }
+   }
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   if(glob(pszPath,GLOB_NOSORT,NULL,&(pDirInfo->g)))
+      return(kODRCNoMatch);
+   if(pDirInfo->g.gl_pathc==0)  {
+      globfree(&(pDirInfo->g));
+      return(kODRCNoMatch);
+   }
+   pDirInfo->pos=0;
+   pDirInfo->wAttributes = wAttributes;
+#endif
+
+   /* Now that open operation is complete, give the caller a directory */
+   /* handle. */
+   *phDir = ODPTR2HANDLE(pDirInfo, tODDirInfo);
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirRead()
+ *
+ * Reads the next directory entry from an open directory, placing the directory
+ * information into the tODDirEntry structure pointed to by pDirEntry.
+ *
+ * Parameters: hDir      - Handle to an open directory, as provided by
+ *                         ODDirOpen().
+ *
+ *             pDirEntry - Pointer to structure into which directory entry
+ *                         information should be placed.
+ *
+ *     Return: A tODResult indicating success or reason for failure. After the
+ *             last directory entry has been read, all subsequent calls to
+ *             ODDirRead() will return kODRCEndOfFile.
+ */
+tODResult ODDirRead(tODDirHandle hDir, tODDirEntry *pDirEntry)
+{
+   tODDirInfo *pDirInfo = ODHANDLE2PTR(hDir, tODDirInfo);
+#ifdef ODPLAT_WIN32
+   WORD wDOSDate;
+   WORD wDOSTime;
+#endif /* ODPLAT_WIN32 */
+#ifdef ODPLAT_NIX
+   struct stat st;
+#endif
+   
+   ASSERT(pDirEntry != NULL);
+   ASSERT(pDirInfo != NULL);
+
+   /* Check whether the last directory entry has been returned yet. */
+   if(pDirInfo->bEOF)
+   {
+      /* Return this state information to the caller. */
+      return(kODRCEndOfFile);
+   }
+
+#ifdef ODPLAT_DOS
+   /* Provide the caller with the information from the previously read */
+   /* directory entry.                                                 */
+
+   /* Copy the filename to the caller's structure. */
+   ODStringCopy(pDirEntry->szFileName, pDirInfo->FindBlock.szFileName,
+      DIR_FILENAME_SIZE);
+
+   /* Copy the attributes to the caller's structure. */
+   pDirEntry->wAttributes = pDirInfo->FindBlock.btAttrib;
+
+   /* Copy the file size to the caller's structure. */
+   pDirEntry->dwFileSize = pDirInfo->FindBlock.dwFileSize;
+
+   /* Determine the last file write time, in C library time format. */
+   pDirEntry->LastWriteTime = DOSToCTime(pDirInfo->FindBlock.wFileDate,
+      pDirInfo->FindBlock.wFileTime);
+
+   /* Read next directory entry, if any. */
+   pDirInfo->bEOF = ODDirDOSFindNext(&pDirInfo->FindBlock);
+
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   /* Provide the caller with the information from the previously read */
+   /* directory entry.                                                 */
+
+   /* Copy filename from Win32 8.3 filename. */
+   if(strlen(pDirInfo->WindowsDirEntry.cAlternateFileName) == 0)
+   {
+      ODStringCopy(pDirEntry->szFileName,
+         pDirInfo->WindowsDirEntry.cFileName, DIR_FILENAME_SIZE);
+   }
+   else
+   {
+      ODStringCopy(pDirEntry->szFileName,
+         pDirInfo->WindowsDirEntry.cAlternateFileName, DIR_FILENAME_SIZE);
+   }
+
+   /* Copy attribute bits. */
+   pDirEntry->wAttributes = DIR_ATTRIB_NORMAL;
+   if(pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
+   {
+      pDirEntry->wAttributes |= DIR_ATTRIB_ARCH;
+   }
+   if(pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+   {
+      pDirEntry->wAttributes |= DIR_ATTRIB_DIREC;
+   }
+   if(pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
+   {
+      pDirEntry->wAttributes |= DIR_ATTRIB_HIDDEN;
+   }
+   if(pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+   {
+      pDirEntry->wAttributes |= DIR_ATTRIB_RDONLY;
+   }
+   if(pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
+   {
+      pDirEntry->wAttributes |= DIR_ATTRIB_SYSTEM;
+   }
+
+   /* Copy the file size to the caller's structure. */
+   pDirEntry->dwFileSize = (long)pDirInfo->WindowsDirEntry.nFileSizeLow;
+
+   /* Determine the last file write time, in C library time format. */
+   FileTimeToDosDateTime(&pDirInfo->WindowsDirEntry.ftLastWriteTime, &wDOSDate,
+      &wDOSTime);
+   pDirEntry->LastWriteTime = DOSToCTime(wDOSDate, wDOSTime);
+
+   /* Find next matching entry, if any. */
+   do
+   {
+      if(!FindNextFile(pDirInfo->hWindowsDir, &pDirInfo->WindowsDirEntry))
+      {
+         pDirInfo->bEOF = TRUE;
+      }
+   } while(!ODDirWinMatchesAttributes(pDirInfo));
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   while(!pDirInfo->bEOF)  {
+      if(strrchr(pDirInfo->g.gl_pathv[pDirInfo->pos],DIRSEP)==NULL)
+	     strcpy(pDirEntry->szFileName,pDirInfo->g.gl_pathv[pDirInfo->pos]);
+	  else
+	     strcpy(pDirEntry->szFileName,strrchr(pDirInfo->g.gl_pathv[pDirInfo->pos],DIRSEP));
+	  stat(pDirInfo->g.gl_pathv[pDirInfo->pos],&st);
+	  pDirEntry->wAttributes=DIR_ATTRIB_NORMAL;
+	  if(st.st_mode & S_IFDIR)
+	  	 pDirEntry->wAttributes |= DIR_ATTRIB_DIREC;
+	  if(!st.st_mode & S_IWUSR)
+	  	 pDirEntry->wAttributes |= DIR_ATTRIB_RDONLY;
+	  if(!st.st_mode & S_IRUSR)
+	  	 pDirEntry->wAttributes |= DIR_ATTRIB_SYSTEM;
+	  pDirEntry->LastWriteTime=st.st_mtime;
+	  pDirEntry->dwFileSize=st.st_size;
+	  pDirInfo->pos++;
+	  if(pDirInfo->pos==pDirInfo->g.gl_pathc)
+	     pDirInfo->bEOF=TRUE;
+	  if(pDirEntry->wAttributes==pDirInfo->wAttributes)
+	     return(kODRCSuccess);
+	  if(pDirInfo->bEOF==TRUE)
+	    return(kODRCEndOfFile);
+   }
+#endif
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirClose()
+ *
+ * Closes and open directory handle.
+ *
+ * Parameters: hDir - Handle to an open directory handle, as provided by the
+ *                    ODDirOpen() function.
+ *
+ *     Return: void
+ */
+void ODDirClose(tODDirHandle hDir)
+{
+   tODDirInfo *pDirInfo = ODHANDLE2PTR(hDir, tODDirInfo);
+
+   ASSERT(pDirInfo != NULL);
+
+#ifdef ODPLAT_WIN32
+   /* Under Win32, close directory handle. */
+   FindClose(pDirInfo->hWindowsDir);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   globfree(&(pDirInfo->g));
+#endif
+
+   /* Free the directory information structure. */
+   free(pDirInfo);
+}
+
+
+#if defined(ODPLAT_DOS) || defined(ODPLAT_WIN32)
+/* ----------------------------------------------------------------------------
+ * DOSToCTime()                                        *** PRIVATE FUNCTION ***
+ *
+ * Converts DOS directory entry time format to the C library time format.
+ *
+ * Parameters: uDate - Date portion of the time to be converted.
+ *
+ *             uTime - Time of day portion of the time to be converted.
+ *
+ *     Return: The specified time, represented as a time_t.
+ */
+static time_t DOSToCTime(WORD wDate, WORD wTime)
+{
+   struct tm TimeStruct;
+
+   TimeStruct.tm_sec = (wTime & 0x001f) * 2;
+   TimeStruct.tm_min = (wTime & 0x07e0) >> 5;
+   TimeStruct.tm_hour = (wTime & 0xf800) >> 11;
+   TimeStruct.tm_mday = wDate & 0x001f;
+   TimeStruct.tm_mon = ((wDate & 0x01e0) >> 5) - 1;
+   TimeStruct.tm_year = 80 + ((wDate & 0xfe00) >> 9);
+
+   return(mktime(&TimeStruct));
+}
+#endif
+
+
+/* MS-DOS specific functions for directory access. */
+#ifdef ODPLAT_DOS
+
+/* ----------------------------------------------------------------------------
+ * ODDirDOSFindFirst()                                 *** PRIVATE FUNCTION ***
+ *
+ * MS-DOS specific "Find First" function for reading directory entries. This
+ * is essentially just a C-language interface to the interrupt function call
+ * that is provided by DOS.
+ *
+ * Parameters: pszPath     - Pointer to string containing directory and
+ *                           filespec to search for.
+ *
+ *             pBlock      - Pointer to directory block.
+ *
+ *             nAttributes - Attributes to match, if any.
+ *
+ *     Return: 0 on success, -1 on failure.
+ */
+static int ODDirDOSFindFirst(CONST char *pszPath, tDOSDirEntry *pBlock,
+   WORD wAttributes)
+{
+   int nToReturn;
+
+   ASSERT(pszPath != NULL);
+   ASSERT(pBlock != NULL);
+
+   ASM     push ds
+   ASM     mov ah, 0x2f            /* Int 0x21, ah=0x2f: Get current DOS DTA */
+   ASM     int 0x21                                       /* Get current DTA */
+   ASM     push bx                   /* Store offset of current DTA on stack */
+   ASM     push es                  /* Store segment of current DTA on stack */
+   ASM     mov ah, 0x1a                /* Int 0x21, ah=0x1a: Set new DOS DTA */
+#ifdef LARGEDATA                                    /* If using far pointers */
+   ASM     lds dx, pBlock           /* Load DS:DX with far address of pBlock */
+#else                                              /* If using near pointers */
+   ASM     mov dx, pBlock             /* Load DX with near address of pBlock */
+#endif
+   ASM     int 0x21                                           /* Set DOS DTA */
+   ASM     mov ah, 0x4e         /* Int 0x21, ah=0x4e: DOS findfirst function */
+   ASM     mov cx, wAttributes                    /* Load attributes into CX */
+#ifdef LARGEDATA                                    /* If using far pointers */
+   ASM     lds dx, pszPath         /* Load DS:DX with far address in pszPath */
+#else                                              /* If using near pointers */
+   ASM     mov dx, pszPath           /* Load DX with near address in pszPath */
+#endif
+   ASM     int 0x21                               /* Call findfirst function */
+   ASM     jc error       /* If carry flag is set, then an error has ocurred */
+   ASM     mov word ptr nToReturn, 0                /* If no error, return 0 */
+   ASM     jmp after_result
+error:
+   ASM     mov word ptr nToReturn, -1                 /* If error, return -1 */
+after_result:
+   ASM     mov ah, 0x1a                /* Int 0x21, ah=0x1a: Set new DOS DTA */
+   ASM     pop ds                   /* Pop original DTA segment off of stack */
+   ASM     pop dx                      /* Pop original DTA offest from stack */
+   ASM     int 0x21                             /* Reset DOS DTA to original */
+   ASM     pop ds                   /* Restore DS stored at function startup */
+   return(nToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirDOSFindNext()                                  *** PRIVATE FUNCTION ***
+ *
+ * MS-DOS specific "Find Next" function for reading directory entries. This
+ * is essentially just a C-language interface to the interrupt function call
+ * that is provided by DOS.
+ *
+ * Parameters: pBlock - Pointer to block in which to store next directory
+ *                      entry.
+ *
+ *     Return: 0 on success, -1 on failure.
+ */
+static int ODDirDOSFindNext(tDOSDirEntry *pBlock)
+{
+   int nToReturn;
+
+   ASSERT(pBlock != NULL);
+
+   ASM     push ds                                                /* Save DS */
+   ASM     mov ah, 0x2f            /* Int 0x21, ah=0x2f: Get current DOS DTA */
+   ASM     int 0x21                                       /* Get current DTA */
+   ASM     push bx                   /* Store offset of current DTA on stack */
+   ASM     push es                  /* Store segment of current DTA on stack */
+   ASM     mov ah, 0x1a                /* Int 0x21, ah=0x1a: Set new DOS DTA */
+#ifdef LARGEDATA                                    /* If using far pointers */
+   ASM     lds dx, pBlock           /* Load DS:DX with far address of pBlock */
+#else                                              /* If using near pointers */
+   ASM     mov dx, pBlock             /* Load DX with near address of pBlock */
+#endif
+   ASM     int 0x21                                           /* Set DOS DTA */
+   ASM     mov ah, 0x4f          /* Int 0x21, ah=0x4f: DOS findnext function */
+   ASM     int 0x21                               /* Call findfirst function */
+   ASM     jc error       /* If carry flag is set, then an error has ocurred */
+   ASM     mov word ptr nToReturn, 0                /* If no error, return 0 */
+   ASM     jmp after_result
+error:
+   ASM     mov word ptr nToReturn, -1                 /* If error, return -1 */
+after_result:
+   ASM     mov ah, 0x1a                /* Int 0x21, ah=0x1a: Set new DOS DTA */
+   ASM     pop ds                   /* Pop original DTA segment off of stack */
+   ASM     pop dx                      /* Pop original DTA offest from stack */
+   ASM     int 0x21                             /* Reset DOS DTA to original */
+   ASM     pop ds                   /* Restore DS stored at function startup */
+   return(nToReturn);
+}
+
+#endif /* ODPLAT_DOS */
+
+
+/* Win32 specific private functions for directory access. */
+#ifdef ODPLAT_WIN32
+/* ----------------------------------------------------------------------------
+ * ODDirWinMatchesAttributes()                         *** PRIVATE FUNCTION ***
+ *
+ * Determines whether or not the directory entry pDirInfo->WindowsDirEntry
+ * meets the attribute requirements specified in pDirInfo->nAttributes
+ *
+ * Parameters: pDirInfo - Pointer to a directory information structure with
+ *                        attribute and directory entry values.
+ *
+ *     Return: TRUE if the file matches the attributes, FALSE if it does not.
+ */
+static BOOL ODDirWinMatchesAttributes(tODDirInfo *pDirInfo)
+{
+   if((pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+      && !(pDirInfo->wAttributes & DIR_ATTRIB_DIREC))
+   {
+      return(FALSE);
+   }
+
+   if((pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
+      && !(pDirInfo->wAttributes & DIR_ATTRIB_ARCH))
+   {
+      return(FALSE);
+   }
+
+   if((pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
+      && !(pDirInfo->wAttributes & DIR_ATTRIB_HIDDEN))
+   {
+      return(FALSE);
+   }
+
+   if((pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+      && !(pDirInfo->wAttributes & DIR_ATTRIB_RDONLY))
+   {
+      return(FALSE);
+   }
+
+   if((pDirInfo->WindowsDirEntry.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
+      && !(pDirInfo->wAttributes & DIR_ATTRIB_SYSTEM))
+   {
+      return(FALSE);
+   }
+
+   return(TRUE);
+}
+#endif /* ODPLAT_WIN32 */
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirChangeCurrent()
+ *
+ * Changes current directory to the one specified.
+ *
+ * Parameters: pszPath - String containing path to change to.
+ *
+ *     Return: void
+ */
+void ODDirChangeCurrent(char *pszPath)
+{
+#ifdef ODPLAT_DOS
+   int nDrive = 0;
+
+   if(pszPath[1] == ':')
+   {
+      nDrive = (toupper(pszPath[0]) - 'A');
+   }
+
+   _setdrvcd(nDrive, (char *)pszPath);
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   SetCurrentDirectory(pszPath);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   chdir(pszPath);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDirGetCurrent()
+ *
+ * Obtains the name of the  current directory, including the current drive
+ * designator.
+ *
+ * Parameters: pszPath       - String containing path to change to.
+ *
+ *             nMaxPathChars - Maximum characters in the buffer pointer to by
+ *                             pszPath.
+ *
+ *     Return: void
+ */
+void ODDirGetCurrent(char *pszPath, INT nMaxPathChars)
+{
+   ASSERT(pszPath != NULL);
+   ASSERT(nMaxPathChars > 0);
+
+#ifdef ODPLAT_DOS
+   UNUSED(nMaxPathChars);
+
+   strcpy(pszPath, "X:\\");
+   pszPath[0] = 'A' + _getdrv();
+   _getcd(0, (char *)pszPath + 3);
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   GetCurrentDirectory(nMaxPathChars, pszPath);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   getcwd(pszPath,nMaxPathChars);
+#endif
+
+   ASSERT((INT)strlen(pszPath) + 1 <= nMaxPathChars);
+}
+
+
+/* ========================================================================= */
+/* Misc. Functions                                                           */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODFileDelete()
+ *
+ * Deletes the file with the specified filename.
+ *
+ * Parameters: pszPath - Filename, possibly with path, to delete.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODFileDelete(CONST char *pszPath)
+{
+#ifdef ODPLAT_DOS
+   {
+      tODResult Result;
+
+      ASM    push ds
+#ifdef LARGEDATA
+      ASM    lds dx, pszPath
+#else /* !LARGEDATA */
+      ASM    mov ax, ss
+      ASM    mov ds, ax
+      ASM    mov dx, pszPath
+#endif /* !LARGEDATA */
+      ASM    mov ah, 0x41
+      ASM    int 0x21
+      ASM    jc Failure
+      ASM    mov word ptr Result, kODRCSuccess
+      ASM    jmp Done
+Failure:
+      ASM    mov word ptr Result, kODRCGeneralFailure
+Done:
+      ASM    pop ds
+
+      return(Result);
+   }
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   return(DeleteFile(pszPath) ? kODRCSuccess : kODRCGeneralFailure);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   return(unlink(pszPath));
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFileAccessMode()
+ *
+ * Determines the access permissions of a file.
+ *
+ * Parameters: pszFilename - Name of file to test.
+ *
+ *             nAccessMode - Indicates which file access mode to test for.
+ *                           A value of 0 indicates existance, 2 indicates
+ *                           write permission, 4 indicates read permission,
+ *                           and 6 indicates read/write permission.
+ *
+ *     Return: FALSE if file can be accessed or TRUE if file cannot be
+ *             accessed.
+ */
+BOOL ODFileAccessMode(char *pszFilename, int nAccessMode)
+{
+   FILE *pfFileToTest;
+   char *pszModeString;
+   tODDirHandle hDir;
+
+#ifdef ODPLAT_DOS
+   BYTE nLength;
+   /* If we are looking for the root directory. */
+   nLength = strlen(pszFilename);
+   if((nLength == 3 && pszFilename[1] == ':' && pszFilename[2] == DIRSEP) ||
+      (nLength == 1 && pszFilename[0] == DIRSEP))
+   {
+      if(nAccessMode == 0)
+      {
+          int to_return = FALSE;
+
+#ifdef LARGEDATA
+         ASM push ds
+         ASM lds dx, pszFilename
+#else
+         ASM mov dx, pszFilename
+#endif
+         ASM mov ax, 0x4300
+         ASM int 0x21
+         ASM jnc done
+         ASM mov word ptr to_return, TRUE
+done:
+#ifdef LARGEDATA
+         ASM pop ds
+#endif
+          return(to_return);
+      }
+      else
+      {
+          return(TRUE);
+      }
+   }
+#endif /* ODPLAT_DOS */
+
+   /* If the file doesn't exit, we fail in any mode. */
+   if(ODDirOpen(pszFilename,
+      DIR_ATTRIB_ARCH | DIR_ATTRIB_RDONLY | DIR_ATTRIB_DIREC,
+      &hDir) != kODRCSuccess)
+   {
+      return(TRUE);
+   }
+
+   /* If directory open succeeded, then close it again. */
+   ODDirClose(hDir);
+
+   /* If the file does exist, then amode 0 is satisfied. */
+   if(nAccessMode == 0) return(FALSE);
+
+   /* If testing for an access permission, determine corresponding fopen() */
+   /* mode.                                                                */
+   switch(nAccessMode)
+   {
+      case 2:
+        pszModeString = "a";
+        break;
+      case 4:
+        pszModeString = "r";
+        break;
+      default:
+         pszModeString = "r+";
+   }
+
+   /* Attempt to open the file, if unable to do so return failure. */
+   if((pfFileToTest=fopen(pszFilename,pszModeString)) == NULL) return(TRUE);
+
+   /* If file open was successful, close it again, and return success. */
+   fclose(pfFileToTest);
+   return(FALSE);
+}

+ 203 - 0
odoors/ODPlat.h

@@ -0,0 +1,203 @@
+/* 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: ODPlat.h
+ *
+ * Description: Contains platform-related definitions and prototypes for
+ *              those function's whose implementation is platform-specific
+ *              (functions implemented in odplat.c). Non-platform specific
+ *              utility functions are defined in odutil.h and implemented in
+ *              odutil.c.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 14, 1994  6.00  BP   Created.
+ *              Nov 01, 1994  6.00  BP   Added ODDir...() functions.
+ *              Dec 31, 1994  6.00  BP   Added timing, file delete functions.
+ *              Dec 31, 1994, 6.00  BP   Added ODMultitasker and ODPlatInit()
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              Nov 17, 1995  6.00  BP   Added multithreading functions.
+ *              Dec 13, 1995  6.00  BP   Added ODThreadWaitForExit().
+ *              Dec 13, 1995  6.00  BP   Added ODThreadGetCurrent().
+ *              Jan 23, 1996  6.00  BP   Added ODProcessExit().
+ *              Jan 30, 1996  6.00  BP   Add semaphore timeout.
+ *              Jan 31, 1996  6.00  BP   Add ODTimerLeft(), rm ODTimerSleep().
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODPLAT
+#define _INC_ODPLAT
+
+#include <time.h>
+
+#include "ODTypes.h"
+#include "ODGen.h"
+
+#ifdef ODPLAT_NIX
+#include <sys/time.h>
+#endif
+
+#ifdef ODPLAT_WIN32
+#include "windows.h"
+#endif /* ODPLAT_WIN32 */
+
+/* odplat.c initialization function prototype */
+void ODPlatInit(void);
+
+
+/* ========================================================================= */
+/* Millisecond timer functions.                                              */
+/* ========================================================================= */
+
+/* Timer data type. */
+typedef struct
+{
+#ifdef ODPLAT_DOS
+   clock_t Start;
+   clock_t Duration;
+#elif defined(ODPLAT_NIX)
+   time_t Start;
+   tODMilliSec Duration;
+#else /* !ODPLAT_DOS */
+   tODMilliSec Start;
+   tODMilliSec Duration;
+#endif /* !ODPLAT_DOS */
+} tODTimer;
+
+/* Timer function prototypes. */
+void ODTimerStart(tODTimer *pTimer, tODMilliSec Duration);
+BOOL ODTimerElapsed(tODTimer *pTimer);
+void ODTimerWaitForElapse(tODTimer *pTimer);
+tODMilliSec ODTimerLeft(tODTimer *pTimer);
+
+
+/* ========================================================================= */
+/* Multithreading and synchronization support.                               */
+/* ========================================================================= */
+
+#ifdef OD_MULTITHREADED
+
+/* Thread handle data type. */
+#ifdef ODPLAT_WIN32
+typedef HANDLE tODThreadHandle;
+#endif /* ODPLAT_WIN32 */
+
+/* Thread priority enumeration. */
+typedef enum
+{
+   OD_PRIORITY_LOWEST,
+   OD_PRIORITY_BELOW_NORMAL,
+   OD_PRIORITY_NORMAL,
+   OD_PRIORITY_ABOVE_NORMAL,
+   OD_PRIORITY_HIGHEST
+} tODThreadPriority;
+
+/* Thread start proceedure type. */
+#define OD_THREAD_FUNC WINAPI
+#ifdef ODPLAT_WIN32
+typedef DWORD (OD_THREAD_FUNC ptODThreadProc)(void *);
+#endif /* ODPLAT_WIN32 */
+
+/* Thread creation, temination and suspension. */
+tODResult ODThreadCreate(tODThreadHandle *phThread,
+   ptODThreadProc *pfThreadProc, void *pThreadParam);
+void ODThreadExit();
+tODResult ODThreadTerminate(tODThreadHandle hThread);
+tODResult ODThreadSuspend(tODThreadHandle hThread);
+tODResult ODThreadResume(tODThreadHandle hThread);
+tODResult ODThreadSetPriority(tODThreadHandle hThread,
+   tODThreadPriority ThreadPriority);
+void ODThreadWaitForExit(tODThreadHandle hThread);
+tODThreadHandle ODThreadGetCurrent(void);
+
+
+/* Semaphore handle data type. */
+#ifdef ODPLAT_WIN32
+typedef HANDLE tODSemaphoreHandle;
+#endif /* ODPLAT_WIN32 */
+
+/* Semaphore manipulation functions. */
+tODResult ODSemaphoreAlloc(tODSemaphoreHandle *phSemaphore, INT nInitialCount,
+   INT nMaximumCount);
+void ODSemaphoreFree(tODSemaphoreHandle hSemaphore);
+void ODSemaphoreUp(tODSemaphoreHandle hSemaphore, INT nIncrementBy);
+tODResult ODSemaphoreDown(tODSemaphoreHandle hSemaphore, tODMilliSec Timeout);
+
+#endif /* OD_MULTITHREADED */
+
+void ODProcessExit(INT nExitCode);
+
+
+/* ========================================================================= */
+/* DOS multitasker information.                                              */
+/* ========================================================================= */
+#ifdef ODPLAT_DOS
+typedef enum
+{
+   kMultitaskerNone,
+   kMultitaskerDV,
+   kMultitaskerWin,
+   kMultitaskerOS2
+} tODMultitasker;
+
+extern tODMultitasker ODMultitasker;
+#endif /* ODPLAT_DOS */
+
+
+/* ========================================================================= */
+/* Directory Access.                                                         */
+/* ========================================================================= */
+
+/* Open directory handle type. */
+typedef tODHandle tODDirHandle;
+
+/* Directory entry structure. */
+#define DIR_FILENAME_SIZE 1024
+
+#define DIR_ATTRIB_NORMAL   0x00
+#define DIR_ATTRIB_RDONLY   0x01
+#define DIR_ATTRIB_HIDDEN   0x02
+#define DIR_ATTRIB_SYSTEM   0x04
+#define DIR_ATTRIB_LABEL    0x08
+#define DIR_ATTRIB_DIREC    0x10
+#define DIR_ATTRIB_ARCH     0x20
+
+typedef struct
+{
+   char szFileName[DIR_FILENAME_SIZE];
+   WORD wAttributes;
+   time_t LastWriteTime;
+   DWORD dwFileSize;
+} tODDirEntry;
+
+/* Directory function prototypes. */
+tODResult ODDirOpen(CONST char *pszPath, WORD wAttributes, tODDirHandle *phDir);
+tODResult ODDirRead(tODDirHandle hDir, tODDirEntry *pDirEntry);
+void ODDirClose(tODDirHandle hDir);
+void ODDirChangeCurrent(char *pszPath);
+void ODDirGetCurrent(char *pszPath, INT nMaxPathChars);
+
+
+/* ========================================================================= */
+/* Miscellaneous Functions.                                                  */
+/* ========================================================================= */
+tODResult ODFileDelete(CONST char *pszPath);
+BOOL ODFileAccessMode(char *pszFilename, int nAccessMode);
+
+#endif /* !_INC_ODPLAT */

+ 716 - 0
odoors/ODPopup.c

@@ -0,0 +1,716 @@
+/* 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: ODPopUp.c
+ *
+ * Description: Implements od_popup_menu(), for displaying a menu in
+ *              a window, allowing the user to make a selection using
+ *              "hot keys" or by using arrow keys.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Jan 15, 1995  6.00  BP   Free menu structure on menu destroy.
+ *              Feb 02, 1995  6.00  BP   Added od_yield() call in for(;;) loop.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   Change valid range of nLevel to 0-10.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
+ *              Dec 23, 1995  6.00  BP   Restore original color on exit.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 04, 1996  6.00  BP   Use od_get_input().
+ *              Jan 12, 1996  6.00  BP   Claim exclusive use of arrow keys.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
+ *              Jan 31, 1996  6.00  BP   Add ODPopupCheckForKey() wait param.
+ *              Jan 31, 1996  6.00  BP   Ignore left & right if !MENU_PULLDOWN.
+ *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODKrnl.h"
+#include "ODStat.h"
+
+
+/* Configurable od_popup_menu() parameters. */
+
+/* Maximum menu level. */
+#define MENU_LEVELS        11
+
+/* Maximum number of items in a menu. */
+#define MAX_MENU_ITEMS     21
+
+/* Maximum width of any menu item. */
+#define MAX_ITEM_WIDTH     76
+
+
+/* Other manifest constants. */
+#define NO_COMMAND         -10
+
+
+/* Local data types. */
+
+/* Information on an individual menu item. */
+typedef struct
+{
+   char szItemText[MAX_ITEM_WIDTH + 1];
+   BYTE btKeyIndex;
+} tMenuItem;
+
+/* Information on a popup menu level. */
+typedef struct
+{
+   tMenuItem *paMenuItems;
+   BYTE btNumMenuItems;
+   BYTE btWidth;
+   BYTE btRight;
+   BYTE btBottom;
+   BYTE btCursor;
+   BYTE btLeft;
+   BYTE btTop;
+   WORD wFlags;
+   void *pWindow;
+} tMenuLevelInfo;
+
+
+/* Private variables. */
+
+/* Array of information on each menu level. */
+tMenuLevelInfo MenuLevelInfo[MENU_LEVELS];
+
+/* Current menu settings. */
+static BYTE btCorrectItem;
+static INT nCommand;
+static WORD wCurrentFlags;
+static BYTE btCurrentNumMenuItems;
+static INT nCurrentLevel;
+
+
+/* Private helper functions used by od_popup_menu(). */
+static void ODPopupCheckForKey(BOOL bWaitForInput);
+static void ODPopupDisplayMenuItem(BYTE btLeft, BYTE btTop,
+   tMenuItem *paMenuItems, BYTE btItemIndex, BOOL bHighlighted, BYTE btWidth,
+   BOOL bPositionCursor);
+
+
+/* ----------------------------------------------------------------------------
+ * od_popup_menu()
+ *
+ * Displays a popup menu on the local and remote screens.
+ *
+ * Parameters: pszTitle - Text to show as the window title of the popup menu.
+ *                        If no title is desired, this parameter should be set
+ *                        to either "" or NULL.
+ *
+ *             pszText  - String which contains the menu definition. In the
+ *                        menu definition string, individual menu items are
+ *                        separated by a pipe ('|') character, and hotkeys are
+ *                        proceeded by a carat ('^') character.
+ *
+ *             nLeft    - The 1-based column number of the upper right corner
+ *                        of the menu.
+ *
+ *             nTop     - The 1-based row number of the upper right corner of
+ *                        the menu.
+ *
+ *             nLevel   - Menu level, which must be a value between 0 and
+ *                        MENU_LEVELS.
+ *
+ *             uFlags   - One or more flags, combined by the bitwise or (|)
+ *                        operator.
+ *
+ *     Return: POPUP_ERROR on error, POPUP_ESCAPE if user pressed the Escape
+ *             key, POPUP_LEFT if the user choose to move to the next menu to
+ *             the left, POPUP_RIGHT if the user choose to move to the next
+ *             menu to the right, or a postive value if the user choose an item
+ *             from the menu. In this case, the return value is the 1-based
+ *             index of the selected menu item.
+ */
+ODAPIDEF INT ODCALL od_popup_menu(char *pszTitle, char *pszText, INT nLeft,
+   INT nTop, INT nLevel, WORD uFlags)
+{
+   tMenuItem *paMenuItems = NULL;
+   BYTE btCount;
+   BYTE btWidth;
+   BYTE btRight;
+   BYTE btBottom;
+   BYTE btCursor;
+   BYTE btLeft;
+   BYTE btTop;
+   void *pWindow;
+   BYTE btBetweenSize;
+   BYTE btTitleSize;
+   BYTE btRemaining;
+   BYTE btLineCount;
+   INT16 nOriginalAttrib;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_popup_menu()");
+
+   /* Initialize OpenDoors, if not already done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Setup od_box_chars appropriately. */
+   if(od_control.od_box_chars[BOX_BOTTOM] == 0)
+   {
+      od_control.od_box_chars[BOX_BOTTOM] = od_control.od_box_chars[BOX_TOP];
+   }
+   if(od_control.od_box_chars[BOX_RIGHT] == 0)
+   {
+      od_control.od_box_chars[BOX_RIGHT] = od_control.od_box_chars[BOX_LEFT];
+   }
+
+   /* Store initial display color. */
+   nOriginalAttrib = od_control.od_cur_attrib;
+
+
+   /* check level bounds */
+   if(nLevel < 0 || nLevel > MENU_LEVELS)
+   {
+      od_control.od_error = ERR_LIMIT;
+      OD_API_EXIT();
+      return(POPUP_ERROR);
+   }
+   /* normalize level */
+   nCurrentLevel = nLevel;
+
+   if(MenuLevelInfo[nLevel].pWindow == NULL)
+   {
+      btLeft = nLeft;
+      btTop = nTop;
+      wCurrentFlags = uFlags;
+
+      if(pszText == NULL)
+      {
+         od_control.od_error = ERR_PARAMETER;
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      if(paMenuItems == NULL)
+      {
+         if((paMenuItems = malloc(sizeof(tMenuItem) * MAX_MENU_ITEMS)) == NULL)
+         {
+            od_control.od_error = ERR_PARAMETER;
+            OD_API_EXIT();
+            return(POPUP_ERROR);
+         }
+      }
+      MenuLevelInfo[nLevel].paMenuItems = paMenuItems;
+
+      btCurrentNumMenuItems = 0;
+      btWidth = 0;
+      btCount = 0;
+      nCommand = NO_COMMAND;
+      paMenuItems[0].btKeyIndex = 0;
+      while(*pszText && btCurrentNumMenuItems < MAX_MENU_ITEMS)
+      {
+         switch(*pszText)
+         {
+            case '|':
+                  paMenuItems[btCurrentNumMenuItems++].szItemText[btCount]
+                     = '\0';
+                  if(btCount > btWidth) btWidth = btCount;
+                  btCount = 0;
+                  paMenuItems[btCurrentNumMenuItems].btKeyIndex = 0;
+               break;
+
+            case '^':
+               if(btCount < MAX_ITEM_WIDTH)
+               {
+                  paMenuItems[btCurrentNumMenuItems].btKeyIndex = btCount;
+               }
+               break;
+
+            default:
+               if(btCount < MAX_ITEM_WIDTH)
+               {
+                  paMenuItems[btCurrentNumMenuItems].szItemText[btCount++] =
+                     *pszText;
+               }
+         }
+         ++pszText;
+      }
+
+      /* If we were in the middle of a menu item when we encountered the end */
+      /* of the string, then it should form an additional menu entry. This   */
+      /* handles the case of a menu string to no terminating | for the last  */
+      /* entry.                                                              */
+      if(btCount != 0)
+      {
+         /* null-terminate current menu entry string */
+         paMenuItems[btCurrentNumMenuItems++].szItemText[btCount] = '\0';
+
+         /* If this is the widest entry, update he menu width appropriately  */
+         if(btCount > btWidth) btWidth = btCount;
+      }
+
+      /* If the menu description string does not contain any menu items */
+      if(btCurrentNumMenuItems == 0)
+      {
+         /* Return with parameter error */
+         od_control.od_error = ERR_PARAMETER;
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      /* Adjust menu width to allow title to fit, if possible               */
+      /* If a title string was passed, and that string is wider than widest */
+      /* menu entry ...                                                     */
+      if(pszTitle != NULL && strlen(pszTitle) + 2 > btWidth)
+      {
+         /* Then width of menu window should be large enough to allow up to */
+         /* the first 76 characters of the title to fit.                    */
+         btWidth = strlen(pszTitle) + 2 > MAX_ITEM_WIDTH
+            ? MAX_ITEM_WIDTH : strlen(pszTitle) + 2;
+      }
+
+      /* Based on number and size of menu items, and width of title,         */
+      /* determine the bottom, right and inside width of the menu.           */
+      btBottom = btTop + btCurrentNumMenuItems + 1;
+      btRight = btLeft + btWidth + 3;
+      btBetweenSize = (btRight - btLeft) - 1;
+
+      /* If neither ANSI nor AVATAR mode is available, return with an error */
+      if(!(od_control.user_ansi || od_control.user_avatar))
+      {
+         od_control.od_error = ERR_NOGRAPHICS;
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      /* If menu would "fall off" edge of screen, return with an error */
+      if(btLeft < 1 || btTop < 1 || btRight > OD_SCREEN_WIDTH
+         || btBottom > OD_SCREEN_HEIGHT || btRight - btLeft < 2
+         || btBottom - btTop < 2)
+      {
+         od_control.od_error = ERR_PARAMETER;
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      /* Allocate space to store window information. If unable to allocate */
+      /* enough space, return with an error.                               */
+      if((pWindow = malloc((btRight - btLeft + 1) * 2
+         + (btBottom - btTop + 1) * 160)) == NULL)
+      {
+         od_control.od_error = ERR_MEMORY;
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      /* Store contents of screen where memu will be drawn in the temporary */
+      /* buffer.                                                            */
+      if(!od_gettext(btLeft, btTop, btRight, btBottom, pWindow))
+      {
+         free(pWindow);
+         pWindow = NULL;
+
+         /* Note that od_error code has been set in od_gettext(). */
+         OD_API_EXIT();
+         return(POPUP_ERROR);
+      }
+
+      /* Determine number of characters of title to be displayed */
+      if(pszTitle == NULL)
+      {
+         btTitleSize = 0;
+      }
+      else
+      {
+         if((btTitleSize = strlen(pszTitle)) > (btBetweenSize - 4))
+         {
+            btTitleSize = btBetweenSize - 4;
+         }
+      }
+
+      od_set_cursor(btTop,btLeft);
+      od_set_attrib(od_control.od_menu_border_col);
+      od_putch(od_control.od_box_chars[BOX_UPPERLEFT]);
+      if(btTitleSize == 0)
+      {
+         od_repeat(od_control.od_box_chars[BOX_TOP], btBetweenSize);
+      }
+      else
+      {
+         od_repeat(od_control.od_box_chars[BOX_TOP],
+            btRemaining = ((btBetweenSize - btTitleSize - 2) / 2));
+         od_set_attrib(od_control.od_menu_title_col);
+         od_putch(' ');
+         od_disp(pszTitle,btTitleSize, TRUE);
+         od_putch(' ');
+         od_set_attrib(od_control.od_menu_border_col);
+         od_repeat(od_control.od_box_chars[BOX_TOP],
+            (BYTE)(btBetweenSize - btRemaining - btTitleSize - 2));
+      }
+      od_putch(od_control.od_box_chars[BOX_UPPERRIGHT]);
+
+      btLineCount = btTop + 1;
+      btCorrectItem = 0;
+      ODPopupCheckForKey(FALSE);
+      btCursor = btCorrectItem;
+      for(btCount = 0; btCount < btCurrentNumMenuItems
+         && btLineCount < btBottom; ++btCount)
+      {
+         ODPopupCheckForKey(FALSE);
+         if(nCommand != NO_COMMAND && !(wCurrentFlags & MENU_KEEP))
+         {
+            goto exit_now;
+         }
+
+         od_set_cursor(btLineCount,btLeft);
+         od_putch(od_control.od_box_chars[BOX_LEFT]);
+         od_set_attrib(od_control.od_menu_text_col);
+
+         if(btCount == btCursor)
+         {
+            ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCount,
+               TRUE, btWidth, FALSE);
+         }
+         else
+         {
+            ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCount,
+               FALSE, btWidth, FALSE);
+         }
+
+         od_set_attrib(od_control.od_menu_border_col);
+         od_putch(od_control.od_box_chars[BOX_RIGHT]);
+         ++btLineCount;
+      }
+
+      od_set_cursor(btBottom, btLeft);
+      od_putch(od_control.od_box_chars[BOX_LOWERLEFT]);
+      od_repeat(od_control.od_box_chars[BOX_BOTTOM], btBetweenSize);
+      od_putch(od_control.od_box_chars[BOX_LOWERRIGHT]);
+      od_set_cursor(btTop + 1, btLeft + 1);
+   }
+   else
+   {
+      paMenuItems = MenuLevelInfo[nLevel].paMenuItems;
+      btCurrentNumMenuItems = MenuLevelInfo[nLevel].btNumMenuItems;
+      btWidth = MenuLevelInfo[nLevel].btWidth;
+      btRight = MenuLevelInfo[nLevel].btRight;
+      btBottom = MenuLevelInfo[nLevel].btBottom;
+      btLeft = MenuLevelInfo[nLevel].btLeft;
+      btTop = MenuLevelInfo[nLevel].btTop;
+      wCurrentFlags = MenuLevelInfo[nLevel].wFlags;
+      pWindow = MenuLevelInfo[nLevel].pWindow;
+      btCorrectItem = btCursor = MenuLevelInfo[nLevel].btCursor;
+      nCommand = NO_COMMAND;
+
+      if(uFlags & MENU_DESTROY)
+      {
+         nCommand = POPUP_ESCAPE;
+         goto destroy;
+      }
+
+      /* Otherwise, position flashing hardware cursor appropriately */
+      od_set_cursor(btTop + btCursor + 1, btLeft + 1);
+   }
+
+   /* Claim exclusive use of arrow keys. */
+   ODStatStartArrowUse();
+
+   for(;;)
+   {
+      ODPopupCheckForKey(TRUE);
+      if(btCorrectItem != btCursor)
+      {
+         ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCursor,
+            FALSE, btWidth, TRUE);
+         btCursor = btCorrectItem;
+         ODWaitDrain(25);
+         ODPopupCheckForKey(FALSE);
+         ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCursor,
+            TRUE, btWidth, TRUE);
+      }
+
+      if(nCommand != NO_COMMAND)
+      {
+         goto exit_now;
+      }
+   }
+
+exit_now:
+   if((!(wCurrentFlags & MENU_KEEP)) || nCommand <= 0)
+   {
+destroy:
+      od_puttext(btLeft, btTop, btRight, btBottom, pWindow);
+      free(pWindow);
+      MenuLevelInfo[nLevel].pWindow = NULL;
+      if(paMenuItems != NULL)
+      {
+         free(paMenuItems);
+         MenuLevelInfo[nLevel].paMenuItems = NULL;
+      }
+   }
+   else if(wCurrentFlags & MENU_KEEP)
+   {
+      MenuLevelInfo[nLevel].paMenuItems = paMenuItems;
+      MenuLevelInfo[nLevel].btNumMenuItems = btCurrentNumMenuItems;
+      MenuLevelInfo[nLevel].btWidth = btWidth;
+      MenuLevelInfo[nLevel].btRight = btRight;
+      MenuLevelInfo[nLevel].btBottom = btBottom;
+      MenuLevelInfo[nLevel].btCursor = btCursor;
+      MenuLevelInfo[nLevel].btLeft = btLeft;
+      MenuLevelInfo[nLevel].btTop = btTop;
+      MenuLevelInfo[nLevel].wFlags = wCurrentFlags;
+      MenuLevelInfo[nLevel].pWindow = pWindow;
+   }
+
+   /* Restore original display color. */
+   od_set_attrib(nOriginalAttrib);
+
+   /* Release exclusive use of arrow keys. */
+   ODStatEndArrowUse();
+
+   OD_API_EXIT();
+   return(nCommand);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODPopupCheckForKey()                                *** PRIVATE FUNCTION ***
+ *
+ * Checks whether or not the user has pressed any key. If one or more keys
+ * have been pressed, then these keystrokes are processed. This function
+ * returns when no more keys are waiting in the inbound buffer, or when a key
+ * has been pressed that requires immediate action (such as the [ENTER] key).
+ *
+ * Parameters: bWaitForInput - Indicates whether this function should return
+ *                             immediately if no input is waiting (FALSE), or
+ *                             wait for the next input even before returning
+ *                             (TRUE).
+ *
+ *     Return: void
+ */
+static void ODPopupCheckForKey(BOOL bWaitForInput)
+{
+   BYTE btCount;
+   tODInputEvent InputEvent;
+   BOOL bDoneAnythingYet = FALSE;
+
+   /* Loop, processing keys. If a command has been selected, stop looping */
+   /* immediately. If there are no more keys waiting, stop looping        */
+   while(nCommand == NO_COMMAND)
+   {
+      CALL_KERNEL_IF_NEEDED();
+
+      if(!od_get_input(&InputEvent, bWaitForInput && !bDoneAnythingYet
+         ? OD_NO_TIMEOUT : 0, GETIN_NORMAL))
+      {
+         /* Return right away if no input event is waiting. */
+         return;
+      }
+
+      bDoneAnythingYet = TRUE;
+
+      if(InputEvent.EventType == EVENT_EXTENDED_KEY)
+      {
+         switch(InputEvent.chKeyPress)
+         {
+            case OD_KEY_UP:
+up_arrow:
+               if(btCorrectItem == 0)
+               {
+                  btCorrectItem = btCurrentNumMenuItems - 1;
+               }
+               else
+               {
+                  --btCorrectItem;
+               }
+               break;
+
+            case OD_KEY_DOWN:
+down_arrow:
+               if(++btCorrectItem >= btCurrentNumMenuItems)
+               {
+                  btCorrectItem = 0;
+               }
+               break;
+
+            case OD_KEY_LEFT:
+left_arrow:
+               if(wCurrentFlags & MENU_PULLDOWN)
+               {
+                  nCommand = POPUP_LEFT;
+                  return;
+               }
+               break;
+
+            case OD_KEY_RIGHT:
+right_arrow:
+               if(wCurrentFlags & MENU_PULLDOWN)
+               {
+                  nCommand = POPUP_RIGHT;
+                  return;
+               }
+               break;
+         }
+      }
+
+      else if(InputEvent.EventType == EVENT_CHARACTER)
+      {
+         if(InputEvent.chKeyPress == '\n' || InputEvent.chKeyPress == '\r')
+         {
+            nCommand = btCorrectItem + 1;
+            return;
+         }
+
+         else if(InputEvent.chKeyPress == 27)
+         {
+            if(wCurrentFlags & MENU_ALLOW_CANCEL)
+            {
+               nCommand = POPUP_ESCAPE;
+               return;
+            }
+         }
+
+         else
+         {
+            /* Check whether key is a menu "hot key" */
+            for(btCount = 0; btCount < btCurrentNumMenuItems; ++btCount)
+            {
+               if(toupper(MenuLevelInfo[nCurrentLevel].paMenuItems[btCount]
+                  .szItemText[MenuLevelInfo[nCurrentLevel].paMenuItems[btCount]
+                  .btKeyIndex]) == toupper(InputEvent.chKeyPress))
+               {
+                  btCorrectItem = btCount;
+                  nCommand = btCorrectItem + 1;
+                  return;
+               }
+            }
+
+            /* At this point, we know that key was not one of the "hot keys" */
+            /* Check for 4, 6, 8 and 2 keys as arrow keys.                   */
+            if(InputEvent.chKeyPress == '4')
+            {
+               goto left_arrow;
+            }
+            else if(InputEvent.chKeyPress == '6')
+            {
+               goto right_arrow;
+            }
+            else if(InputEvent.chKeyPress == '8')
+            {
+               goto up_arrow;
+            }
+            else if(InputEvent.chKeyPress == '2')
+            {
+               goto down_arrow;
+            }
+         }
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODPopupDisplayMenuItem()                            *** PRIVATE FUNCTION ***
+ *
+ * Displays an individual menu item.
+ *
+ * Parameters: btLeft          - Column number where the menu item will be
+ *                               displayed.
+ *
+ *             btTop           - Row number where the menu item will be
+ *                               displayed.
+ *
+ *             paMenuItems     - Pointer to array of available menu items.
+ *
+ *             btItemIndex     - Index into paMenuItems of the menu item that
+ *                               is to be displayed.
+ *
+ *             bHighlighted    - TRUE if the items is to be displayed as
+ *                               highlighted, FALSE if it is to be displayed as
+ *                               non-highlighted.
+ *
+ *             btWidth         - Width of the menu item, in characters.
+ *
+ *             bPositionCursor - TRUE if the cursor needs to be positioned
+ *                               prior to drawing the menu item, FALSE if the
+ *                               cursor is already in the required position.
+ *
+ *     Return: void
+ */
+static void ODPopupDisplayMenuItem(BYTE btLeft, BYTE btTop,
+   tMenuItem *paMenuItems, BYTE btItemIndex, BOOL bHighlighted, BYTE btWidth,
+   BOOL bPositionCursor)
+{
+   BYTE btCount;
+   char *pchItemText;
+   BYTE btKeyPosition;
+   BYTE btTextColor;
+   BYTE btKeyColor;
+
+   /* Check that parameters are reasonable when operating in debug mode. */
+   ASSERT(paMenuItems != NULL);
+   ASSERT(btItemIndex < MAX_MENU_ITEMS);
+   ASSERT(btWidth < OD_SCREEN_WIDTH);
+
+   ++btLeft;
+   ++btTop;
+
+   btTextColor = bHighlighted ? od_control.od_menu_highlight_col
+      : od_control.od_menu_text_col;
+   btKeyColor = bHighlighted ? od_control.od_menu_highkey_col
+      : od_control.od_menu_key_col;
+
+   pchItemText = (char *)(paMenuItems[btItemIndex].szItemText);
+   btKeyPosition = paMenuItems[btItemIndex].btKeyIndex;
+
+   if(bPositionCursor) od_set_cursor(btTop + btItemIndex, btLeft);
+
+   od_set_attrib(btTextColor);
+   od_putch(' ');
+
+   for(btCount = 0; btCount < btWidth && *pchItemText; ++btCount)
+   {
+       if(btCount == btKeyPosition)
+       {
+          od_set_attrib(btKeyColor);
+          od_putch(*pchItemText++);
+          od_set_attrib(btTextColor);
+       }
+       else
+       {
+          od_putch(*pchItemText++);
+       }
+   }
+
+   od_repeat(' ', (BYTE)((btWidth - btCount) + 1));
+
+   if(bPositionCursor) od_set_cursor(btTop + btItemIndex, btLeft);
+}

+ 197 - 0
odoors/ODPrntf.c

@@ -0,0 +1,197 @@
+/* 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: ODPrntf.c
+ *
+ * Description: Implements the od_printf() function.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              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 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Use ODVCALL instead of ODCALL.
+ *              Jan 04, 1996  6.00  BP   Add missing OD_API_EXIT() at end.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODKrnl.h"
+
+
+/* Size of od_printf() working buffer. Adjust this upwards if you are    */
+/* encountering difficulties when calling od_printf() with long strings. */
+#define WORK_BUFFER_SIZE   2048
+
+
+/* ----------------------------------------------------------------------------
+ * od_printf()
+ *
+ * The OpenDoors equivalent of the C printf() function, this function performs
+ * formatted string output to both the local and remote screens.
+ *
+ * Parameters: pszFormat - Format string, in the same format as the printf()
+ *                         format string.
+ *
+ *             The semantics of any further parameters are dicated by the
+ *             contents of the format string.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODVCALL od_printf(const char *pszFormat,...)
+{
+   va_list pArgumentList;
+   static char *pszWorkBuffer = NULL;
+   char *pchCurrent;
+   char *pchStart;
+   BOOL bNotFound;
+   INT nCharCount;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_printf()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   /* Allocate work buffer if none has been allocated yet. */
+   if(pszWorkBuffer == NULL &&
+      (pszWorkBuffer = malloc(WORK_BUFFER_SIZE)) == NULL)
+   {
+      /* If we are unable to allocate a buffer, return with a memory error. */
+      od_control.od_error = ERR_MEMORY;
+      OD_API_EXIT();
+      return;
+   }
+
+   /* Copy the arguments after the format string. */
+   va_start(pArgumentList, pszFormat);
+
+   /* Perform a string printf to the working buffer. */
+   vsnprintf(pszWorkBuffer, WORK_BUFFER_SIZE, pszFormat, pArgumentList);
+
+   va_end(pArgumentList);
+
+   /* If no color characters are defined, then just display the entire */
+   /* buffer in one shot.                                              */
+   if(!od_control.od_color_char && !od_control.od_color_delimiter)
+       goto quick_print;
+
+   chColorCheck = od_control.od_color_delimiter;
+
+   bNotFound = TRUE;
+   pchCurrent = (char *)pszWorkBuffer;
+   pchStart = (char *)pszWorkBuffer;
+   nCharCount = 0;
+   while(*pchCurrent)
+   {
+      if(*pchCurrent == od_control.od_color_delimiter)
+      {
+         bNotFound = FALSE;
+
+         if(nCharCount != 0)
+         {
+            od_disp(pchStart, nCharCount, TRUE);
+         }
+
+         if(!*(++pchCurrent))
+         {
+            chColorCheck = 0;
+            OD_API_EXIT();
+            return;
+         }
+         od_set_attrib(od_color_config(pchCurrent));
+         if(!*(pchCurrent = (char *)pchColorEndPos))
+         {
+            chColorCheck = 0;
+            OD_API_EXIT();
+            return;
+         }
+
+         if(!*(++pchCurrent))
+         {
+            OD_API_EXIT();
+            return;
+         }
+         pchStart = (char *)pchCurrent;
+         nCharCount = 0;
+      }
+
+      else if(*pchCurrent == od_control.od_color_char)
+      {
+         bNotFound = FALSE;
+
+         if(nCharCount != 0)
+         {
+            od_disp(pchStart, nCharCount, TRUE);
+         }
+
+         if(!*(++pchCurrent))
+         {
+            OD_API_EXIT();
+            return;
+         }
+         od_set_attrib(*pchCurrent);
+
+         if(!*(++pchCurrent))
+         {
+            OD_API_EXIT();
+            return;
+         }
+         pchStart = (char *)pchCurrent;
+         nCharCount = 0;
+      }
+      else
+      {
+         ++nCharCount;
+         ++pchCurrent;
+      }
+   }
+
+   chColorCheck = 0;
+
+   if(bNotFound)
+   {
+quick_print:
+      /* Display the entire string in one shot. */
+      od_disp_str(pszWorkBuffer);
+   }
+   else if(nCharCount != 0)
+   {
+      /* If there are remaining characters in the string, then display them. */
+      od_disp(pchStart, nCharCount, TRUE);
+   }
+
+   OD_API_EXIT();
+}

+ 619 - 0
odoors/ODRA.c

@@ -0,0 +1,619 @@
+/* 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: ODRA.c
+ *
+ * Description: Implements the RemoteAccess personality.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Oct 19, 1994  6.00  BP   Use new od_page constants.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Jul 18, 1995  6.00  BP   Fix ODStatGetUserAge() bug.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Dec 24, 1995  6.00  BP   Fixed black square at pos 25x80.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Display connect speed with %lu.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODStat.h"
+#include "ODInEx.h"
+
+
+/* Private variables, local to this module. */
+static BOOL bRAPersHasBeenOn = FALSE;
+
+
+/* Private function prototypes. */
+static void ODRADisplayPageInfo(void);
+static void ODRADisplayDate(char *pszDateString);
+static void ODRADisplayFlags(BYTE btFlags);
+static void ODRADisplayTime(void);
+
+
+/* ----------------------------------------------------------------------------
+ * pdef_ra()
+ *
+ * Personality function for the RemoteAccess-like status line / function key
+ * personality.
+ *
+ * Parameters: btOperation - Indicates personality operation to be performed.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL pdef_ra(BYTE btOperation)
+{
+   BYTE btInfoType = od_control.od_info_type;
+
+   switch(btOperation)
+   {
+      case PEROP_DISPLAY1:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("                                                                     (Node      ");
+         ODScrnSetCursorPos(1, 24);
+         ODScrnPrintf("%s of %s at %lu BPS", od_control.user_name,
+            od_control.user_location, od_control.od_connect_speed);
+
+         if(!od_control.od_user_keyboard_on)
+         {
+            ODScrnSetCursorPos(49, 24);
+            ODScrnSetAttribute(0x99);
+            ODScrnDisplayString("(Keyboard)");
+            ODScrnSetAttribute(0x70);
+            bRAPersHasBeenOn = TRUE;
+         }
+
+         ODRADisplayPageInfo();
+
+         ODScrnSetCursorPos(76, 24);
+         if(od_control.od_node < 1000)
+         {
+            ODScrnPrintf("%u)", od_control.od_node);
+         }
+         else
+         {
+            ODScrnDisplayString("?)");
+         }
+         ODScrnSetCursorPos(1, 25);
+         ODScrnDisplayString("Security:        Time:                                               (F9)=Help ");
+
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+
+         ODScrnSetCursorPos(11, 25);
+         ODScrnPrintf("%u", od_control.user_security);
+         ODScrnSetCursorPos(24, 25);
+         ODScrnPrintf("%d mins   ", od_control.user_timelimit);
+         if(od_control.user_ansi)
+         {
+            ODScrnSetCursorPos(42, 25);
+            ODScrnDisplayString("(ANSI)");
+         }
+
+         if(od_control.user_avatar)
+         {
+            ODScrnSetCursorPos(48, 25);
+            ODScrnDisplayString("(AVT)");
+         }
+
+         if(od_control.sysop_next)
+         {
+            ODScrnSetCursorPos(53, 25);
+            ODScrnDisplayString("(SN) ");
+         }
+
+         if(od_control.user_wantchat)
+         {
+            ODScrnSetCursorPos(57, 25);
+            ODScrnSetAttribute(0x99);
+            ODScrnDisplayString("(Wants Chat)");
+            ODScrnSetAttribute(0x70);
+         }
+         break;
+
+
+      case PEROP_DISPLAY2:
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("Voice#:               Last Call   :                       First Call:           ");
+         ODScrnSetCursorPos(1, 25);
+         ODScrnDisplayString("Data #:               Times Called:            Age:        Birthdate:          ");
+         if(od_control.od_extended_info || btInfoType == SFDOORSDAT
+            || btInfoType == DOORSYS_GAP || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnSetCursorPos(8, 24);
+            ODScrnPrintf("%13.13s", od_control.user_homephone);
+         }
+         if(od_control.od_extended_info || btInfoType == DOORSYS_GAP
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnSetCursorPos(8, 25);
+            ODScrnPrintf("%13.13s", od_control.user_dataphone);
+         }
+         if(od_control.od_extended_info || btInfoType == DOORSYS_GAP
+            || btInfoType == CHAINTXT || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnSetCursorPos(37, 24);
+            ODRADisplayDate(od_control.user_lastdate);
+         }
+         if(od_control.od_extended_info || btInfoType == DOORSYS_GAP
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnSetCursorPos(37, 25);
+            ODScrnPrintf("%lu", od_control.user_numcalls);
+         }
+         if(btInfoType == RA1EXITINFO || btInfoType == RA2EXITINFO
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            char szUserAge[7];
+            ODScrnSetCursorPos(53, 25);
+            ODStatGetUserAge(szUserAge);
+            ODScrnDisplayString(szUserAge);
+            ODScrnSetCursorPos(71, 24);
+            ODRADisplayDate(od_control.user_firstcall);
+            ODScrnSetCursorPos(71, 25);
+            ODRADisplayDate(od_control.user_birthday);
+         }
+         break;
+
+
+      case PEROP_DISPLAY3:
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1, 24);
+         if(od_control.od_extended_info || btInfoType == SFDOORSDAT
+            || btInfoType == DOORSYS_GAP || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnDisplayString("Uploads:                Downloads:               Tagged: 0k (0)                 ");
+            if(btInfoType == DOORSYS_GAP)
+            {
+               ODScrnSetCursorPos(10, 24);
+               ODScrnPrintf("%lu", od_control.user_uploads);
+               ODScrnSetCursorPos(36, 24);
+               ODScrnPrintf("%lu", od_control.user_downloads);
+            }
+            else
+            {
+               ODScrnSetCursorPos(10, 24);
+               ODScrnPrintf("%luk (%lu)", od_control.user_upk,
+                  od_control.user_uploads);
+               ODScrnSetCursorPos(36,24);
+               ODScrnPrintf("%luk (%lu)", od_control.user_downk,
+                  od_control.user_downloads);
+            }
+         }
+         else
+         {
+            ODScrnDisplayString("                                                                                ");
+         }
+         ODScrnSetCursorPos(1, 25);
+         if(od_control.od_extended_info)
+         {
+            ODScrnDisplayString("Flags: (A):--------  (B):--------  (C):--------  (D):--------                  ");
+            ODScrnSetCursorPos(12, 25);
+            ODRADisplayFlags(od_control.user_flags[0]);
+            ODScrnSetCursorPos(26, 25);
+            ODRADisplayFlags(od_control.user_flags[1]);
+            ODScrnSetCursorPos(40, 25);
+            ODRADisplayFlags(od_control.user_flags[2]);
+            ODScrnSetCursorPos(54, 25);
+            ODRADisplayFlags(od_control.user_flags[3]);
+         }
+         else
+         {
+            ODScrnDisplayString("                                                                               ");
+         }
+         break;
+
+
+      case PEROP_DISPLAY4:
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("                                                                   (Time      ) ");
+         if(od_control.od_extended_info)
+         {
+            ODScrnSetCursorPos(1, 24);
+            ODScrnPrintf("Last Caller: %s    Total System Calls: %lu",
+               od_control.system_last_caller, od_control.system_calls);
+         }
+         ODRADisplayTime();
+
+         ODScrnSetCursorPos(1, 25);
+         if(od_control.od_extended_info || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnDisplayString("(Printer OFF)      (Local Screen ON )        Next Event                        ");
+            ODScrnSetCursorPos(57, 25);
+            if(od_control.event_status == ES_ENABLED
+               || btInfoType == DOORSYS_WILDCAT)
+            {
+               ODScrnPrintf("(%s, errorlevel %u)",od_control.event_starttime,od_control.event_errorlevel);
+            }
+            else
+            {
+               ODScrnDisplayString("none, errorlevel 0");
+            }
+         }
+         else
+         {
+            ODScrnDisplayString("                                                                               ");
+         }
+         break;
+
+
+      case PEROP_DISPLAY5:
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1, 24);
+         if(od_control.od_extended_info || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnDisplayString("Msgs posted    :           Highread :                                Group 1    ");
+            ODScrnSetCursorPos(18,24);
+            ODScrnPrintf("%u",od_control.user_messages);
+            ODScrnSetCursorPos(39,24);
+            ODScrnPrintf("%u",od_control.user_lastread);
+            if(btInfoType == RA1EXITINFO || btInfoType == RA2EXITINFO)
+            {
+               ODScrnSetCursorPos(76, 24);
+               ODScrnPrintf("%u", od_control.user_group);
+            }
+         }
+         else
+         {
+            ODScrnDisplayString("                                                                                ");
+         }
+
+         ODScrnSetCursorPos(1, 25);
+         if(od_control.od_extended_info || btInfoType == CHAINTXT
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnDisplayString("Credit         :           Handle   :                                          ");
+            if(btInfoType == EXITINFO || btInfoType == RA1EXITINFO
+               || btInfoType == RA2EXITINFO)
+            {
+               ODScrnSetCursorPos(18, 25);
+               ODScrnPrintf("%u.00", (unsigned int)od_control.user_net_credit);
+            }
+            if(btInfoType == CHAINTXT || btInfoType == RA1EXITINFO
+               || btInfoType == RA2EXITINFO || btInfoType == DOORSYS_WILDCAT)
+            {
+               ODScrnSetCursorPos(39, 25);
+               ODScrnDisplayString(od_control.user_handle);
+            }
+         }
+         else
+         {
+            ODScrnDisplayString("                                                                               ");
+         }
+         break;
+
+
+      case PEROP_DISPLAY6:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("                                                                                ");
+         ODScrnSetCursorPos(1, 25);
+         ODScrnDisplayString("                                                                               ");
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         if(btInfoType == RA1EXITINFO || btInfoType == RA2EXITINFO
+            || btInfoType == DOORSYS_WILDCAT)
+         {
+            ODScrnSetCursorPos(1, 24);
+            ODScrnDisplayString(od_control.user_comment);
+         }
+         if(od_control.user_wantchat
+            && strlen(od_control.user_reasonforchat) !=0 )
+         {
+            ODScrnSetCursorPos(1, 25);
+            strcpy(szStatusText, od_control.user_reasonforchat);
+            szStatusText[69 - strlen(od_control.user_name)] = '\0';
+            ODScrnPrintf("Chat (%s): %s", od_control.user_name, szStatusText);
+         }
+         break;
+
+      case PEROP_DISPLAY7:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("                                                                                ");
+         ODScrnSetCursorPos(1, 25);
+         ODScrnDisplayString("                                                                               ");
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         break;
+
+      case PEROP_DISPLAY8:
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString("ALT: (C)hat (H)angup (J)Shell (L)ockOut (K)eyboard (N)extOn (D)rop To BBS       ");
+         ODScrnDisplayString("                                   -Inc Time -Dec Time  (F1)-(F7)=Extra Stats");
+         break;
+
+      case PEROP_UPDATE1:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(24, 25);
+
+         ODScrnPrintf("%d mins   ", od_control.user_timelimit);
+
+         ODScrnSetCursorPos(42, 25);
+         if(od_control.user_ansi)
+         {
+            ODScrnDisplayString("(ANSI)");
+         }
+         else
+         {
+            ODScrnDisplayString("      ");
+         }
+
+         if(od_control.user_avatar)
+         {
+            ODScrnDisplayString("(AVT)");
+         }
+         else
+         {
+            ODScrnDisplayString("     ");
+         }
+
+         if(od_control.sysop_next)
+         {
+            ODScrnDisplayString("(SN)");
+         }
+         else
+         {
+            ODScrnDisplayString("    ");
+         }
+
+         if(od_control.user_wantchat)
+         {
+            ODScrnSetAttribute(0x99);
+            ODScrnDisplayString("(Wants Chat)");
+            ODScrnSetAttribute(0x70);
+         }
+         else
+         {
+            ODScrnDisplayString("            ");
+         }
+
+         ODRADisplayPageInfo();
+
+         if(od_control.od_user_keyboard_on && bRAPersHasBeenOn)
+         {
+            ODScrnSetCursorPos(49, 24);
+            ODScrnDisplayString("          ");
+         }
+         if(!od_control.od_user_keyboard_on)
+         {
+            bRAPersHasBeenOn = TRUE;
+            ODScrnSetCursorPos(49, 24);
+            ODScrnSetAttribute(0x99);
+            ODScrnDisplayString("(Keyboard)");
+            ODScrnSetAttribute(0x70);
+         }
+
+         break;
+
+      case PEROP_UPDATE4:
+         ODScrnSetAttribute(0x70);
+         ODRADisplayTime();
+         break;
+
+      case PEROP_INITIALIZE:
+         bRAStatus = TRUE;
+         od_control.key_hangup = 0x2300;
+         od_control.key_drop2bbs = 0x2000;
+         od_control.key_dosshell = 0x2400;
+         od_control.key_chat = 0x2e00;
+         od_control.key_sysopnext = 0x3100;
+         od_control.key_lockout = 0x2600;
+         od_control.key_status[0] = 0x3b00;
+         od_control.key_status[1] = 0x3c00;
+         od_control.key_status[2] = 0x3d00;
+         od_control.key_status[3] = 0x3e00;
+         od_control.key_status[4] = 0x3f00;
+         od_control.key_status[5] = 0x4000;
+         od_control.key_status[6] = 0x4100;
+         od_control.key_status[7] = 0x4300;
+         od_control.key_status[8] = 0x4400;
+         od_control.key_keyboardoff = 0x2500;
+         od_control.key_moretime = 0x4800;
+         od_control.key_lesstime = 0x5000;
+         od_control.od_page_statusline = 5;
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODRADisplayPageInfo()                               *** PRIVATE FUNCTION ***
+ *
+ * Displays the current sysop page information on the RemoteAccess status line,
+ * at the appropriate position, and in the appropriate color.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODRADisplayPageInfo(void)
+{
+   time_t nUnixTime;
+   struct tm *TimeBlock;
+   BOOL bFailed = FALSE;
+   INT nMinute;
+
+   ODScrnSetCursorPos(60, 24);
+
+   switch(od_control.od_okaytopage)
+   {
+      case PAGE_ENABLE:
+         ODScrnSetAttribute(0x19);
+         ODScrnDisplayString("(PAGE ON) ");
+         ODScrnSetAttribute(0x70);
+         break;
+
+      case PAGE_DISABLE:
+         ODScrnSetAttribute(0x19);
+         ODScrnDisplayString("(PAGE OFF)");
+         ODScrnSetAttribute(0x70);
+         break;
+
+      case PAGE_USE_HOURS:
+         nUnixTime = time(NULL);
+         TimeBlock = localtime(&nUnixTime);
+         nMinute = (60 * TimeBlock->tm_hour) + TimeBlock->tm_min;
+         if(od_control.od_pagestartmin < od_control.od_pageendmin)
+         {
+            if(nMinute < od_control.od_pagestartmin
+               || nMinute >= od_control.od_pageendmin)
+            {
+               bFailed = TRUE;
+            }
+         }
+         else
+         {
+            if(nMinute < od_control.od_pagestartmin
+               && nMinute >= od_control.od_pageendmin)
+            {
+               bFailed=TRUE;
+            }
+         }
+
+         if(bFailed)
+         {
+            ODScrnDisplayString("(PAGE OFF)");
+         }
+         else
+         {
+            ODScrnDisplayString("(PAGE ON) ");
+         }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODRADisplayTime()                                   *** PRIVATE FUNCTION ***
+ *
+ * Displays the current time on the RemoteAccess status line, at the
+ * appropriate position. The time is displayed in the current color.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODRADisplayTime(void)
+{
+   time_t nUnixTime;
+   struct tm *TimeBlock;
+
+   nUnixTime = time(NULL);
+   TimeBlock = localtime(&nUnixTime);
+   ODScrnSetCursorPos(74, 24);
+   ODScrnPrintf("%02.2d:%02.2d", TimeBlock->tm_hour, TimeBlock->tm_min);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODRADisplayDate()                                   *** PRIVATE FUNCTION ***
+ *
+ * Displays a date stored in a string on the RemoteAccess status line, if
+ * the string represents a valid date. The date is displayed at the current
+ * cursor location, in the current color.
+ *
+ * Parameters: pszDateString - Pointer to a string containing the date to
+ *                             display.
+ *
+ *     Return: void
+ */
+static void ODRADisplayDate(char *pszDateString)
+{
+   INT nMonth;
+   INT nTemp;
+
+   ASSERT(pszDateString != NULL);
+
+   if(strlen(pszDateString) != 8) return;
+
+   nMonth = atoi(pszDateString) - 1;
+   if(nMonth < 0 || nMonth > 11) return;
+
+   nTemp=atoi((char *)pszDateString + 3);
+   if(nTemp < 1 || nTemp > 31) return;
+
+   if(pszDateString[6] < '0' || pszDateString[6] > '9'
+      || pszDateString[7] < '0' || pszDateString[7] > '9')
+   {
+      return;
+   }
+
+   ODScrnDisplayChar(pszDateString[3]);
+   ODScrnDisplayChar(pszDateString[4]);
+   ODScrnDisplayChar('-');
+   ODScrnDisplayString(od_control.od_month[nMonth]);
+   ODScrnDisplayChar('-');
+   ODScrnDisplayChar(pszDateString[6]);
+   ODScrnDisplayChar(pszDateString[7]);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODRADisplayFlags()                                  *** PRIVATE FUNCTION ***
+ *
+ * Displays sysop-defined user flags on the status line. The flags are
+ * displayed at the current cursor location, in the current color.
+ *
+ * Parameters: btFlags - Byte of eight flags to display.
+ *
+ *     Return: void
+ */
+static void ODRADisplayFlags(BYTE btFlags)
+{
+   BYTE btMask = 0x01;
+   INT nFlag;
+
+   for(nFlag = 0; nFlag < 8; ++nFlag)
+   {
+      if(btFlags & btMask)
+      {
+         ODScrnDisplayChar('X');
+      }
+      else
+      {
+         ODScrnDisplayChar('-');
+      }
+      btMask <<= 1;
+   }
+}

BIN
odoors/ODRes.aps


+ 78 - 0
odoors/ODRes.h

@@ -0,0 +1,78 @@
+/* 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: ODRes.h
+ *
+ * Description: OpenDoors resource-related definitions. This file is only
+ *              applicable when building the Win32 version of OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Dec 02, 1995  6.00  BP   Created.
+ *              Jan 20, 1996  6.00  BP   Added login dialog box.
+ *              Jan 21, 1996  6.00  BP   Added message dialog box.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 23, 1996  6.00  BP   Remove unused IDs.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 14, 1996  6.10  BP   Added configuration menu option.
+ */
+
+/* Resource IDs. */
+#define IDR_FRAME_MENU                  200
+#define IDD_ABOUT                       201
+#define IDI_OPENDOORS                   202
+#define IDB_TOOLBAR                     203
+#define IDR_FRAME                       204
+#define IDD_LOGIN                       205
+#define IDI_MESSAGE_INFO                206
+#define IDD_MESSAGE                     207
+
+
+/* Dialog box control IDs. (The same values can safely be used by other */
+/* dialog boxes.)                                                       */
+/* Help dialog box. */
+#define IDC_DOORNAME                    1000
+#define IDC_COPYRIGHT                   1001
+#define IDC_VERSION                     1002
+
+/* Login dialog box. */
+#define IDC_USER_NAME                   1000
+
+/* Message dialog box. */
+#define IDC_MESSAGE_ICON                1000
+#define IDC_MESSAGE_TEXT1               1001
+
+
+/* Command IDs. */
+#define ID_DOOR_CHATMODE                50000
+#define ID_DOOR_USERKEYBOARDOFF         50001
+#define ID_DOOR_SYSOPNEXT               50002
+#define ID_DOOR_HANGUP                  50003
+#define ID_VIEW_STATUSBAR               50006
+#define ID_USER_ADDONEMINUTE            50007
+#define ID_USER_ADDFIVEMINUTES          50008
+#define ID_USER_SUBTRACTONEMINUTE       50009
+#define ID_USER_SUBTRACTFIVEMINUTES     50010
+#define ID_USER_INACTIVITYTIMER         50011
+#define ID_HELP_ABOUT                   50012
+#define ID_HELP_CONTENTS                50013
+#define ID_VIEW_TOOL_BAR                50014
+#define ID_DOOR_EXIT                    50015
+#define ID_DOOR_LOCKOUT                 50016
+#define ID_VIEW_STAT_BAR                50017
+#define ID_DOOR_CONFIG                  50018

+ 197 - 0
odoors/ODRes.rc

@@ -0,0 +1,197 @@
+/* OpenDoors 6.10
+ * (C) Copyright 1991 - 1997 by Brian Pirie. All Rights Reserved.
+ *
+ *
+ *        File: ODRes.rc
+ *
+ * Description: OpenDoors resource script. Contains defintions for OpenDoors
+ *              menus, dialog boxes, icons, bitmaps, accelerator table and
+ *              string resources. This file is only applicable when building
+ *              the Win32 version of OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Dec 02, 1995  6.00  BP   Created.
+ *              Jan 01, 1996  6.00  BP   Changed copyright to 1996 in About Box
+ *              Jan 20, 1996  6.00  BP   Added login dialog box.
+ *              Jan 21, 1996  6.00  BP   Added message dialog box.
+ *              Jan 21, 1996  6.00  BP   Renamed opendoor.ico to odapp.ico.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 14, 1996  6.10  BP   Added configuration menu option.
+ */
+
+#include "windows.h"
+#include "ODRes.h"
+
+
+/* ========================================================================= */
+/* The OpenDoors frame window menu.                                          */
+/* ========================================================================= */
+
+IDR_FRAME_MENU MENU DISCARDABLE 
+BEGIN
+    POPUP "&Door"
+    BEGIN
+        MENUITEM "C&onfigure..."                ID_DOOR_CONFIG
+        MENUITEM "&Chat Mode\tAlt+C",          ID_DOOR_CHATMODE
+        MENUITEM SEPARATOR
+        MENUITEM "User &Keyboard Off\tAlt+K",  ID_DOOR_USERKEYBOARDOFF
+        MENUITEM "Sysop &Next\tAlt+N",         ID_DOOR_SYSOPNEXT
+        MENUITEM SEPARATOR
+        MENUITEM "&Hangup\tAlt+H",             ID_DOOR_HANGUP
+        MENUITEM "&Lockout\tAlt+L",            ID_DOOR_LOCKOUT
+        MENUITEM SEPARATOR
+        MENUITEM "E&xit To BBS\tAlt+X",        ID_DOOR_EXIT
+    END
+    POPUP "&View"
+    BEGIN
+        MENUITEM "&Toolbar",                    ID_VIEW_TOOL_BAR, CHECKED
+        MENUITEM "Status Bar",                  ID_VIEW_STAT_BAR, CHECKED
+    END
+    POPUP "&User"
+    BEGIN
+        MENUITEM "Add &One Minute\tShift+Up Arrow",   ID_USER_ADDONEMINUTE
+        MENUITEM "Add &Five Minutes\tAlt+Up Arrow", ID_USER_ADDFIVEMINUTES
+        MENUITEM SEPARATOR
+        MENUITEM "&Subtract One Minute\tShift+Down Arrow", 
+                                                ID_USER_SUBTRACTONEMINUTE
+        MENUITEM "S&ubtract Five Minutes\tAlt+Down Arrow", 
+                                                ID_USER_SUBTRACTFIVEMINUTES
+        MENUITEM SEPARATOR
+        MENUITEM "&Inactivity Timer",           ID_USER_INACTIVITYTIMER
+        , CHECKED
+    END
+    POPUP "&Help"
+    BEGIN
+        MENUITEM "&Contents\tF1"                ID_HELP_CONTENTS
+        MENUITEM "&About...",                   ID_HELP_ABOUT
+    END
+END
+
+
+/* ========================================================================= */
+/* Dialog Boxes.                                                             */
+/* ========================================================================= */
+
+IDD_ABOUT DIALOG DISCARDABLE  0, 0, 217, 89
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "About"
+FONT 8, "MS Sans Serif"
+BEGIN
+    ICON            IDI_OPENDOORS,-1,6,7,18,20
+    LTEXT           "BBS Door Program",IDC_DOORNAME,32,5,120,8
+    LTEXT           "",IDC_VERSION,32,14,123,7
+    LTEXT           "",IDC_COPYRIGHT,32,23,119,8
+    LTEXT           "Written using:",-1,31,48,69,8
+    LTEXT           "OpenDoors 6.24, Win32 Edition",-1,31,57,127,8
+    LTEXT           "Copyright \251 1991-1997 by Brian Pirie.",-1,31,
+                    66,139,8,SS_NOPREFIX
+    LTEXT           "All Rights Reserved.",-1,31,75,90,8
+    DEFPUSHBUTTON   "OK",IDOK,161,6,50,14
+END
+
+IDD_LOGIN DIALOG DISCARDABLE  0, 0, 194, 80
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "OpenDoors"
+FONT 8, "MS Sans Serif"
+BEGIN
+    LTEXT           "This program has been started in local",-1,6,6,128,8
+    LTEXT           "mode, independently of a BBS system.",-1,6,14,128,8
+    LTEXT           "When operating in this mode, you may",-1,6,22,128,8
+    LTEXT           "specify what name you should be",-1,6,30,128,8
+    LTEXT           "known to the program by.",-1,6,38,128,8
+    LTEXT           "Your &name:",-1,6,52,42,8
+    EDITTEXT        IDC_USER_NAME,6,61,121,12,ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "OK",IDOK,138,6,50,14
+    PUSHBUTTON      "Cancel",IDCANCEL,138,23,50,14
+END
+
+IDD_MESSAGE DIALOG DISCARDABLE  0, 0, 186, 31
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "OpenDoors"
+FONT 8, "MS Sans Serif"
+BEGIN
+    ICON            IDI_MESSAGE_INFO,IDC_MESSAGE_ICON,6,6,18,20
+    LTEXT           "",IDC_MESSAGE_TEXT1,32,10,149,8
+END
+
+
+/* ========================================================================= */
+/* Icons.                                                                    */
+/* ========================================================================= */
+
+IDI_OPENDOORS           ICON    DISCARDABLE     "odapp.ico"
+IDI_MESSAGE_INFO        ICON    DISCARDABLE     "odinfo.ico"
+
+
+/* ========================================================================= */
+/* Bitmaps.                                                                  */
+/* ========================================================================= */
+
+IDB_TOOLBAR             BITMAP  DISCARDABLE     "toolbar.bmp"
+
+
+/* ========================================================================= */
+/* Accelerator Table.                                                        */
+/* ========================================================================= */
+
+/* Dumb dumb dumb.
+IDR_FRAME ACCELERATORS DISCARDABLE 
+BEGIN
+    "C",            ID_DOOR_CHATMODE,       VIRTKEY, CONTROL, NOINVERT
+    "H",            ID_DOOR_HANGUP,         VIRTKEY, CONTROL, NOINVERT
+    "K",            ID_DOOR_USERKEYBOARDOFF, VIRTKEY, CONTROL, NOINVERT
+    "L",            ID_DOOR_LOCKOUT,        VIRTKEY, CONTROL, NOINVERT
+    "N",            ID_DOOR_SYSOPNEXT,      VIRTKEY, CONTROL, NOINVERT
+    "X",            ID_DOOR_EXIT,           VIRTKEY, CONTROL, NOINVERT
+    VK_DOWN,        ID_USER_SUBTRACTFIVEMINUTES, VIRTKEY, CONTROL, NOINVERT
+    VK_DOWN,        ID_USER_SUBTRACTONEMINUTE, VIRTKEY, SHIFT, NOINVERT
+    VK_F1,          ID_HELP_CONTENTS,       VIRTKEY, NOINVERT
+    VK_UP,          ID_USER_ADDFIVEMINUTES, VIRTKEY, CONTROL, NOINVERT
+    VK_UP,          ID_USER_ADDONEMINUTE,   VIRTKEY, SHIFT, NOINVERT
+END
+*/
+IDR_FRAME ACCELERATORS DISCARDABLE 
+BEGIN
+    "C",            ID_DOOR_CHATMODE,       VIRTKEY, ALT, NOINVERT
+    "H",            ID_DOOR_HANGUP,         VIRTKEY, ALT, NOINVERT
+    "K",            ID_DOOR_USERKEYBOARDOFF, VIRTKEY, ALT, NOINVERT
+    "L",            ID_DOOR_LOCKOUT,        VIRTKEY, ALT, NOINVERT
+    "N",            ID_DOOR_SYSOPNEXT,      VIRTKEY, ALT, NOINVERT
+    "X",            ID_DOOR_EXIT,           VIRTKEY, ALT, NOINVERT
+    VK_DOWN,        ID_USER_SUBTRACTFIVEMINUTES, VIRTKEY, ALT, NOINVERT
+    VK_DOWN,        ID_USER_SUBTRACTONEMINUTE, VIRTKEY, SHIFT, NOINVERT
+    VK_F1,          ID_HELP_CONTENTS,       VIRTKEY, NOINVERT
+    VK_UP,          ID_USER_ADDFIVEMINUTES, VIRTKEY, ALT, NOINVERT
+    VK_UP,          ID_USER_ADDONEMINUTE,   VIRTKEY, SHIFT, NOINVERT
+END
+
+
+/* ========================================================================= */
+/* String Resources.                                                         */
+/* ========================================================================= */
+
+STRINGTABLE DISCARDABLE 
+BEGIN
+    ID_DOOR_CHATMODE        "Enters or exits chat mode, allowing you to communicate with the remote user."
+    ID_DOOR_USERKEYBOARDOFF "Causes any keys or commands from the remote user to be ignored."
+    ID_DOOR_SYSOPNEXT       "Reserves the system for the sysop after this user logs off (if supported by BBS)."
+    ID_DOOR_HANGUP          "Hangs up the modem and exits the door."
+    ID_USER_ADDONEMINUTE    "Increases the user's time remaining by one minute."
+    ID_USER_ADDFIVEMINUTES  "Increases the user's time remaining by five minutes."
+    ID_USER_SUBTRACTONEMINUTE 
+                            "Decreases the user's time remaining by one minute."
+    ID_USER_SUBTRACTFIVEMINUTES 
+                            "Decreases the user's time remaining by five minutes."
+    ID_USER_INACTIVITYTIMER "Enables the timer that will log off the user after a long period of no activity."
+    ID_HELP_ABOUT           "Displays program information and copyright."
+    ID_VIEW_TOOL_BAR        "Shows or hides the toolbar."
+END
+
+STRINGTABLE DISCARDABLE 
+BEGIN
+    ID_DOOR_EXIT            "Exits the door without hanging up."
+    ID_DOOR_LOCKOUT         "Hangs up the modem, denying any further access to the user (if supported by BBS)."
+    ID_VIEW_STAT_BAR        "Shows or hides the status bar."
+END

+ 2554 - 0
odoors/ODScrn.c

@@ -0,0 +1,2554 @@
+/* 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: ODScrn.c
+ *
+ * Description: Functions used to access the local display screen buffer, which
+ *              keeps a copy of the text that is displayed on the remote
+ *              terminal. The local display screen buffer also displays the
+ *              OpenDoors status lines on some platforms. In addition to
+ *              maintaining the current screen buffer, the odscrn.c module
+ *              also contains the code to display this buffer on the screen.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Dec 31, 1994  6.00  BP   Use new multitasker variable.
+ *              Nov 11, 1995  6.00  BP   Removed register keyword.
+ *              Nov 14, 1995  6.00  BP   32-bit portability.
+ *              Nov 14, 1995  6.00  BP   Created odscrn.h.
+ *              Nov 14, 1995  6.00  BP   Make screen size configurable.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 21, 1995  6.00  BP   Port to Win32.
+ *              Jan 20, 1996  6.00  BP   Prompt for user name if force_local.
+ *              Jan 21, 1996  6.00  BP   Added ODScrnShowMessage() and related.
+ *              Jan 27, 1996  6.00  BP   Expand tab ('\t') characters.
+ *              Jan 27, 1996  6.00  BP   Added ODScrollUpAndInvalidate().
+ *              Jan 27, 1996  6.00  BP   Made text-mode window f'ns static.
+ *              Jan 31, 1996  6.00  BP   Made them non-static again.
+ *              Jan 31, 1996  6.00  BP   Added ODScrnLocalInput().
+ *              Feb 06, 1996  6.00  BP   Added od_silent_mode.
+ *              Feb 16, 1996  6.00  BP   Make caret visible after local login.
+ *              Feb 17, 1996  6.00  BP   Recognize non-ASCII keys under Win32.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 21, 1996  6.00  BP   Forward SC_KEYMENU to frame thread.
+ *              Feb 21, 1996  6.00  BP   Don't beep in "silent mode".
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 13, 1996  6.10  BP   Added od_local_win_col.
+ *              Mar 17, 1996  6.10  BP   Terminate string in ODScrnLocalInput()
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODPlat.h"
+#include "ODScrn.h"
+#include "ODUtil.h"
+#include "ODFrame.h"
+#include "ODInEx.h"
+#ifdef ODPLAT_WIN32
+#include "ODKrnl.h"
+#include "ODRes.h"
+#endif /* ODPLAT_WIN32 */
+
+
+/* ========================================================================= */
+/* Definitions of variables used by the local screen module.                 */
+/* ========================================================================= */
+
+/* Manifest constants used in this module. */
+#define SCREEN_BUFFER_SIZE          (OD_SCREEN_WIDTH * OD_SCREEN_HEIGHT * 2)
+#define SCREEN_BUFFER_SEGMENT_SIZE  (SCREEN_BUFFER_SIZE / 16)
+#define BYTES_PER_CHAR              2
+#define BUFFER_LINE_BYTES           (OD_SCREEN_WIDTH * BYTES_PER_CHAR)
+#define LINE_BUFFER_SIZE            (OD_SCREEN_WIDTH + 1)
+
+
+/* Private variables used by the screen I/O functions. */
+
+#if defined(ODPLAT_DOS) || defined(ODPLAT_NIX)
+static void *pAllocatedBufferMemory;
+#endif /* ODPLAT_DOS */
+
+/* Far pointer to video buffer. */
+static void ODFAR *pScrnBuffer;
+
+/* Current cursor position. */
+static BYTE btCursorColumn;
+static BYTE btCursorRow;
+
+/* Current output boundaries. */
+static BYTE btLeftBoundary;
+static BYTE btTopBoundary;
+static BYTE btRightBoundary;
+static BYTE btBottomBoundary;
+
+/* Current display color. */
+static BYTE btCurrentAttribute;
+
+/* Is scrolling enabled. */
+static BOOL bScrollEnabled;
+
+#ifdef ODPLAT_DOS
+/* Segment address of video buffer. */
+static WORD wBufferSegment;
+/* Display page to use. */
+static BYTE btDisplayPage;
+#endif
+
+/* Is cursor currently on. */
+static BYTE bCaretOn;
+
+/* Static temporary working buffer. */
+static char szBuffer[LINE_BUFFER_SIZE];
+
+
+/* Private function prototypes. */
+static void ODScrnGetCursorPos(void);
+static void ODScrnUpdateCaretPos(void);
+static void ODScrnScrollUpOneLine(void);
+static void ODScrnScrollUpAndInvalidate(void);
+
+
+/* ========================================================================= */
+/* Implementation of the local screen window for the Win32 platform.         */
+/* ========================================================================= */
+
+#ifdef ODPLAT_WIN32
+
+/* Screen thread startup information. */
+typedef struct
+{
+   HWND hwndFrame;
+   HANDLE hInstance;
+} tODScrnThreadInfo;
+
+/* Handle to the screen window. */
+static HWND hwndScreenWindow;
+
+/* Does the screen window currently have input focus? */
+BOOL bScreenHasFocus;
+
+/* Current font-related information. */
+static HFONT hCurrentFont;
+static INT nFontCellWidth;
+static INT nFontCellHeight;
+
+/* Table to translate from PC text color values used in the screen buffer */
+/* to their corresponding RGB values.                                     */
+COLORREF acrPCTextColors[] =
+{
+   RGB(0x00, 0x00, 0x00),
+   RGB(0x00, 0x00, 0xc0),
+   RGB(0x00, 0xc0, 0x00),
+   RGB(0x00, 0xc0, 0xc0),
+   RGB(0xc0, 0x00, 0x00),
+   RGB(0xc0, 0x00, 0xc0),
+   RGB(0xc0, 0xc0, 0x00),
+   RGB(0xc0, 0xc0, 0xc0),
+   RGB(0x7f, 0x7f, 0x7f),
+   RGB(0x00, 0x00, 0xff),
+   RGB(0x00, 0xff, 0x00),
+   RGB(0x00, 0xff, 0xff),
+   RGB(0xff, 0x00, 0x00),
+   RGB(0xff, 0x00, 0xff),
+   RGB(0xff, 0xff, 0x00),
+   RGB(0xff, 0xff, 0xff),
+};
+
+/* Table to translate from Windows key codes to OpenDoors key codes. */
+typedef struct
+{
+   int nVirtKey;
+   BYTE btODKey;
+} tWinKeyToODKey;
+
+tWinKeyToODKey aWinKeyToODKey[] =
+{
+   {VK_UP,        OD_KEY_UP},
+   {VK_DOWN,      OD_KEY_DOWN},
+   {VK_LEFT,      OD_KEY_LEFT},
+   {VK_RIGHT,     OD_KEY_RIGHT},
+   {VK_INSERT,    OD_KEY_INSERT},
+   {VK_DELETE,    OD_KEY_DELETE},
+   {VK_END,       OD_KEY_END},
+   {VK_HOME,      OD_KEY_HOME},
+   {VK_PRIOR,     OD_KEY_PGUP},
+   {VK_NEXT,      OD_KEY_PGDN},
+   {VK_F1,        OD_KEY_F1},
+   {VK_F2,        OD_KEY_F2},
+   {VK_F3,        OD_KEY_F3},
+   {VK_F4,        OD_KEY_F4},
+   {VK_F5,        OD_KEY_F5},
+   {VK_F6,        OD_KEY_F6},
+   {VK_F7,        OD_KEY_F7},
+   {VK_F8,        OD_KEY_F8},
+   {VK_F9,        OD_KEY_F9},
+   {VK_F10,       OD_KEY_F10},
+};
+
+/* Utility macros. */
+#define COLUMN_AS_XPIXEL(nColumn)   (((INT)(nColumn)) * nFontCellWidth)
+#define ROW_AS_YPIXEL(nRow)         (((INT)(nRow)) * nFontCellHeight)
+#define XPIXEL_AS_COLUMN(nX)        (((INT)(nX)) / nFontCellWidth)
+#define YPIXEL_AS_ROW(nY)           (((INT)(nY)) / nFontCellHeight)
+
+/* User defined messages. */
+#define WM_MOVE_YOUR_CARET          (WM_USER + 1)
+#define WM_KEYDOWN_RELAY            (WM_USER + 2)
+
+/* Height of the flashing caret, in pixels. */
+#define CARET_HEIGHT   3
+
+/* Local function prototypes. */
+LRESULT CALLBACK ODScrnWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam);
+static HWND ODScrnCreateWin(HWND hwndFrame, HANDLE hInstance);
+static void ODScrnMessageLoop(HANDLE hInstance, HWND hwndScreen);
+DWORD OD_THREAD_FUNC ODScrnThreadProc(void *pParam);
+static void ODScrnPaint(HDC hdc, INT nLeft, INT nTop, INT nRight, INT nBottom);
+static void ODScrnInvalidate(BYTE btLeft, BYTE btTop, BYTE btRight,
+   BYTE btBottom);
+static void ODScrnSetCurrentFont(HWND hwndScreen, HFONT hNewFont);
+static void ODScrnSetWinCaretPos(void);
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnCreateWin()                                   *** PRIVATE FUNCTION ***
+ *
+ * Creates the local screen window, which covers the client area of the
+ * OpenDoors frame window.
+ *
+ * Parameters: hwndFrame   - Handle to the frame window.
+ *
+ *             hInstance   - Handle to application instance.
+ *
+ *     Return: A handle to the newly created window, or NULL on failure.
+ */
+static HWND ODScrnCreateWin(HWND hwndFrame, HANDLE hInstance)
+{
+   HWND hwndScreen = NULL;
+   WNDCLASS wcScreenWindow;
+
+   ASSERT(hwndFrame != NULL);
+   ASSERT(hInstance != NULL);
+
+   /* Register the screen window's window class. */
+   memset(&wcScreenWindow, 0, sizeof(wcScreenWindow));
+   wcScreenWindow.style = CS_HREDRAW | CS_VREDRAW;
+   wcScreenWindow.lpfnWndProc = ODScrnWindowProc;
+   wcScreenWindow.cbClsExtra = 0;
+   wcScreenWindow.cbWndExtra = 0;
+   wcScreenWindow.hInstance = hInstance;
+   wcScreenWindow.hIcon = NULL;
+   wcScreenWindow.hCursor = LoadCursor(NULL, IDC_ARROW);
+   wcScreenWindow.hbrBackground = NULL;
+   wcScreenWindow.lpszMenuName = NULL;
+   wcScreenWindow.lpszClassName = "ODScreen";
+
+   RegisterClass(&wcScreenWindow);
+
+   /* Create the screen window. */
+   if((hwndScreen = CreateWindowEx(
+      WS_EX_CLIENTEDGE,
+      wcScreenWindow.lpszClassName,
+      "",
+      WS_CHILD | WS_BORDER,
+      0,
+      0,
+      500,
+      300,
+      hwndFrame,
+      NULL,
+      hInstance,
+      (LPVOID)hInstance)) == NULL)
+   {
+      /* On window creation failure, return NULL. */
+      return(NULL);
+   }
+
+   /* Store handle to screen window for access from screen. */
+   hwndScreenWindow = hwndScreen;
+
+   return(hwndScreen);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnWindowProc()                                  *** PRIVATE FUNCTION ***
+ *
+ * The local screen window proceedure.
+ *
+ * Parameters: hwnd   - Handle to the local screen window.
+ *
+ *             uMsg   - Specifies the message.
+ *
+ *             wParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *             lParam - Specifies additional message information. The content
+ *                      of this parameter depends on the value of the uMsg
+ *                      parameter.
+ *
+ *     Return: The return value is the result of the message processing and
+ *             depends on the message.
+ */
+LRESULT CALLBACK ODScrnWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+   LPARAM lParam)
+{
+   HINSTANCE hInstance;
+
+   ASSERT(hwnd != NULL);
+
+   hInstance = (HINSTANCE)GetWindowLong(hwnd, GWL_USERDATA);
+
+   switch(uMsg)
+   {
+      case WM_SYSCOMMAND:
+         /* We move any SC_KEYMENU WM_SYSCOMMAND messages to the frame */
+         /* window's message queue so that the screen window thread    */
+         /* can continue to process messages when the menu is          */
+         /* activated from the keyboard when the screen window has     */
+         /* the keyboard focus. If this isn't done, the menu will not  */
+         /* behave correctly when activated this way.                  */
+         if(wParam == SC_KEYMENU)
+         {
+            PostMessage(GetParent(hwnd), uMsg, wParam, lParam);
+         }
+         else
+         {
+            return(DefWindowProc(hwnd, uMsg, wParam, lParam));
+         }
+         break;
+
+      case WM_CREATE:
+      {
+         CREATESTRUCT *pCreateStruct = (CREATESTRUCT *)lParam;
+         hInstance = (HINSTANCE)pCreateStruct->lpCreateParams;
+         SetWindowLong(hwnd, GWL_USERDATA, (LONG)hInstance);
+         break;
+      }
+
+      case WM_PAINT:
+      {
+         PAINTSTRUCT PaintStruct;
+         HDC hdc;
+
+         /* Obtain device context and paint information. */
+         hdc = BeginPaint(hwnd, &PaintStruct);
+
+         if(hdc != NULL)
+         {
+            /* Redraw the portion of the window that has been invalidated. */
+            ODScrnPaint(hdc,
+               XPIXEL_AS_COLUMN(PaintStruct.rcPaint.left),
+               YPIXEL_AS_ROW(PaintStruct.rcPaint.top),
+               XPIXEL_AS_COLUMN(PaintStruct.rcPaint.right),
+               YPIXEL_AS_ROW(PaintStruct.rcPaint.bottom));
+
+            /* Release device context. */
+            EndPaint(hwnd, &PaintStruct);
+         }
+         break;
+      }
+
+      case WM_MOVE_YOUR_CARET:
+         ODScrnSetWinCaretPos();
+         break;
+
+      case WM_LBUTTONDOWN:
+         SetFocus(hwnd);
+         break;
+
+      case WM_SETFOCUS:
+         /* Turn on the caret when we receive the input focus. */
+         /* First, create the caret. */
+         CreateCaret(hwnd, NULL, nFontCellWidth, CARET_HEIGHT);
+
+         /* Remember that we now have the input focus. */
+         bScreenHasFocus = TRUE;
+
+         /* Update the position of the caret. */
+         ODScrnSetWinCaretPos();
+
+         /* Now, make the caret visible. */
+         ShowCaret(hwnd);
+         break;
+
+      case WM_KILLFOCUS:
+         /* Remember that we no longer have the input focus. */
+         bScreenHasFocus = FALSE;
+
+         /* Turn off the caret when we loose the input focus. */
+         DestroyCaret();
+         break;
+
+      case WM_KEYDOWN_RELAY:
+      {
+         int nVirtKeyPressed = (int)wParam;
+         WORD wRepeatCount = LOWORD(lParam);
+         int nKeyTableIndex;
+         WORD wKey = 0;
+
+         /* Look for a matching key in the OpenDoors key table. */
+         for(nKeyTableIndex = 0; nKeyTableIndex < DIM(aWinKeyToODKey);
+            ++nKeyTableIndex)
+         {
+            if(aWinKeyToODKey[nKeyTableIndex].nVirtKey == nVirtKeyPressed)
+            {
+               wKey = MAKEWORD(0, aWinKeyToODKey[nKeyTableIndex].btODKey);
+               break;
+            }
+         }
+
+         /* If a matching key was found, then add it to the queue. */
+         if(wKey != 0)
+         {
+            while(wRepeatCount--)
+            {
+               ODKrnlHandleLocalKey(wKey);
+            }
+         }
+
+         break;
+      }
+
+      case WM_CHAR:
+      {
+         WORD wRepeatCount = LOWORD(lParam);
+         BYTE btScanCode = LOBYTE(HIWORD(lParam));
+         TCHAR chCharCode = (TCHAR)wParam;
+         WORD wKey;
+         
+         wKey = MAKEWORD(chCharCode, btScanCode);
+
+         /* Loop for each repitition of this key. */
+         while(wRepeatCount--)
+         {
+            ODKrnlHandleLocalKey(wKey);
+         }
+         break;
+      }
+
+      default:
+         /* Pass messages that we don't explicitly handle on to the */
+         /* default window proc.                                    */
+         return(DefWindowProc(hwnd, uMsg, wParam, lParam));
+   }
+
+   return(0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnPaint()                                       *** PRIVATE FUNCTION ***
+ *
+ * Draws the specified portion of the screen on the provided device context.
+ *
+ * Parameters: hdc     - Handle to the device context to draw on.
+ *
+ *             nLeft   - Left column to draw.
+ *
+ *             nTop    - Top row to draw.
+ *
+ *             nRight  - Right column to draw.
+ *
+ *             nBottom - Bottom row to draw.
+ *
+ *     Return: void.
+ */
+static void ODScrnPaint(HDC hdc, INT nLeft, INT nTop, INT nRight, INT nBottom)
+{
+   INT nIDSavedState;
+   INT nCurrentLine;
+   INT nStartColumn;
+   INT nEndColumn;
+   BYTE *pbtBufferContents;
+   char achStringToOutput[OD_SCREEN_WIDTH];
+   char *pchNextChar;
+   BYTE btCurrentAttribute;
+
+   ASSERT(hdc != NULL);
+   ASSERT(nLeft >= 0);
+   ASSERT(nTop >= 0);
+   ASSERT(nRight >= nLeft);
+   ASSERT(nBottom >= nTop);
+
+   /* Ensure that parameters are within valid range. */
+   if(nRight >= OD_SCREEN_WIDTH) nRight = OD_SCREEN_WIDTH - 1;
+   if(nBottom >= OD_SCREEN_HEIGHT) nBottom = OD_SCREEN_HEIGHT - 1;
+
+   /* Save the current state of the device context so that we can restore */
+   /* it before returning.                                                */
+   nIDSavedState = SaveDC(hdc);
+
+   /* Setup device context for displaying text from the screen buffer. */
+   SetBkMode(hdc, OPAQUE);
+   SelectObject(hdc, hCurrentFont);
+
+   /* Loop through each line that is to be painted. */
+   for(nCurrentLine = nTop; nCurrentLine <= nBottom; ++nCurrentLine)
+   {
+      /* Obtain a pointer to the first byte representing this line in */
+      /* the screen buffer.                                           */
+      pbtBufferContents = (BYTE *)(pScrnBuffer) +
+         ((nCurrentLine * OD_SCREEN_WIDTH) + nLeft) * 2;
+
+      /* Loop for each portion of this line that can be drawn in a single */
+      /* TextOut() call.                                                  */
+      for(nStartColumn = nLeft; nStartColumn <= nRight;
+         nStartColumn = nEndColumn)
+      {
+         /* Begin constructing a string containing the text to output */
+         /* in this call to TextOut().                                */
+         pchNextChar = achStringToOutput;
+
+         /* Determine the color of this portion. */
+         btCurrentAttribute = pbtBufferContents[1];
+
+         /* Loop, finding the first column that has an incompatible color. */
+         for(nEndColumn = nStartColumn; nEndColumn <= nRight; ++nEndColumn)
+         {
+            /* Stop looping if we come to a non-equivalent color */
+            /* attribute.                                        */
+            if(btCurrentAttribute != pbtBufferContents[1])
+            {
+               break;
+            }
+
+            /* Otherwise, add this character to the string to output. */
+            *pchNextChar++ = *pbtBufferContents;
+
+            /* Move to the next position in the buffer. */
+            pbtBufferContents += 2;
+         }
+
+         /* Change current display colors to match the current color */
+         /* attribute.                                               */
+         SetTextColor(hdc, acrPCTextColors[btCurrentAttribute & 0x0f]);
+         SetBkColor(hdc, acrPCTextColors[(btCurrentAttribute & 0xf0) >> 4]);
+
+         /* Output the string. */
+         TextOut(hdc,
+            COLUMN_AS_XPIXEL(nStartColumn),
+            ROW_AS_YPIXEL(nCurrentLine),
+            achStringToOutput,
+            (nEndColumn - nStartColumn));
+      }
+   }
+
+   /* Restore the device context to its original state before this function */
+   /* was called.                                                           */
+   RestoreDC(hdc, nIDSavedState);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnInvalidate()                                  *** PRIVATE FUNCTION ***
+ *
+ * Marks the specified area of the screen window as invalid, forcing the
+ * screen thread to redraw it.
+ *
+ * Parameters: btLeft   - The left most column to invalidate.
+ *
+ *             btTop    - The top most row to invalidate.
+ *
+ *             btRight  - The right most column to invalidate.
+ *
+ *             btBottom - The bottom most row to invalidate.
+ *
+ *     Return: void.
+ */
+static void ODScrnInvalidate(BYTE btLeft, BYTE btTop, BYTE btRight,
+   BYTE btBottom)
+{
+   RECT rcToInvalidate;
+
+   /* If the screen window has not been created yet, then return without */
+   /* doing anything.                                                    */
+   if(hwndScreenWindow == NULL) return;
+
+   /* Obtain rectangle in client window coordinates, to be invalidated. */
+   rcToInvalidate.left = COLUMN_AS_XPIXEL(btLeft);
+   rcToInvalidate.top = ROW_AS_YPIXEL(btTop);
+   rcToInvalidate.right = COLUMN_AS_XPIXEL(btRight + 1);
+   rcToInvalidate.bottom = ROW_AS_YPIXEL(btBottom + 1);
+
+   /* Mark this rectangle as invalid. */
+   InvalidateRect(hwndScreenWindow, &rcToInvalidate, FALSE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetCurrentFont()                              *** PRIVATE FUNCTION ***
+ *
+ * Changes the current font to be used for drawing, updating anything that
+ * needs updating.
+ *
+ * Parameters: hwndScreen - Handle to the screen window.
+ *
+ *             nNewFont   - Handle to the font to switch to.
+ *
+ *     Return: void.
+ */
+static void ODScrnSetCurrentFont(HWND hwndScreen, HFONT hNewFont)
+{
+   HDC hdc;
+   INT nIDSavedState;
+   TEXTMETRIC TextMetrics;
+
+   /* Obtain a handle to the a device context for the screen window. */
+   hdc = GetDC(hwndScreen);
+
+   /* If we are unable to obtian a device context, then return without */
+   /* doing anything.                                                  */
+   if(hdc == NULL)
+   {
+      return;
+   }
+
+   /* Change the current font. */
+   hCurrentFont = hNewFont;
+
+   /* Obtain text metrics from the device context, and then release the */
+   /* device context.                                                   */
+   nIDSavedState = SaveDC(hdc);
+   SelectObject(hdc, hCurrentFont);
+   GetTextMetrics(hdc, &TextMetrics);
+   RestoreDC(hdc, nIDSavedState);
+   ReleaseDC(hwndScreen, hdc);
+
+   /* Determine the new size of a character cell. */
+   nFontCellWidth = TextMetrics.tmMaxCharWidth;
+   nFontCellHeight = TextMetrics.tmHeight;
+
+   /* Force window sizes to be adjusted for the new font size. */
+   ODScrnAdjustWindows();
+   ODScrnAdjustWindows();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnAdjustWindows()
+ *
+ * Resizes and repositions the screen window to the appropriate size based
+ * on the current font, portions of the frame window's client area that are
+ * in use, etc. Other windows whose size depends on the size of the screen
+ * window are also updated.
+ *
+ * Parameters: None.
+ *
+ *     Return: void.
+ */
+void ODScrnAdjustWindows(void)
+{
+   INT nNewClientWidth;
+   INT nNewClientHeight;
+   RECT rcClient;
+   RECT rcWindow;
+   INT nNonClientWidth;
+   INT nNonClientHeight;
+   INT nScreenWindowWidth;
+   INT nScreenWindowHeight;
+   HWND hwndFrame;
+   INT nTopFrameUsed;
+   INT nBottomFrameUsed;
+   HWND hwndScreen;
+
+   hwndScreen = hwndScreenWindow;
+   ASSERT(hwndScreen != NULL);
+
+   hwndFrame = GetParent(hwndScreen);
+   ASSERT(hwndFrame != NULL);
+
+   /* Determine areas of the frame window's client area that are already */
+   /* in use.                                                            */
+   nTopFrameUsed = ODFrameGetUsedClientAtTop(hwndFrame);
+   nBottomFrameUsed = ODFrameGetUsedClientAtBottom(hwndFrame);
+
+   /* Determine the new required size of the window's client area. */
+   nNewClientWidth = nFontCellWidth * OD_SCREEN_WIDTH;
+   nNewClientHeight = nFontCellHeight * OD_SCREEN_HEIGHT;
+
+   /* Determine the size of the window's non-client area. */
+   GetClientRect(hwndScreen, &rcClient);
+   GetWindowRect(hwndScreen, &rcWindow);
+   nNonClientWidth = (rcWindow.right - rcWindow.left)
+      - (rcClient.right - rcClient.left);
+   nNonClientHeight = (rcWindow.bottom - rcWindow.top)
+      - (rcClient.bottom - rcClient.top);
+
+   /* Determine the overall size required for the screen window. */
+   nScreenWindowWidth = nNewClientWidth + nNonClientWidth;
+   nScreenWindowHeight = nNewClientHeight + nNonClientHeight;
+
+   /* Resize the screen window accordingly. */
+   SetWindowPos(hwndScreen, NULL, 0, nTopFrameUsed, nScreenWindowWidth,
+      nScreenWindowHeight, SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOZORDER);
+
+   /* Resize the OpenDoors frame window (which is the screen window's  */
+   /* parent) so that the screen window just fill's the frame window's */
+   /* remaining client area.                                           */
+   GetClientRect(hwndFrame, &rcClient);
+   GetWindowRect(hwndFrame, &rcWindow);
+   nNonClientWidth = (rcWindow.right - rcWindow.left)
+      - (rcClient.right - rcClient.left);
+   nNonClientHeight = (rcWindow.bottom - rcWindow.top)
+      - (rcClient.bottom - rcClient.top);
+
+   SetWindowPos(hwndFrame, NULL, 0, 0, nScreenWindowWidth + nNonClientWidth,
+      nScreenWindowHeight + nNonClientHeight + nTopFrameUsed
+      + nBottomFrameUsed,
+      SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnMessageLoop()                                 *** PRIVATE FUNCTION ***
+ *
+ * Message loop for OpenDoors screen window thread.
+ *
+ * Parameters: hInstance   - Handle to current instance.
+ *
+ *             hwndScreen  - Handle to the screen window.
+ *
+ *     Return: void.
+ */
+static void ODScrnMessageLoop(HANDLE hInstance, HWND hwndScreen)
+{
+   MSG msg;
+   HWND hwndFrame;
+
+   ASSERT(hInstance != NULL);
+   ASSERT(hwndScreen != NULL);
+
+   /* Obtain a handle to the OpenDoors main frame window. */
+   hwndFrame = GetParent(hwndScreen);
+
+   /* Loop, fetching, translating and dispatching messages for any windows */
+   /* created by this thread. (GetMessage() blocks when no messages are    */
+   /* available.)                                                          */
+   while(GetMessage(&msg, NULL, 0, 0))
+   {
+      if(!ODFrameTranslateAccelerator(hwndFrame, &msg))
+      {
+         TranslateMessage(&msg);
+         if(msg.message == WM_KEYDOWN)
+         {
+            PostMessage(hwndScreen, WM_KEYDOWN_RELAY, msg.wParam, msg.lParam);
+         }
+         DispatchMessage(&msg);
+      }
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnThreadProc()                                  *** PRIVATE FUNCTION ***
+ *
+ * Function that execute the OpenDoors screen window thread. This thread's
+ * primary task is to draw the screen window contents, when needed.
+ *
+ * Parameters: pParam   - The thread parameter, which is a pointer to a
+ *                        tODScrnThreadInfo structure.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+DWORD OD_THREAD_FUNC ODScrnThreadProc(void *pParam)
+{
+   tODScrnThreadInfo *pScrnThreadInfo = (tODScrnThreadInfo *)pParam;
+   HWND hwndScreen;
+   HANDLE hInstance = pScrnThreadInfo->hInstance;
+   HWND hwndFrame = pScrnThreadInfo->hwndFrame;
+
+   /* We are now done with the thread startup information structure, */
+   /* so deallocate it.                                              */
+   free(pScrnThreadInfo);
+
+   /* Create the screen window. */
+   hwndScreen = ODScrnCreateWin(hwndFrame, hInstance);
+
+   if(hwndScreen == NULL)
+   {
+      return(FALSE);
+   }
+
+   /* Set the current font for the window. This, in turn will force the  */
+   /* window to be adjusted to the appropriate size, and will adjust the */
+   /* size of the OpenDoors frame window accordingly.                    */
+   ODScrnSetCurrentFont(hwndScreen, GetStockObject(OEM_FIXED_FONT));
+
+   /* Prompt for the user's name before showing the windows, if required. */   
+#ifdef ODPLAT_WIN32
+   if(bPromptForUserName)
+   {
+      if(DialogBox(hInstance, MAKEINTRESOURCE(IDD_LOGIN), hwndFrame,
+         ODInitLoginDlgProc) == IDCANCEL)
+      {
+         exit(od_control.od_errorlevel[1]);
+      }
+
+      PostMessage(hwndScreen, WM_SETFOCUS, 0, 0L);
+   }
+#endif /* ODPLAT_WIN32 */
+
+   /* Now, we can make the frame window visible. */
+   if(od_control.od_cmd_show == SW_MINIMIZE ||
+      od_control.od_cmd_show == SW_SHOWMINIMIZED ||
+      od_control.od_cmd_show == SW_SHOWMINNOACTIVE)
+   {
+      ShowWindow(hwndFrame, SW_SHOWMINNOACTIVE);
+   }
+   else
+   {
+      ShowWindow(hwndFrame, SW_RESTORE);
+   }
+
+   /* Now, show the screen window. */
+   ShowWindow(hwndScreen, SW_SHOW);
+
+   /* Loop, processing messages for the screen window. */
+   ODScrnMessageLoop(hInstance, hwndScreen);
+
+   /* Destroy the screen window. */
+   DestroyWindow(hwndScreen);
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnStartWindow()
+ *
+ * Function that starts up the screen window thread, which in turn creates
+ * and manages the screen window.
+ *
+ * Parameters: hInstance      - Handle to the current application instance.
+ *
+ *             phScreenThread - Pointer to location where screen thread handle
+ *                              should be stored.
+ *
+ *             hwndFrame      - Handle to already created frame window.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODScrnStartWindow(HANDLE hInstance, tODThreadHandle *phScreenThread,
+   HWND hwndFrame)
+{
+   tODScrnThreadInfo *pScrnThreadInfo;
+
+   ASSERT(hInstance != NULL);
+   ASSERT(phScreenThread != NULL);
+   ASSERT(hwndFrame != NULL);
+
+   /* Setup thread information to pass into the screen thread at startup. */
+   if((pScrnThreadInfo = malloc(sizeof(tODScrnThreadInfo))) == NULL)
+   {
+      return(kODRCNoMemory);
+   }
+
+   pScrnThreadInfo->hInstance = hInstance;
+   pScrnThreadInfo->hwndFrame = hwndFrame;
+
+   /* Create the screen thread. */
+   return(ODThreadCreate(phScreenThread, ODScrnThreadProc,
+      pScrnThreadInfo));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetFocusToWindow()
+ *
+ * Sets the current input focus to the screen window.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODScrnSetFocusToWindow(void)
+{
+   if(hwndScreenWindow != NULL)
+   {
+      SetFocus(hwndScreenWindow);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetWinCaretPos()
+ *
+ * Repositions the Windows caret to the position of our cursor, if
+ * appropriate.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODScrnSetWinCaretPos(void)
+{
+   /* Only move the caret if we have focus, and thus we are the one who */
+   /* owns the caret.                                                   */
+   if(bScreenHasFocus)
+   {
+      SetCaretPos(COLUMN_AS_XPIXEL(btCursorColumn + btLeftBoundary),
+         ROW_AS_YPIXEL(btCursorRow + btTopBoundary + 1) - CARET_HEIGHT);
+   }
+}
+
+
+#endif /* ODPLAT_WIN32 */
+
+
+
+/* ========================================================================= */
+/* Functions used throughout OpenDoors to manipulate local screen buffer.    */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODScrnInitialize()
+ *
+ * Initializes the local screen module.
+ *
+ * Parameters: none
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODScrnInitialize(void)
+{
+   BOOL bClear = TRUE;
+
+#if defined(ODPLAT_DOS) || defined(ODPLAT_NIX)
+   /* In silent mode, we perform all output in a block of memory that is */
+   /* never displayed.                                                   */
+   /* *nix is always in "silent mode"                                    */
+#ifndef ODPLAT_NIX
+   if(od_control.od_silent_mode)
+   {
+#endif
+      /* Allocate memory for screen buffer, using standard pointer type */
+      /* for current memory model.                                      */
+      pAllocatedBufferMemory = malloc(SCREEN_BUFFER_SIZE);
+
+      if(pAllocatedBufferMemory == NULL)
+      {
+         return(kODRCNoMemory);
+      }
+
+      /* Set the screen buffer far pointer to point to the allocated */
+      /* buffer.                                                     */
+      pScrnBuffer = pAllocatedBufferMemory;
+#ifndef ODPLAT_NIX
+   }
+   else
+   {
+      BYTE btDisplayMode;
+
+      /* Get current video mode. */
+      ASM    push si
+      ASM    push di
+      ASM    mov ah, 0x0f
+      ASM    int 0x10
+      ASM    mov btDisplayMode, al
+      ASM    pop di
+      ASM    pop si
+
+      switch(btDisplayMode & 0x7f)
+      {
+         /* No need to change mode, already colour 80x25. */
+         case 0x02:
+         case 0x03:
+            wBufferSegment = 0xb800;
+            pScrnBuffer = (void ODFAR *)0xb8000000L;
+            bClear = TRUE;
+            break;
+
+         /* No need to change mode, already monochrome 80x25. */
+         case 0x07:
+            wBufferSegment = 0xb000;
+            pScrnBuffer = (void ODFAR *)0xb0000000L;
+            bClear = TRUE;
+            break;
+
+         /* Must change mode to monochrome 80x25. */
+         case 0x21:
+            wBufferSegment = 0xb000;
+            pScrnBuffer = (void ODFAR *)0xb0000000L;
+            bClear = FALSE;
+
+            /* set mode to 0x07 */
+            ASM    push si
+            ASM    push di
+            ASM    mov ax, 0x0007
+            ASM    int 0x10
+            ASM    pop di
+            ASM    pop si
+            break;
+
+         /* Must change mode to colour 80x25. */
+         default:
+            wBufferSegment = 0xb800;
+            pScrnBuffer = (void ODFAR *)0xb8000000L;
+            bClear = FALSE;
+
+            /* set mode to 0x03. */
+            ASM    push si
+            ASM    push di
+            ASM    mov ax, 0x0003
+            ASM    int 0x10
+            ASM    pop di
+            ASM    pop si
+      }
+
+
+
+      /* Adjust address for display page which is being used. */
+      ASM    push si
+      ASM    push di
+      ASM    mov ah, 0x0f
+      ASM    int 0x10
+      ASM    mov btDisplayPage, bh
+      ASM    pop di
+      ASM    pop si
+
+      if(btDisplayPage!=0)
+      {
+         wBufferSegment += (SCREEN_BUFFER_SEGMENT_SIZE * btDisplayPage);
+         ((char ODFAR *)pScrnBuffer) += (SCREEN_BUFFER_SIZE * btDisplayPage);
+      }
+
+      if(ODMultitasker == kMultitaskerDV)
+      {
+         /* Determine address of DV screen buffer. */
+         /* This doesn't check rows, bh = rows, bl = columns. */
+         ASM    mov ax, 0x2b02
+         ASM    mov cx, 0x4445
+         ASM    mov dx, 0x5351
+         ASM    int 0x21
+         ASM    cmp bx, 0x1950
+         ASM    jne no_change
+         ASM    mov wBufferSegment, dx
+
+         (long)pScrnBuffer = ODDWordShiftLeft((long)wBufferSegment, 16);
+   no_change: ;
+      }
+   }
+#endif /* ODPLAT_DOS */
+#endif /* ODPLAT_DOS/NIX */
+
+#ifdef ODPLAT_WIN32
+   /* Allocate memory for screen buffer. */
+   pScrnBuffer = malloc(SCREEN_BUFFER_SIZE);
+
+   if(pScrnBuffer == NULL)
+   {
+      return(kODRCNoMemory);
+   }
+#endif /* ODPLAT_WIN32 */
+
+   /* Initialize display system variables. */
+   btLeftBoundary = 0;
+   btRightBoundary = 79;
+   btTopBoundary = 0;
+   btBottomBoundary = 24;
+   btCurrentAttribute = 0x07;
+   bScrollEnabled = 1;
+
+   /* Clear local screen. */
+   if(bClear)
+   {
+      ODScrnClear();
+   }
+
+   /* Enable flashing cursor. */
+   bCaretOn = FALSE;
+   ODScrnEnableCaret(TRUE);
+
+   /* Return with success. */
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnShutdown()
+ *
+ * De-initializes the screen module.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODScrnShutdown(void)
+{
+#ifdef ODPLAT_WIN32
+   /* Deallocate screen buffer memory. */
+   if(pScrnBuffer != NULL)
+   {
+      free(pScrnBuffer);
+      pScrnBuffer = NULL;
+   }
+#else /* !ODPLAT_WIN32 */
+   /* In silent mode, we must deallocate screen buffer memory. */
+   /* *nix is always in silent mode                            */
+#ifndef ODPLAT_NIX
+   if(od_control.od_silent_mode && pAllocatedBufferMemory != NULL)
+   {
+#endif
+      free(pAllocatedBufferMemory);
+      pAllocatedBufferMemory = NULL;
+      pScrnBuffer = NULL;
+#ifndef ODPLAT_NIX
+   }
+#endif
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetBoundary()
+ *
+ * Sets the current boundary area on the screen. All output is constrained
+ * within this boundary area.
+ *
+ * Parameters: btLeft   - 1-based column number of the left edge of the area.
+ *
+ *             btTop    - 1-based row number of the top edge of the area.
+ *
+ *             btRight  - 1-based column number of the right edge of the area.
+ *
+ *             btBottom - 1-based row number of the bottom edge of the area.
+ *
+ *     Return: void
+ */
+void ODScrnSetBoundary(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom)
+{
+   /* Set internal window location variables. */
+   btLeftBoundary = btLeft - 1;
+   btRightBoundary = btRight - 1;
+   btTopBoundary = btTop - 1;
+   btBottomBoundary = btBottom - 1;
+
+   /* Ensure that the cursor is located within the new window boundaries. */
+   if(btCursorColumn > btRightBoundary - btLeftBoundary)
+   {
+      btCursorColumn = btRightBoundary - btLeftBoundary;
+   }
+   else if(btCursorColumn < btLeftBoundary)
+   {
+      btCursorColumn = btLeftBoundary;
+   }
+
+   if(btCursorRow > btBottomBoundary - btTopBoundary)
+   {
+      btCursorRow = btBottomBoundary - btTopBoundary;
+   }
+   else if(btCursorRow < btTopBoundary)
+   {
+      btCursorRow = btTopBoundary;
+   }
+
+   /* Execute the position flashing cursor primitive. */
+   ODScrnUpdateCaretPos();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetCursorPos()
+ *
+ * Sets the current cursor position. The cursor position is where the caret
+ * (flashing cursor) appears (if it is currently turned on), and is the
+ * location where ODScrnDisplayChar(), ODScrnDisplayString() and ODScrnPrintf()
+ * will perform their output. Each of these functions, update the cursor
+ * position to the next character cell after the end of their output. Other
+ * ODScrn...() functions may also change the current cursor position.
+ *
+ * Parameters: btColumn - The 1-based column number where the cursor will
+ *                        be placed.
+ *
+ *     Return: void
+ */
+void ODScrnSetCursorPos(BYTE btColumn, BYTE btRow)
+{
+   /* Set internal cursor position values. */
+   btCursorColumn = btColumn - 1;
+   btCursorRow = btRow - 1;
+
+   /* Ensure that cursor falls within the current output window. */
+   if(btCursorColumn > btRightBoundary - btLeftBoundary)
+      btCursorColumn = btRightBoundary - btLeftBoundary;
+
+   if(btCursorRow > btBottomBoundary - btTopBoundary)
+      btCursorRow = btBottomBoundary - btTopBoundary;
+
+   /* Execute the position flashing cursor primitive. */
+   ODScrnUpdateCaretPos();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnSetAttribute()
+ *
+ * Sets the current display attribute, to be used by ODScrnDisplayChar(),
+ * ODScrnDisplayString(), ODScrnPrintf() and ODScrnClear(). The display
+ * attribute byte is always in the IBM color attribute format, with the
+ * lower 4 bits indicating the foreground color, and the next 3 bits
+ * indicating the background color. The upper bit specifies whether the text
+ * is flashing, although this code may not actually show flashing text on
+ * all platforms.
+ *
+ * Parameters: btAttribute - The new color attribute to use.
+ *
+ *     Return: void
+ */
+void ODScrnSetAttribute(BYTE btAttribute)
+{
+   /* Set internal display colour attribute. */
+   btCurrentAttribute = btAttribute;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnEnableScrolling()
+ *
+ * Enables or disables scrolling of text within the currently defined boundary
+ * area when a carriage return is sent with the cursor located on the bottom
+ * line of bounary area.
+ *
+ * Parameters: bEnable - TRUE to enable scrolling, FALSE to disable scrolling.
+ *
+ *     Return: void
+ */
+void ODScrnEnableScrolling(BOOL bEnable)
+{
+   /* Stores the current scrolling setting. */
+   bScrollEnabled = bEnable;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnEnableCaret()
+ *
+ * Turns the caret (flashing indicator of the current cursor location) on or
+ * off. Under the Win32 platform, the caret is always active when the
+ * window has input focus, and inactive at any other time. Hene, under
+ * Win32, this function has no effect.
+ *
+ * Parameters: bEnable - TRUE to turn on the flashing caret, FALSE to turn it
+ *                       off.
+ *
+ *     Return: void
+ */
+void ODScrnEnableCaret(BOOL bEnable)
+{
+#ifdef ODPLAT_DOS
+   if(bCaretOn == bEnable) return;
+
+   bCaretOn = bEnable;
+
+   /* Execute the cursor on / off primitive. */
+   ASM    push si
+   ASM    push di
+   ASM    mov ah, 0x03
+   ASM    mov bh, btDisplayPage
+   ASM    int 0x10
+
+   /* ch = start line, cl = end line. */
+   ASM    push cx                
+   ASM    mov ah, 0x0f
+   ASM    int 0x10
+   ASM    pop cx
+
+   /* al = video mode. */
+   ASM    push ax
+   ASM    and ch, 0x1f
+   ASM    mov al, bCaretOn
+   ASM    and al, al
+   ASM    jnz set_cursor
+   /* ch bits 5-6 = blink attr */
+   /*               00 = normal */
+   /*               01 = invisible */
+   ASM    or ch, 0x20
+set_cursor:
+   ASM    pop ax
+   ASM    mov bh, btDisplayPage
+   ASM    mov ah, 0x01
+   ASM    int 0x10
+   ASM    pop di
+   ASM    pop si
+
+
+   if(bCaretOn)
+   {
+      /* Turn on the local caret, updating its position. */
+      ODScrnUpdateCaretPos();
+   }
+   else
+   {
+      /* Turn off the local caret. */
+      ASM    mov ah, 0x02
+      ASM    mov bh, btDisplayPage
+      ASM    mov dh, OD_SCREEN_HEIGHT
+      ASM    mov dl, OD_SCREEN_WIDTH
+      ASM    push si
+      ASM    push di
+      ASM    int 0x10
+      ASM    pop di
+      ASM    pop si
+   }
+#endif /* ODPLAT_DOS */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnGetTextInfo()
+ *
+ * Fills a structure with information about the current display settings,
+ * including the position of the current boundary area (output window),
+ * color attribute and cursor location.
+ *
+ * Parameters: pTextInfo - Pointer to the structure to store the current text
+ *                         settings information in.
+ *
+ *     Return: void
+ */
+void ODScrnGetTextInfo(tODScrnTextInfo *pTextInfo)
+{
+   pTextInfo->wintop = btTopBoundary + 1;
+   pTextInfo->winleft = btLeftBoundary + 1;
+   pTextInfo->winright = btRightBoundary + 1;
+   pTextInfo->winbottom = btBottomBoundary + 1;
+   pTextInfo->attribute = btCurrentAttribute;
+   pTextInfo->curx = btCursorColumn + 1;
+   pTextInfo->cury = btCursorRow + 1;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnPrintf()
+ *
+ * Performs formatted output within the current boundary area.
+ *
+ * Parameters: pszFormat - Format string, which is in the same format as is
+ *                         used by the standard C printf() function.
+ *
+ *             The semantics of additional parameters is specified by the
+ *             contents of the pszFormat string.
+ *
+ *     Return: The standard printf() return value.
+ */
+INT ODScrnPrintf(char *pszFormat, ...)
+{
+   va_list pArgumentList;
+   INT nToReturn;
+
+   /* Generate string to display. */
+   va_start(pArgumentList, pszFormat);
+   nToReturn = vsprintf(szBuffer, pszFormat, pArgumentList);
+   va_end(pArgumentList);
+
+   /* Ensure that we didn't overrun the buffer. */
+   ASSERT(strlen(szBuffer) <= sizeof(szBuffer) - 1);
+
+   /* Display generated string. */
+   ODScrnDisplayString(szBuffer);
+
+   /* Return appropriate value. */
+   return (nToReturn);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnDisplayChar()
+ *
+ * Writes a single character within the current boundary area, advancing the
+ * cursor.
+ *
+ * Parameters: chToOutput  - The character to display.
+ *
+ *     Return: void
+ */
+void ODScrnDisplayChar(unsigned char chToOutput)
+{
+   BYTE ODFAR *pbtDest;
+   
+   ODScrnGetCursorPos();
+
+   if(btCursorColumn > btRightBoundary - btLeftBoundary)
+   {
+      btCursorColumn = btRightBoundary - btLeftBoundary;
+   }
+
+   if(btCursorRow > btBottomBoundary - btTopBoundary)
+   {
+      btCursorRow = btBottomBoundary - btTopBoundary;
+   }
+
+   switch(chToOutput)
+   {
+      /* If character is a carriage return. */
+      case '\r':
+         btCursorColumn = 0;
+         break;
+
+      /* If character is a line feed. */
+      case '\n':
+         /* If cursor is at bottom of output window. */
+         if(btCursorRow == btBottomBoundary - btTopBoundary)
+         {
+            /* Scroll the screen up by one line. */
+            ODScrnScrollUpAndInvalidate();
+         }
+         /* If cursor is not at bottom of output window. */
+         else
+         {
+            /* Move the cursor down one line. */
+            ++btCursorRow;
+         }
+         break;
+
+      case '\b':
+         /* If backspace. */
+         if(btCursorColumn != 0) --btCursorColumn;
+         break;
+
+      case '\t':
+         /* If tab character. */
+         btCursorColumn = ((btCursorColumn / 8) + 1) * 8;
+         if(btCursorColumn > btRightBoundary - btLeftBoundary)
+         {
+            btCursorColumn = 0;
+
+            /* If moving cursor down one line advances past end of window. */
+            if(++btCursorRow > btBottomBoundary - btTopBoundary)
+            {
+               /* Move cursor back to bottom line of window. */
+               btCursorRow = btBottomBoundary - btTopBoundary;
+
+               /* Scroll the screen up by one line. */
+               ODScrnScrollUpAndInvalidate();
+            }
+         }
+         break;
+
+      case '\a':
+         /* If bell. */
+         if(!od_control.od_silent_mode)
+         {
+#ifdef ODPLAT_DOS
+            ASM    mov ah, 0x02
+            ASM    mov dl, 7
+            ASM    int 0x21
+#endif /* ODPLAT_DOS */
+#ifdef ODPLAT_WIN32
+            MessageBeep(0xffffffff);
+#endif /* ODPLAT_WIN32 */
+         }
+         break;
+
+      /* If character is not a control character. */
+      default:
+         /* Output character to display buffer. */
+         pbtDest = (BYTE ODFAR *)pScrnBuffer
+            + ((btTopBoundary + btCursorRow) * BUFFER_LINE_BYTES
+            + (btLeftBoundary + btCursorColumn) * BYTES_PER_CHAR);
+         *pbtDest++ = chToOutput;
+         *pbtDest = btCurrentAttribute;
+
+         ASSERT(pbtDest >= (BYTE ODFAR *)pScrnBuffer);
+         ASSERT(pbtDest < (BYTE ODFAR *)pScrnBuffer + SCREEN_BUFFER_SIZE);
+
+#ifdef ODPLAT_WIN32
+         /* Force the updated area of the screen window to be redrawn. */
+         ODScrnInvalidate((BYTE)(btCursorColumn + btLeftBoundary),
+            (BYTE)(btCursorRow + btTopBoundary),
+            (BYTE)(btCursorColumn + btLeftBoundary),
+            (BYTE)(btCursorRow + btTopBoundary));
+#endif /* ODPLAT_WIN32 */
+
+         /* Advance cursor. If at end of line ... */
+         if(++btCursorColumn > btRightBoundary - btLeftBoundary)
+         {
+            /* Wrap cursor if necessary. */
+            btCursorColumn = 0;
+
+            /* If moving cursor down one line advances past end of window. */
+            if(++btCursorRow > btBottomBoundary - btTopBoundary)
+            {
+               /* Move cursor back to bottom line of window. */
+               btCursorRow = btBottomBoundary - btTopBoundary;
+
+               /* Scroll the screen up by one line. */
+               ODScrnScrollUpAndInvalidate();
+            }
+         }
+   }
+
+   /* Execute the update flashing cursor primitive. */
+   ODScrnUpdateCaretPos();
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnGetCursorPos()                                *** PRIVATE FUNCTION ***
+ *
+ * Updates the current cursor position (output position) from the location of
+ * the caret (flashing cursor). This function doesn't do anything on the
+ * Win32 platform, since we nobody else can reposition the cursor.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODScrnGetCursorPos(void)
+{
+#ifdef ODPLAT_DOS
+   if(!bCaretOn) return;
+
+   ASM    mov ah, 0x03
+   ASM    mov bh, btDisplayPage
+   ASM    push si
+   ASM    push di
+   ASM    int 0x10
+   ASM    pop di
+   ASM    pop si
+   ASM    sub dh, btTopBoundary
+   ASM    mov btCursorRow, dh
+   ASM    sub dl, btLeftBoundary
+   ASM    mov btCursorColumn, dl
+#endif /* ODPLAT_DOS */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnUpdateCaretPos()                              *** PRIVATE FUNCTION ***
+ *
+ * Updates the position of the caret (flashing cursor) from the current cursor
+ * location (output position).
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODScrnUpdateCaretPos(void)
+{
+#ifdef ODPLAT_DOS
+   if(!bCaretOn) return;
+
+   /* Update position of flashing cursor on screen */
+   ASM    mov ah, 0x02
+   ASM    mov bh, btDisplayPage
+   ASM    mov dh, btCursorRow
+   ASM    add dh, btTopBoundary
+   ASM    mov dl, btCursorColumn
+   ASM    add dl, btLeftBoundary
+   ASM    push si
+   ASM    push di
+   ASM    int 0x10
+   ASM    pop di
+   ASM    pop si
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   if(hwndScreenWindow != NULL)
+   {
+      PostMessage(hwndScreenWindow, WM_MOVE_YOUR_CARET, 0, 0);
+   }
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnClear()
+ *
+ * Clears the text within the currently defined boundary area, setting the
+ * display attribute of the entire boundary area to the current display
+ * color.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODScrnClear(void)
+{
+   WORD ODFAR *pDest = (WORD ODFAR *)pScrnBuffer +
+      ((btTopBoundary * OD_SCREEN_WIDTH) + btLeftBoundary);
+   WORD wBlank = (((WORD)btCurrentAttribute) << 8) | 32;
+   BYTE btCurColumn;
+   BYTE btCurLine = (btBottomBoundary - btTopBoundary) + 1;
+   BYTE btColumnStart = (btRightBoundary - btLeftBoundary) + 1;
+   BYTE btSkip = OD_SCREEN_WIDTH - btColumnStart;
+
+   /* Clear contents of current window. */
+   do
+   {
+      btCurColumn = btColumnStart;
+      do
+      {
+         ASSERT(pDest >= (WORD ODFAR *)pScrnBuffer);
+         ASSERT(pDest <= (WORD ODFAR *)pScrnBuffer + 2000);
+         *(pDest++) = wBlank;
+      } while ((--btCurColumn) != 0);
+      pDest += btSkip;
+   } while((--btCurLine) != 0);
+
+   /* Move cursor to top left-hand corner of current window. */
+   btCursorColumn = btCursorRow = 0;
+
+   /* Execute the update flashing cursor primitive. */
+   ODScrnUpdateCaretPos();
+
+#ifdef ODPLAT_WIN32
+   /* Force the updated area of the screen window to be redrawn. */
+   ODScrnInvalidate(btLeftBoundary, btTopBoundary, btRightBoundary,
+      btBottomBoundary);
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnScrollUpAndInvalidate()                       *** PRIVATE FUNCTION ***
+ *
+ * Scrolls the entire screen up by one line, only if scrolling is enabled.
+ * If scrolling is performed, invalidates area that was scrolled. Scrolling
+ * is accomplished using ODScrnScrollUpOneLine().
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODScrnScrollUpAndInvalidate(void)
+{
+   /* If scrolling is enabled. */
+   if(bScrollEnabled)
+   {
+      /* Execute the scroll primitive. */
+      ODScrnScrollUpOneLine();
+
+#ifdef ODPLAT_WIN32
+      /* Force the updated area of the screen window to be redrawn. */
+      ODScrnInvalidate(btLeftBoundary, btTopBoundary, btRightBoundary,
+         btBottomBoundary);
+#endif /* ODPLAT_WIN32 */
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnScrollUpOneLine()                             *** PRIVATE FUNCTION ***
+ *
+ * Scrolls the area within the current output boundary up one line, leaving the
+ * newly created line at the bottom of the area blank, with the current display
+ * attribute.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void ODScrnScrollUpOneLine(void)
+{
+   WORD ODFAR *pwDest = (WORD ODFAR *)pScrnBuffer
+      + (btTopBoundary * OD_SCREEN_WIDTH + btLeftBoundary);
+   WORD ODFAR *pwSource;
+   BYTE btCurColumn;
+   BYTE btCurLine = btBottomBoundary - btTopBoundary;
+   BYTE btColumnStart = btRightBoundary - btLeftBoundary + 1;
+   BYTE btSkip = OD_SCREEN_WIDTH - btColumnStart;
+   WORD wBlank = (((WORD)btCurrentAttribute) << 8) | 32;
+
+   pwSource = pwDest + OD_SCREEN_WIDTH;
+
+   ASSERT(btSkip >= 0 && btSkip <= OD_SCREEN_WIDTH);
+
+   /* Move text in area of window up one line. */
+   do
+   {
+      btCurColumn = btColumnStart;
+      do
+      {
+         ASSERT(pwDest >= (WORD ODFAR *)pScrnBuffer);
+         ASSERT(pwDest <= (WORD ODFAR *)pScrnBuffer + 2000);
+         ASSERT(pwSource >= (WORD ODFAR *)pScrnBuffer);
+         ASSERT(pwSource <= (WORD ODFAR *)pScrnBuffer+2000);
+         *(pwDest++) = *(pwSource++);
+      } while((--btCurColumn) != 0);
+      pwDest += btSkip;
+      pwSource += btSkip;
+   } while ((--btCurLine) != 0);
+
+   /* Clear newly created line at bottom of window. */
+   btCurColumn = btColumnStart;
+   do
+   {
+      ASSERT(pwDest >= (WORD ODFAR *)pScrnBuffer);
+      ASSERT(pwDest <= (WORD ODFAR *)pScrnBuffer + 2000);
+      *(pwDest++) = wBlank;
+   } while((--btCurColumn) != 0);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnGetText()
+ *
+ * Copies a portion of the currently displayed text and corresponding color
+ * attributes to a buffer provided by the caller.
+ *
+ * Parameters: btLeft    - Column number of the left edge of the area to copy
+ *                         from.
+ *
+ *             btTop     - Row number of the top edge of the area to copy from.
+ *
+ *             btRight   - Column number of the right edge of the area to copy
+ *                         from.
+ *
+ *             btBottom  - Row number of the bottom edge of the area to copy
+ *                         from.
+ *
+ *             pbtBuffer - A pointer to the buffer to copy to. It is the
+ *                         caller's responsibility to ensure that this buffer
+ *                         is large enough. This buffer must be at least
+ *                         2 x (Width of area) x (Height of area) bytes in size.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+BOOL ODScrnGetText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   void *pbtBuffer)
+{
+   WORD *pwBuffer = (WORD *)pbtBuffer;
+   WORD ODFAR *pSource = (WORD ODFAR *)pScrnBuffer
+      + ((((--btTop) + btTopBoundary)
+      * OD_SCREEN_WIDTH) + btLeftBoundary + (--btLeft));
+   BYTE btCurColumn;
+   BYTE btCurLine = (--btBottom) - btTop + 1;
+   BYTE btColumnStart = (--btRight) - btLeft + 1;
+   BYTE btSkip = OD_SCREEN_WIDTH - btColumnStart;
+
+   ASSERT(btLeft >= 0);
+   ASSERT(btTop >= 0);
+   ASSERT(btRight <= btRightBoundary - btLeftBoundary);
+   ASSERT(btBottom <= btBottomBoundary - btTopBoundary);
+   ASSERT(pbtBuffer);
+
+   /* Copy contents of screen block to buffer */
+   do 
+   {
+      btCurColumn = btColumnStart;
+      do
+      {
+         ASSERT(pSource >= (WORD ODFAR *)pScrnBuffer);
+         ASSERT(pSource <= (WORD ODFAR *)pScrnBuffer + 2000);
+         ASSERT(pwBuffer >= (WORD *)pbtBuffer);
+         ASSERT(pwBuffer <= (WORD *)pbtBuffer + 2000);
+         *(pwBuffer++) = *(pSource++);
+      } while ((--btCurColumn) != 0);
+      pSource += btSkip;
+   } while((--btCurLine) != 0);
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnPutText()
+ *
+ * Changes the currently displayed text and corresponding color attributes in
+ * the specified area, to the values taken from the buffer. This buffer is in
+ * the same format as is produce by the ODScrnGetText() function.
+ *
+ * Parameters: btLeft    - Column number of the left edge of the area to copy
+ *                         to.
+ *
+ *             btTop     - Row number of the top edge of the area to copy to.
+ *
+ *             btRight   - Column number of the right edge of the area to copy
+ *                         to.
+ *
+ *             btBottom  - Row number of the bottom edge of the area to copy
+ *                         to.
+ *
+ *             pbtBuffer - A pointer to the buffer to copy from.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+BOOL ODScrnPutText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   void *pbtBuffer)
+{
+   WORD *pwBuffer = (WORD *)pbtBuffer;
+   WORD ODFAR *pDest = (WORD ODFAR *)pScrnBuffer
+      + ((((--btTop) + btTopBoundary)
+      * OD_SCREEN_WIDTH) + btLeftBoundary + (--btLeft));
+   BYTE btCurColumn;
+   BYTE btCurLine = (--btBottom) - btTop + 1;
+   BYTE btColumnStart = (--btRight) - btLeft + 1;
+   BYTE btSkip = OD_SCREEN_WIDTH - btColumnStart;
+
+   ASSERT(btLeft >= 0 && btTop >= 0);
+   ASSERT(btLeft <= btRightBoundary - btLeftBoundary);
+   ASSERT(btTop <= btBottomBoundary - btTopBoundary);
+   ASSERT(btRight >= 0 && btBottom >= 0);
+   ASSERT(btRight <= btRightBoundary - btLeftBoundary);
+   ASSERT(btBottom <= btBottomBoundary - btTopBoundary);
+   ASSERT(pbtBuffer != NULL);
+
+   /* Copy contents of screen block to buffer. */
+   do
+   {
+      btCurColumn = btColumnStart;
+      do
+      {
+         ASSERT(pDest >= (WORD ODFAR *)pScrnBuffer);
+         ASSERT(pDest <= (WORD ODFAR *)pScrnBuffer + 2000);
+         ASSERT(pwBuffer >= (WORD *)pbtBuffer);
+         ASSERT(pwBuffer <= (WORD *)pbtBuffer + 2000);
+         *(pDest++) = *(pwBuffer++);
+      } while ((--btCurColumn) != 0);
+      pDest += btSkip;
+   } while((--btCurLine) != 0);
+
+#ifdef ODPLAT_WIN32
+   /* Force the updated area of the screen window to be redrawn. */
+   ODScrnInvalidate((BYTE)(btLeftBoundary + btLeft), 
+      (BYTE)(btTopBoundary + btTop),
+      (BYTE)(btRightBoundary + btRight),
+      (BYTE)(btBottomBoundary + btBottom));
+#endif /* ODPLAT_WIN32 */
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnDisplayString()
+ *
+ * Copies the contents of a string to the display, using the currently set
+ * color attributes. The cursor location is updated to the end of the string
+ * on the screen.
+ *
+ * Parameters: pszString - Pointer to the string to display.
+ *
+ *     Return: void.
+ */
+void ODScrnDisplayString(const char *pszString)
+{
+   ODScrnDisplayBuffer(pszString, strlen(pszString));
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnDisplayBuffer()
+ *
+ * Copies the contents of a buffer to the display, using the currently set
+ * color attributes. The cursor location is updated to the end of the text
+ * displayed to the screen.
+ *
+ * Parameters: pBuffer         - Pointer to a buffer containing the
+ *                               character(s) to display.
+ *
+ *             nCharsToDisplay - Count of number of characters to display from
+ *                               the buffer.
+ *
+ *     Return: void.
+ */
+void ODScrnDisplayBuffer(const char *pBuffer, INT nCharsToDisplay)
+{
+   const char *pchCurrentChar = pBuffer;
+   INT nCharsLeft = nCharsToDisplay;
+   BYTE ODFAR *pDest;
+   BYTE btLeftColumn;
+   BYTE btAttribute = btCurrentAttribute;
+   BYTE btCurrentColumn;
+   BYTE btBottom = btBottomBoundary - btTopBoundary;
+#ifdef ODPLAT_WIN32
+   BOOL bAnythingInvalid = FALSE;
+   BYTE btLeftMost;
+   BYTE btRightMost;
+   BYTE btTopMost;
+   BYTE btBottomMost;
+#endif /* ODPLAT_WIN32 */
+
+   ASSERT(pBuffer != NULL);
+   ASSERT(nCharsToDisplay >= 0);
+
+   ODScrnGetCursorPos();
+
+   if(btCursorColumn > btRightBoundary - btLeftBoundary)
+   {
+      btCursorColumn = btRightBoundary - btLeftBoundary;
+   }
+
+   if(btCursorRow > btBottomBoundary - btTopBoundary)
+   {
+      btCursorRow = btBottomBoundary - btTopBoundary;
+   }
+
+   btCurrentColumn = btCursorColumn;
+
+   btLeftColumn = btRightBoundary - (btCurrentColumn + btLeftBoundary);
+   pDest = (BYTE ODFAR *) pScrnBuffer + (((btTopBoundary + btCursorRow)
+      * BUFFER_LINE_BYTES)
+      + (btLeftBoundary + btCursorColumn) * BYTES_PER_CHAR);
+
+   while(nCharsLeft--)
+   {
+      ASSERT(pDest >= (BYTE ODFAR *)pScrnBuffer);
+      ASSERT(pDest <= (BYTE ODFAR *)pScrnBuffer + SCREEN_BUFFER_SIZE);
+      switch(*pchCurrentChar)
+      {
+         case '\r':
+            btCurrentColumn = 0;
+            btLeftColumn = btRightBoundary - btLeftBoundary;
+            pDest = (BYTE ODFAR *)pScrnBuffer + ((btTopBoundary + btCursorRow)
+               * BUFFER_LINE_BYTES + btLeftBoundary * BYTES_PER_CHAR);
+            pchCurrentChar++;
+            break;
+         case '\n':
+            if (btCursorRow < btBottom)
+            {
+               ++btCursorRow;
+               pDest += BUFFER_LINE_BYTES;
+            }
+            else if(bScrollEnabled)
+            {
+               ODScrnScrollUpOneLine();
+#ifdef ODPLAT_WIN32
+               /* Entire boundary area is now invalid. */
+               bAnythingInvalid = TRUE;
+               btLeftMost = btLeftBoundary;
+               btRightMost = btRightBoundary;
+               btTopMost = btTopBoundary;
+               btBottomMost = btBottomBoundary;
+#endif /* ODPLAT_WIN32 */
+            }
+            pchCurrentChar++;
+            break;
+
+         case '\a':
+            /* If bell */
+            if(!od_control.od_silent_mode)
+            {
+#ifdef ODPLAT_DOS
+               ASM    mov ah, 0x02
+               ASM    mov dl, 7
+               ASM    int 0x21
+#endif /* ODPLAT_DOS */
+#ifdef ODPLAT_WIN32
+               MessageBeep(0xffffffff);
+#endif /* ODPLAT_WIN32 */
+               pchCurrentChar++;
+            }
+            break;
+
+         case '\t':
+            /* If tab character. */
+            btCurrentColumn = ((btCurrentColumn / 8) + 1) * 8;
+            if(btCurrentColumn > btRightBoundary - btLeftBoundary)
+            {
+               btCurrentColumn = 0;
+
+               /* If moving cursor down one line advances past end of window. */
+               if(++btCursorRow > btBottomBoundary - btTopBoundary)
+               {
+                  /* Move cursor back to bottom line of window. */
+                  btCursorRow = btBottomBoundary - btTopBoundary;
+
+                  /* If scrolling is enabled. */
+                  if(bScrollEnabled)
+                  {
+                     /* Execute the scroll primitive .*/
+                     ODScrnScrollUpOneLine();
+#ifdef ODPLAT_WIN32
+                     /* Entire boundary area is now invalid. */
+                     bAnythingInvalid = TRUE;
+                     btLeftMost = btLeftBoundary;
+                     btRightMost = btRightBoundary;
+                     btTopMost = btTopBoundary;
+                     btBottomMost = btBottomBoundary;
+#endif /* ODPLAT_WIN32 */
+                  }
+               }
+            }
+
+            /* Determine new buffer destination address. */
+            pDest = (BYTE ODFAR *) pScrnBuffer
+               + (((btTopBoundary + btCursorRow) * BUFFER_LINE_BYTES)
+               + (btLeftBoundary + btCursorColumn) * BYTES_PER_CHAR);
+            break;
+
+         case '\b':
+            if(btCurrentColumn > 0)
+            {
+               --btCurrentColumn;
+               pDest-=2;
+               btLeftColumn++;
+            }
+            pchCurrentChar++;
+            break;
+
+         default:
+            *(pDest++) = *(pchCurrentChar++);
+            *(pDest++) = btAttribute;
+
+#ifdef ODPLAT_WIN32
+            /* Expand area to invalidate, if needed. */
+            if(!bAnythingInvalid)
+            {
+               bAnythingInvalid = TRUE;
+               btLeftMost = btLeftBoundary + btCurrentColumn;
+               btRightMost = btLeftBoundary + btCurrentColumn;
+               btTopMost = btTopBoundary + btCursorRow;
+               btBottomMost = btTopBoundary + btCursorRow;
+            }
+            else
+            {
+               BYTE btColumn = btLeftBoundary + btCurrentColumn;
+               BYTE btRow = btTopBoundary + btCursorRow;
+
+               if(btColumn < btLeftMost) btLeftMost = btColumn;
+               if(btColumn > btRightMost) btRightMost = btColumn;
+               if(btRow < btTopMost) btTopMost = btRow;
+               if(btRow > btBottomMost) btBottomMost = btRow;
+            }
+#endif /* ODPLAT_WIN32 */
+
+            if(btLeftColumn--)
+            {
+               ++btCurrentColumn;
+            }
+            else
+            {
+               btCurrentColumn = 0;
+               btLeftColumn = btRightBoundary - btLeftBoundary;
+
+               if(btCursorRow < btBottom)
+               {
+                  ++btCursorRow;
+               }
+               else if(bScrollEnabled)
+               {
+                  ODScrnScrollUpOneLine();
+#ifdef ODPLAT_WIN32
+                  /* Entire boundary area is now invalid. */
+                  bAnythingInvalid = TRUE;
+                  btLeftMost = btLeftBoundary;
+                  btRightMost = btRightBoundary;
+                  btTopMost = btTopBoundary;
+                  btBottomMost = btBottomBoundary;
+#endif /* ODPLAT_WIN32 */
+               }
+
+               pDest = (BYTE ODFAR *)pScrnBuffer
+                  + ((btTopBoundary + btCursorRow)
+                  * BUFFER_LINE_BYTES + btLeftBoundary * BYTES_PER_CHAR);
+            }
+      }
+   }
+
+   btCursorColumn = btCurrentColumn;
+   ODScrnUpdateCaretPos();
+
+#ifdef ODPLAT_WIN32
+   if(bAnythingInvalid)
+   {
+      /* Force the updated area of the screen window to be redrawn. */
+      ODScrnInvalidate(btLeftMost, btTopMost, btRightMost,
+         btBottomMost);
+   }
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnCopyText()
+ *
+ * Copies the contents of the specified area on the screen to another location
+ * on the screen. The destination location must be such that the entire area
+ * specified as the source can be displayed without falling off the edge of the
+ * screen.
+ *
+ * Parameters: btLeft       - Column number of the left edge of the area to
+ *                            copy from.
+ *
+ *             btTop        - Row number of the top edge of the area to copy
+ *                            from.
+ *
+ *             btRight      - Column number of the right edge of the area to
+ *                            copy from.
+ *
+ *             btBottom     - Row number of the bottom edge of the area to
+ *                            copy from.
+ *
+ *             btDestColumn - Column number where the upper right corner of
+ *                            the area should be copied to.
+ *
+ *             btDestRow    - Row number where the upper right cornder of the
+ *                            area should be copied to.
+ *
+ *     Return: TRUE on success, or FALSE on failure. May fail due to
+ *             insufficient available memory.
+ */
+BOOL ODScrnCopyText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   BYTE btDestColumn, BYTE btDestRow)
+{
+   void *pScrnBuffer;
+
+   ASSERT(btLeft >= 0 && btTop >= 0);
+   ASSERT(btLeft <= btRightBoundary - btLeftBoundary);
+   ASSERT(btTop <= btBottomBoundary - btTopBoundary);
+   ASSERT(btRight >= 0 && btBottom >= 0);
+   ASSERT(btRight <= btRightBoundary - btLeftBoundary);
+   ASSERT(btBottom <= btBottomBoundary - btTopBoundary);
+   ASSERT(btDestColumn >= 0 && btDestRow >= 0);
+   ASSERT(btDestColumn <= btRightBoundary - btLeftBoundary);
+   ASSERT(btDestRow <= btBottomBoundary - btTopBoundary);
+
+   if(   !(btLeft <= btRightBoundary - btLeftBoundary
+         && btTop <= btBottomBoundary - btTopBoundary)
+      || !(btRight <= btRightBoundary - btLeftBoundary
+         && btBottom <= btBottomBoundary - btTopBoundary)
+      || !(btDestColumn <= btRightBoundary - btLeftBoundary
+         && btDestRow <= btBottomBoundary - btTopBoundary))
+   {
+      return(FALSE);
+   }
+
+
+   if((pScrnBuffer = malloc((btRight - btLeft + 1) * (btBottom - btTop + 1)
+      * BYTES_PER_CHAR)) == NULL)
+   {
+      /* Insufficient memory, return with failure. */
+      return (FALSE);
+   }
+
+   ODScrnGetText(btLeft, btTop, btRight, btBottom, pScrnBuffer);
+   ODScrnPutText(btDestColumn, btDestRow,
+      (BYTE)(btRight + (btDestColumn - btLeft)),
+      (BYTE)(btBottom + (btDestRow - btTop)), pScrnBuffer);
+   free(pScrnBuffer);
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnClearToEndOfLine()
+ *
+ * Clears the contents of the current line, from the current cursor location
+ * to the end of the line.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+void ODScrnClearToEndOfLine(void)
+{
+   unsigned char btCharsToDelete = btRightBoundary
+      - (btLeftBoundary + btCursorColumn);
+   BYTE ODFAR *pDest = (BYTE ODFAR *) pScrnBuffer
+      + (((btTopBoundary + btCursorRow) * BUFFER_LINE_BYTES)
+      + (btLeftBoundary + btCursorColumn) * BYTES_PER_CHAR);
+   BYTE btAttribute = btCurrentAttribute;
+
+   while(btCharsToDelete--)
+   {
+      *(pDest++) = ' ';
+      *(pDest++) = btAttribute;
+   }
+
+#ifdef ODPLAT_WIN32
+   /* Force the updated area of the screen window to be redrawn. */
+   ODScrnInvalidate((BYTE)(btLeftBoundary + btCursorColumn),
+      (BYTE)(btTopBoundary + btCursorRow), btRightBoundary,
+      (BYTE)(btTopBoundary + btCursorRow));
+#endif /* ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnCreateWindow()
+ *
+ * Creates a text-based window on the local terminal.
+ *
+ * Parameters: btLeft           - Column numebr of the left of the window.
+ *
+ *             btTop            - Row number of the top of the window.
+ *
+ *             btRight          - Column number of the right of the window.
+ *
+ *             btBottom         - Row number of the bottom of the window.
+ *
+ *             btAttribute      - Display attribute for the window boarder and
+ *                                the area inside the window.
+ *
+ *             pszTitle         - Pointer to a string containing the title to
+ *                                display, or "" for none.
+ *
+ *             btTitleAttribute - Display attribute for the title text.
+ *
+ *     Return: void
+ */
+#ifdef OD_TEXTMODE
+void *ODScrnCreateWindow(BYTE btLeft, BYTE btTop, BYTE btRight,
+   BYTE btBottom, BYTE btAttribute, char *pszTitle, BYTE btTitleAttribute)
+{
+   void *pUnder;
+   INT nBetween;
+   INT nCount;
+   INT nFirst;
+   char *pszString;
+   int nTitleWidth;
+
+   ASSERT(pszTitle != NULL);
+
+   /* Alocate space to store screen contents "under" window. */
+   if((pUnder = malloc((btRight - btLeft + 1) * (btBottom - btTop + 1)
+      * BYTES_PER_CHAR + 4 * sizeof(BYTE))) == NULL)
+   {
+      return(NULL);
+   }
+
+   /* Store the window's position in the buffer. */
+   ((BYTE *)pUnder)[0] = btLeft;
+   ((BYTE *)pUnder)[1] = btTop;
+   ((BYTE *)pUnder)[2] = btRight;
+   ((BYTE *)pUnder)[3] = btBottom;
+
+   /* Retrieve screen contents in window area. */
+   ODScrnGetText(btLeft, btTop, btRight, btBottom, ((BYTE *)pUnder) + 4);
+
+   /* Determine area between left & right of window, distance of line before */
+   /* title, and distance of line after title.                               */
+   if(strlen(pszTitle) == 0)
+   {
+      nTitleWidth = 0;
+   }
+   else
+   {
+      nTitleWidth = strlen(pszTitle) + 2;
+   }
+   nCount = (nBetween = btRight - btLeft - 1) - nTitleWidth;
+   nCount -= (nFirst = nCount / 2);
+
+   /* Prepare to begin drawing window at upper left corner */
+   ODScrnSetCursorPos(btLeft, btTop);
+   ODScrnSetAttribute(btAttribute);
+
+   /* Draw first line of window */
+   ODScrnDisplayChar((unsigned char)214);
+   while(nFirst--) ODScrnDisplayChar((unsigned char)196);
+   if(strlen(pszTitle) != 0)
+   {
+      ODScrnSetAttribute(btTitleAttribute);
+      ODScrnDisplayChar(' ');
+      ODScrnDisplayString(pszTitle);
+      ODScrnDisplayChar(' ');
+      ODScrnSetAttribute(btAttribute);
+   }
+   while(nCount--) ODScrnDisplayChar((unsigned char)196);
+   ODScrnDisplayChar((unsigned char)183);
+
+   /* Build string for working lines */
+   pszString = szBuffer;
+   *pszString++ = (unsigned char)186;
+   nCount = nBetween;
+   while(nCount--) *pszString++ = ' ';
+   *pszString++ = (unsigned char)186;
+   *pszString++ = '\0';
+
+   /* Draw working lines of window */
+   for(nCount = btTop + 1; nCount < btBottom; ++nCount)
+   {
+      ODScrnSetCursorPos(btLeft, (BYTE)nCount);
+      ODScrnDisplayString(szBuffer);
+   }
+
+   /* Draw last line of window */
+   ODScrnSetCursorPos(btLeft, btBottom);
+   ODScrnDisplayChar((unsigned char)211);
+   while(nBetween--) ODScrnDisplayChar((unsigned char)196);
+   ODScrnDisplayChar((unsigned char)189);
+
+   /* return pointer to buffer */
+   return(pUnder);
+}
+#endif /* OD_TEXTMODE */
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnDestroyWindow()
+ *
+ * Removes a text-based window that was created by ODScrnCreateWindow().
+ *
+ * Parameters: pWindow   - Pointer to the buffer returned by the corresponding
+ *                         call to ODScrnCreateWindow().
+ *
+ *     Return: void
+ */
+#ifdef OD_TEXTMODE
+void ODScrnDestroyWindow(void *pWindow)
+{
+   BYTE btLeft;
+   BYTE btTop;
+   BYTE btRight;
+   BYTE btBottom;
+   BYTE *pabtWindow = (BYTE *)pWindow;
+
+   ASSERT(pWindow != NULL);
+
+   /* Determine the location of the window. */
+   btLeft = pabtWindow[0];
+   btTop = pabtWindow[1];
+   btRight = pabtWindow[2];
+   btBottom = pabtWindow[3];
+
+   /* Restore original screen contents under the window. */
+   ODScrnPutText(btLeft, btTop, btRight, btBottom, ((BYTE *)pWindow) + 4);
+
+   /* Deallocate window buffer. */
+   free(pWindow);
+}
+#endif /* OD_TEXTMODE */
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnLocalInput()
+ *
+ * Inputs a string, only displaying input on local screen.
+ *
+ * Parameters: btLeft    - Column number of the left end of the input
+ *                         field.
+ *
+ *             btRow     - Row number where the input field appears.
+ *
+ *             pszString - Location where user's input should be stored. Must
+ *                         be initialized.
+ *
+ *             nMaxChars - The maximum number of characters that may be
+ *                         accepted for input into the string.
+ *
+ *     Return: void
+ */
+#ifdef OD_TEXTMODE
+void ODScrnLocalInput(BYTE btLeft, BYTE btRow, char *pszString,
+   BYTE btMaxChars)
+{
+   BYTE btCount;
+   BYTE btCurrentPos;
+   BOOL bAnyKeysPressed = FALSE;
+   tODInputEvent InputEvent;
+
+   /* Draw initial input field. */
+   ODScrnSetCursorPos(btLeft, btRow);
+   ODScrnDisplayString(pszString);
+   for(btCount = strlen(pszString); btCount <= btMaxChars; ++btCount)
+   {
+      ODScrnDisplayChar(177);
+   }
+
+   /* Start with the cursor at the end of the input field. */
+   btCurrentPos = strlen(pszString);
+
+   /* Loop until the user presses enter. */
+   for(;;)
+   {
+      /* Position the cursor at the appropriate location. */
+      ODScrnSetCursorPos((BYTE)(btLeft + btCurrentPos), btRow);
+
+      /* Obtain the next input event. */
+      ODInQueueGetNextEvent(hODInputQueue, &InputEvent, OD_NO_TIMEOUT);
+
+      switch(InputEvent.chKeyPress)
+      {
+         case '\b':
+            /* If user presses [Backspace], then move back if we are not at */
+            /* the left of the input field.                                 */
+            if(btCurrentPos > 0)
+            {
+               /* Backspace, removing last character from string. */
+               btCurrentPos--;
+               ODScrnSetCursorPos((BYTE)(btLeft + btCurrentPos), btRow);
+               ODScrnDisplayChar(177);
+               pszString[btCurrentPos] = '\0';
+            }
+            break;
+
+         case '\n':
+         case '\r':
+            /* If user presses [Enter], then exit from the function. */
+            return;
+
+         case '\0':
+            /* In the case of a multi-character sequence, skip the next */
+            /* character from the input queue.                          */
+            ODInQueueGetNextEvent(hODInputQueue, &InputEvent, OD_NO_TIMEOUT);
+            break;
+
+         default:
+            /* If this is a valid string character for the string. */
+            if(InputEvent.chKeyPress >= ' ')
+            {
+               /* If no keys have been pressed yet, then erase the entire */
+               /* string first.                                           */
+               if(!bAnyKeysPressed)
+               {
+                  btCurrentPos = 0;
+                  ODScrnSetCursorPos(btLeft, btRow);
+                  for(btCount = 0; btCount <= btMaxChars; ++btCount)
+                  {
+                     ODScrnDisplayChar(177);
+                  }
+                  ODScrnSetCursorPos(btLeft, btRow);
+               }
+
+               /* If we are not at the end of the string, then add the */
+               /* character to the string.                             */
+               if(btCurrentPos < btMaxChars)
+               {
+                  /* Display the new character. */
+                  ODScrnDisplayChar(InputEvent.chKeyPress);
+
+                  /* Add the character to the string. */
+                  pszString[btCurrentPos] = InputEvent.chKeyPress;
+
+                  /* Update the current cursor position. */
+                  ++btCurrentPos;
+
+                  /* Terminate the string. */
+                  pszString[btCurrentPos] = '\0';
+               }
+            }
+      }
+
+      /* Note that a key has now been pressed. */
+      bAnyKeysPressed = TRUE;
+   }
+}
+#endif /* OD_TEXTMODE */
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnShowMessage()
+ *
+ * Displays a message window with the specified message text. Unlike the
+ * Windows MessageBox() function, this message box is removed by the caller
+ * of the function rather than the user.
+ *
+ * Parameters: pszText - Pointer to message text to be displayed. This string
+ *                       must continue to exist until after the
+ *                       ODScrnRemoveMessage() function is called.
+ *
+ *             nFlags  - Currently unused, must be 0.
+ *
+ *     Return: A pointer which must be passed to ODScrnRemoveMessage() in
+ *             order to remove this message from the screen. A return value
+ *             of NULL does not necessarily indicate window creation failure,
+ *             and should still be passed to a corresponding call to
+ *             ODScrnRemoveMessage().
+ */
+void *ODScrnShowMessage(char *pszText, int nFlags)
+{
+   ASSERT(pszText != NULL);
+   ASSERT(nFlags == 0);
+
+   /* In silent mode, this function does nothing. */
+   if(od_control.od_silent_mode) return(NULL);
+
+#ifdef ODPLAT_WIN32
+
+   /* Place a message in the frame window's message queue, asking it to  */
+   /* create the message window.                                         */
+   PostMessage(GetParent(hwndScreenWindow), WM_SHOW_MESSAGE, (WPARAM)nFlags,
+      (LPARAM)pszText);
+
+   return(NULL);
+
+#else /* !ODPLAT_WIN32 */
+   {
+      int nWindowWidth;
+      int nLeftColumn;
+      char szMessage[74];
+      void *pWindow;
+
+      UNUSED(nFlags);
+
+      ODStringCopy(szMessage, pszText, sizeof(szMessage));
+
+      ODStoreTextInfo();
+
+      nWindowWidth = strlen(szMessage) + 4;
+      nLeftColumn = 40 - (nWindowWidth / 2);
+      if((pWindow = ODScrnCreateWindow((BYTE)nLeftColumn, 10,
+         (BYTE)(nLeftColumn + (nWindowWidth - 1)), 14,
+         od_control.od_local_win_col, "", od_control.od_local_win_col))
+         == NULL)
+      {
+         return(NULL);
+      }
+
+      ODScrnSetCursorPos((BYTE)(42 - (nWindowWidth / 2)), 12);
+      ODScrnDisplayString(szMessage);
+      ODRestoreTextInfo();
+
+      ODScrnEnableCaret(FALSE);
+
+      return(pWindow);
+   }
+#endif /* !ODPLAT_WIN32 */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODScrnRemoveMessage()
+ *
+ * Removes a message that was shown by a previous call to ODScrnShowMessage().
+ *
+ * Parameters: pMessageInfo - Pointer to the buffer returned by the
+ *                            corresponding call to ODScrnShowMessage().
+ *
+ *     Return: void
+ */
+void ODScrnRemoveMessage(void *pMessageInfo)
+{
+   /* In silent mode, this function does nothing. */
+   if(od_control.od_silent_mode) return;
+
+#ifdef ODPLAT_WIN32
+   /* Place a message in the frame window's message queue, asking it to  */
+   /* remove the message window.                                         */
+   SendMessage(GetParent(hwndScreenWindow), WM_REMOVE_MESSAGE, 0, 0L);
+#else /* !ODPLAT_WIN32 */
+   /* If pMessageInfo is NULL, then we do nothing. */
+   if(pMessageInfo == NULL) return;
+
+   ODStoreTextInfo();
+   ODScrnDestroyWindow(pMessageInfo);
+   ODRestoreTextInfo();
+   ODScrnEnableCaret(TRUE);
+#endif /* !ODPLAT_WIN32 */
+}
+

+ 110 - 0
odoors/ODScrn.h

@@ -0,0 +1,110 @@
+/* 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: ODScrn.h
+ *
+ * Description: Functions used to access the local display screen buffer, which
+ *              keeps a copy of the text that is displayed on the remote
+ *              terminal. The local display screen buffer also displays the
+ *              OpenDoors status lines on some platforms. In addition to
+ *              maintaining the current screen buffer, the odscrn.c module
+ *              also contains the code to display this buffer on the screen.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 14, 1995  6.00  BP   Created.
+ *              Jan 21, 1996  6.00  BP   Added ODScrnShowMessage() and related.
+ *              Jan 27, 1996  6.00  BP   Made text-mode window f'ns static.
+ *              Jan 31, 1996  6.00  BP   Made them non-static again.
+ *              Jan 31, 1996  6.00  BP   Added ODScrnLocalInput().
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ */
+
+#ifndef _INC_ODSCRN
+#define _INC_ODSCRN
+
+#include "ODTypes.h"
+#include "ODPlat.h"
+
+/* Text information structure. */
+typedef struct
+{
+   unsigned char winleft;
+   unsigned char wintop;
+   unsigned char winright;
+   unsigned char winbottom;
+   unsigned char attribute;
+   unsigned char curx;
+   unsigned char cury;
+} tODScrnTextInfo;
+
+
+/* Screen buffer initialization and shutdown functions. */
+tODResult ODScrnInitialize(void);
+void ODScrnShutdown(void);
+
+/* Basic text output functions. */
+void ODScrnDisplayChar(unsigned char chToOutput);
+void ODScrnDisplayBuffer(const char *pBuffer, INT nCharsToDisplay);
+void ODScrnDisplayString(const char *pszString);
+INT ODScrnPrintf(char *pszFormat, ...);
+
+/* Functions for manipulating rectangular areas of the screen buffer. */
+BOOL ODScrnGetText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   void *pbtBuffer);
+BOOL ODScrnPutText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   void *pbtBuffer);
+BOOL ODScrnCopyText(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom,
+   BYTE btDestColumn, BYTE btDestRow);
+
+/* Functions for clearing portions of the screen buffer. */
+void ODScrnClear(void);
+void ODScrnClearToEndOfLine(void);
+
+/* Functions for setting or obtaining current display settings. */
+void ODScrnSetBoundary(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE btBottom);
+void ODScrnSetCursorPos(BYTE btColumn, BYTE btRow);
+void ODScrnSetAttribute(BYTE btAttribute);
+void ODScrnEnableScrolling(BOOL bEnable);
+void ODScrnEnableCaret(BOOL bEnable);
+void ODScrnGetTextInfo(tODScrnTextInfo *pTextInfo);
+
+/* Functions for displaying OpenDoors message window. */
+void *ODScrnShowMessage(char *pszText, int nFlags);
+void ODScrnRemoveMessage(void *pMessageInfo);
+
+/* Additional local output functions for text mode based versions. */
+#ifdef OD_TEXTMODE
+void *ODScrnCreateWindow(BYTE btLeft, BYTE btTop, BYTE btRight,
+   BYTE btBottom, BYTE btAttribute, char *pszTitle, BYTE btTitleAttribute);
+void ODScrnDestroyWindow(void *pWindow);
+void ODScrnLocalInput(BYTE btLeft, BYTE btRow, char *pszString,
+   BYTE btMaxChars);
+#endif /* OD_TEXTMODE */
+
+/* Functions for local screen window under Win32 version. */
+#ifdef ODPLAT_WIN32
+tODResult ODScrnStartWindow(HANDLE hInstance, tODThreadHandle *phScreenThread,
+   HWND hwndFrame);
+void ODScrnSetFocusToWindow(void);
+void ODScrnAdjustWindows(void);
+#endif /* ODPLAT_WIN32 */
+
+#endif /* _INC_ODSCRN */

+ 1102 - 0
odoors/ODSpawn.c

@@ -0,0 +1,1102 @@
+/* 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: ODSpawn.c
+ *
+ * Description: Implements the od_spawn...() functions for suspending this
+ *              program and executing a sub-program. Can be called by the
+ *              user explicitly, or invoked for sysop OS shell.
+ *
+ *   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   Use new directory access functions.
+ *              Dec 13, 1994  6.00  BP   Standardized coding style.
+ *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
+ *              Jan 01, 1995  6.00  BP   _waitdrain -> ODWaitDrain()
+ *              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 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Nov 17, 1995  6.00  BP   Use new input queue mechanism.
+ *              Nov 21, 1995  6.00  BP   Ported to Win32.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 23, 1996  6.00  BP   Finished port to Win32.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 23, 1996  6.00  BP   Enable and test under Win32.
+ *              Feb 27, 1996  6.00  BP   Store screen info in our own struct.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support - some functions not supported (Yet)
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <errno.h>
+
+#include "OpenDoor.h"
+#ifdef ODPLAT_NIX
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODCom.h"
+#include "ODPlat.h"
+#include "ODScrn.h"
+#include "ODInQue.h"
+#include "ODInEx.h"
+#include "ODUtil.h"
+#include "ODKrnl.h"
+#include "ODSwap.h"
+
+#ifdef ODPLAT_WIN32
+#include "ODFrame.h"
+#endif /* ODPLAT_WIN32 */
+
+#if defined(ODPLAT_WIN32) && defined(_MSC_VER)
+#undef P_WAIT
+#undef P_NOWAIT
+#include <process.h>
+#endif /* ODPLAT_WIN32 && _MSC_VER */
+
+#ifdef ODPLAT_DOS
+
+/* Local and global variables for memory swapping spawn routines. */
+
+int _swap = 0;                     /* if 0, do swap */
+char *_swappath = NULL;            /* swap path */
+int _useems = 0;                   /* if 0, use EMS */
+int _required = 0;                 /* child memory requirement in K */
+static long swapsize;              /* swap size requirement in bytes */
+static int ems = 2;                /* if 0, EMS is available */
+static int mapsize;                /* size of page map information */
+static unsigned int tempno = 1;    /* tempfile number */
+static char errtab[] =             /* error table */
+{
+   0,
+   EINVAL,
+   ENOENT,
+   ENOENT,
+   EMFILE,
+   EACCES,
+   EBADF,
+   ENOMEM,
+   ENOMEM,
+   ENOMEM,
+   E2BIG,
+   ENOEXEC,
+   EINVAL,
+   EINVAL,
+   -1,
+   EXDEV,
+   EACCES,
+   EXDEV,
+   ENOENT,
+   -1
+};
+
+static VECTOR vectab1[]=
+{
+    0,    1,     0,  0,
+    1,    1,     0,  0,
+    2,    1,     0,  0,
+    3,    1,     0,  0,
+    0x1B, 1,     0,  0,
+    0x23, 1,     0,  0,
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    2,     0,  0,            /* free record */
+    0,    3,     0,  0          /* end record */
+};
+
+static VECTOR vectab2[(sizeof vectab1)/(sizeof vectab1[0])];
+
+/* Location function prototypes. */
+int _spawnvpe(int nModeFlag, char *pszPath, char *papszArgs[],
+   char *papszEnviron[]);
+int _spawnve(int nModeFlag, char *pszPath, char *papszArgs[],
+   char * papszEnviron[]);
+static void savevect(void);
+
+
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_NIX
+/* Location function prototypes. */
+int _spawnvpe(int nModeFlag, char *pszPath, char *papszArgs[],
+   char *papszEnviron[]);
+#endif /* ODPLAT_NIX */
+
+/* ----------------------------------------------------------------------------
+ * od_spawn()
+ *
+ * Executes the specified command line, suspending OpenDoors operations while
+ * the spawned-to program is running.
+ *
+ * Parameters: pszCommandLine - Command to execute along with any parameters.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_spawn(const char *pszCommandLine)
+{
+#ifdef ODPLAT_DOS
+   char *apszArgs[4];
+   INT16 nReturnCode;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_spawn()");
+
+   *apszArgs=getenv("COMSPEC");
+
+   apszArgs[1] = "/c";
+   apszArgs[2] = pszCommandLine;
+   apszArgs[3] = NULL;
+   
+   if(*apszArgs != NULL)
+   {
+      if((nReturnCode = od_spawnvpe(P_WAIT, *apszArgs, apszArgs, NULL)) != -1
+         || errno != ENOENT)
+      {
+         return(nReturnCode != -1);
+      }
+   }
+
+   *apszArgs = "command.com";
+
+   return(od_spawnvpe(P_WAIT, *apszArgs, apszArgs, NULL) != -1);
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_WIN32
+   char *pch;
+   char *apszArgs[3];
+   char szProgName[80];
+
+   /* Build command and arguments list. */
+   /* Build program name. */
+   ODStringCopy(szProgName, pszCommandLine, sizeof(szProgName));
+   pch = strchr(szProgName, ' ');
+   if(pch != NULL) *pch = '\0';
+   apszArgs[0] = szProgName;
+
+   /* Build arguments. */
+   pch = strchr(pszCommandLine, ' ');
+   if(pch == NULL)
+   {
+      apszArgs[1] = NULL;
+   }
+   else
+   {
+      apszArgs[1] = pch + 1;
+      apszArgs[2] = NULL;
+   }
+
+   /* Now, call od_spawnvpe(). */
+   return(od_spawnvpe(P_WAIT, *apszArgs, apszArgs, NULL) != -1);
+#endif /* ODPLAT_WIN32 */
+
+#ifdef ODPLAT_NIX
+   sigset_t		block;
+   int retval;
+
+   /* Suspend kernel */
+   sigemptyset(&block);
+   sigaddset(&block,SIGALRM);
+   sigprocmask(SIG_BLOCK,&block,NULL);
+   retval=system(pszCommandLine);
+
+   /* Restore kernel */
+   sigemptyset(&block);
+   sigaddset(&block,SIGALRM);
+   sigprocmask(SIG_UNBLOCK,&block,NULL);
+
+   return(retval!=-1 && retval != 127);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_spawnvpe()
+ *
+ * Executes the specified program, using the specified arguments and
+ * environment variables, optionally suspending OpenDoors operations while
+ * the spawned-to program is running.
+ *
+ * Parameters: nModeFlag - P_WAIT to for OpenDoors operations to be suspended
+ *                         while the spawned-to program is running, or
+ *                         P_NOWAIT if the calling program should continue to
+ *                         run while the spawned-to program is running. In
+ *                         non-multitasking environments, the only valid value
+ *                         of this parameters is P_WAIT.
+ *
+ *             pszPath   - Complete path and filename of the program to
+ *                         exectute.
+ *
+ *             papszArg  - Array of string pointers to command line arguments.
+ *
+ *             papszEnv  - Array of string pointers to environment variables.
+ *
+ *     Return: -1 on failure or the spawned-to program's return value on
+ *             success.
+ */
+ODAPIDEF INT16 ODCALL od_spawnvpe(INT16 nModeFlag, char *pszPath,
+   char *papszArg[], char *papszEnv[])
+{
+   INT16 nToReturn;
+   time_t nStartUnixTime;
+   DWORD dwQuotient;
+#ifdef ODPLAT_WIN32
+   void *pWindow;
+#endif /* ODPLAT_WIN32 */   
+#ifdef ODPLAT_DOS
+   char *pszDir;
+   BYTE *abtScreenBuffer;
+   INT nDrive;
+   tODScrnTextInfo TextInfo;
+#endif /* ODPLAT_DOS */
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_spawnvpe()");
+
+   /* Initialize OpenDoors if it hasn't already been done. */
+   if(!bODInitialized) od_init();
+
+#ifdef ODPLAT_DOS
+   /* Ensure the nModeFlag is P_WAIT, which is the only valid value for */
+   /* the MS-DOS version of OpenDoors.                                  */
+   if(nModeFlag != P_WAIT)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      return(-1);
+   }
+
+   /* Store current screen contents. */
+   if((abtScreenBuffer = malloc(4000)) == NULL)
+   {
+      od_control.od_error = ERR_MEMORY;
+      return(-1);
+   }
+   if((pszDir = malloc(256)) == NULL)
+   {
+      od_control.od_error = ERR_MEMORY;
+      free(abtScreenBuffer);
+      return(-1);
+   }
+
+   /* Store current display settings. */
+   ODScrnGetTextInfo(&TextInfo);
+
+   /* Set current output area to the full screen. */
+   ODScrnSetBoundary(1,1,80,25);
+
+   /* Store contents of entire screen. */   
+   ODScrnGetText(1, 1, 80, 25, (char *)abtScreenBuffer);
+
+   /* Set the current display colour to grey on black. */
+   ODScrnSetAttribute(0x07);
+
+   /* Clear the screen if required. Otherwise, move the cursor to the */
+   /* upper left corner of the screen.                                */
+   if(od_control.od_clear_on_exit)
+   {
+      ODScrnClear();
+   }
+   else
+   {
+      ODScrnSetCursorPos(1, 1);
+   }
+
+   /* Store current directory. */
+   strcpy(pszDir, "X:\\");
+   pszDir[0] = 'A' + (nDrive = _getdrv());
+   _getcd(0, (char *)pszDir + 3);
+#endif /* ODPLAT_DOS */
+
+   /* Remember when spawned to program was executed. */
+   nStartUnixTime = time(NULL);
+
+   if(nModeFlag == P_WAIT)
+   {
+      /* Display the spawn message box under Win32. */
+#ifdef ODPLAT_WIN32
+      pWindow = ODScrnShowMessage("Running sub-program...", 0);
+#endif /* ODPLAT_WIN32 */
+
+      /* Wait for up to ten seconds for outbound buffer to drain. */
+      ODWaitDrain(10000);
+
+#ifdef OD_MULTITHREADED
+      /* Mutlithreaded versions of OpenDoors must shutdown the kernel */
+      /* before closing the serial port.                              */
+      ODKrnlShutdown();
+#endif /* OD_MULTITHREADED */
+
+      /* Close serial port. */
+      if(od_control.baud != 0)
+      {
+#ifdef ODPLAT_WIN32
+         /* Disable DTR response by the modem before closing the serial */
+         /* port, if this is required.                                  */
+         ODInExDisableDTR();
+#endif /* ODPLAT_WIN32 */         
+         ODComClose(hSerialPort);
+      }
+   }
+
+   /* Execute specified program with the specified arguments. */
+   nToReturn = _spawnvpe(nModeFlag, pszPath, papszArg, papszEnv);
+
+   if(nModeFlag == P_WAIT)
+   {
+      /* Re-open serial port. */
+      if(od_control.baud != 0)
+      {
+         ODComOpen(hSerialPort);
+      }
+
+#ifdef OD_MULTITHREADED
+      /* Mutlithreaded versions of OpenDoors must shutdown the kernel    */
+      /* before closing the serial port, so reinitialize the kernel now. */
+      ODKrnlInitialize();
+#endif /* OD_MULTITHREADED */
+
+      if(!(bIsShell || od_control.od_spawn_freeze_time))
+      {
+        ODDWordDivide(&dwQuotient, NULL, time(NULL) - nStartUnixTime, 60L);
+        od_control.user_timelimit -= (int)dwQuotient;
+      }
+      else
+      {
+        nNextTimeDeductTime += time(NULL) - nStartUnixTime;
+      }
+
+      /* Reset the time of the last input activity to the current time. */
+      /* This will prevent an immediate inactity timeout, regardless of */
+      /* how long the spawned-to program was active.                    */
+      ODInQueueResetLastActivity(hODInputQueue);
+
+      /* Clear inbound buffer. */
+      od_clear_keybuffer();
+
+      /* Remove the spawn message box under Win32. */
+#ifdef ODPLAT_WIN32
+      ODScrnRemoveMessage(pWindow);
+#endif /* ODPLAT_WIN32 */
+   }
+
+#ifdef ODPLAT_DOS
+   /* Redisplay the door screen. */
+   ODScrnPutText(1, 1, 80, 25, (char *)abtScreenBuffer);
+
+   /* Restore cursor to old position. */
+   ODScrnSetBoundary(TextInfo.winleft, TextInfo.wintop,
+      TextInfo.winright, TextInfo.winbottom);
+   ODScrnSetAttribute(TextInfo.attribute);
+   ODScrnSetCursorPos(TextInfo.curx, TextInfo.cury);
+
+   _setdrvcd(nDrive, pszDir);
+
+   /* Free allocated space. */
+   free(abtScreenBuffer);
+   free(pszDir);
+#endif /* ODPLAT_DOS */
+
+   /* Return appropriate value. */
+   return(nToReturn);
+}
+
+
+#ifdef ODPLAT_DOS
+
+/* ----------------------------------------------------------------------------
+ * _spawnvpe()                                         *** PRIVATE FUNCTION ***
+ *
+ * Executes a child program in the MS-DOS environment, swapping the calling
+ * program out of memory if enabled.
+ *
+ * Parameters: nModeFlag    - Must be P_WAIT.
+ *
+ *             pszPath      - Name of program to execute.
+ *
+ *             papszArgs    - Array of command-line arguments.
+ *
+ *             papszEnviron - Array of environment variables.
+ *
+ *     Return: -1 on failure or the spawned-to program's return value on
+ *             success.
+ */
+int _spawnvpe(int nModeFlag, char *pszPath, char *papszArgs[],
+   char *papszEnviron[])
+{
+   char *e;
+   char *p;
+   char buf[80];
+   int nReturnCode;
+
+
+   _swappath = (char *)(strlen(od_control.od_swapping_path) == 0 ? NULL 
+      : (char *)od_control.od_swapping_path);
+   _useems = od_control.od_swapping_noems;
+   _swap = od_control.od_swapping_disable;
+
+   if((nReturnCode=_spawnve(nModeFlag, pszPath, papszArgs, papszEnviron))!=-1
+       || errno!=ENOENT || *pszPath=='\\' || *pszPath=='/'
+       || *pszPath && *(pszPath+1)==':' || (e=getenv("PATH"))==NULL)
+   {
+      return(nReturnCode);
+   }
+
+   for (;;e++)
+   {
+      if((p=strchr(e,';'))!=NULL)
+      {
+     if(p-e > 66)
+         {
+        e=p;
+        continue;
+         }
+      }
+      else if(strlen(e)>66)
+      {
+     return( -1 );
+      }
+
+      p=buf;
+
+      while(*e && *e!=';') *p++=*e++;
+
+      if(p>buf)
+      {
+     if(*(p-1)!='\\' && *(p-1)!='/') *p++ = '\\';
+     strcpy(p,pszPath);
+
+     if((nReturnCode=_spawnve(nModeFlag,buf,papszArgs,papszEnviron))!=-1 || errno!=ENOENT)
+         {
+        return(nReturnCode);
+         }
+      }
+      if(*e=='\0') return(-1);
+   }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * addvect()                                           *** PRIVATE FUNCTION ***
+ *
+ * Adds a vector to the vector table.
+ *
+ * Parameters: number - The vector number.
+ *
+ *             opcode - Vector flags.
+ *
+ *     Return: -1 on failure, or 0 on success.
+ */
+int addvect(int number, int opcode)
+{
+    VECTOR *vect = vectab1;
+
+    if ( number < 0 || number > 0xFF ||
+    ( opcode != IRET && opcode != CURRENT ))
+    {
+    errno = EINVAL;
+    return( -1 );
+    }
+
+    /* see if number is already in table */
+    while ( vect->flag != 3 && ( vect->flag == 2 ||
+    vect->number != ( char )number ))
+    {
+    vect++;
+    }
+
+    if ( vect->flag == 3 )
+    {
+    /* look for a free record */
+    vect = vectab1;
+    while ( vect->flag == CURRENT || vect->flag == IRET )
+        vect++;
+    }
+
+    if ( vect->flag != 3 )
+    {
+    vect->number = ( char )number;
+    vect->flag = ( char )opcode;
+    if ( opcode == CURRENT )
+    _getvect( number, &vect->vseg, &vect->voff );
+    return( 0 );
+    }
+
+    errno = ENOMEM;
+    return( -1 );
+}
+
+
+/* ----------------------------------------------------------------------------
+ * savevect()                                          *** PRIVATE FUNCTION ***
+ *
+ * Saves current vector in vector table.
+ *
+ * Parameters: none
+ *
+ *     Return: void
+ */
+static void savevect(void)
+{
+    VECTOR *vect1 = vectab1;
+    VECTOR *vect2 = vectab2;
+
+    while ( vect1->flag != 3 )
+    {
+    if ( vect1->flag != 2 )
+    {
+        vect2->number = vect1->number;
+        vect2->flag = CURRENT;
+        _getvect( vect1->number, &vect2->vseg, &vect2->voff );
+    }
+    else
+        vect2->flag = 2;           /* free */
+    vect1++;
+    vect2++;
+    }
+    vect2->flag = 3;               /* end */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * testfile()                                          *** PRIVATE FUNCTION ***
+ *
+ * Tests swap file.
+ *
+ * Parameters: p      - Path.
+ *
+ *             file   - File name.
+ *
+ *             handle - File handle.
+ *
+ *     Return: 1 on failure.
+ */
+static int testfile(char *p, char *file, int *handle)
+{
+    unsigned int startno = tempno;
+    int nDrive = ( *file | 32 ) - 96;   /* a = 1, b = 2, etc. */
+    int root;
+    unsigned int bytes;            /* bytes per cluster */
+    unsigned int clusters;         /* free clusters */
+    int need;                  /* clusters needed for swap file */
+    int nReturnCode;                /* return code */
+    unsigned long dwQuotient;
+    unsigned long remainder;
+
+    if ( file + 2 == p )
+    {
+    *p++ = '\\';
+    if ( _getcd( nDrive, p ))       /* get current directory */
+        return( 1 );           /* invalid drive */
+    p = file + strlen( file );
+    }
+    else
+    {
+    *p = '\0';
+    if ( ODFileAccessMode( file, 0 ))
+        return( 1 );           /* path does not exist */
+    }
+    if ( *( p - 1 ) != '\\' && *( p - 1 ) != '/' )
+    *p++ = '\\';
+    if ( p - file == 3 )
+    root = 1;              /* is root directory */
+    else
+    root = 0;              /* is not root directory */
+    strcpy( p, "swp" );
+    p += 3;
+
+    if ( _dskspace( nDrive, &bytes, &clusters ) != 0 )
+    return( 1 );               /* invalid drive */
+
+
+
+    ODDWordDivide(&dwQuotient, &remainder, swapsize, bytes);
+    need = (int)dwQuotient;
+
+    if ( remainder )
+    need++;
+    if ( root == 0 )               /* if subdirectory */
+    need++;                /* in case the directory needs space */
+    if ( clusters < ( unsigned int )need )
+    return( 1 );               /* insufficient free disk space */
+
+    do
+    {
+again:  tempno = ( ++tempno ) ? tempno : 1;
+    if ( tempno == startno )
+        return( 1 );           /* extremely unlikely */
+    ltoa(( long )tempno, p, 10 );
+    }
+    while ( !ODFileAccessMode( file, 0 ));
+
+/*
+ *  The return code from _create will equal 80 if the user is running DOS 3.0
+ *  or above and the file was created by another program between the access
+ *  call and the _create call.
+ */
+
+    if (( nReturnCode = _create( file, handle )) == 80 )
+    goto again;
+    return( nReturnCode );
+}
+
+
+/* ----------------------------------------------------------------------------
+ * tempfile()                                          *** PRIVATE FUNCTION ***
+ *
+ * Creates a temporary swap file.
+ *
+ * Parameters: file   - Filename
+ *
+ *             handle - Handle to file.
+ *
+ *     Return: 0 on success, or 1 on failure.
+ */
+static int tempfile(char *file, int *handle)
+{
+    char *s = _swappath;
+    char *p = file;
+
+    if ( s )
+    {
+    for ( ;; s++ )
+    {
+        while ( *s && *s != ';' )
+        *p++ = *s++;
+        if ( p > file )
+        {
+        if ( p == file + 1 || file[ 1 ] != ':' )
+        {
+            memmove( file + 2, file, ( int )( p - file ));
+            *file = ( char )( _getdrv() + 'a' );
+            file[ 1 ] = ':';
+            p += 2;
+        }
+        if ( testfile( p, file, handle ) == 0 )
+            return( 0 );
+        p = file;
+        }
+        if ( *s == '\0' )
+        break;
+    }
+    }
+    else                   /* try the current directory */
+    {
+    *p++ = ( char )( _getdrv() + 'a' );
+    *p++ = ':';
+    if ( testfile( p, file, handle ) == 0 )
+        return( 0 );
+    }
+
+    errno = EACCES;
+    return( 1 );
+}
+
+
+/* ----------------------------------------------------------------------------
+ * cmdenv()                                            *** PRIVATE FUNCTION ***
+ *
+ * Constructs environment.
+ *
+ * Parameters: papszArgs     - Array of arguments.
+ *
+ *             papszEnviron  - Array of environment variables to add.
+ *
+ *             command       - The command specified.
+ *
+ *             env           - Pointer to environment.
+ *
+ *             memory        - Allocated memory.
+ *
+ *     Return: Environment length.
+ */
+static int cmdenv(char **papszArgs, char **papszEnviron, char *command,
+   char **env, char **memory)
+{
+    char **vp;
+    unsigned int elen = 0;         /* environment length */
+    char *p;
+    int cnt;
+    int len;
+
+    /* construct environment */
+
+    if ( papszEnviron == NULL )
+    {
+       char far *parent_env;
+       char far *env_ptr;
+       int nul_count;
+
+       ASM mov ah, 0x62
+       ASM int 0x21
+       ASM push es
+       ASM mov es, bx
+       ASM mov ax, es:[0x2c]
+       ASM pop es
+       ASM mov word ptr parent_env, 0
+       ASM mov word ptr parent_env + 2, ax
+
+       env_ptr = parent_env;
+       nul_count = 0;
+       while(nul_count < 2)
+       {
+      if(*env_ptr)
+      {
+         nul_count = 0;
+      }
+      else
+      {
+         ++nul_count;
+      }
+
+      ++env_ptr;
+      ++elen;
+       }
+
+       if ( elen > 32766 )    /* 32K - 2 */
+       {
+      errno = E2BIG;
+      return( -1 );
+       }
+
+       if (( p = malloc(elen + 15 )) == NULL )
+       {
+       errno = ENOMEM;
+       return( -1 );
+       }
+       *memory = p;
+
+       *( unsigned int * )&p = *( unsigned int * )&p + 15 & ~15;
+       *env = p;
+
+       len = elen;
+       while(len--)
+       {
+      *p++ = *parent_env++;
+       }
+    }
+    else
+    {
+       for ( vp = papszEnviron; *vp; vp++ )
+       {
+       elen += strlen( *vp ) + 1;
+       if ( elen > 32766 )        /* 32K - 2 */
+       {
+           errno = E2BIG;
+           return( -1 );
+       }
+       }
+
+       if (( p = malloc( ++elen + 15 )) == NULL )
+       {
+       errno = ENOMEM;
+       return( -1 );
+       }
+       *memory = p;
+
+       *( unsigned int * )&p = *( unsigned int * )&p + 15 & ~15;
+       *env = p;
+
+       for ( vp = papszEnviron; *vp; vp++ )
+       p = strchr( strcpy( p, *vp ), '\0' ) + 1;
+
+       *p = '\0';                         /* final element */
+    }
+
+
+    /* construct command-line */
+
+    vp = papszArgs;
+    p = command + 1;
+    cnt = 0;
+
+    if (vp!=NULL &&  *vp )
+    {
+    while ( *++vp )
+    {
+        *p++ = ' ';
+        cnt++;
+        len = strlen( *vp );
+        if ( cnt + len > 125 )
+        {
+        errno = E2BIG;
+        free( *memory );
+        return( -1 );
+        }
+        strcpy( p, *vp );
+        p += len;
+        cnt += len;
+    }
+    }
+
+    *p = '\r';
+    *command = ( char )cnt;
+
+    return(( int )elen );          /* return environment length */
+}
+
+
+/* ----------------------------------------------------------------------------
+ * doxspawn()                                          *** PRIVATE FUNCTION ***
+ *
+ * Performs spawn using memory swapping.
+ *
+ * Parameters: pszPath      - Path to command to exectute.
+ *
+ *             papszArg     - Array of arugments.
+ *
+ *             papszEnviron - Pointer to the environment.
+ *
+ *     Return: 0 on success, or -1 on failure.
+ */
+static int doxspawn(char *pszPath, char *papszArgs[], char *papszEnviron[])
+{
+    int nReturnCode = 0;        /* assume do xspawn */
+    int doswap = 0;            /* assume do swap */
+    int elen;                  /* environment length */
+    char *memory;
+    char *env;                 /* environment */
+    char command[ 128 ];           /* command-line */
+    long totalsize;            /* parent and free memory in bytes */
+    int handle;
+    int pages;
+    char file[ 79 ];
+    char *mapbuf = NULL;           /* buffer for map information */
+
+    /* construct the command-line and the environment */
+    if (( elen = cmdenv( papszArgs, papszEnviron, command, &env, &memory )) == -1 )
+    return( -1 );
+
+    if ( _swap == 0 )
+    {
+    if ( _useems == 0 )
+    {
+        if ( ems == 2 )
+        ems = _chkems( "EMMXXXX0", &mapsize );
+        if ( ems == 0 && ( mapbuf = malloc( mapsize )) == NULL )
+        {
+        errno = ENOMEM;
+        free( memory );
+        return( -1 );
+        }
+    }
+    if (( nReturnCode = _xsize( _psp, &swapsize, &totalsize )) == 0 )
+    {
+        if ( _required == 0 || totalsize - swapsize - 272
+           < (long)ODDWordShiftLeft(( long )_required , 10 ))
+        {
+        if ( ems == 0 && _useems == 0 )
+        {
+            pages = ( int )ODDWordShiftRight( swapsize , 14);
+            if ((long)ODDWordShiftLeft(( long )pages , 14 ) < swapsize )
+            pages++;
+            if ( _savemap( mapbuf ) == 0 &&
+            _getems( pages, &handle ) == 0 )
+            *file = '\0';  /* use EMS */
+            else if ( tempfile( file, &handle ) != 0 )
+            nReturnCode = -1;       /* don't do xspawn */
+        }
+        else if ( tempfile( file, &handle ) != 0 )
+            nReturnCode = -1;           /* don't do xspawn */
+        }
+        else
+        doswap = 1;        /* don't do swap */
+    }
+    else
+    {
+        errno = errtab[ nReturnCode ];
+        nReturnCode = -1;               /* don't do xspawn */
+    }
+    }
+    else
+    doswap = 1;            /* don't do swap */
+
+    if ( nReturnCode == 0 )
+    {
+    savevect();            /* save current vectors */
+    nReturnCode = _xspawn( pszPath, command, env, vectab1, doswap, elen, file,
+        handle );
+    _setvect( vectab2 );           /* restore saved vectors */
+        if ( nReturnCode == 0 )
+            nReturnCode = _getrc();         /* get child return code */
+    else
+    {
+        errno = errtab[ nReturnCode ];
+        nReturnCode = -1;
+    }
+    /*
+     *  If EMS was used, restore the page-mapping state of the expanded
+     *  memory hardware.
+     */
+    if ( doswap == 0 && *file == '\0' && _restmap( mapbuf ) != 0 )
+    {
+        errno = EACCES;
+        nReturnCode = -1;
+    }
+    }
+
+    if ( mapbuf )
+    free( mapbuf );
+    free( memory );
+    return( nReturnCode );
+}
+
+
+/* ----------------------------------------------------------------------------
+ * _spawnve()                                          *** PRIVATE FUNCTION ***
+ *
+ * Performs a spawn.
+ *
+ * Parameters: nModeFlag    - Must be P_WAIT
+ *
+ *             pszPath      - Command to execute.
+ *
+ *             papszArgs    - Command line arguments.
+ *
+ *             papszEnviron - Pointer to environment.
+ *
+ *     Return: void
+ */
+int _spawnve(int nModeFlag, char *pszPath, char *papszArgs[],
+   char * papszEnviron[])
+{
+    char *p;
+    char *s;
+    int nReturnCode = -1;
+    char buf[ 80 ];
+
+    if ( nModeFlag != P_WAIT )
+    {
+    errno = EINVAL;
+    return( -1 );
+    }
+
+    p = strrchr( pszPath, '\\' );
+    s = strrchr( pszPath, '/' );
+    if ( p == NULL && s == NULL )
+    p = pszPath;
+    else if ( p == NULL || s > p )
+    p = s;
+
+    if ( strchr( p, '.' ))
+    {
+    if ( !ODFileAccessMode( pszPath, 0 ))
+        nReturnCode = doxspawn( pszPath, papszArgs, papszEnviron );
+    /* If file not found, access will have set errno to ENOENT. */
+    }
+    else
+    {
+    strcpy( buf, pszPath );
+    strcat( buf, ".com" );
+    if ( !ODFileAccessMode( buf, 0 ))
+        nReturnCode = doxspawn( buf, papszArgs, papszEnviron );
+    else
+    {
+        strcpy( strrchr( buf, '.' ), ".exe" );
+        if ( !ODFileAccessMode( buf, 0 ))
+        nReturnCode = doxspawn( buf, papszArgs, papszEnviron );
+        /* If file not found, access will have set errno to ENOENT. */
+    }
+    }
+
+    return( nReturnCode );
+}
+
+#endif /* ODPLAT_DOS */
+
+#ifdef ODPLAT_NIX
+/* ----------------------------------------------------------------------------
+ * _spawnvpe()                                         *** PRIVATE FUNCTION ***
+ *
+ * Executes a child program in the *nix environment.
+ *
+ * Parameters: nModeFlag    - Must be P_WAIT or P_NOWAIT
+ *
+ *             pszPath      - Name of program to execute.
+ *
+ *             papszArgs    - Array of command-line arguments.
+ *
+ *             papszEnviron - Array of environment variables.
+ *
+ *     Return: -1 on failure or the spawned-to program's return value on
+ *             success.
+ */
+int _spawnvpe(int nModeFlag, char *pszPath, char *papszArgs[],
+   char *papszEnviron[])
+{
+   pid_t	child;
+   int		status;
+   pid_t	wret;
+   struct sigaction act;
+
+
+   child=fork();
+
+   if(nModeFlag == P_WAIT)  {
+      /* require wait for child */
+      act.sa_handler=SIG_IGN;
+      sigemptyset(&(act.sa_mask));
+      act.sa_flags=SA_NOCLDSTOP;
+      sigaction(SIGCHLD,&act,NULL);
+   }
+   else  {
+      /* Ignore SIGCHLD for backgrounded spawned processes */
+      act.sa_handler=SIG_IGN;
+      sigemptyset(&(act.sa_mask));
+      act.sa_flags=SA_NOCLDSTOP|SA_NOCLDWAIT;
+      sigaction(SIGCHLD,&act,NULL);
+   }
+
+   if(!child)  {
+      /* Do the exec stuff here */
+	  execve(pszPath,papszArgs,papszEnviron);
+	  exit(-1); /* this should never happen! */
+   }
+   if(nModeFlag == P_WAIT)  {
+      wret=waitpid(child,&status,0);
+	  if(WIFEXITED(status))  {
+	     return(WEXITSTATUS(status));
+	  }
+	  return(-1);
+   }
+   return(0);
+}
+#endif /* ODPLAT_NIX */

+ 226 - 0
odoors/ODStand.c

@@ -0,0 +1,226 @@
+/* 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: ODStand.c
+ *
+ * Description: Implements the OpenDoors standard (default) personality.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Nov 13, 1995  6.00  BP   32-bit portability.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Dec 24, 1995  6.00  BP   Fixed black square at pos 25x80.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODInEx.h"
+
+
+/* ----------------------------------------------------------------------------
+ * pdef_opendoors()
+ *
+ * Personality function for the OpenDoors standard status line / function key
+ * personality.
+ *
+ * Parameters: btOperation - Indicates personality operation to be performed.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL pdef_opendoors(BYTE btOperation)
+{
+   switch(btOperation)
+   {
+      case PEROP_DISPLAY1:
+         /* Display standard status line. */
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnDisplayString(od_control.od_status_line[0]);
+         ODScrnSetCursorPos(1, 24);
+         ODScrnPrintf(od_control.od_status_line[1], od_control.user_name,
+            od_control.user_location, od_control.od_connect_speed);
+         ODScrnSetCursorPos(77, 24);
+         if(od_control.od_node < 1000)
+         {
+            ODScrnPrintf("%d]", od_control.od_node);
+         }
+         else
+         {
+            ODScrnDisplayString("?]");
+         }
+         ODScrnSetCursorPos(1,25);
+         ODScrnDisplayString(od_control.od_status_line[2]);
+
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+
+         ODScrnSetCursorPos(11,25);
+         ODScrnPrintf("%u",od_control.user_security);
+         ODScrnSetCursorPos(24,25);
+         ODScrnPrintf(od_control.od_time_left,od_control.user_timelimit);
+         if(od_control.user_ansi)
+         {
+            ODScrnSetCursorPos(40,25);
+            ODScrnDisplayString("[ANSI]");
+         }
+
+         if(od_control.user_avatar)
+         {
+            ODScrnSetCursorPos(47,25);
+            ODScrnDisplayString("[AVT]");
+         }
+
+         if(od_control.sysop_next)
+         {
+            ODScrnSetCursorPos(35,25);
+            ODScrnDisplayString(od_control.od_sysop_next);
+         }
+
+         if(od_control.user_wantchat)
+         {
+            ODScrnSetCursorPos(57,25);
+            ODScrnSetAttribute(0xf0);
+            ODScrnDisplayString(od_control.od_want_chat);
+         }
+
+         if(!od_control.od_user_keyboard_on)
+         {
+            ODScrnSetCursorPos(58,24);
+            ODScrnSetAttribute(0xf0);
+            ODScrnDisplayString(od_control.od_no_keyboard);
+         }
+         break;
+
+      case PEROP_DISPLAY8:
+         /* Display help status line. */
+         ODScrnSetAttribute(0x70);
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+         ODScrnSetCursorPos(1,24);
+         ODScrnDisplayString(od_control.od_help_text);
+         ODScrnSetCursorPos(1,25);
+         /* Display copyright inforomation. */
+         if(bUserFull)/**/
+         {
+            ODScrnDisplayString(od_control.od_help_text2);
+         }
+         else
+         {
+            ODScrnDisplayString(OD_VER_UNREG_STAT);
+         }
+         break;
+
+      case PEROP_UPDATE1:
+         /* Update primary status line. */
+         ODScrnSetAttribute(0x70);
+
+         /* Update user's time limit. */
+         ODScrnSetCursorPos(24,25);
+         ODScrnPrintf(od_control.od_time_left,od_control.user_timelimit);
+
+         /* Update "sysop next" setting. */
+         ODScrnSetCursorPos(35,25);
+         if(od_control.sysop_next)
+         {
+            ODScrnDisplayString(od_control.od_sysop_next);
+         }
+         else
+         {
+            ODScrnDisplayString("     ");
+         }
+
+         /* Update ANSI mode indicator. */
+         if(od_control.user_ansi)
+         {
+            ODScrnDisplayString("[ANSI] ");
+         }
+         else
+         {
+            ODScrnDisplayString("       ");
+         }
+
+         /* Update AVATAR mode indicator. */
+         if(od_control.user_avatar)
+         {
+            ODScrnDisplayString("[AVT] ");
+         }
+         else
+         {
+            ODScrnDisplayString("      ");
+         }
+
+         /* Update keyboard-off indicator. */
+         ODScrnSetCursorPos(58,24);
+         if(od_control.od_user_keyboard_on)
+         {
+            ODScrnDisplayString("          ");
+         }
+         else
+         {
+            ODScrnSetAttribute(0xf0);
+            ODScrnDisplayString(od_control.od_no_keyboard);
+         }
+
+         /* Update want-chat indicator. */
+         ODScrnSetCursorPos(57,25);
+         if(od_control.user_wantchat)
+         {
+            ODScrnSetAttribute(0xf0);
+            ODScrnDisplayString(od_control.od_want_chat);
+         }
+         else
+         {
+            ODScrnDisplayString("           ");
+         }
+         break;
+
+      case PEROP_INITIALIZE:
+         od_control.key_hangup=0x2300;
+         od_control.key_drop2bbs=0x2000;
+         od_control.key_dosshell=0x2400;
+         od_control.key_chat=0x2e00;
+         od_control.key_sysopnext=0x3100;
+         od_control.key_lockout=0x2600;
+         od_control.key_status[0]=0x3b00;
+         od_control.key_status[1]=0x0000;
+         od_control.key_status[2]=0x0000;
+         od_control.key_status[3]=0x0000;
+         od_control.key_status[4]=0x0000;
+         od_control.key_status[5]=0x0000;
+         od_control.key_status[6]=0x0000;
+         od_control.key_status[7]=0x4300;
+         od_control.key_status[8]=0x4400;
+         od_control.key_keyboardoff=0x2500;
+         od_control.key_moretime=0x4800;
+         od_control.key_lesstime=0x5000;
+         od_control.od_page_statusline=-1;
+         break;
+   }
+}

+ 197 - 0
odoors/ODStat.c

@@ -0,0 +1,197 @@
+/* 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: ODStat.c
+ *
+ * Description: Helper functions used by various built-in personalities.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Jul 18, 1995  6.00  BP   Fix ODStatGetUserAge() bug.
+ *              Nov 13, 1995  6.00  BP   32-bit portability.
+ *              Nov 13, 1995  6.00  BP   Created odstat.h.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Jan 12, 1996  6.00  BP   Added ODStatStartArrowUse(), etc.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 13, 1996  6.10  BP   bOnlyShiftArrow -> nArrowUseCount.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODCore.h"
+#include "ODGen.h"
+#include "ODStat.h"
+#include "ODKrnl.h"
+
+
+/* Global working string available to all personalities for status line */
+/* generation.                                                          */
+char szStatusText[80];
+
+
+/* ----------------------------------------------------------------------------
+ * ODStatAddKey()
+ *
+ * Adds another hot key to the array of custom local keys.
+ *
+ * Parameters: wKeyCode - IBM scan-code/ASCII-code style key identification
+ *                         code to add.
+ *
+ *     Return: void
+ */
+void ODStatAddKey(WORD wKeyCode)
+{
+   if(od_control.od_num_keys < 16)
+      od_control.od_hot_key[od_control.od_num_keys++] = wKeyCode;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStatRemoveKey()
+ *
+ * Removes a custom sysop hotkey that was previously added with ODStatAddKey().
+ *
+ * Parameters: wKeyCode - The scan code / ASCII code key identification code
+ *                        to remove.
+ *
+ *     Return: void
+ */
+void ODStatRemoveKey(WORD wKeyCode)
+{
+   INT nCount;
+
+   for(nCount = 0; nCount < od_control.od_num_keys; ++nCount)
+      if((WORD)od_control.od_hot_key[nCount] == wKeyCode)
+      {
+         if(nCount != od_control.od_num_keys - 1)
+         {
+            od_control.od_hot_key[nCount] =
+               od_control.od_hot_key[od_control.od_num_keys-1];
+         }
+         --od_control.od_num_keys;
+         return;
+      }
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStatGetUserAge()
+ *
+ * Generates a string containing the age, in years, of the current
+ * user, based on the current date and the user's birthday.
+ *
+ * Parameters: pszAge - Pointer to a string where user's age should be stored.
+ *
+ *     Return: void
+ */
+void ODStatGetUserAge(char *pszAge)
+{
+   INT nAge;
+   INT n;
+   time_t Time;
+   struct tm *TimeBlock;
+
+   if(od_control.od_info_type==RA2EXITINFO || od_control.od_info_type==DOORSYS_WILDCAT)
+   {
+      nAge = atoi(od_control.user_birthday) - 1;
+
+      if(strlen(od_control.user_birthday) == 8 && nAge <= 11)
+      {
+         if(od_control.user_birthday[6] >= '0'
+            && od_control.user_birthday[6] <= '9'
+            && od_control.user_birthday[7] >= '0'
+            && od_control.user_birthday[7] <= '9')
+         {
+            if(od_control.user_birthday[3] >= '0'
+               && od_control.user_birthday[3] <= '3'
+               && od_control.user_birthday[4] >= '0'
+               && od_control.user_birthday[4] <= '9')
+            {
+               Time = time(NULL);
+               TimeBlock = localtime(&Time);
+
+               n = (TimeBlock->tm_year % 100)
+                  - atoi((char *)od_control.user_birthday + 6);
+
+               if(n < 0) nAge = n + 100; else nAge = n;
+
+               n = atoi(od_control.user_birthday) - 1;
+               if(TimeBlock->tm_mon < n)
+                  --nAge;
+               else if(TimeBlock->tm_mon == n)
+               {
+                  n=atoi((char *)od_control.user_birthday + 3);
+
+                  if(TimeBlock->tm_mday < n) --nAge;
+               }
+
+               sprintf(pszAge, "%d", nAge);
+               return;
+              }
+           }
+      }
+   }
+
+   strcpy(pszAge, "?");
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStatStartArrowUse()
+ *
+ * Called by OpenDoors when it needs to use the arrow keys, and so they
+ * shouldn't be used by the status line.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+void ODStatStartArrowUse(void)
+{
+   ++nArrowUseCount;
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStatEndArrowUse()
+ *
+ * Called by OpenDoors when it no longer needs to use the arrow keys, and so
+ * they can again be used by the status line.
+ *
+ * Parameters: None
+ *
+ *     Return: void
+ */
+void ODStatEndArrowUse(void)
+{
+   ASSERT(nArrowUseCount > 0);
+   --nArrowUseCount;
+}

+ 49 - 0
odoors/ODStat.h

@@ -0,0 +1,49 @@
+/* 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: ODStat.h
+ *
+ * Description: Public functions provided by the odstat.c module, for status
+ *              line functions shared among personalities.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 13, 1995  6.00  BP   Created.
+ *              Jan 12, 1996  6.00  BP   Added ODStatStartArrowUse(), etc.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODSTAT
+#define _INC_ODSTAT
+
+
+/* Global working string available to all personalities for status line */
+/* generation.                                                          */
+extern char szStatusText[80];
+
+
+/* Public status line function prototypes. */
+void ODStatAddKey(WORD wKeyCode);
+void ODStatRemoveKey(WORD wKeyCode);
+void ODStatGetUserAge(char *pszAge);
+void ODStatStartArrowUse(void);
+void ODStatEndArrowUse(void);
+
+
+#endif /* _INC_ODSTAT */

+ 55 - 0
odoors/ODStr.c

@@ -0,0 +1,55 @@
+/* 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: ODStr.c
+ *
+ * Description: Functions used to mainuplate strings
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#include <ctype.h>
+#include "OpenDoor.h"
+
+#ifdef ODPLAT_NIX
+#include <string.h>
+
+char *
+od_strlwr(char *str)
+{
+	char	*p;
+	
+	for(p=str;*p;p++) 
+		*p=tolower(*p);
+
+	return(str);
+}
+
+char *
+od_strupr(char *str)
+{
+	char	*p;
+	
+	for(p=str;*p;p++) 
+		*p=toupper(*p);
+
+	return(str);
+}
+#endif

+ 42 - 0
odoors/ODStr.h

@@ -0,0 +1,42 @@
+/* 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: ODStr.h
+ *
+ * Description: Functions used to maipulate strings
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Aug 10, 2003  6.23  SH   Initial rev.
+ */
+
+#include "OpenDoor.h"
+
+#ifndef _INC_ODSTR
+#define _INC_ODSTR
+
+#ifdef ODPLAT_NIX
+#define strnicmp      strncasecmp
+#define stricmp       strcasecmp
+#define strlwr(x)	od_strlwr(x)
+#define strupr(x)	od_strupr(x)
+char *od_strlwr(char *str);
+char *od_strupr(char *str);
+#endif
+
+#endif

+ 1453 - 0
odoors/ODSwap.asm

@@ -0,0 +1,1453 @@
+;  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: ODSwap.asm
+;
+;  Description: Performs EMS/disk swapping and low level spawning
+;               activities. This file should only be included when building
+;               the MS-DOS version of OpenDoors.
+;
+;    Revisions: Date          Ver   Who  Change
+;               ---------------------------------------------------------------
+;               Oct 13, 1994  6.00  BP   New file header format.
+;               Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+;               Mar 03, 1996  6.10  BP   Begin version 6.10.
+
+
+; If you have increased the file handle table size so that more than 20 files
+; may be open in the parent process, set FHTSZ to the new size.
+
+FHTSZ           EQU     20
+
+IFDEF LCODE
+ARG_1           EQU     6
+ELSE
+ARG_1           EQU     4
+ENDIF
+
+arena           struc                           ; arena header
+sig             db      0                       ; 'M' or 'Z' if last block
+own             dw      0                       ; PSP of owner or 0 if free
+siz             dw      0                       ; size not including header
+arena           ends
+
+vector          struc
+number          db      0                       ; vector number
+flag            db      0                       ; 0-CURRENT,1-IRET,2-free,3-end
+vseg            dw      0                       ; vector segment
+voff            dw      0                       ; vector offset
+vector          ends
+
+_TEXT           SEGMENT word public 'CODE'
+                ASSUME  cs:_TEXT
+                ASSUME  ds:nothing
+                ASSUME  es:nothing
+                ASSUME  ss:nothing
+
+; The code between slidetop and slidebot constitutes the spawn kernel.  The
+; kernel is copied to the front of the parent process immediately following the
+; parent's PSP.  The environment passed to the child is copied to immediately
+; following the kernel.
+
+slidetop:
+
+path            db      79 dup (0)              ; program to execute
+command         db      128 dup (0)             ; command-line
+file            db      79 dup (0)              ; swap file
+
+parmblk         label   byte                    ; parameter block
+environ         dw      0                       ; environment block
+cmd             dw      0,0                     ; command-line tail
+fcb1            dw      0,0                     ; first file control block
+fcb2            dw      0,0                     ; second file control block
+
+fcb5c           db      10h dup (0)             ; first file control block
+fcb6c           db      10h dup (0)             ; second file control block
+
+cntsvl          dw      0                       ; count save low
+cntsvh          dw      0                       ; count save high
+tmpcode         dw      0                       ; temporary return code
+env             dw      0                       ; environment segment
+envlen          dw      0                       ; environment length
+parsz           dw      0                       ; parent size
+ttlsz           dw      0                       ; total size
+oldsz           dw      0                       ; old size
+newsz           dw      0                       ; new size
+emsseg          dw      0                       ; EMS page frame segment
+handle          dw      0                       ; EMS handle
+useems          db      0                       ; if 0, use EMS
+save            db      4 dup (0)               ; save 4 bytes at DS:[2Eh]
+f1add           dd      0                       ; fnish1 address
+last            db      0                       ; if 0, last block swap
+IF FHTSZ - 20
+fhtsv           db      FHTSZ dup (0)           ; file handle table save
+ENDIF
+
+errmsg          db      'spawn error',0Dh,0Ah
+msglen          EQU     $-errmsg
+
+                EVEN
+lclstk          dw      64 dup (0)              ; local stack
+stktop          label   word                    ; stack top
+
+slide1:         mov     ax,cs                   ; install local stack
+                cli
+                mov     ss,ax
+                mov     sp,offset stktop - offset slidetop + 100h
+                sti
+
+; copy environment
+
+                mov     bx,offset slidebot - offset slidetop + 15 + 100h
+                mov     cl,4
+                shr     bx,cl                   ; convert to paragraphs
+                add     bx,ax                   ; add CS (actually PSP)
+index = offset environ - offset slidetop + 100h
+                mov     cs:[index],bx           ; parameter block
+                mov     es,bx
+                xor     di,di
+index = offset env - offset slidetop + 100h
+                mov     ds,cs:[index]
+                xor     si,si
+index = offset envlen - offset slidetop + 100h
+                mov     cx,cs:[index]
+                shr     cx,1                    ; translate to word count
+                rep     movsw                   ; CF set if one byte left over
+                adc     cx,cx                   ; CX = 1 or 0, depending CF
+                rep     movsb                   ; possible final byte
+
+                dec     ax                      ; PSP segment
+                mov     es,ax                   ; program arena header
+                mov     bx,es:[siz]
+index = offset oldsz - offset slidetop + 100h
+                mov     cs:[index],bx           ; old size
+                mov     byte ptr es:[sig],'M'   ; not last
+index = offset newsz - offset slidetop + 100h
+                mov     bx,cs:[index]           ; new size
+                mov     es:[siz],bx
+
+                inc     ax                      ; PSP segment
+                add     ax,bx                   ; add new size
+                mov     es,ax                   ; new last arena header
+                mov     byte ptr es:[sig],'Z'   ; last
+index = offset last - offset slidetop + 100h
+                cmp     byte ptr cs:[index],0
+                je      slide2                  ; jump if last block swap
+                mov     byte ptr es:[sig],'M'   ; not last
+
+slide2:         mov     word ptr es:[own],0     ; free
+index = offset ttlsz - offset slidetop + 100h
+                mov     ax,cs:[index]           ; total size
+                sub     ax,bx                   ; subtract new size
+                dec     ax                      ; account for arena header
+                mov     es:[siz],ax
+
+; save 4 bytes destroyed by DOS 2.0 at DS:2Eh
+
+                mov     ax,cs                   ; PSP segment
+                mov     es,ax
+                mov     bx,es:[2Eh]
+index = offset save - offset slidetop + 100h
+                mov     cs:[index],bx
+                mov     bx,es:[30h]
+index = offset save - offset slidetop + 102h
+                mov     cs:[index],bx
+
+                mov     bx,offset parmblk - offset slidetop + 100h
+                mov     ds,ax                   ; PSP segment
+                mov     dx,100h                 ; offset path
+                mov     ax,4B00h                ; load and execute program
+                int     21h
+                jnc     slide3                  ; jump if no error
+index = offset tmpcode - offset slidetop + 100h
+                mov     cs:[index],ax           ; temporary return code
+
+slide3:         mov     ax,cs                   ; install local stack
+                cli
+                mov     ss,ax
+                mov     sp,offset stktop - offset slidetop + 100h
+                sti
+
+; restore 4 bytes destroyed by DOS 2.0 at DS:2Eh
+
+                mov     es,ax                   ; PSP segment
+index = offset save - offset slidetop + 100h
+                mov     bx,cs:[index]
+                mov     es:[2Eh],bx
+index = offset save - offset slidetop + 102h
+                mov     bx,cs:[index]
+                mov     es:[30h],bx
+
+index = offset oldsz - offset slidetop + 100h
+                mov     bx,cs:[index]           ; old size
+                mov     ah,4Ah                  ; resize memory block
+                int     21h
+                jnc     slide7
+
+index = offset useems - offset slidetop + 100h
+slide4:         cmp     byte ptr cs:[index],0
+                jne     slide6                  ; jump if don't use EMS
+index = offset handle - offset slidetop + 100h
+                mov     dx,cs:[index]           ; EMS handle
+
+slide5:         mov     ah,45h                  ; release handle and memory
+                int     67h
+                cmp     ah,82h                  ; memory manager busy?
+                je      slide5                  ; jump if busy
+
+slide6:         jmp     slide18                 ; exit
+
+index = offset parsz - offset slidetop + 100h
+slide7:         mov     bx,cs:[index]           ; parent size
+index = offset ttlsz - offset slidetop + 100h
+                mov     ax,cs:[index]           ; total size
+                sub     ax,bx                   ; subtract parent size
+                or      ax,ax
+                jz      slide9
+                mov     dx,cs                   ; PSP segment
+                add     dx,bx                   ; add parent size
+                mov     es,dx                   ; new last arena header
+                mov     byte ptr es:[sig],'Z'   ; last
+index = offset last - offset slidetop + 100h
+                cmp     byte ptr cs:[index],0
+                je      slide8                  ; jump if last block swap
+                mov     byte ptr es:[sig],'M'   ; not last
+
+slide8:         mov     word ptr es:[own],0     ; free
+                dec     ax                      ; account for arena header
+                mov     es:[siz],ax
+
+slide9:         push    cs                      ; PSP segment
+index = offset useems - offset slidetop + 100h
+                cmp     byte ptr cs:[index],0
+                jne     slide14                 ; jump if don't use EMS
+                pop     es                      ; PSP segment
+                mov     di,offset slidebot - offset slidetop + 100h
+index = offset emsseg - offset slidetop + 100h
+                mov     ds,cs:[index]           ; EMS page frame segment
+                mov     si,offset slidebot - offset slidetop
+index = offset handle - offset slidetop + 100h
+                mov     dx,cs:[index]           ; EMS handle
+                xor     bx,bx                   ; logical page number
+                mov     cx,16384 - ( offset slidebot - offset slidetop )
+                jmp     short slide13
+
+index = offset cntsvl - offset slidetop + 100h
+slide10:        sub     cs:[index],cx
+index = offset cntsvh - offset slidetop + 100h
+                sbb     word ptr cs:[index],0
+                xor     al,al                   ; physical page number
+
+slide11:        mov     ah,44h                  ; map memory
+                int     67h
+                or      ah,ah
+                jz      slide12
+                cmp     ah,82h                  ; memory manager busy?
+                je      slide11                 ; jump if busy
+                jmp     slide4                  ; exit
+
+slide12:        shr     cx,1                    ; translate to word count
+                rep     movsw                   ; CF set if one byte left over
+                adc     cx,cx                   ; CX = 1 or 0, depending CF
+                rep     movsb                   ; possible final byte
+                xor     si,si
+                mov     cx,16384                ; assume copy full block
+                inc     bx                      ; logical page number
+                cmp     bx,1
+                je      slide13
+                mov     ax,es
+                add     ax,1024                 ; 16384 bytes
+                mov     es,ax
+                mov     di,16640                ; 16384 + 100h
+
+index = offset cntsvh - offset slidetop + 100h
+slide13:        cmp     word ptr cs:[index],0
+                jne     slide10                 ; jump if more than full block
+index = offset cntsvl - offset slidetop + 100h
+                cmp     cs:[index],cx
+                jae     slide10                 ; jump if at least full block
+                mov     cx,cs:[index]           ; CX = cntsvl
+                cmp     cx,0
+                jne     slide10                 ; jump if more left to copy
+                jmp     short slide17
+
+slide14:        pop     ds                      ; PSP segment
+IF FHTSZ - 20
+
+; restore the file handle table from the kernel
+
+                mov     si,offset fhtsv - offset slidetop + 100h
+                mov     es,ds:[36h]             ; file handle table segment
+                mov     di,ds:[34h]             ; file handle table offset
+                mov     cx,FHTSZ                ; file handle table size
+                rep     movsb
+ENDIF
+                mov     dx,offset file - offset slidetop + 100h
+                mov     ax,3D00h                ; open file read only
+                int     21h
+                jc      slide18                 ; exit if error
+                mov     bx,ax                   ; handle
+
+                xor     cx,cx
+                mov     dx,offset slidebot - offset slidetop
+                mov     ax,4200h                ; move file pointer
+                int     21h                     ;  from beginning of file
+
+                mov     dx,offset slidebot - offset slidetop + 100h
+                mov     cx,65520                ; assume read full block
+                jmp     short slide16
+
+index = offset cntsvl - offset slidetop + 100h
+slide15:        sub     cs:[index],cx
+index = offset cntsvh - offset slidetop + 100h
+                sbb     word ptr cs:[index],0
+                mov     ah,3Fh                  ; read file
+                int     21h
+                jc      slide18                 ; exit if error
+                cmp     ax,cx
+                jne     slide18                 ; exit if not all read
+
+                mov     ax,ds
+                add     ax,4095                 ; 65520 bytes
+                mov     ds,ax
+
+index = offset cntsvh - offset slidetop + 100h
+slide16:        cmp     word ptr cs:[index],0
+                jne     slide15                 ; jump if more than full block
+index = offset cntsvl - offset slidetop + 100h
+                cmp     word ptr cs:[index],65520
+                jae     slide15                 ; jump if at least full block
+                mov     cx,cs:[index]           ; CX = cntsvl
+                cmp     cx,0
+                jne     slide15                 ; jump if more left to read
+
+index = offset tmpcode - offset slidetop + 100h
+slide17:        mov     ax,cs:[index]           ; temporary return code
+index = offset f1add - offset slidetop + 100h
+                jmp     dword ptr cs:[index]
+
+slide18:        push    cs                      ; PSP segment
+                pop     ds
+                mov     dx,offset errmsg - offset slidetop + 100h
+                mov     cx,msglen               ; errmsg length
+                mov     bx,2                    ; standard error device handle
+                mov     ah,40h                  ; write error message
+                int     21h
+                mov     ax,4C01h                ; terminate with return code
+                int     21h
+
+handler:        iret                            ; interrupt handler
+
+slidebot:
+
+cntl            dw      0                       ; count low
+cnth            dw      0                       ; count high
+stks            dw      0                       ; original SS contents
+stkp            dw      0                       ; original SP contents
+psp             dw      0                       ; PSP segment
+s1add           dd      0                       ; slide1 address
+rcode           dw      0                       ; return code
+useems2         db      0                       ; if 0, use EMS
+vtabseg         dw      0                       ; vectab1 segment
+vtaboff         dw      0                       ; vectab1 offset
+
+errmsg2         db      'spawn error',0Dh,0Ah
+msglen2         EQU     $-errmsg2
+
+;
+; int _xspawn( char *, char *, char *, VECTOR *, int, int, char *, int );
+;
+
+                PUBLIC  __xspawn
+IFDEF LCODE
+__xspawn        PROC    far
+ELSE
+__xspawn        PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    di                      ; preserve register variables
+                push    si
+                push    ds
+
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1]
+ELSE
+                mov     si,word ptr [bp+ARG_1]
+ENDIF
+                mov     di,offset path
+
+start1:         mov     al,ds:[si]              ; copy path string
+                mov     cs:[di],al              ;  to code segment
+                inc     si
+                inc     di
+                or      al,al                   ; null char?
+                jnz     start1                  ; no, get next char
+
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+4]
+ELSE
+                mov     si,word ptr [bp+ARG_1+2]
+ENDIF
+                mov     bx,si                   ; preserve si
+                mov     di,offset command
+                mov     cx,2                    ; account for count and '\r'
+                add     cl,byte ptr ds:[bx]     ; add count byte
+
+start2:         mov     al,ds:[bx]              ; copy command
+                mov     cs:[di],al              ;  to code segment
+                inc     bx
+                inc     di
+                loop    start2                  ; get next char
+
+                inc     si                      ; skip count byte
+                push    cs
+                pop     es
+                mov     di,offset fcb5c
+                mov     ax,2901h                ; parse filename
+                int     21h                     ;  skip leading separators
+                mov     di,offset fcb6c
+                mov     al,1                    ; parse filename
+                int     21h                     ;  skip leading separators
+
+IFDEF LDATA
+                mov     ax,word ptr [bp+ARG_1+8]
+ELSE
+                mov     ax,word ptr [bp+ARG_1+4]
+ENDIF
+                mov     cl,4
+                shr     ax,cl                   ; convert to paragraphs
+IFDEF LDATA
+                mov     bx,word ptr [bp+ARG_1+10]
+ELSE
+                mov     bx,ds
+ENDIF
+                add     ax,bx
+                mov     cs:[env],ax             ; environment segment
+
+IFDEF LDATA
+                lds     bx,dword ptr [bp+ARG_1+12] ; vectab1
+ELSE
+                mov     bx,word ptr [bp+ARG_1+6] ; vectab1
+ENDIF
+                mov     cs:[vtabseg],ds         ; vectab1 segment
+                mov     cs:[vtaboff],bx         ; vectab1 offset
+
+                mov     cs:[stks],ss            ; original SS contents
+                mov     cs:[stkp],sp            ; original SP contents
+                mov     cs:[rcode],0            ; assume success
+
+IFDEF LDATA
+                mov     ax,word ptr [bp+ARG_1+16]
+ELSE
+                mov     ax,word ptr [bp+ARG_1+8]
+ENDIF
+                or      ax,ax                   ; do swap?
+                jz      start3                  ; yes, jump
+                jmp     noswap1
+
+IFDEF LDATA
+start3:         mov     ax,word ptr [bp+ARG_1+18]
+ELSE
+start3:         mov     ax,word ptr [bp+ARG_1+10]
+ENDIF
+                mov     cs:[envlen],ax          ; environment length
+                add     ax,offset slidebot - offset slidetop + 30 + 100h
+                mov     cl,4
+                shr     ax,cl                   ; convert to paragraphs
+                mov     cs:[newsz],ax           ; new size
+
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+20]
+ELSE
+                mov     si,word ptr [bp+ARG_1+12]
+ENDIF
+                mov     di,offset file
+                mov     cs:[useems2],1          ; assume don't use EMS
+                cmp     byte ptr ds:[si],0
+                jne     start4
+                mov     cs:[useems2],0          ; use EMS
+
+start4:         mov     al,ds:[si]              ; copy file string
+                mov     cs:[di],al              ;  to code segment
+                inc     si
+                inc     di
+                or      al,al                   ; null char?
+                jnz     start4                  ; no, get next char
+
+; save fnish1 address
+
+                mov     word ptr cs:[f1add+2],cs
+                mov     word ptr cs:[f1add],offset fnish1
+
+; initialize parameter block
+
+                mov     ax,cs:[psp]             ; PSP segment
+                mov     cs:[cmd],offset command - offset slidetop + 100h
+                mov     cs:[cmd+2],ax
+                mov     cs:[fcb1],offset fcb5c - offset slidetop + 100h
+                mov     cs:[fcb1+2],ax
+                mov     cs:[fcb2],offset fcb6c - offset slidetop + 100h
+                mov     cs:[fcb2+2],ax
+
+                cld                             ; left to right direction
+
+                mov     ds,ax                   ; PSP segment
+IFDEF LDATA
+                mov     dx,word ptr [bp+ARG_1+24]
+ELSE
+                mov     dx,word ptr [bp+ARG_1+14]
+ENDIF
+IF FHTSZ - 20
+                cmp     word ptr ds:[32h],FHTSZ ; file handle table size
+                je      start5                  ; jump if OK
+                mov     cs:[rcode],5
+                jmp     short start6
+ENDIF
+
+start5:         mov     ax,cs:[newsz]           ; new size
+                cmp     ax,cs:[ttlsz]           ; new size < total size?
+                jb      start8                  ; yes, jump
+                mov     cs:[rcode],7            ; extremely unlikely
+
+start6:         cmp     cs:[useems2],0
+                jne     start7                  ; jump if don't use EMS
+                jmp     fnish2
+
+start7:         mov     bx,dx                   ; file handle
+                jmp     fnish6
+
+start8:         cmp     cs:[useems2],0
+                jne     start12                 ; jump if don't use EMS
+                mov     cs:[useems],0           ; use EMS
+                mov     cs:[handle],dx          ; EMS handle
+                mov     es,cs:[emsseg]          ; EMS page frame segment
+                xor     bx,bx                   ; logical page number
+                jmp     short start11
+
+start9:         sub     cs:[cntl],cx
+                sbb     cs:[cnth],0
+                call    mapems
+                or      ah,ah
+                jz      start10                 ; jump if map succeeded
+                jmp     fnish2
+
+start10:        mov     si,100h
+                xor     di,di
+                shr     cx,1                    ; translate to word count
+                rep     movsw                   ; CF set if one byte left over
+                adc     cx,cx                   ; CX = 1 or 0, depending CF
+                rep     movsb                   ; possible final byte
+                inc     bx                      ; logical page number
+                mov     ax,ds
+                add     ax,1024                 ; 16384 bytes
+                mov     ds,ax
+
+start11:        mov     cx,16384                ; assume copy full block
+                cmp     cs:[cnth],0
+                jne     start9                  ; jump if more than full block
+                cmp     cs:[cntl],16384
+                jae     start9                  ; jump if at least full block
+                mov     cx,cs:[cntl]
+                cmp     cx,0
+                jne     start9                  ; jump if more left to copy
+                jmp     short start17
+
+start12:        mov     cs:[useems],1           ; don't use EMS
+                mov     bx,dx                   ; handle
+                mov     dx,100h                 ; DS:DX segment:offset buffer
+                mov     cx,65520                ; assume write full block
+                jmp     short start16
+
+start13:        sub     cs:[cntl],cx
+                sbb     cs:[cnth],0
+                mov     ah,40h                  ; write file
+                int     21h
+                jc      start14                 ; jump if error
+                cmp     ax,cx
+                je      start15                 ; jump if all written
+
+start14:        mov     ah,3Eh                  ; close file
+                int     21h
+                mov     cs:[rcode],5
+                jmp     fnish7
+
+start15:        mov     ax,ds
+                add     ax,4095                 ; 65520 bytes
+                mov     ds,ax
+
+start16:        cmp     cs:[cnth],0
+                jne     start13                 ; jump if more than full block
+                cmp     cs:[cntl],65520
+                jae     start13                 ; jump if at least full block
+                mov     cx,cs:[cntl]
+                cmp     cx,0
+                jne     start13                 ; jump if more left to write
+
+                mov     ah,3Eh                  ; close file
+                int     21h
+IF FHTSZ - 20
+
+; save the file handle table in the kernel
+
+                mov     es,cs:[psp]             ; PSP segment
+                mov     ds,es:[36h]             ; file handle table segment
+                mov     si,es:[34h]             ; file handle table offset
+                push    cs
+                pop     es
+                mov     di,offset fhtsv         ; file handle table save
+                mov     cx,FHTSZ                ; file handle table size
+                rep     movsb
+ENDIF
+
+start17:        mov     cx,cs
+                mov     dx,offset handler       ; interrupt handler offset
+                call    safevect                ; set vectors in vectab1
+
+; time to copy the kernel
+
+                mov     es,cs:[psp]             ; PSP segment
+                mov     di,100h
+                mov     ds,cx                   ; DS = CS
+                mov     si,offset slidetop
+                mov     cx,offset slidebot - offset slidetop
+                shr     cx,1                    ; translate to word count
+                rep     movsw                   ; CF set if one byte left over
+                adc     cx,cx                   ; CX = 1 or 0, depending CF
+                rep     movsb                   ; possible final byte
+
+                mov     word ptr cs:[s1add+2],es ; PSP segment
+index = offset slide1 - offset slidetop + 100h
+                mov     word ptr cs:[s1add],index ; slide1 offset
+
+                mov     cx,es                   ; PSP segment
+index = offset handler - offset slidetop + 100h
+                mov     dx,index                ; interrupt handler offset
+                call    safevect                ; set vectors in vectab1
+
+                jmp     dword ptr cs:[s1add]    ; jump to the kernel
+
+; If all goes well, this is where we come back to from the kernel.
+
+fnish1:         mov     cs:[rcode],ax           ; return code
+
+                cli                             ; restore original stack
+                mov     ss,cs:[stks]
+                mov     sp,cs:[stkp]
+                sti
+
+                push    ds                      ; maybe EMS page frame segment
+                push    dx                      ; maybe EMS handle
+                push    bx                      ; maybe swap file handle
+                mov     cx,cs
+                mov     dx,offset handler       ; interrupt handler offset
+                call    safevect                ; set vectors in vectab1
+                pop     bx
+                pop     dx
+                pop     ds
+
+                cmp     cs:[useems2],0
+                jne     fnish4                  ; jump if don't use EMS
+
+; DS = EMS page frame segment
+; DX = EMS handle
+
+                mov     cx,offset slidebot - offset slidetop
+                mov     es,cs:[psp]             ; PSP segment
+                mov     di,100h
+                xor     si,si
+                xor     bx,bx                   ; logical page number
+                call    mapems
+                or      ah,ah
+                jnz     fnish3                  ; jump if map failed
+                shr     cx,1                    ; translate to word count
+                rep     movsw                   ; CF set if one byte left over
+                adc     cx,cx                   ; CX = 1 or 0, depending CF
+                rep     movsb                   ; possible final byte
+
+fnish2:         mov     ah,45h                  ; release handle and memory
+                int     67h
+                cmp     ah,82h                  ; memory manager busy?
+                je      fnish2                  ; jump if busy
+                jmp     short fnish7
+
+fnish3:         mov     ah,45h                  ; release handle and memory
+                int     67h
+                cmp     ah,82h                  ; memory manager busy?
+                je      fnish3                  ; jump if busy
+                jmp     short fnish5            ; exit
+
+; BX = swap file handle
+
+fnish4:         xor     cx,cx                   ; offset 0
+                xor     dx,dx                   ; offset 0
+                mov     ax,4200h                ; move file pointer
+                int     21h                     ;  from beginning of file
+
+                mov     cx,offset slidebot - offset slidetop
+                mov     ds,cs:[psp]             ; PSP segment
+                mov     dx,100h
+                mov     ah,3Fh                  ; read file
+                int     21h
+                jc      fnish5                  ; exit if error
+                cmp     ax,cx
+                je      fnish6
+
+fnish5:         push    cs
+                pop     ds
+                mov     dx,offset errmsg2
+                mov     cx,msglen2              ; errmsg2 length
+                mov     bx,2                    ; standard error device handle
+                mov     ah,40h                  ; write error message
+                int     21h
+                mov     ax,4C01h                ; terminate with return code
+                int     21h
+
+fnish6:         mov     ah,3Eh                  ; close file
+                int     21h
+                push    cs
+                pop     ds
+                mov     dx,offset file
+                mov     ah,41h                  ; delete file
+                int     21h
+
+fnish7:         pop     ds
+                pop     si                      ; restore register variables
+                pop     di
+                pop     bp
+
+                mov     ax,cs:[rcode]           ; return code
+                or      ax,ax
+                jz      fnish11
+                push    ax
+                mov     ax,3000h                ; get DOS version number
+                int     21h
+                cmp     al,3                    ; major version number
+                pop     ax
+                jb      fnish8
+                cmp     al,34                   ; unknown error - 3.0
+                jae     fnish9
+                cmp     al,32                   ; sharing violation
+                jb      fnish8
+                mov     al,5                    ; access denied
+                jmp     short fnish10
+
+fnish8:         cmp     al,19                   ; unknown error - 2.0
+                jbe     fnish10
+
+fnish9:         mov     al,19                   ; unknown error - 2.0
+
+fnish10:        xor     ah,ah
+
+fnish11:        ret
+
+; If we are not swapping, we jump here.
+
+noswap1:        mov     ax,cs
+
+; initialize parameter block
+
+                mov     bx,cs:[env]
+                mov     cs:[environ],bx
+                mov     cs:[cmd],offset command
+                mov     cs:[cmd+2],ax
+                mov     cs:[fcb1],offset fcb5c
+                mov     cs:[fcb1+2],ax
+                mov     cs:[fcb2],offset fcb6c
+                mov     cs:[fcb2+2],ax
+
+; save 4 bytes destroyed by DOS 2.0 at DS:2Eh
+
+                mov     si,cs:[2Eh]
+                mov     word ptr cs:[save],si
+                mov     si,cs:[30h]
+                mov     word ptr cs:[save+2],si
+
+                mov     cx,cs
+                mov     dx,offset handler       ; interrupt handler offset
+                call    safevect                ; set vectors in vectab1
+
+                mov     es,cx                   ; ES = CS
+                mov     bx,offset parmblk
+                mov     ds,cx                   ; DS = CS
+                mov     dx,offset path
+                mov     ax,4B00h                ; load and execute program
+                int     21h
+                jnc     noswap2                 ; jump if no error
+                mov     cs:[rcode],ax           ; return code
+
+noswap2:        cli                             ; restore original stack
+                mov     ss,cs:[stks]
+                mov     sp,cs:[stkp]
+                sti
+
+; restore 4 bytes destroyed by DOS 2.0 at DS:2Eh
+
+                mov     si,word ptr cs:[save]
+                mov     cs:[2Eh],si
+                mov     si,word ptr cs:[save+2]
+                mov     cs:[30h],si
+
+                jmp     fnish7
+
+__xspawn        ENDP
+
+mapems          PROC    near
+
+; DX = handle
+; BX = logical page number
+
+                xor     al,al                   ; physical page number
+
+map1:           mov     ah,44h                  ; map memory
+                int     67h
+                cmp     ah,82h                  ; memory manager busy?
+                je      map1                    ; jump if busy
+
+                ret
+
+mapems          ENDP
+
+setvectsub      PROC    near
+
+; ES = vector table segment
+; BX = vector table offset
+
+                mov     ah,25h                  ; set interrupt vector
+
+setvectsub1:    mov     al,es:[bx+flag]         ; 0-CURRENT,1-IRET,2-free,3-end
+                cmp     al,3                    ; is it the end?
+                je      setvectsub3             ; yes, jump
+                cmp     al,2                    ; is it free?
+                je      setvectsub2             ; yes, jump
+                mov     al,es:[bx+number]       ; vector number
+                mov     ds,es:[bx+vseg]         ; vector segment
+                mov     dx,es:[bx+voff]         ; vector offset
+                int     21h                     ; set interrupt vector
+
+setvectsub2:    add     bx,6                    ; size of vector structure
+                jmp     setvectsub1             ; next
+
+setvectsub3:    ret
+
+setvectsub      ENDP
+
+safevect        PROC    near
+
+; CX = handler segment
+; DX = handler offset
+
+                mov     es,cs:[vtabseg]         ; vectab1 segment
+                mov     bx,cs:[vtaboff]         ; vectab1 offset
+
+safevect1:      mov     al,es:[bx+flag]         ; 0-CURRENT,1-IRET,2-free,3-end
+                cmp     al,3                    ; is it the end?
+                je      safevect3               ; yes, jump
+                cmp     al,1                    ; is it IRET?
+                jne     safevect2               ; no, jump
+                mov     es:[bx+vseg],cx         ; handler segment
+                mov     es:[bx+voff],dx         ; handler offset
+
+safevect2:      add     bx,6                    ; size of vector structure
+                jmp     safevect1               ; next
+
+safevect3:      mov     bx,cs:[vtaboff]         ; vectab1 offset
+                call    setvectsub
+
+                ret
+
+safevect        ENDP
+
+;
+; int _xsize( unsigned int, long *, long * );
+;
+
+                PUBLIC  __xsize
+IFDEF LCODE
+__xsize         PROC    far
+ELSE
+__xsize         PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    di                      ; preserve register variables
+                push    si
+                push    ds
+
+                mov     cs:[last],0             ; assume last block swap
+                mov     bx,word ptr [bp+ARG_1]  ; PSP segment
+                mov     cs:[psp],bx
+                mov     dx,bx
+                dec     bx                      ; program arena header
+
+size1:          mov     es,bx                   ; current arena header
+                mov     ax,es:[own]
+                or      ax,ax                   ; is it free?
+                jz      size2                   ; yes, count it
+                cmp     ax,dx                   ; do we own it?
+                jne     size4                   ; no, jump
+                mov     cx,bx                   ; last owned block
+
+size2:          inc     bx
+                add     bx,es:[siz]             ; block size
+                jc      size3                   ; carry, arena is trashed
+                mov     al,es:[sig]             ; get arena signature
+                cmp     al,'M'                  ; are we at end of memory?
+                je      size1                   ; no, jump
+                cmp     al,'Z'
+                je      size5
+
+size3:          mov     bx,-1                   ; request maximum memory
+                mov     ah,48h                  ; allocate memory block
+                int     21h
+                mov     cs:[rcode],7
+                jmp     fnish7
+
+size4:          mov     cs:[last],1             ; not last block swap
+
+size5:          sub     bx,dx                   ; subtract PSP segment
+                mov     ax,cx                   ; last owned block
+                mov     es,cx
+                inc     ax
+                add     ax,es:[siz]             ; block size
+                sub     ax,dx                   ; subtract PSP segment
+                mov     cs:[parsz],ax           ; parent size
+                sub     ax,10h                  ; subtract PSP size
+                xor     dx,dx                   ; convert to bytes
+                mov     cx,4
+
+size6:          shl     ax,1
+                rcl     dx,1
+                loop    size6
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+2]
+ELSE
+                mov     si,word ptr [bp+ARG_1+2]
+ENDIF
+                mov     ds:[si],ax              ; swap size requirement
+                mov     ds:[si+2],dx
+                mov     cs:[cntl],ax            ; count low
+                mov     cs:[cnth],dx            ; count high
+                mov     cx,offset slidebot - offset slidetop
+                sub     ax,cx
+                sbb     dx,0
+                mov     cs:[cntsvl],ax          ; count save low
+                mov     cs:[cntsvh],dx          ; count save high
+                mov     cs:[ttlsz],bx           ; total size
+                xor     dx,dx                   ; convert to bytes
+                mov     cx,4
+
+size7:          shl     bx,1
+                rcl     dx,1
+                loop    size7
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+6]
+ELSE
+                mov     si,word ptr [bp+ARG_1+4]
+ENDIF
+                mov     ds:[si],bx              ; parent and free memory
+                mov     ds:[si+2],dx
+
+                pop     ds
+                pop     si                      ; restore register variables
+                pop     di
+                pop     bp
+                xor     ax,ax
+                ret
+
+__xsize         ENDP
+
+;
+; int _chkems( char *, int * );
+;
+
+                PUBLIC  __chkems
+IFDEF LCODE
+__chkems        PROC    far
+ELSE
+__chkems        PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+
+; determine whether expanded memory is available
+
+IFDEF LDATA
+                push    ds
+                lds     dx,dword ptr [bp+ARG_1] ; EMM device driver name
+ELSE
+                mov     dx,word ptr [bp+ARG_1]  ; EMM device driver name
+ENDIF
+                mov     ax,3D00h                ; open file read only
+                int     21h
+IFDEF LDATA
+                pop     ds
+ENDIF
+                jc      check2                  ; expanded memory unavailable
+
+; determine whether we opened the EMM device driver or a file
+
+                mov     bx,ax                   ; handle
+                mov     ax,4400h                ; IOCTL - get device info
+                int     21h
+                jc      check1                  ; expanded memory unavailable
+                test    dx,80h                  ; test bit 7
+                jz      check1                  ; expanded memory unavailable
+
+                mov     ax,4407h                ; IOCTL - get output status
+                int     21h
+                jc      check1                  ; expanded memory unavailable
+                or      al,al
+                jz      check1                  ; expanded memory unavailable
+
+; close EMM device driver to reclaim handle
+
+                mov     ah,3Eh                  ; close file
+                int     21h
+
+; determine whether the EMM is functional
+
+                mov     ah,40h                  ; get manager status
+                int     67h
+                or      ah,ah
+                jnz     check2                  ; expanded memory unavailable
+
+; check EMM version
+
+                mov     ah,46h                  ; get EMM version
+                int     67h
+                or      ah,ah
+                jnz     check2                  ; expanded memory unavailable
+                cmp     al,32h                  ; version 3.2
+                jb      check2                  ; expanded memory unavailable
+
+; get page frame segment
+
+                mov     ah,41h                  ; get page frame segment
+                int     67h
+                or      ah,ah
+                jnz     check2                  ; expanded memory unavailable
+                mov     cs:[emsseg],bx          ; segment
+
+; get size of page map information
+
+                mov     ax,4E03h                ; get size of page map info
+                int     67h
+                or      ah,ah
+                jnz     check2                  ; expanded memory unavailable
+IFDEF LDATA
+                les     bx,dword ptr [bp+ARG_1+4] ; mapsize address
+                mov     es:[bx],ax
+ELSE
+                mov     bx,word ptr [bp+ARG_1+2] ; mapsize address
+                mov     ds:[bx],ax
+ENDIF
+                xor     ax,ax                   ; expanded memory available
+                pop     bp
+                ret
+
+; close EMM device driver or file to reclaim handle
+
+check1:         mov     ah,3Eh                  ; close file
+                int     21h
+
+check2:         mov     ax,1                    ; expanded memory unavailable
+                pop     bp
+                ret
+
+__chkems        ENDP
+
+;
+; int _savemap( char * );
+;
+
+                PUBLIC  __savemap
+IFDEF LCODE
+__savemap       PROC    far
+ELSE
+__savemap       PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    di                      ; preserve register variable
+
+IFDEF LDATA
+                les     di,dword ptr [bp+ARG_1] ; buffer address
+ELSE
+                mov     di,word ptr [bp+ARG_1]  ; buffer address
+                push    ds
+                pop     es
+ENDIF
+                mov     ax,4E00h                ; save page map
+                int     67h
+
+                pop     di                      ; restore register variable
+                pop     bp
+                ret
+
+__savemap       ENDP
+
+;
+; int _restmap( char * );
+;
+
+                PUBLIC  __restmap
+IFDEF LCODE
+__restmap       PROC    far
+ELSE
+__restmap       PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    ds
+                push    si                      ; preserve register variable
+
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1] ; buffer address
+ELSE
+                mov     si,word ptr [bp+ARG_1]  ; buffer address
+ENDIF
+                mov     ax,4E01h                ; restore page map
+                int     67h
+                xor     al,al
+
+                pop     si                      ; restore register variable
+                pop     ds
+                pop     bp
+                ret
+
+__restmap       ENDP
+
+;
+; int _getems( int, int * );
+;
+
+                PUBLIC  __getems
+IFDEF LCODE
+__getems        PROC    far
+ELSE
+__getems        PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+
+                mov     bx,word ptr [bp+ARG_1]  ; number of pages
+                mov     ah,43h                  ; get handle and allocate
+                                                ;  memory
+                int     67h
+IFDEF LDATA
+                les     bx,dword ptr [bp+ARG_1+2] ; handle address
+                mov     es:[bx],dx              ; handle
+ELSE
+                mov     bx,word ptr [bp+ARG_1+2] ; handle address
+                mov     ds:[bx],dx              ; handle
+ENDIF
+                xor     al,al
+                pop     bp
+                ret
+
+__getems        ENDP
+
+;
+; int _dskspace( int, unsigned int *, unsigned int * );
+;
+
+                PUBLIC  __dskspace
+IFDEF LCODE
+__dskspace      PROC    far
+ELSE
+__dskspace      PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    di                      ; preserve register variable
+
+                mov     ah,36h                  ; get free disk space
+                mov     dl,byte ptr [bp+ARG_1]  ; drive code (0=default, 1=A)
+                int     21h
+                cmp     ax,0FFFFh               ; was drive invalid?
+                je      space1                  ; yes, jump
+                mul     cx                      ; bytes per sector *
+                                                ;  sectors per cluster
+IFDEF LDATA
+                les     di,dword ptr [bp+ARG_1+2]
+                mov     es:[di],ax              ; bytes per cluster
+ELSE
+                mov     di,word ptr [bp+ARG_1+2]
+                mov     ds:[di],ax              ; bytes per cluster
+ENDIF
+IFDEF LDATA
+                les     di,dword ptr [bp+ARG_1+6]
+                mov     es:[di],bx              ; number of available clusters
+ELSE
+                mov     di,word ptr [bp+ARG_1+4]
+                mov     ds:[di],bx              ; number of available clusters
+ENDIF
+                xor     ax,ax
+
+space1:         pop     di                      ; restore register variable
+                pop     bp
+                ret
+
+__dskspace      ENDP
+
+;
+; int _getrc( void );
+;
+
+                PUBLIC  __getrc
+IFDEF LCODE
+__getrc         PROC    far
+ELSE
+__getrc         PROC    near
+ENDIF
+
+                mov     ah,4Dh                  ; get child return code
+                int     21h
+
+                ret
+
+__getrc         ENDP
+
+;
+; int _create( char *, int * );
+;
+
+                PUBLIC  __create
+IFDEF LCODE
+__create        PROC    far
+ELSE
+__create        PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+IFDEF LDATA
+                push    ds
+ENDIF
+
+                mov     ax,3000h                ; get DOS version number
+                int     21h
+IFDEF LDATA
+                lds     dx,dword ptr [bp+ARG_1] ; file
+ELSE
+                mov     dx,word ptr [bp+ARG_1]  ; file
+ENDIF
+                xor     cx,cx                   ; normal attribute
+                mov     ah,5Bh                  ; create new file
+                cmp     al,3                    ; major version number
+                jae     create1
+                mov     ah,3Ch                  ; create file
+
+create1:        int     21h
+                jc      create2
+IFDEF LDATA
+                lds     bx,dword ptr [bp+ARG_1+4] ; handle address
+ELSE
+                mov     bx,word ptr [bp+ARG_1+2] ; handle address
+ENDIF
+                mov     ds:[bx],ax
+                xor     ax,ax
+
+create2:
+IFDEF LDATA
+                pop     ds
+ENDIF
+                pop     bp
+                ret
+
+__create        ENDP
+
+
+;
+; int _getcd( int, char * );
+;
+
+
+                PUBLIC  __getcd
+IFDEF LCODE
+__getcd         PROC    far
+ELSE
+__getcd         PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    si                      ; preserve register variable
+IFDEF LDATA
+                push    ds
+ENDIF
+
+                mov     dl,byte ptr [bp+ARG_1]  ; drive code (0=default, 1=A)
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+2] ; buffer
+ELSE
+                mov     si,word ptr [bp+ARG_1+2] ; buffer
+ENDIF
+                mov     ah,47h                  ; get current directory
+                int     21h
+                jc      getcd1
+                xor     ax,ax
+
+getcd1:
+IFDEF LDATA
+                pop     ds
+ENDIF
+                pop     si                      ; restore register variable
+                pop     bp
+                ret
+
+__getcd         ENDP
+
+;
+; int _getdrv( void );
+;
+
+                PUBLIC  __getdrv
+IFDEF LCODE
+__getdrv        PROC    far
+ELSE
+__getdrv        PROC    near
+ENDIF
+
+                mov     ah,19h                  ; get default disk drive
+                int     21h
+                xor     ah,ah
+
+; AX = drive (0 = A, 1 = B, etc.)
+
+                ret
+
+__getdrv        ENDP
+
+;
+; void _getvect( int, unsigned int *, unsigned int * );
+;
+
+                PUBLIC  __getvect
+IFDEF LCODE
+__getvect       PROC    far
+ELSE
+__getvect       PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    ds
+                push    si                      ; preserve register variable
+
+                mov     ah,35h                  ; get interrupt vector
+                mov     al,byte ptr [bp+ARG_1]  ; interrupt number
+                int     21h
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+2]
+ELSE
+                mov     si,word ptr [bp+ARG_1+2]
+ENDIF
+                mov     ds:[si],es              ; segment
+IFDEF LDATA
+                lds     si,dword ptr [bp+ARG_1+6]
+ELSE
+                mov     si,word ptr [bp+ARG_1+4]
+ENDIF
+                mov     ds:[si],bx              ; offset
+
+                pop     si                      ; restore register variable
+                pop     ds
+                pop     bp
+                ret
+
+__getvect       ENDP
+
+;
+; void _setvect( VECTOR * );
+;
+
+                PUBLIC  __setvect
+IFDEF LCODE
+__setvect       PROC    far
+ELSE
+__setvect       PROC    near
+ENDIF
+
+                push    bp
+                mov     bp,sp
+                push    ds                      ; modified in setvectsub
+
+IFDEF LDATA
+                les     bx,dword ptr [bp+ARG_1] ; vector table
+ELSE
+                mov     bx,word ptr [bp+ARG_1]  ; vector table
+                push    ds
+                pop     es
+ENDIF
+
+                call    setvectsub
+
+                pop     ds
+                pop     bp
+                ret
+
+__setvect       ENDP
+
+
+;
+; void _setdrvcd(int drive, char * string);
+;
+
+                PUBLIC  __setdrvcd
+IFDEF LCODE
+__setdrvcd      PROC    far
+ELSE
+__setdrvcd      PROC    near
+ENDIF
+                push    bp
+                mov     bp,sp
+IFDEF LDATA
+                push    ds
+ENDIF
+                mov     dl,byte ptr [bp+ARG_1]  ; drive code (0=A, 1=B)
+                mov     ah,0eh
+                int     21h
+IFDEF LDATA
+                lds     dx,dword ptr [bp+ARG_1+2] ; buffer
+ELSE
+                mov     dx,word ptr [bp+ARG_1+2] ; buffer
+ENDIF
+                mov     ah,3bh                  ; set current directory
+                int     21h
+
+IFDEF LDATA
+                pop     ds
+ENDIF
+                pop     bp
+                ret
+
+__setdrvcd      ENDP
+
+_TEXT           ENDS
+
+                END
+

+ 68 - 0
odoors/ODSwap.h

@@ -0,0 +1,68 @@
+/* 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: ODSwap.h
+ *
+ * Description: Provides memory swapping, spawning and certain other low-
+ *              level assembly routines needed by OpenDoors. This file is only
+ *              applicable when building the MS-DOS version of OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 25, 1995  6.00  BP   Created.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODSWAP
+#define _INC_ODSWAP
+
+#ifdef ODPLAT_DOS
+
+/* Data types. */
+typedef struct _vector
+{
+   char number;                        /* vector number */
+   char flag;                          /* 0-CURRENT, 1-IRET, 2-free, 3-end */
+   unsigned int vseg;                  /* vector segment */
+   unsigned int voff;                  /* vector offset */
+} VECTOR;
+
+/* Global variables. */
+extern char **environ;
+
+/* Public functions. */
+int _chkems(char *, int *);
+int _create(char *, int *);
+int _dskspace(int, unsigned int *, unsigned int *);
+int _getcd(int, char *);
+int _getdrv(void);
+int _getems(int, int *);
+int _getrc(void);
+void _getvect(int, unsigned int *, unsigned int *);
+int _restmap(char *);
+int _savemap(char *);
+void _setdrvcd(int, char * );
+void _setvect(VECTOR *);
+int _xsize(unsigned int, long *, long *);
+int _xspawn(char *, char *, char *, VECTOR *, int, int, char *, int);
+
+#endif /* ODPLAT_DOS */
+
+
+#endif /* _INC_ODSWAP */

+ 70 - 0
odoors/ODTypes.h

@@ -0,0 +1,70 @@
+/* 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: ODTypes.c
+ *
+ * Description: Defines special data types used by OpenDoors.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 15, 1994  6.00  BP   Created.
+ *              Jan 30, 1996  6.00  BP   Added kODRCTimeout.
+ *              Feb 08, 1996  6.00  BP   Added kODRCSafeFailure.
+ *              Feb 08, 1996  6.00  BP   Added kODRCUnrecoverableFailure.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ */
+
+#ifndef _INC_ODTYPES
+#define _INC_ODTYPES
+
+/* Generic handle data type. */
+typedef void * tODHandle;
+#define ODHANDLE2PTR(h, t) ((t *) h)
+#define ODPTR2HANDLE(p, t) ((tODHandle) p)
+
+/* OpenDoors function call result codes. */
+typedef enum
+{
+   kODRCSuccess,
+   kODRCGeneralFailure,
+   kODRCNoMemory,
+   kODRCNothingWaiting,
+   kODRCNoMatch,
+   kODRCEndOfFile,
+   kODRCNoPortAddress,
+   kODRCNoUART,
+   kODRCInvalidCall,
+   kODRCFileAccessError,
+   kODRCFilenameTooLong,
+   kODRCTimeout,
+   kODRCSafeFailure,
+   kODRCUnrecoverableFailure,
+   kODRCUnsupported
+} tODResult;
+
+/* Callback function types. */
+#ifdef _MSC_VER
+typedef void ODCALL OD_COMPONENT_CALLBACK(void);
+typedef void ODCALL OD_PERSONALITY_CALLBACK(BYTE btOperation);
+#else /* !_MSC_VER */
+typedef void OD_COMPONENT_CALLBACK(void);
+typedef void OD_PERSONALITY_CALLBACK(BYTE btOperation);
+#endif /* !_MSC_VER */
+
+#endif /* !_INC_ODTYPES */

+ 461 - 0
odoors/ODUtil.c

@@ -0,0 +1,461 @@
+/* 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: ODUtil.c
+ *
+ * Description: Implements the non-platform specific utility functions that
+ *              are defined in odutil.h. Platform specific utility functions
+ *              are implemented in odplat.c.
+ *
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 01, 1994  6.00  BP   Created.
+ *              Dec 31, 1994  6.00  BP   Added ODMakeFilename().
+ *              Nov 13, 1995  6.00  BP   32-bit portability.
+ *              Nov 23, 1995  6.00  BP   Added ODDWordDivide().
+ *              Nov 23, 1995  6.00  BP   Added ODDStringHasTail().
+ *              Nov 23, 1995  6.00  BP   Added ODFileSize().
+ *              Nov 24, 1995  6.00  BP   ODMakeFilename(): handle empty path.
+ *              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   Added ODDWordMultiply().
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODStr.h"
+#include "ODUtil.h"
+#include "ODGen.h"
+
+
+/* ========================================================================= */
+/* General string manipulation functions.                                    */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODStringCopy()
+ *
+ * Safely copies one string to another. Unlike strncpy(), ODStringCopy()
+ * ensures that the destination string is always '\0' terminated.
+ *
+ * Parameters: pszDest     - Pointer to destination string to which to copy
+ *                           characters.
+ *
+ *             pszSource   - Pointer to source string from which to copy
+ *                           characters.
+ *
+ *             nSizeOfDest - Maximum number of characters to place in pszDest,
+ *                           INCLUDING the '\0' string terminator.
+ *
+ *     Return: void
+ */
+void ODStringCopy(char *pszDest, CONST char *pszSource, INT nSizeofDest)
+{
+   ASSERT(pszDest != NULL);
+   ASSERT(pszSource != NULL);
+   ASSERT(nSizeofDest > 0);
+
+   /* Copy at most the specified number of bytes from source to dest, using */
+   /* (presumably well optimized) strncpy().                                */
+   strncpy(pszDest, pszSource, nSizeofDest);
+
+   /* Ensure that destination string is '\0' terminated. This will not */
+   /* already be the case if strlen(pszSource) >= nSizeofDest.         */
+   pszDest[nSizeofDest - 1] = '\0';
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStringCToPascal()
+ *
+ * Converts a string from C's zero-terminated string format to Pascal's
+ * length byte + string data format.
+ *
+ * Parameters: psPascalString    - Pointer to the destination string.
+ *
+ *             btMaxPascalLength - Size of the destination string, as declared
+ *                                 in Pascal.
+ *
+ *             pszCString        - Pointer to the source string, in C format.
+ *
+ *     Return: A pointer to psPascalString.
+ */
+char *ODStringCToPascal(char *psPascalString, BYTE btMaxPascalLength,
+   char *pszCString)
+{
+   BYTE btCStringLength = strlen(pszCString);
+
+   ASSERT(psPascalString != NULL);
+   ASSERT(btMaxPascalLength > 0);
+   ASSERT(pszCString != NULL);
+
+   memcpy((char *)psPascalString + 1,
+      pszCString, *psPascalString = (btCStringLength < btMaxPascalLength)
+      ? btCStringLength : btMaxPascalLength);
+   return(psPascalString);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStringPascalToC()
+ *
+ * Converts a string from Pascal's length byte + string data format to C's
+ * zero-terminated string format.
+ *
+ * Parameters: pszCString     - Pointer to destination string.
+ *
+ *             psPascalString - Pointer to Pascal format source string.
+ *
+ *             btMaxLength    - Length of C string.
+ *
+ *     Return: A pointer to pszCString.
+ */
+char *ODStringPascalToC(char *pszCString, char *psPascalString,
+   BYTE btMaxLength)
+{
+   ASSERT(pszCString != NULL);
+   ASSERT(psPascalString != NULL);
+   ASSERT(btMaxLength > 0);
+
+   if(*(BYTE *)psPascalString <= btMaxLength)
+   {
+      memcpy(pszCString, (char *)psPascalString + 1, *psPascalString);
+      pszCString[(int)psPascalString[0]] = '\0';
+   }
+   else
+   {
+      pszCString[0] = '\0';
+   }
+   return(pszCString);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODStringHasTail()
+ *
+ * Determines whether a string ends in exactly the specified sequence of
+ * characters.
+ *
+ * Parameters: pszFullString  - String to examine.
+ *
+ *             pszTail        - String to look for at the end of
+ *                              pszFullString.
+ *
+ *     Return: TRUE if the pszFullString does end with pszTail, FALSE if
+ *             it does not.
+ */
+BOOL ODStringHasTail(char *pszFullString, char *pszTail)
+{
+   INT nTailLength = strlen(pszTail);
+   INT nFullStringLength = strlen(pszFullString);
+
+   ASSERT(pszFullString != NULL);
+   ASSERT(pszTail != NULL);
+
+   if(nFullStringLength < nTailLength)
+   {
+      return(FALSE);
+   }
+
+   return(stricmp(pszFullString + (nFullStringLength - nTailLength), pszTail) == 0);
+}
+
+
+/* ========================================================================= */
+/* File-related functions.                                                   */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODMakeFilename()
+ *
+ * Generates a fully-qualified filename from a path and base filename.
+ *
+ * Parameters: pszOut      - String to store generated filename in.
+ *
+ *             pszPath     - Directory name. May be the same as pszOut, or
+ *                           may be different.
+ *
+ *             pszFilename - Base filename.
+ *
+ *             nMaxOutSize - Size of pszOut. This value should be one more
+ *                           than the maximum number of characters to be
+ *                           stored in the output string.
+ *
+ *     Return: kODRCSuccess on success, or an error code on failure.
+ */
+tODResult ODMakeFilename(char *pszOut, CONST char *pszPath,
+   CONST char *pszFilename, INT nMaxOutSize)
+{
+   /* Validate parameters in debug mode */
+   ASSERT(pszPath != NULL);
+   ASSERT(pszFilename != NULL);
+   ASSERT(pszOut != NULL);
+   ASSERT(pszFilename != pszOut);
+   ASSERT(nMaxOutSize > 0);
+
+   /* Check that there is enough room in the destination string to hold */
+   /* both source strings plus possibly an additional \-seperator.      */
+   if((INT)(strlen(pszPath) + strlen(pszFilename) + 1) > nMaxOutSize - 1)
+   {
+      return(kODRCFilenameTooLong);
+   }
+
+   /* Copy path to output filename, if the addresses are different. */
+   if(pszPath != pszOut)
+   {
+      strcpy(pszOut, pszPath);
+   }
+
+   /* Ensure there is a trailing backslash, if path was not empty. */
+#ifdef ODPLAT_NIX
+
+#else
+   if(pszOut[strlen(pszOut) - 1] != DIRSEP && strlen(pszOut) > 0)
+   {
+      strcat(pszOut, DIRSEP_STR);
+   }
+#endif
+
+   /* Append base filename. */
+   strcat(pszOut, pszFilename);
+
+   return(kODRCSuccess);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODFileSize()
+ *
+ * Determines the size of a currently open file.
+ *
+ * Parameters: pfFile - Pointer to an already open file to examine.
+ *
+ *     Return: The size of the file. In the case of a file that is open in
+ *             binary mode, this will be the file length in bytes.
+ */
+DWORD ODFileSize(FILE *pfFile)
+{
+   DWORD dwOriginal;
+   DWORD dwFileSize;
+
+   ASSERT(pfFile != NULL);
+
+   dwOriginal = ftell(pfFile);
+   fseek(pfFile, 0L, SEEK_END);
+   dwFileSize = ftell(pfFile);
+   fseek(pfFile, dwOriginal, SEEK_SET);
+   return(dwFileSize);
+}
+
+
+/* ========================================================================= */
+/* DWORD math functions.                                                     */
+/* ========================================================================= */
+
+/* ----------------------------------------------------------------------------
+ * ODDWordShiftLeft()
+ *
+ * Shifts a DWORD to the left by the specified number of bits.
+ *
+ * Parameters: dwValue    - Value to be shifted.
+ *
+ *             btDistance - Distance to shift dwValue by.
+ *
+ *     Return: Result of the shift operation.
+ */
+DWORD ODDWordShiftLeft(DWORD dwValue, BYTE btDistance)
+{
+   WORD wUpper;
+   WORD wLower;
+
+   wLower = (WORD)dwValue;
+   wUpper = *(WORD *)(((BYTE *)(&dwValue)) + 2);
+
+   while(btDistance--)
+   {
+      wUpper <<= 1;
+      wUpper |= (wLower & 0x8000) >> 15;
+      wLower <<= 1;
+   }
+
+   dwValue = wLower;
+   *(WORD *)(((BYTE *)(&dwValue)) + 2) = wUpper;
+
+   return(dwValue);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDWordShiftRight()
+ *
+ * Shifts a DWORD to the right by the specified number of bits.
+ *
+ * Parameters: dwValue    - Value to be shifted.
+ *
+ *             btDistance - Distance to shift dwValue by.
+ *
+ *     Return: Result of the shift operation.
+ */
+DWORD ODDWordShiftRight(DWORD dwValue, BYTE btDistance)
+{
+   WORD wUpper;
+   WORD wLower;
+
+   wLower = (WORD)dwValue;
+   wUpper = *(WORD *)(((BYTE *)(&dwValue)) + 2);
+
+   while(btDistance--)
+   {
+      wLower >>= 1;
+      wLower |= (wUpper & 0x0001) << 15;
+      wUpper >>= 1;
+   }
+
+   dwValue=wLower;
+   *(WORD *)(((BYTE *)(&dwValue)) + 2) = wUpper;
+   return(dwValue);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDWordDivide()
+ *
+ * Divides one DWORD by another DWORD, calculating the quotient and remainder.
+ *
+ * Parameters: pdwQuotient   - Location where the quotient should be stored,
+ *                             or NULL if quotient is not required.
+ *
+ *             pdwRemainder  - Location where remainder should be stored,
+ *                             or NULL if remainder is not required.
+ *
+ *             dwDividend    - Dividend to be divided by divisor.
+ *
+ *             dwDivisor     - Divisor to divide dividend by.
+ *
+ *     Return: TRUE on success or FALSE on failure.
+ */
+BOOL ODDWordDivide(DWORD *pdwQuotient, DWORD *pdwRemainder,
+   DWORD dwDividend, DWORD dwDivisor)
+{
+   INT nTimes = 0;
+   DWORD dwQuotient;
+   DWORD dwRemainder;
+
+   /* Check for divide by zero in debug versions. */
+   ASSERT(dwDivisor != 0);
+
+   /* Check that divisor is not zero. (An attempt to divide by zero will */
+   /* put this algorithm into an infinite loop, rather than triggering   */
+   /* a divide fault.)                                                   */
+   if(dwDivisor == 0L)
+   {
+      return(FALSE);
+   }
+
+   /* Initialize remainder to be entire dividend */
+   dwRemainder = dwDividend;
+
+   /* Initialize quotient to 0 */
+   dwQuotient = 0L;
+
+   /* Determine largest required multiple of divisor */
+   while(dwRemainder >=  dwDivisor)
+   {
+      dwDivisor = ODDWordShiftLeft(dwDivisor, 1);
+      ++nTimes;
+   }
+
+   /* Loop across for all multiples of divisor, beginning with the largest */
+   do
+   {
+      dwQuotient = ODDWordShiftLeft(dwQuotient, 1);
+
+      /* If current remainder is >= this multiple of the divisor */
+      if(dwRemainder >= dwDivisor)
+      {
+         /* Subtract the multiple of the divisor from the remainder */
+         dwRemainder -= dwDivisor;
+
+         /* The next bit of the quotient should be a 1 */
+         dwQuotient |= 1L;
+      }
+
+      /* Divide current multiple of divisor by two */
+      dwDivisor = ODDWordShiftRight(dwDivisor, 1);
+
+      /* Repeat for all multiples of the divisor */
+   } while(nTimes--);
+
+   /* If caller asked for quotient, then return it */
+   if(pdwQuotient != NULL)
+   {
+      *pdwQuotient = dwQuotient;
+   }
+
+   /* If caller asked for remainder, then return it */
+   if(pdwRemainder != NULL)
+   {
+      *pdwRemainder = dwRemainder;
+   }
+
+   return(TRUE);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * ODDWordDivide()
+ *
+ * Multiplies one DWORD by another, returning the product. Multiplication
+ * is performed by using at most 32 additions.
+ *
+ * Parameters: dwMultiplicand - The multiplicand.
+ *
+ *             dwMultiplier   - The multiplier.
+ *
+ *     Return: Result of the multiplication.
+ */
+DWORD ODDWordMultiply(DWORD dwMultiplicand, DWORD dwMultiplier)
+{
+   DWORD dwResult = 0;
+
+   /* Loop while multiplier is not zero */
+   while(dwMultiplier != 0)
+   {
+      /* If least significant bit of multiplier is set */
+      if(dwMultiplier & 0x00000001)
+      {
+         /* Add multiplicand to product */
+         dwResult += dwMultiplicand;
+      }
+
+      /* Shift multiplicand left one bit */
+      dwMultiplicand = ODDWordShiftLeft(dwMultiplicand, 1);
+
+      /* Shift multiplier right one bit */
+      dwMultiplier = ODDWordShiftRight(dwMultiplier, 1);
+   }
+
+   /* Return the final result to the caller. */
+   return(dwResult);
+}

+ 66 - 0
odoors/ODUtil.h

@@ -0,0 +1,66 @@
+/* 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: ODUtil.h
+ *
+ * Description: Contains prototypes and definitions for use by non-platform
+ *              specific utility functions. These functions are implemented in
+ *              odutil.c. Platform-specific utility functions are defined in
+ *              odplat.h, and implemented in odplat.c.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Nov 01, 1994  6.00  BP   Created.
+ *              Dec 31, 1994  6.00  BP   Added ODMakeFilename().
+ *              Nov 13, 1995  6.00  BP   32-bit portability.
+ *              Nov 23, 1995  6.00  BP   Added ODDWordDivide().
+ *              Nov 23, 1995  6.00  BP   Added ODDStringHasTail().
+ *              Nov 23, 1995  6.00  BP   Added ODFileSize().
+ *              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   Added ODDWordMultiply().
+ */
+
+#ifndef _INC_ODUTIL
+#define _INC_ODUTIL
+
+#include "ODGen.h"
+#include "ODTypes.h"
+#include "OpenDoor.h"
+
+/* General string manipulation functions. */
+void ODStringCopy(char *pszDest, CONST char *pszSource, INT nSizeofDest);
+char *ODStringCToPascal(char *psPascalString, BYTE btMaxPascalLength,
+   char *pszCString);
+char *ODStringPascalToC(char *pszCString, char *psPascalString,
+   BYTE btMaxLength);
+BOOL ODStringHasTail(char *pszFullString, char *pszTail);
+
+/* File-related functions. */
+tODResult ODMakeFilename(char *pszOut, CONST char *pszPath,
+   CONST char *pszFilename, INT nMaxOutSize);
+DWORD ODFileSize(FILE *pfFile);
+
+/* DWORD math functions. */
+DWORD ODDWordShiftLeft(DWORD dwValue, BYTE btDistance);
+DWORD ODDWordShiftRight(DWORD dwValue, BYTE btDistance);
+BOOL ODDWordDivide(DWORD *pdwQuotient, DWORD *pdwRemainder,
+   DWORD dwDividend, DWORD dwDivisor);
+DWORD ODDWordMultiply(DWORD dwMultiplicand, DWORD dwMultiplier);
+
+#endif /* !_INC_ODUTIL */

+ 270 - 0
odoors/ODWCat.c

@@ -0,0 +1,270 @@
+/* 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: ODWCat.c
+ *
+ * Description: Implements the Wildcat personality.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Jul 18, 1995  6.00  BP   Fix ODStatGetUserAge() bug.
+ *              Nov 13, 1995  6.00  BP   32-bit portability.
+ *              Nov 14, 1995  6.00  BP   Added include of odscrn.h.
+ *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
+ *              Dec 24, 1995  6.00  BP   Fixed black square at pos 25x80.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 03, 1996  6.00  BP   Display connect speed with %lu.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 19, 1996  6.10  BP   MSVC15 source-level compatibility.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <time.h>
+#include <stdio.h>
+
+#include "OpenDoor.h"
+#include "ODGen.h"
+#include "ODScrn.h"
+#include "ODCore.h"
+#include "ODStat.h"
+#include "ODKrnl.h"
+
+
+/* ----------------------------------------------------------------------------
+ * pdef_wildcat()
+ *
+ * Personality function for the Wildcat BBS-like status line / function key
+ * personality.
+ *
+ * Parameters: btOperation - Indicates personality operation to be performed.
+ *
+ *     Return: void
+ */
+ODAPIDEF void ODCALL pdef_wildcat(BYTE btOperation)
+{
+   BYTE btInfoType = od_control.od_info_type;
+
+   switch(btOperation)
+   {
+      case PEROP_DISPLAY1:
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(1,24);
+         ODScrnDisplayString("                                    Baud:                                       ");
+         ODScrnSetCursorPos(1,25);
+         ODScrnDisplayString("Phone:                               Sec:                      Time Left:      ");
+         ODScrnPutText(80, 25, 80, 25, abtGreyBlock);
+
+         ODScrnSetAttribute(0x71);
+         ODScrnSetCursorPos(1,24);
+         sprintf(szStatusText, "(%s), ", od_control.user_name,
+            od_control.user_location);
+         ODScrnPrintf("%34.34s", szStatusText);
+         ODScrnSetCursorPos(43, 24);
+         ODScrnPrintf("%lu", od_control.od_connect_speed);
+
+         ODScrnSetCursorPos(8,25);
+         if(od_control.od_extended_info || btInfoType==SFDOORSDAT || btInfoType==DOORSYS_GAP || btInfoType==DOORSYS_WILDCAT)
+         {
+            ODScrnDisplayString(od_control.user_homephone);
+         }
+
+         if(btInfoType==RA1EXITINFO || btInfoType==RA2EXITINFO || btInfoType==DOORSYS_WILDCAT)
+         {
+            char szUserAge[7];
+            ODScrnSetCursorPos(28,25);
+            ODScrnSetAttribute(0x70);
+            ODScrnDisplayString("Age: ");
+            ODScrnSetAttribute(0x71);
+            ODStatGetUserAge(szUserAge);
+            ODScrnDisplayString(szUserAge);
+         }
+
+         ODScrnSetCursorPos(43,25);
+         ODScrnPrintf("%u",od_control.user_security);
+
+         if(btInfoType==RA1EXITINFO || btInfoType==RA2EXITINFO || btInfoType==DOORSYS_WILDCAT)
+         {
+            if(strlen(od_control.user_firstcall)==8)
+            {
+               ODScrnSetCursorPos(49,25);
+               ODScrnSetAttribute(0x70);
+               ODScrnDisplayString("Since: ");
+               ODScrnSetAttribute(0x71);
+               ODScrnDisplayChar(od_control.user_firstcall[0]);
+               ODScrnDisplayChar(od_control.user_firstcall[1]);
+               ODScrnDisplayChar('/');
+               ODScrnDisplayChar(od_control.user_firstcall[6]);
+               ODScrnDisplayChar(od_control.user_firstcall[7]);
+            }
+         }
+
+      case PEROP_UPDATE1:
+         ODScrnSetAttribute(0x71);
+         ODScrnSetCursorPos(74,25);
+         if(od_control.user_timelimit<=9)
+         {
+            ODScrnPrintf("   %d",od_control.user_timelimit);
+         }
+         else if(od_control.user_timelimit<=99)
+         {
+            ODScrnPrintf("  %d",od_control.user_timelimit);
+         }
+         else if(od_control.user_timelimit<=999)
+         {
+            ODScrnPrintf(" %d",od_control.user_timelimit);
+         }
+         else
+         {
+            ODScrnPrintf("%d",od_control.user_timelimit);
+         }
+
+         ODScrnSetAttribute(0x70);
+         ODScrnSetCursorPos(56,24);
+
+         if(od_control.od_okaytopage==TRUE)
+            ODScrnDisplayString("Page Bell ");
+         else
+            ODScrnDisplayString("          ");
+
+         if(od_control.od_user_keyboard_on)
+            ODScrnDisplayString("Kybd ");
+         else
+            ODScrnDisplayString("     ");
+
+         if(od_control.sysop_next)
+            ODScrnDisplayString("Local-Next");
+         else
+            ODScrnDisplayString("          ");
+         break;
+
+      case PEROP_INITIALIZE:
+         od_control.key_hangup=0x0000;
+         od_control.key_drop2bbs=0x4400;
+         od_control.key_dosshell=0x2000;
+         od_control.key_chat=0x4100;
+         od_control.key_sysopnext=0x3b00;
+         od_control.key_lockout=0x8100;
+         od_control.key_status[0]=0x0000;
+         od_control.key_status[1]=0x0000;
+         od_control.key_status[2]=0x0000;
+         od_control.key_status[3]=0x0000;
+         od_control.key_status[4]=0x0000;
+         od_control.key_status[5]=0x0000;
+         od_control.key_status[6]=0x0000;
+         od_control.key_status[7]=0x0000;
+         od_control.key_status[8]=0x0000;
+         od_control.key_keyboardoff=0x2500;
+         od_control.key_moretime=0x0000;
+         od_control.key_lesstime=0x0000;
+         od_control.od_page_statusline=-1;
+         ODStatAddKey(0x4200);         /* Key to end chat */
+         ODStatAddKey(0x4800);         /* Key to add five minutes */
+         ODStatAddKey(0x5000);         /* key to subtract five minutes */
+         ODStatAddKey(0x7800);         /* key to hangup */
+         ODStatAddKey(0x7900);         /* key to hangup */
+         ODStatAddKey(0x7a00);         /* key to hangup */
+         ODStatAddKey(0x7b00);         /* key to hangup */
+         ODStatAddKey(0x7c00);         /* key to hangup */
+         ODStatAddKey(0x7d00);         /* key to hangup */
+         ODStatAddKey(0x7e00);         /* key to hangup */
+         ODStatAddKey(0x7f00);         /* key to hangup */
+         ODStatAddKey(0x8000);         /* key to hangup */
+         ODStatAddKey(0x3f00);         /* key to toggle bell */
+         ODStatAddKey(0x3e00);         /* key to toggle bell */
+         break;
+
+      case PEROP_CUSTOMKEY:
+         switch((WORD)od_control.od_last_hot)
+         {
+            case 0x4200:
+               od_control.od_chat_active = FALSE;
+               break;
+
+            case 0x4800:
+               if(od_control.user_timelimit <= 1435)
+               {
+                  od_control.user_timelimit += 5;
+                  bForceStatusUpdate = TRUE;
+                  CALL_KERNEL_IF_NEEDED();
+               }
+               break;
+
+            case 0x5000:
+               od_control.user_timelimit -= 5;
+               bForceStatusUpdate = TRUE;
+               CALL_KERNEL_IF_NEEDED();
+               break;
+
+            case 0x7800:
+            case 0x7900:
+            case 0x7a00:
+            case 0x7b00:
+            case 0x7c00:
+            case 0x7d00:
+            case 0x7e00:
+            case 0x7f00:
+            case 0x8000:
+               od_exit(2,TRUE);
+               break;
+
+            case 0x3f00:
+            case 0x3e00:
+               if(od_control.od_okaytopage!=TRUE)
+               {
+                  od_control.od_okaytopage=TRUE;
+               }
+               else
+               {
+                  od_control.od_okaytopage=FALSE;
+               }
+               bForceStatusUpdate = TRUE;
+               CALL_KERNEL_IF_NEEDED();
+               break;
+
+            default:
+               return;
+         }
+         od_control.od_last_hot=0;
+         break;
+
+      case PEROP_DEINITIALIZE:
+         ODStatRemoveKey(0x4200);
+         ODStatRemoveKey(0x4800);
+         ODStatRemoveKey(0x5000);
+         ODStatRemoveKey(0x7800);
+         ODStatRemoveKey(0x7900);
+         ODStatRemoveKey(0x7a00);
+         ODStatRemoveKey(0x7b00);
+         ODStatRemoveKey(0x7c00);
+         ODStatRemoveKey(0x7d00);
+         ODStatRemoveKey(0x7e00);
+         ODStatRemoveKey(0x7f00);
+         ODStatRemoveKey(0x8000);
+         ODStatRemoveKey(0x3e00);
+         ODStatRemoveKey(0x3f00);
+         break;
+   }
+}

+ 319 - 0
odoors/ODWin.c

@@ -0,0 +1,319 @@
+/* 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: ODWin.c
+ *
+ * Description: Implements the od_window_...() functions for creating
+ *              and removing text-mode windows.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Oct 13, 1994  6.00  BP   New file header format.
+ *              Nov 01, 1994  6.00  BP   Include stdlib.h for malloc prototype.
+ *              Dec 09, 1994  6.00  BP   Standardized coding style.
+ *              Dec 12, 1994  6.00  BP   Set od_error on window remove failure.
+ *              Aug 19, 1995  6.00  BP   32-bit portability.
+ *              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 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Aug 10, 2003  6.23  SH   *nix support
+ */
+
+#define BUILDING_OPENDOORS
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "OpenDoor.h"
+#include "ODGen.h"
+#include "ODCore.h"
+#include "ODKrnl.h"
+
+
+/* ----------------------------------------------------------------------------
+ * od_window_create()
+ *
+ * Creates a window on the screen, storing information on the original screen
+ * contents "under" the window in order to restore the screen after the window
+ * is removed. A window that is created with this function must be destroyed
+ * by od_window_remove() in order to free up memory that is allocated by this
+ * function.
+ *
+ * Parameters: nLeft        - 1-based column number of left edge of window.
+ *
+ *             nTop         - 1-based row number of top edge of window.
+ *
+ *             nRight       - 1-based column number of right edge of window.
+ *
+ *             nBottom      - 1-based row number of bottom edge of window.
+ *
+ *             pszTitle     - Pointer to a string containing title for window.
+ *                            If this string is empty, no title is displayed.
+ *
+ *             btBoarderCol - Colour of window boarder.
+ *
+ *             btTitleCol   - Colour of window title.
+ *
+ *             btInsideCol  - Colour of rest of window.
+ *
+ *             nReserved    - Should always be 0 for this version.
+ *
+ *     Return: Pointer to window description buffer (which must later be
+ *             passed to od_window_remove(), or NULL on failure.
+ */
+ODAPIDEF void * ODCALL od_window_create(INT nLeft, INT nTop, INT nRight,
+   INT nBottom, char *pszTitle, BYTE btBorderCol, BYTE btTitleCol,
+   BYTE btInsideCol, INT nReserved)
+{
+   BYTE btLine;
+   BYTE btBetweenSize;
+   void *pBuffer;
+   BYTE btTitleSize;
+   BYTE btRemaining;
+
+   /* Log function entry if running in trace mode. */
+   TRACE(TRACE_API, "od_window_create()");
+
+   /* Ensure that OpenDoors has been initialized */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   nReserved &= 0x00;
+
+   btBetweenSize = (nRight - nLeft) - 1;
+
+   /* Setup od_box_chars appropriately. */
+   if(od_control.od_box_chars[BOX_BOTTOM]==0)
+   {
+      od_control.od_box_chars[BOX_BOTTOM] = od_control.od_box_chars[BOX_TOP];
+   }
+   if(od_control.od_box_chars[BOX_RIGHT]==0)
+   {
+      od_control.od_box_chars[BOX_RIGHT] = od_control.od_box_chars[BOX_LEFT];
+   }
+
+   /* Ensure that the current display mode can support the capabilities */
+   /* required to display and remove windows.                           */
+   if(!(od_control.user_ansi || od_control.user_avatar))
+   {
+      od_control.od_error = ERR_NOGRAPHICS;
+      OD_API_EXIT();
+      return(NULL);
+   }
+
+   /* Validate parameters. */
+   if(nLeft < 1 || nTop < 1 || nRight > 80 || nBottom > 25 || nRight-nLeft < 2
+      || nBottom-nTop < 2)
+   {
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(NULL);
+   }
+
+   /* Allocate a buffer large enough to hold all window information. */
+   if((pBuffer = malloc((nRight - nLeft + 1) * 2 + (nBottom - nTop + 1) * 160
+      + 4)) == NULL)
+   {
+      od_control.od_error = ERR_MEMORY;
+      OD_API_EXIT();
+      return(NULL);
+   }
+
+   /* Store current contents of screen where window will be drawn. */
+   if(!od_gettext(nLeft, nTop, nRight, nBottom, (char *)pBuffer+4))
+   {
+      free(pBuffer);
+
+      /* Note: od_control.od_error code has been set by od_gettext(). */
+
+      OD_API_EXIT();
+      return(NULL);
+   }
+
+   /* Store window information in buffer. */
+   ((char *)pBuffer)[0]=nLeft;
+   ((char *)pBuffer)[1]=nTop;
+   ((char *)pBuffer)[2]=nRight;
+   ((char *)pBuffer)[3]=nBottom;
+
+   /* Determine number of characters of title to display. */
+   if(pszTitle==NULL)
+   {
+      btTitleSize = 0;
+   }
+   else
+   {
+      if((btTitleSize = strlen(pszTitle)) > (btBetweenSize - 4))
+      {
+         btTitleSize = btBetweenSize - 4;
+      }
+   }
+
+   /* Move to position of window's top corner, prepare to begin drawing the */
+   /* window.                                                               */
+   od_set_cursor(nTop,nLeft);
+   od_set_attrib(btBorderCol);
+
+   /* Display corner character. */
+   od_putch(od_control.od_box_chars[BOX_UPPERLEFT]);
+
+   /* If there is no title, display top line all in one piece. */
+   if(btTitleSize == 0)
+   {
+      /* Display top line. */
+      od_repeat(od_control.od_box_chars[BOX_TOP],btBetweenSize);
+   }
+   else
+   {
+      /* If there is a title, display the top line with a title centered in */
+      /* it.                                                                */
+      od_repeat(od_control.od_box_chars[BOX_TOP],btRemaining =
+         ((btBetweenSize - btTitleSize - 2) / 2));
+      od_set_attrib(btTitleCol);
+      od_putch(' ');
+      od_disp(pszTitle,btTitleSize,TRUE);
+      od_putch(' ');
+      od_set_attrib(btBorderCol);
+      od_repeat(od_control.od_box_chars[BOX_TOP],
+         (BYTE)(btBetweenSize - btRemaining - btTitleSize - 2));
+   }
+
+   /* Display top right corner character. */
+   od_putch(od_control.od_box_chars[BOX_UPPERRIGHT]);
+
+   /* If AVATAR mode is available. */
+   if(od_control.user_avatar)
+   {
+      /* Display first left verticle line. */
+      od_set_cursor(nTop + 1, nLeft);
+      od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+      /* Fill in center of window with AVATAR clear area control sequence. */
+      od_emulate(22);
+      od_emulate(12);
+      od_emulate(btInsideCol);
+      od_emulate((BYTE)((nBottom - nTop) - 1));
+      od_emulate(btBetweenSize);
+
+      od_set_attrib(btBorderCol);
+      od_set_cursor(nTop + 1 , nRight);
+
+      /* Display first right verticle line. */
+      od_putch(od_control.od_box_chars[BOX_RIGHT]);
+
+      /* Display remaining verticle lines. */
+      for(btLine=nTop+2;btLine<nBottom;++btLine)
+      {
+         /* Move to line start and display left line character. */
+         od_set_cursor(btLine,nLeft);
+         od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+         /* Move to line start and display right line character. */
+         od_set_cursor(btLine,nRight);
+         od_putch(od_control.od_box_chars[BOX_RIGHT]);
+      }
+   }
+
+   /* If AVATAR mode is not available. */
+   else
+   {
+      /* Loop through middle lines of window. */
+      for(btLine=nTop+1;btLine<nBottom;++btLine)
+      {
+         /* Move to line start and display left line character. */
+         od_set_cursor(btLine,nLeft);
+         od_putch(od_control.od_box_chars[BOX_LEFT]);
+
+         /* Set window colour. */
+         od_set_attrib(btInsideCol);
+
+         /* display blank area between left and right vertical lines. */
+         od_repeat(' ',btBetweenSize);
+
+         /* Set border colour. */
+         od_set_attrib(btBorderCol);
+
+         /* Display right line. */
+         od_putch(od_control.od_box_chars[BOX_RIGHT]);
+      }
+   }
+
+   /* Display bottom border of window */
+   od_set_cursor(nBottom,nLeft);
+   od_putch(od_control.od_box_chars[BOX_LOWERLEFT]);
+   od_repeat(od_control.od_box_chars[BOX_BOTTOM],btBetweenSize);
+   od_putch(od_control.od_box_chars[BOX_LOWERRIGHT]);
+
+   /* Return a pointer to the window information buffer. */
+   OD_API_EXIT();
+   return(pBuffer);
+}
+
+
+/* ----------------------------------------------------------------------------
+ * od_window_remove()
+ *
+ * Removes window from the screen, restoring the screen contents that where
+ * in the window area when the window was first created.
+ *
+ * Parameters: pWinInfo - Pointer to buffer returned by od_window_create().
+ *                        This buffer is deallocated before od_window_remove()
+ *                        returns.
+ *
+ *     Return: TRUE on success, or FALSE on failure.
+ */
+ODAPIDEF BOOL ODCALL od_window_remove(void *pWinInfo)
+{
+   /* Log function entry if running in trace mode */
+   TRACE(TRACE_API, "od_window_remove()");
+
+   /* Ensure that OpenDoors has been initialized */
+   if(!bODInitialized) od_init();
+
+   OD_API_ENTRY();
+
+   if(pWinInfo == NULL)
+   {
+      /* Set error code and return with failure. */
+      od_control.od_error = ERR_PARAMETER;
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   if(!od_puttext(((char *)pWinInfo)[0], ((char *)pWinInfo)[1], ((char *)pWinInfo)[2], ((char *)pWinInfo)[3], (char *)pWinInfo + 4))
+   {
+      /* Deallocate memory assigned to window information structure. */
+      free(pWinInfo);
+
+      /* Note: od_control.od_error code has been set by od_puttext(). */
+
+      /* Return with failure. */
+      OD_API_EXIT();
+      return(FALSE);
+   }
+
+   /* Deallocate memory assigned to window information structure. */
+   free(pWinInfo);
+
+   /* Return with success. */
+   OD_API_EXIT();
+   return(TRUE);
+}

BIN
odoors/ODoorW.lib


+ 14805 - 0
odoors/OPENDOOR.TXT

@@ -0,0 +1,14805 @@
+
+
+
+
+
+
+
+
+
+
+
+
+    ÛÛÛÛÛÛÛÛÛÛ                         ÛÛÛÛÛÛÛÜ
+    ÛÛÛßßßßÛÛÛ                         ÛÛÛßßßÛÛÛ
+    ÛÛÛ    ÛÛÛ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÛÛÛ   ÛÛÛ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜ ÜÜÜÜÜÜÜ
+    ÛÛÛ    ÛÛÛ ÛÛÛßÛÛÛ ÛÛÛßÛÛÛ ÛÛÛßÛÛÛ ÛÛÛ   ÛÛÛ ÛÛÛßÛÛÛ ÛÛÛßÛÛÛ ÛÛÛßßß ÛÛÛßßßß
+    ÛÛÛÜÜÜÜÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛßßßß ÛÛÛ ÛÛÛ ÛÛÛÜÜÜÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ    ßßßßÛÛÛ
+    ÛÛÛÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛß  ÛÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ    ÛÛÛÛÛÛÛ
+               ÛÛÛ
+               ÛÛÛ
+               ßßß                          Online Software Programming Toolkit
+    ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+
+                                                            Programmer's Manual
+
+
+                                                                   Version 6.00
+
+
+                                                         DOS and Win32 Editions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+          NOTE: Since you will likely want to refer to this manual while
+                working with OpenDoors, it is highly recommended that you take
+                the time to print it out. Simply type COPY OPENDOOR.TXT PRN
+                from your DOS prompt. With the exception of this title page,
+                this document contains only 7-bit ASCII characters.
+
+
+
+
+        (C) Copyright 1991 - 1996 by Brian Pirie. All Rights Reserved.
+
+                              TABLE OF CONTENTS
+
+
+CHAPTER 1 - INTRODUCTION TO OPENDOORS.......................................5
+    WELCOME! ...............................................................5
+    FEATURES OF THE OPENDOORS TOOLKIT ......................................6
+
+CHAPTER 2 - ABOUT THIS EVALUATION COPY AND ORDERING.........................9
+    THE EVALUATION COPY & BENEFITS OF REGISTERING ..........................9
+    HOW TO ORDER ...........................................................10
+    HOW TO ORDER BY MAIL ...................................................11
+    SENDING YOUR ORDER FEE IN THE MAIL .....................................12
+    ORDERING BY CREDIT CARD ................................................14
+    HOW YOU CAN RECEIVE YOUR ORDER .........................................15
+    ORDERING THE SOURCE CODE ...............................................17
+    OPENDOORS 6.00 ORDER FORM ..............................................18
+    OPENDOORS 6.00 FEEDBACK FORM ...........................................19
+    TERMS OF REGISTRATION AND SOURCE CODE USE ..............................20
+
+CHAPTER 3 - OPENDOORS TUTORIAL..............................................21
+    ABOUT THIS MANUAL ......................................................21
+    COMPILING A PROGRAM WITH OPENDOORS .....................................22
+    LINKING WITH OPENDOORS USING A DOS COMPILER ............................23
+    LINKING WITH OPENDOORS USING A WINDOWS COMPILER ........................24
+    RUNNING A DOOR PROGRAM WRITTEN WITH OPENDOORS ..........................26
+    RUNNING DOS-BASED DOOR PROGRAMS ........................................26
+    RUNNING WINDOWS 95/NT DOOR PROGRAMS ....................................26
+    BASICS OF DOOR PROGRAMMING WITH OPENDOORS ..............................29
+    TOUR OF A SAMPLE DOOR PROGRAM: "EX_VOTE" ...............................33
+    OTHER EXAMPLE PROGRAMS INCLUDED WITH OPENDOORS .........................38
+
+CHAPTER 4 - THE OPENDOORS API FUNCTIONS.....................................40
+    OVERVIEW ...............................................................40
+    TABLE OF MOST COMMONLY USED FUNCTIONS ..................................41
+    TABLE OF ALL FUNCTIONS .................................................42
+    OD_ADD_PERSONALITY() ...................................................47
+    OD_AUTODETECT() ........................................................48
+    OD_CHAT() ..............................................................50
+    OD_CARRIER() ...........................................................51
+    OD_CLEAR_KEYBUFFER() ...................................................53
+    OD_CLR_LINE() ..........................................................55
+    OD_CLR_SCR() ...........................................................57
+    OD_COLOR_CONFIG() ......................................................59
+    OD_DISP() ..............................................................60
+    OD_DISP_EMU() ..........................................................62
+    OD_DISP_STR() ..........................................................63
+    OD_DRAW_BOX() ..........................................................65
+    OD_EDIT_STR() ..........................................................68
+    OD_EXIT() ..............................................................79
+    OD_GET_ANSWER() ........................................................81
+    OD_GET_INPUT() .........................................................82
+    OD_GET_KEY() ...........................................................85
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 2
+
+    OD_GETTEXT() ...........................................................89
+    OD_HOTKEY_MENU() .......................................................90
+    OD_INIT() ..............................................................92
+    OD_INPUT_STR() .........................................................95
+    OD_KERNEL() ............................................................97
+    OD_LIST_FILES() ........................................................98
+    OD_LOG_WRITE() .........................................................100
+    OD_MULTILINE_EDIT() ....................................................101
+    OD_PAGE() ..............................................................104
+    OD_PARSE_CMD_LINE() ....................................................105
+    OD_POPUP_MENU() ........................................................107
+    OD_PRINTF() ............................................................110
+    OD_PUTCH() .............................................................115
+    OD_PUTTEXT() ...........................................................116
+    OD_REPEAT() ............................................................118
+    OD_RESTORE_SCREEN() ....................................................120
+    OD_SAVE_SCREEN() .......................................................121
+    OD_SCROLL() ............................................................123
+    OD_SEND_FILE() .........................................................124
+    OD_SET_ATTRIB() ........................................................128
+    OD_SET_COLOR() .........................................................131
+    OD_SET_CURSOR() ........................................................134
+    OD_SET_DTR() ...........................................................135
+    OD_SET_PERSONALITY() ...................................................136
+    OD_SET_STATUSLINE() ....................................................137
+    OD_SLEEP() .............................................................139
+    OD_SPAWN() .............................................................141
+    OD_SPAWNVPE() ..........................................................143
+    OD_WINDOW_CREATE() .....................................................145
+    OD_WINDOW_REMOVE() .....................................................147
+
+CHAPTER 5 - THE OPENDOORS CONTROL STRUCTURE.................................148
+    INTRODUCTION TO THE CONTROL STRUCTURE ..................................148
+    CONTROL STRUCTURE - DOOR INFO FILE STATS ...............................150
+    CONTROL STRUCTURE - SERIAL PORT SETTINGS ...............................153
+    CONTROL STRUCTURE - BBS AND CALLER INFORMATION .........................158
+    CONTROL STRUCTURE - DOOR SETTINGS ......................................182
+    CONTROL STRUCTURE - DIAGNOSTICS ........................................185
+    CONTROL STRUCTURE - OPENDOORS CUSTOMIZATION ............................187
+    CONTROL STRUCTURE - FUNCTION KEYS ......................................212
+    CONTROL STRUCTURE - COLOR CUSTOMIZATION ................................216
+    CONTROL STRUCTURE - TEXT CUSTOMIZATION .................................217
+
+CHAPTER 6 - SPECIAL TOPICS..................................................220
+    ADDITIONAL INFORMATION ON THE WIN32 VERSION ............................220
+    CONFIGURATION FILE SYSTEM ..............................................225
+    DEFINING CUSTOM DOOR INFORMATION FILE FORMATS ..........................230
+    MULTIPLE PERSONALITY SYSTEM ............................................233
+    LOG FILE SYSTEM ........................................................235
+    MAKING DOORS MULTI-NODE-AWARE ..........................................237
+
+CHAPTER 7 - TROUBLESHOOTING AND GETTING ASSISTANCE WITH OPENDOORS...........242
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 3
+
+    ABOUT THIS CHAPTER .....................................................242
+    TROUBLESHOOTING PROBLEMS ...............................................242
+    SOLUTIONS TO COMMON PROBLEMS ...........................................244
+    OPENDOORS SUPPORT ......................................................245
+    THE OPENDOORS SUPPORT BBS ..............................................245
+    THE OPENDOORS WORD WIDE WEB SITE .......................................246
+    THE OPENDOORS CONFERENCE ...............................................246
+    GETTING IN TOUCH WITH ME ...............................................247
+
+APPENDIX A - CONTENTS OF PACKAGE............................................249
+
+APPENDIX B - CHANGES FOR THIS VERSION.......................................250
+
+APPENDIX C - FUTURE VERSIONS................................................254
+
+APPENDIX D - SPECIAL THANKS.................................................255
+
+GLOSSARY....................................................................256
+
+INDEX.......................................................................267
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 4
+
+  11
+ 111
+  11
+  11
+  11
+  11
+ 1111
+-------------------------------------------------------------------------------
+CHAPTER 1 - INTRODUCTION TO OPENDOORS
+
+
+
+
+WELCOME!
+-------------------------------------------------------------------------------
+
+               Welcome to OpenDoors! OpenDoors is a POWERFUL and EASY TO USE
+               online software programming toolkit for C and C++. While
+               OpenDoors is most often used to create add-on "door" programs
+               that run under BBS systems, it can also be used for many other
+               online software applications. By using OpenDoors, you are
+               joining over 500 other programmers from around the world who
+               have used it since it was first released to the public in 1991.
+               Over the years, OpenDoors has grown from a simple BBS door
+               programming library to what is perhaps the most sophisticated,
+               widely used and supported package of its type.
+
+               What exactly is OpenDoors? OpenDoors provides a complete system
+               that allows you to quickly and easily write spectacular,
+               professional quality interactive online software. With
+               OpenDoors, you can write software such as BBS door programs just
+               as you would write any other program - without having to worry
+               about the many of the internal details of door programming.
+               OpenDoors looks after communicating through the modem, providing
+               ANSI/AVATAR/RIP terminal support and interfacing with a wide
+               variety of BBS packages through door information files (such as
+               DOOR.SYS, DORINFO1.DEF, etc.). OpenDoors also looks after status
+               lines and sysop function keys for DOS shells, chatting, hanging
+               up, and so on. In addition, OpenDoors carries out all the work
+               involved in keeping track of carrier detection, user timeouts
+               and much, much more. OpenDoors is also highly flexible, allowing
+               you to take as little or as much control of your program's
+               behavior as you wish.
+
+               This package includes both DOS and Win32 versions of OpenDoors.
+               This allows you to build a plain-DOS version of your program to
+               run under a variety of platforms (DOS, DesqView, Windows 3.x,
+               NT, 95 and OS/2), to build a Win32 version that takes special
+               advantage of Windows 95 / NT, or build both versions of your
+               program - the choice is yours. The DOS version of OpenDoors
+               performs its serial I/O using either a FOSSIL driver, or built-
+               in serial I/O capabilities, making the use of a FOSSIL driver
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 5
+
+               optional. The  Win32 version takes special advantage of 32-bit
+               programming, multithreading and the Windows GUI, and allows you
+               to access many services that are provided by Windows, such as
+               ODBC (for database access) and MAPI (for email and messaging).
+               Both the DOS and Win32 versions of OpenDoors can be run under
+               both DOS and Windows-based BBS packages. The DOS version of
+               OpenDoors can also be run under OS/2-based BBS packages.
+
+               The following section provides more detailed information on the
+               features and capabilities that OpenDoors provides.
+
+
+
+
+FEATURES OF THE OPENDOORS TOOLKIT
+-------------------------------------------------------------------------------
+
+               You will find that OpenDoors provides a solid platform to build
+               BBS door programs and other online software on top of. You may
+               want to write simple utility door programs, on-line games or
+               sophisticated applications. Perhaps you are interested in
+               getting into the market of selling online software, or perhaps
+               you just wish to write some custom door programs for a
+               particular BBS system. With OpenDoors, you can accomplish all of
+               these things - and do it much more easily than ever before. Some
+               of the features that OpenDoors provides to :
+
+               - OpenDoors handles all the "dirty" work involved in writing
+                 BBS door programs. Since OpenDoors looks after all the door-
+                 related operations for you, you need do next to nothing
+                 different when writing door programs than you would when
+                 writing any other program. You simply call OpenDoor's simple
+                 functions to input, output and control door operation. In
+                 fact, many people have converted non-door programs to door
+                 programs in only a matter of minutes using OpenDoors. One of
+                 the most common comments I receive about OpenDoors is how
+                 easy it is to use.
+
+               -  OpenDoors allows you to write software that DIRECTLY support
+                 a wide variety of BBS systems, including RemoteAccess,
+                 QuickBBS, PC-Board, Maximus,  Opus, Wildcat!, WWIV, Spitfire,
+                 SuperBBS, Telegard, TriBBS, GAP, and others.
+
+               - As you would expect, OpenDoors flawlessly monitors the
+                 modem's carrier detect signal, to automatically recover when
+                 a user hangs up - without your having to do anything extra in
+                 your program. OpenDoors also monitors how much time the user
+                 has left in the door, and provides a fully adjustable
+                 inactivity timeout monitor.
+
+               - OpenDoors takes care of all the work involved in reading and
+                 writing BBS door information files, such as DORINFO1.DEF,
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 6
+
+                 EXITINFO.BBS, CHAIN.TXT, DOOR.SYS, etc.  If the particular
+                 information is available to OpenDoors, it will provide you
+                 with just about everything you could ever want to know about
+                 the user on-line, the system your door is running under, and
+                 so on. In addition to the many door information file formats
+                 supported by OpenDoors, you are also able to define your own
+                 custom formats.
+
+               - OpenDoors also does all the work involved in displaying and
+                 automatically updating the door's status line, with
+                 information available to the sysop such as user name,
+                 location, baud rate, time left, function keys,
+                 ANSI/AVATAR/RIP settings, and so on. Using OpenDoors, you can
+                 choose from a number of different "personalities". These
+                 personalities allows OpenDoors to mimic the status lines and
+                 sysop function keys used in various BBS packages. OpenDoors
+                 includes personalities that mimic RemoteAccess, PC-Board and
+                 Wildcat! OpenDoors also allows you to create your own
+                 personalities to mimic any other BBS system.
+
+               - OpenDoors automatically provides the sysop with all the
+                 standard function keys for adjusting user time, hanging up on
+                 or even locking out the user, and so on. OpenDoors also
+                 provides you with a chat mode, which is available to the
+                 sysop by pressing Alt-C. In addition, OpenDoors has full
+                 support for sysop shell to DOS, activated by the Alt-J key.
+
+               - What's more, OpenDoors is designed to be very easy to use.
+                 Even the most novice 'C' programmers are able to write
+                 professional-quality doors with OpenDoors. It takes care of
+                 just about every detail for you, yet still gives you the
+                 ability to completely control and customize every detail of
+                 your door's behavior. There are even people who begin door
+                 programming with OpenDoors, having never programmed in C in
+                 the past.
+
+               - OpenDoors supports both FOSSIL-based and built-in serial I/O
+                 capabilities. FOSSIL-based serial I/O can be used for maximum
+                 compatibility with various systems and serial ports,
+                 including multiple-port serial cards such as DigiBoard.
+                 OpenDoors can also operate without a FOSSIL driver, using
+                 it's own serial I/O capabilities. OpenDoor's built-in
+                 asynchronous communications supports baud rates of up to
+                 115,200 and non-standard serial port configurations.
+                 OpenDoors also has the ability to automatically detect which
+                 of the two serial I/O methods should be used on a particular
+                 system.
+
+               - OpenDoors also automatically detects when the BBS system is
+                 operating in local mode, and supports full local mode
+                 operations itself.
+
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 7
+
+               - Other OpenDoors functions include a built in sysop-page
+                 function that will ask the user why they wish to chat, and
+                 then proceed to page the sysop, just as any BBS package
+                 would. OpenDoors also provides screen clearing functions
+                 (which will detect whether the user has screen clearing
+                 turned on), and various ANSI/AVATAR/RIP control functions
+                 (which again detect if the user has graphics mode turned on).
+
+               - In addition to the basic display features of OpenDoors there
+                 are also a number of advanced screen control functions. These
+                 include functions to save and restore the entire screen,
+                 along with functions to save, restore or scroll portions of
+                 the screen. Other functions allow you to provide overlapping
+                 windows and pop-up menus with highlighted selection bars.
+
+               - OpenDoors provides a multi-line text editor that you can use
+                 to allow the user to enter or edit text files, email
+                 messages, or any other text that spans multiple lines. You
+                 can customize many of the editor's settings to suit your
+                 needs.
+
+               - OpenDoors has a number of special sub-systems that you may
+                 elect to include in your doors. Among these, is a log-file
+                 system that allows you to add log file support to your doors
+                 with only a single line of programming.
+
+               - Another valuable OpenDoors sub-system is the configuration
+                 file system. Again using only a single line of code, you can
+                 add configuration file support to your doors. OpenDoors
+                 configuration files permit the sysop using the door to
+                 customize the door's behavior to their own preferences.
+
+               - OpenDoors can also be fully customized in order that you may
+                 write door programs that use languages other than English.
+
+               - Among the ANSI/AVATAR/RIP features found in OpenDoors is the
+                 ability to send ANSI/AVATAR/RIP files from disk. This allows
+                 you to easily design program screens, and incorporate them
+                 into your doors.
+
+               - OpenDoors also comes with the source code for a number of
+                 example doors, which you can modify, or simply extract bits
+                 and pieces for use in your own doors. Plus, this manual
+                 contains many examples of C source code, to help you in
+                 writing nearly any door program you might wish to build.
+
+               - You may also elect to purchase the source code for OpenDoors,
+                 which will permit you to make modifications to any portion of
+                 OpenDoors, use any portions of the OpenDoors source code in
+                 other programs you write, or merely learn how communications-
+                 type programs are written.
+
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 8
+
+  2222
+ 22  22
+     22
+    22
+   22
+  22
+ 222222
+-------------------------------------------------------------------------------
+CHAPTER 2 - ABOUT THIS EVALUATION COPY AND ORDERING
+
+
+
+
+THE EVALUATION COPY & BENEFITS OF REGISTERING
+-------------------------------------------------------------------------------
+
+               OpenDoors is distributed and sold using the conventional
+               "shareware" approach. This complete package can be freely
+               distributed, both online (through BBS systems and the Internet)
+               and on CD-ROMs or other media. This gives you the chance to try
+               OpenDoors before you buy it. Unlike traditional commercial
+               software, you have the opportunity to see OpenDoors first-hand,
+               and determine whether it meets your needs without first paying
+               for it. However, before registering you are only permitted to
+               use it under the following conditions:
+
+               1.)You may only use this package for a one month period, and
+                  for evaluation purposes only.
+
+               2.) Programs written with this package may not be distributed.
+
+               Also, before registering, any program written with OpenDoors
+               will display a message to the user indicating that OpenDoors is
+               not registered. Of course, this message is removed once you have
+               registered.
+
+               If you decided to register OpenDoors, you will become the
+               licensed owner of a powerful tool for creating BBS door programs
+               and other online software. Registered (licensed) owners of
+               OpenDoors are entitled to:
+
+               1.)Virtually unlimited use of OpenDoors. You may write as many
+                  programs as you wish using OpenDoors, and do what you please
+                  with these programs. They may be freely distributed, or even
+                  sold. What's more, there are no additional royalty fees.
+                  Your one time purchase of OpenDoors entitles you to use it
+                  as you please.
+
+               2.)Your registration entitles you to use both the DOS and Win32
+                  versions of OpenDoors.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                            End of Page 9
+
+               3.)You will also be entitled to free upgrades to newer versions
+                  of OpenDoors. In addition to the great many features and the
+                  quality that this version of OpenDoors has to offer, I am
+                  currently working on a great many additions and enhancements
+                  for the next version. (See the end of this document for an
+                  outline of features currently "in the works".) Any programs
+                  you write using this version will also automatically take on
+                  many of these new features when you upgrade to the new
+                  version.
+
+
+               Perhaps the best news of all is the price of OpenDoors. Similar
+               packages sell for $50, $75, or even more. However, this version
+               of OpenDoors will only cost you $28 US Dollars, $34 Canadian
+               Dollars, or the equivalent in your country's currency! (Note
+               that this price will increase in future versions. By registering
+               now, you will save by being able to upgrade to all future
+               versions at no additional charge.)
+
+               Also, the source code for OpenDoors is now available to licensed
+               users for an additional $28US / $34CDN / equivalent. Ordering a
+               copy of the source code will allow you to customize OpenDoors
+               for your own use, making any changes or additions that you wish.
+               It also gives you the opportunity to see how OpenDoors works,
+               and to use any portions of the OpenDoors code in any other
+               programs you wish to write. If you think you might be interested
+               in ordering the OpenDoors source code, please be sure to read
+               the section entitled "Ordering The Source Code", located on page
+               20.
+
+
+
+HOW TO ORDER
+-------------------------------------------------------------------------------
+
+               There are to ways of ordering and OpenDoors license
+               (registration):
+
+               -The most common way to order is by mailing the OpenDoors order
+                form along with a cheque, money order or cash to the address
+                on this order form.
+
+               - You may order using a major credit card. OpenDoors credit card
+                orders are handled by a third-party credit card order service,
+                named PsL.
+
+               The following sections provide more information on how to order
+               using each of these options.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 10
+
+HOW TO ORDER BY MAIL
+-------------------------------------------------------------------------------
+
+               To order OpenDoors by mailing a cheque, money order or cash,
+               simply follow these three steps:
+
+               1.)  Fill out the registration form. Information on filling out
+                    the form is located on page 15.
+
+               2.)  Send the appropriate payment, $28US/$34CDN/equivalent for
+                    the registration or $56US/$68CDN/equivalent for both the
+                    registration and source code. If you wish more detailed
+                    instructions on sending the registration fee, see the
+                    section that begins page on 12. Included in that section is
+                    a list of equivalent prices for a number of other
+                    countries.
+
+               3.)  Send the above two items to me at:
+
+                              Brian Pirie
+                              117 Cedarock Drive
+                              Kanata ON  K2M 2H5
+                              Canada
+
+
+               Many people who register OpenDoors also order the source code
+               package. You may wish to consider the benefits of having the
+               OpenDoors source code - it allows you to learn how OpenDoors and
+               communications software is written, it allows you to modify and
+               customize OpenDoors to suit your own preferences, and it also
+               allows you to use portions of OpenDoors for other non-door
+               programming projects. If you think you might also be interested
+               in the OpenDoors source code, be sure to read the section on the
+               source code, which begins on page 20.
+
+               Also, you may wish to send the OpenDoors feedback form (located
+               on page 19), along with your registration. The feedback form
+               gives you a chance to tell me what you think of OpenDoors, and
+               what changes you would like to see in future versions. In fact,
+               the majority of suggestions made on these forms in the past have
+               already been implemented in the current version of OpenDoors.
+
+               If you have printed the OpenDoors manual, you can simply remove
+               and mail the forms on pages 18 and 19. If you have not already
+               printed a copy of the manual, and you have a printer, you can
+               quickly print these forms by printing the ORDER.FRM file
+               included in the OpenDoors distribution archive. (Type COPY
+               ORDER.FRM PRN from your DOS prompt.)
+
+NO PRINTER?    Alternatively, if you do not have a printer, simply send a hand-
+               written version of the order form.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 11
+
+               If you have any special instructions for me, or anything that
+               you would like to say when you register, feel free to write this
+               on the back of the registration form, or on a separate piece of
+               paper.
+
+               When filling out the OpenDoors registration form, be sure to
+               indicate how you would prefer to receive your OpenDoors
+               registration key and/or source code. The following options are
+               available:
+
+                         - Having me send the registration and/or source code
+                          by conventional mail
+                         - Internet E-Mail (the fastest option)
+                         - By Fax
+                         - Having me call to your BBS
+                         - You calling the OpenDoors support BBS
+                         - FidoNet "CrashMail"
+
+               Once you have decided which means you would prefer to receive
+               your order by, please read the detailed instructions on your
+               order method, below. Also, if you are ordering the source code,
+               please be sure to read the section on ordering the source code,
+               which begins on page 20.
+
+
+
+
+SENDING YOUR ORDER FEE IN THE MAIL
+-------------------------------------------------------------------------------
+
+               The price of OpenDoors is 34 Canadian Dollars, 28 U.S. Dollars,
+               or equivalent for the registration. The source code costs an
+               additional 34 Canadian Dollars, 28 U.S. Dollars, or equivalent.
+               For your convenience, the equivalent value in a number of other
+               country's currencies, at the time of this writing, is as
+               follows:
+
+                       -----------------------------------------------
+                                                REGISTRATION
+                       REGISTRATION ONLY        AND SOURCE CODE
+                       -----------------------------------------------
+                       34 Canadian Dollars      68 Canadian Dollars
+                       28 US Dollars            56 US Dollars
+                       18 British Pounds        36 British Pounds
+                       150 French Francs        300 French Francs
+                       44 German Marks          88 German Marks
+                       50 Netherland Gilders    100 Netherland Gilders
+                       39 Australian Dollars    78 Australian Dollars
+                       -----------------------------------------------
+
+               If you are ordering by mail, this order fee may be paid using
+               any of the following methods:
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 12
+
+
+               -Cheque or Money Order in Canadian currency, drawn upon a
+                Canadian bank. In this case, your order fee will be either
+                $34CDN for just the registration, or $68CDN for both the
+                registration and source code.
+
+               -Cheque or Money Order in U.S. currency, drawn upon a U.S. bank.
+                In this case, your order fee will be either $28US for just the
+                registration, or $56US for both the registration and source
+                code.
+
+               -An International Money Order or International Bank Draft
+                (available from your bank, post office or companies such as
+                American Express), in Canadian currency. Depending on the
+                particular case, your order fee MAY be sent to me by the postal
+                service, and you will mail your order form by itself. You
+                should have the money order drawn in either $34CDN for just the
+                registration, or $68CDN for both the registration and source
+                code.
+
+               -A cheque drawn on any bank in the world, IN THAT COUNTRY'S
+                CURRENCY, equivalent to 34 Canadian dollars. For instance, a
+                cheque for the appropriate number of British Pounds, drawn on a
+                British bank, is perfectly acceptable. However, I am unable to
+                accept a cheque for $34 Canadian dollars, drawn on a British
+                Bank. UNFORTUNATELY, THE BANKS IN CANADA ARE CURRENTLY
+                UNWILLING TO ACCEPT EUROCHEQUES.
+
+               -Cash. Please note that it is not usually recommended that cash
+                be sent in the mail, and that I cannot be responsible for any
+                cash lost in the mail. Simply put, if you wish to order by
+                cash, it is your responsibility to get the cash to me. However,
+                if I do receive your order in the form of cash, it will be
+                perfectly acceptable to me. I would like to mention that many
+                people have already ordered OpenDoors by sending cash, and I
+                have yet to run across any case of cash being lost in the mail.
+                Nonetheless, if you wish to send cash, you may wish to consider
+                doing so by registered mail, for your added security.
+
+
+               If you are ordering OpenDoors from within Canada, you will most
+               likely choose the first option (a Canadian cheque or money
+               order). If you are ordering OpenDoors from within the United
+               States, you will most likely choose the second option (an
+               American cheque or money order). If you are ordering from
+               outside Canada and the U.S., it would be ideal if you could send
+               your fee by an international money order. However, it should be
+               noted that any of the above order methods will be acceptable
+               from any location. Also, it is quite possible that I may be able
+               to accept other means of sending your order fee. If you are
+               unsure about sending your order fee, please feel free to get in
+               touch with me by any of the means listed on page 247.
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 13
+
+ORDERING BY CREDIT CARD
+-------------------------------------------------------------------------------
+
+               This information applies to CREDIT CARD ORDERS ONLY. Please read
+               this entire section before ordering OpenDoors by credit card.
+
+               In order to cover the additional costs of processing credit card
+               orders, an $8 shipping and handling fee applies to all OpenDoors
+               orders made through PsL. As such, the total prices you will pay
+               are:
+
+               - Just registration ($28 + $8 Handling) = $36 U.S.
+               - Registration and Source Code ($56 + $8 Handling) = $64 U.S.
+                 (All prices will be charged to your credit card in U.S.
+               Dollars.)
+
+               You can order OpenDoors with MC, Visa, Amex, or Discover from
+               Public (software) Library by calling 800-2424-PsL or 713-524-6394
+               or by FAX to 713-524-6398 or by CIS Email to 71355,470. You can
+               also order online through the World Wide Web. For more
+               information on how to do this, visit the OpenDoors Web site.
+               (Information on the OpenDoors web site is provided on page 246.)
+               You can also mail credit card orders to PsL at P.O.Box 35705,
+               Houston, TX 77235-5705. When ordering by phone, you must call
+               between 6:00am and 6:00pm CST on Monday to Thursday, or between
+               6:00am and 12:30pm on Fridays.
+
+               THE ABOVE NUMBERS ARE FOR CREDIT CARD ORDERS ONLY.
+               THE AUTHOR OF THIS PROGRAM CANNOT BE REACHED AT THESE NUMBERS.
+
+               Any questions about the status of the shipment of the order,
+               refunds, registration options, product details, technical
+               support, volume discounts, dealer pricing, site licenses, non-
+               credit card orders, etc., must be directed to:
+
+                              Brian Pirie
+                              117 Cedarock Drive
+                              Kanata ON  K2M 2H5
+                              Canada
+
+               To insure that you get the latest version, PsL will notify me the
+               day of your order and I will ship OpenDoors directly to you. I
+               will send OpenDoors by conventional mail unless I have previously
+               heard from you, asking me to send your order by some other means.
+
+               When ordering by credit card through PsL, please be sure to
+               indicate whether you wish to order just the OpenDoors
+               registration, or both the registration and source code. Also,
+               please be sure to include your credit card billing address.
+               Without this information, PsL will be unable to process your
+               order.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 14
+
+HOW YOU CAN RECEIVE YOUR ORDER
+-------------------------------------------------------------------------------
+
+               For your convenience, I can send your OpenDoors registration key
+               and/or source code by any of the following methods. If you are
+               ordering OpenDoors by mail, simply check one of these options on
+               your order form. If you are ordering through the third-party
+               credit card service, I will automatically send your order by
+               Internet email or conventional mail unless I receive a message
+               from you before you order, asking me to send it by some other
+               means.
+
+
+-------------------------------------------------------------------------------
+RECEIVING      If you wish to receive your OpenDoors registration key by
+ORDER BY       Internet E-Mail (including Internet E-Mail to a CompuServe
+INTERNET       account), fill out the order form and mail it along with your
+E-MAIL         payment as described below. Be sure to include your e-mail
+               address on your order form. Note that the source code cannot be
+               sent via Internet e-mail.
+
+
+-------------------------------------------------------------------------------
+RECEIVING      In order to receive your OpenDoors registration key and/or
+ORDER          source code by conventional mail, simply fill out the order
+BY MAIL        form and mail it along with your payment as described below. I
+               will cover the cost of postage. If your address contains non-
+               Roman characters, also enclose a self-addressed envelope or
+               mailing label.
+
+
+-------------------------------------------------------------------------------
+RECEIVING      If you wish to receive your OpenDoors registration key by
+ORDER BY       FAX, fill out the order form and mail it along with your payment
+FAX            as described below. Be sure to include your fax number on your
+               order form. Do to choose this method if you are ordering the
+               source code.
+
+
+-------------------------------------------------------------------------------
+RECEIVING      You may choose to receive your OpenDoors registration and/or
+ORDER BY       source code by calling the OpenDoors BBS after your registration
+CALLING        form and order fee have been received here. If you are unable to
+OPENDOORS      receive your order by any other electronic means (such as a call
+BBS            to your BBS, or by electronic mail), this may be the quickest
+               way for you to receive your registration information and/or
+               source code. The obvious disadvantage with to option is the fact
+               that you will have to estimate when your order will arrive here
+               in order to receive it as quickly as possible. You may end up
+               calling the OpenDoors BBS more than once before your order has
+               arrived. After your order form has arrived, your registration
+               key and/or source code will be placed on hold for you, and you
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 15
+
+               will be able to receive it on your first call to the BBS. The
+               phone number of the BBS is:
+
+                         +1 (613) 599-5554
+
+
+-------------------------------------------------------------------------------
+RECEIVING      In order to receive your OpenDoors registration key and/or
+ORDER BY       source code by a message and/or upload to your BBS, fill out
+CALL TO        the order form and mail it along with your payment as described
+YOUR BBS       below. Be sure to include the phone number, baud rate, and my
+               login and password for the BBS to which you would like me to
+               call. As always, I will cover any long distance costs. If, for
+               some reason, I am unable to connect to your BBS (not because it
+               is busy, but, for example, if your BBS is no longer online), I
+               will send your order by conventional mail instead.
+
+
+-------------------------------------------------------------------------------
+RECEIVING      In order to receive your OpenDoors registration key and/or
+ORDER BY       source code by FidoNet CrashMail, simply fill out the order
+FIDONET        form and mail it along with your payment as described below.
+CRASHMAIL      Be sure to include the FidoNet node address to which you wish to
+               have your registration key and/or source code sent to (via
+               CrashMail). Again I will cover any long distance costs. If, for
+               some reason, I am unable to connect to your FidoNet system, I
+               will send your order by conventional mail instead.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 16
+
+ORDERING THE SOURCE CODE
+-----------------------------------------------------------------------------
+
+               Many people also choose to order the source code along with
+               their OpenDoors registration. Ordering the source code will
+               allow you to customize OpenDoors for your own use, use parts of
+               the OpenDoors source code in other programs, and learn more
+               about how OpenDoors works. If you have any ideas for changes
+               that you would like to see in OpenDoors, either large or small,
+               ordering the source code will allow you to makes these changes
+               yourself, creating your own customized version of OpenDoors. You
+               will be able to remove copyright notices, change the way certain
+               OpenDoors functions work, or add new capabilities to OpenDoors
+               in surprisingly little time. You will also be able to use any of
+               the OpenDoors source code, be it the DesqView-aware code,
+               EMS/disk swapping routines, configuration file system,
+               communications routines, or anything else, in any other programs
+               that you write. Also, ordering the OpenDoors source code will
+               allow you to learn more about how OpenDoors works, and how to
+               program communications software and BBS door programs.
+
+               When you order the OpenDoors source code, you will receive the
+               source code package. The source code package also includes a
+               short "Source Code Manual", with a description of how the
+               OpenDoors source code is organized, instructions on how to
+               recompile the source code, and more. If you choose to receive
+               the source code package electronically, you will receive it in
+               the form of a single .ZIP file. If you choose to receive the
+               source code package by mail, you will receive it on a 3-1/2"
+               diskette.
+
+REQUIREMENTS   Due to the wide variety of compilers that are available, and the
+               differences between them, I have been unable to test the
+               OpenDoors source code with every compiler. This means that you
+               may need to make some changes to the source code in order to
+               compile it with certain compilers.
+
+               In order to compile the DOS version of OpenDoors, you must be
+               using a compiler that supports inline assembly language
+               keywords. This includes all Borland compilers released since
+               1991, and many other compilers. The one notable exception is
+               Watcom's compiler, which does not support inline assembly
+               language at the time of this writing.
+
+               For your information, the DOS OpenDoors libraries included with
+               this package were compiled using Turbo C++ 1.0 Professional. The
+               Win32 libraries included with this package were compiled using
+               Microsoft Visual C++ 2.0. This means that you can be reasonably
+               certain that OpenDoors will compile with these compilers and any
+               more recent compilers by these companies without any changes.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 17
+
+--------------------------------------------------------------------------
+                       OPENDOORS 6.00 ORDER FORM
+--------------------------------------------------------------------------
+
+ REGISTRATION NAME : _______________________________   (AS SHOULD APPEAR IN
+                                                        REGISTRATION)
+         YOUR NAME : _______________________________   (IF DIFFERENT)
+
+    POSTAL ADDRESS : ______________________________________________________
+
+                     ______________________________________________________
+
+                     ______________________________________________________
+
+  E-MAIL ADDRESSES : ____________________________________   (IF APPLICABLE)
+
+   ADDITIONAL INFO : ______________________________________________________
+                     (EG FAX/BBS PHONE NUMBER & BRIAN'S PASSWORD, IF NEEDED)
+
+I WISH TO RECEIVE MY ORDER BY:
+          ___                                ___
+         |   | - INTERNET E-MAIL (FASTEST)  |   | - I WILL CALL BRIAN'S BBS
+         |___|                              |___|
+          ___                                ___
+         |   | - CONVENTIONAL MAIL          |   | - CALL TO MY BBS
+         |___|                              |___|   (INCLUDE LOGIN INFO)
+          ___                                ___
+         |   | - FAX (INCLUDE FAX #)        |   | - FIDONET "CRASHMAIL"
+         |___|                              |___|
+
+                        ___
+I WOULD LIKE TO ORDER: |   | - BOTH REGISTRATION KEY AND SOURCE CODE
+                       |___|   ($56 US, $68 CANADIAN, OR EQUIVALENT FUNDS)
+                        ___
+                       |   | - JUST MY REGISTRATION KEY
+                       |___|   ($28 US, $34 CANADIAN, OR EQUIVALENT FUNDS)
+                        ___
+                       |   | - UPGRADE TO SOURCE CODE (ONLY IF ALREADY
+                       |___|   REGISTERED) ($28 US, $34 CANADIAN OR EQUIV.)
+
+
+I AGREE TO THE REGISTRATION TERMS,             ____________________________
+SET FORTH ON PAGE 20 OF THE MANUAL             (SIGNATURE)
+
+MAKE CHEQUES PAYABLE TO:   BRIAN PIRIE
+                           117 CEDAROCK DRIVE
+                           KANATA ON  K2M 2H5
+                           CANADA
++-- OFFICIAL USE ONLY ----------------------------------------------------+
+|                                                                         |
+| S.N. : _____________  SENT : _________________________________________  |
++-------------------------------------------------------------------------+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 18
+
+--------------------------------------------------------------------------
+                         OPENDOORS 6.00 FEEDBACK FORM
+--------------------------------------------------------------------------
+
+YOUR NAME :  _______________________________
+
+
+WHICH OPENDOORS LIBRARY(S) DO YOU EXPECT TO USE:
+             ___
+            |   | - DOS VERSION, MEMORY MODELS: _________________________
+            |___|
+             ___
+            |   | - WINDOWS (WIN32) VERSION
+            |___|
+
+
+HOW DID YOU FIRST LEARN OF OPENDOORS?
+
+             ____________________________________________________________
+
+
+WHICH COMPILER(S) AND VERSION(S) ARE YOU USING?
+
+             ____________________________________________________________
+
+
+WHAT DO YOU LIKE MOST ABOUT OPENDOORS?
+
+             ____________________________________________________________
+
+             ____________________________________________________________
+
+
+WHAT CHANGES OR ADDITIONS WOULD YOU LIKE TO SEE IN FUTURE VERSIONS?
+
+             ____________________________________________________________
+
+             ____________________________________________________________
+
+
+DO YOU HAVE ANY ADDITIONAL COMMENTS?
+
+             ____________________________________________________________
+
+             ____________________________________________________________
+
+             ____________________________________________________________
+
+
+-----------------------------------------------------------------------------
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 19
+
+TERMS OF REGISTRATION AND SOURCE CODE USE
+-----------------------------------------------------------------------------
+
+               When you purchase an OpenDoors registration and/or source code
+               license, you are entitled to almost unlimited use of all
+               versions of OpenDoors. However, in order to protect my
+               investment of time and effort in developing OpenDoors, you must
+               also agree to the terms outlined below when licensing OpenDoors
+               and/or the source code. These terms are intended to be very
+               reasonable, and are in no way intended to limit your use of
+               OpenDoors. The primary intent of these terms is that you are not
+               permitted to disclose your OpenDoors registration information,
+               or the OpenDoors source code, to other individuals. The terms of
+               registration and source code use are as follows:
+
+               For the purpose of these terms, "OpenDoors" is defined to be the
+               library files, header files, example programs and programmer's
+               manual of all versions, past and present, for all languages and
+               platforms of the OpenDoors online software programming toolkit.
+               In the case of a source code license, OpenDoors also refers to
+               the source code that is used to build OpenDoors. Upon licensing
+               OpenDoors, the individual or organization named on the order
+               form (the licensee) is entitled to use of all versions of
+               OpenDoors, within the terms set forth below. Violation of these
+               terms will be considered copyright infringement, and grounds for
+               the termination of the registration agreement. The licensee is
+               entitled, at no additional cost, to use, distribute or sell the
+               executable (.EXE or .COM) files that are built from OpenDoors.
+               The licensee is also entitled to use, distribute or sell the
+               example programs, example configuration files and portions of
+               the manual. If licensing the source code, the licensee is also
+               entitled to distribute or sell any executable files that result
+               from using altered versions of the source code, or portions
+               thereof, provided that it is not possible for other programmers
+               to access the OpenDoors API functions through this executable
+               file. The licensee is NOT entitled to distribute the
+               registration key number that is provided by Brian Pirie, nor any
+               portions of the OpenDoors source code. For the purposes of these
+               terms, an organization is considered to be a company or non-
+               profit organization. If the licensee is an organization, the
+               registration key and source code may be shared among members of
+               the organization, under the condition that these individuals are
+               using the registration and/or source code only for official
+               activities of that organization. These terms in no way suggest
+               an agreement on the part of Brian Pirie to develop any future
+               versions of OpenDoors, or fix any bugs in current versions of
+               OpenDoors. OpenDoors is offered "as is", and no warrantees are
+               expressed or implied. In no event shall Brian Pirie be liable
+               for any loss of profit or any other damage, including but not
+               limited to special, incidental, consequential or other damages.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 20
+
+  3333
+ 33  33
+     33
+   333
+     33
+ 33  33
+  3333
+-------------------------------------------------------------------------------
+CHAPTER 3 - OPENDOORS TUTORIAL
+
+
+
+
+ABOUT THIS MANUAL
+-------------------------------------------------------------------------------
+
+               The OpenDoors programmer's manual is intended to serve as a
+               complete tutorial, guide and reference to writing programs with
+               OpenDoors. Chapter 1 of this manual, beginning on page 5,
+               provides an introduction and overview of the features of
+               OpenDoors. If you are unsure of what OpenDoors will do for you,
+               begin with Chapter 1. Chapter 2, beginning on page 9, provides
+               important information related to this evaluation copy of
+               OpenDoors, and how to register your copy. Chapter 3 serves as a
+               tutorial on OpenDoors and BBS door programming in general.
+               Chapter 4 provides a reference to the OpenDoors API functions
+               which you can use in your programs. Chapter 5 provides a
+               reference to the "OpenDoors control structure", which gives you
+               access to a wide array of information, and allows you to
+               customize OpenDoor's appearance and behavior. Chapter 6 provides
+               information on special OpenDoors features and advanced door
+               programming topics. Among the subjects discussed in chapter 6
+               are the Win32 version of OpenDoors, configuration files, multi-
+               node operation, RIP graphics, logfile support, defining custom
+               door information file formats, and more.
+
+               Chapter 7 (which begins on page 242) gives instructions on
+               troubleshooting programs written with OpenDoors, lists solutions
+               to common difficulties, and has information about the many
+               sources for OpenDoors support. If at any time you are having
+               difficulty with OpenDoors, be sure to refer to this chapter for
+               complete step-by-step instruction on tracing the source of your
+               problem, and for solutions to common difficulties with
+               OpenDoors. This chapter also directs you to some of the major
+               sources of support, including information on the OpenDoors email
+               conference, the OpenDoors support BBS, and how to get in touch
+               with me.
+
+               You will also find many useful tools in this manual, which will
+               no doubt come in useful while working with OpenDoors. Beginning
+               on page 2 is a basic table of contents, showing you how the
+               manual is organized, and helping you to locate general topics.
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 21
+
+               At the end of the manual, beginning on page 267, is an index to
+               help you locate more information on specific topics. The manual
+               also includes a glossary, on page 256, which will help you in
+               understanding new terms that you may come across while reading
+               the manual. At the end of the manual, you will also find several
+               useful sections, such as information on what is new in this
+               version, information on how to contact me, and information about
+               new OpenDoors features currently in the works.
+
+               You will likely want to print this manual, to make reading and
+               reference while programming easier. To print this manual, simply
+               type the following line from your DOS prompt. If you are worried
+               about the size of this manual, you might consider using a
+               utility that can print multiple pages of a text file on a single
+               sheet of paper. Printing two manual pages per side of paper
+               should certainly be legible, and even four-up would give you
+               text about the size of average newspaper text. Printing on both
+               sides, you should be able to fit the manual on about 34 sheets
+               of paper (269/8 < 34).
+
+
+
+
+COMPILING A PROGRAM WITH OPENDOORS
+-------------------------------------------------------------------------------
+
+               The process of compiling a program written with OpenDoors is
+               very similar to that of compiling any other program. However,
+               there are two additional steps which you must be sure to
+               remember:
+
+               1.)  You must include the OPENDOOR.H header file.
+
+               2.)  You must link your program with the appropriate OpenDoors
+                    library file.
+
+
+               All programs written with OpenDoors, must "include" the
+               OPENDOOR.H header file. If you have placed the OPENDOOR.H header
+               file in the same directory as your program's source code, place
+               the following line at the beginning of your .C or .CPP file(s):
+
+                    #include "opendoor.h"
+
+               If you have placed the OPENDOOR.H header file in the same
+               directory as other standard header files (such as stdio.h),
+               place the following line at the beginning of your .C or .CPP
+               file(s):
+
+                    #include <opendoor.h>
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 22
+
+               In addition to including the OpenDoors header file in your
+               source code modules, you must also "link" the appropriate
+               OpenDoors library file with your program. The procedure for
+               doing this depends upon which compiler you are using. The
+               following sections describe how to link with the OpenDoors
+               libraries using various compilers.
+
+
+
+LINKING WITH OPENDOORS USING A DOS COMPILER
+-------------------------------------------------------------------------------
+
+               This section describes how to link with the provided OpenDoors
+               library files under a variety of DOS compilers. If you are using
+               a compiler other than those described here, refer to your
+               compiler's manual for information on how to link with third-
+               party libraries.
+
+               If you are using Borland Turbo C 2.00 or earlier, you can cause
+               your compiler to link your program with the OpenDoors library by
+               creating a text file with a .PRJ extension. In this text file,
+               you should list the names of your program's .C modules, along
+               with the name of the appropriate OpenDoors library file, as
+               listed in the table at the end of this section. You should then
+               select this Project file from within the Turbo C IDE prior to
+               compiling your program.
+
+               If you are using Turbo C++ or Borland C++, you can set your
+               compiler to link your program with the OpenDoors library by
+               creating a project file from within the IDE. To do this, choose
+               the Open Project command from the Project menu, and enter the
+               name for your new project file in the Load Project dialog box.
+               Then add the names of your program's .C/.CPP modules, along with
+               the name of the appropriate OpenDoors library file, by pressing
+               [Insert] in the project window. When you return to Turbo C++ or
+               Borland C++ again, you can work with the same project file by
+               using the Open command from the Project menu.
+
+               If you are using any Microsoft C compiler, such as Quick C,
+               Microsoft C or Visual C++, you can set your compiler to link
+               your program with the OpenDoors library by creating a makefile.
+               You can create a new project file from within Quick C by using
+               the Set Program List option from the Make menu. You can do this
+               from within Visual C++ by using the New command from the Project
+               menu. You should add the names of your program's .C/.CPP source
+               files, along with the name of the appropriate OpenDoors library
+               file, to the newly create makefile.
+
+               There are several different DOS library files included with
+               OpenDoors, each one for use with a different memory model. The
+               following chart lists the library file names, along with their
+               corresponding memory model. It is important that you use the
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 23
+
+               library file which corresponds to the memory model you are
+               using. Whenever you change your compiler to use a different
+               memory model, it is important to rebuild all of your source
+               files (using the "Build All" or "Rebuild All" command) in
+               addition to changing the library that your program is being
+               linked with. If you are unfamiliar with the concept of memory
+               models, you should refer to your compiler's manuals. If you are
+               unsure as to what memory model your compiler is currently using,
+               check this setting in the compile options dialog box or command
+               line reference information.
+
+                 +------------------------------------------------+
+                 | Library     | Memory                           |
+                 | Filename    | Model                            |
+                 |-------------|----------------------------------|
+                 | ODOORS.LIB  | DOS small memory model library   |
+                 |             |                                  |
+                 | ODOORM.LIB  | DOS medium memory model library  |
+                 |             | (Available separately)           |
+                 |             |                                  |
+                 | ODOORC.LIB  | DOS compact memory model library |
+                 |             | (Available separately)           |
+                 |             |                                  |
+                 | ODOORL.LIB  | DOS large memory model library   |
+                 |             |                                  |
+                 | ODOORH.LIB  | DOS huge memory model library    |
+                 +------------------------------------------------+
+
+               To understand how to compile a program written with OpenDoors,
+               it is a good idea to try compiling one of the example programs,
+               such as ex_hello.c, that are included in the OpenDoors package.
+
+
+
+
+LINKING WITH OPENDOORS USING A WINDOWS COMPILER
+-------------------------------------------------------------------------------
+
+               The Win32 version of OpenDoors resides in a DLL, ODOORS60.DLL.
+               In order to use OpenDoors from a Win32 program, you will
+               typically link to an import library (although it is also
+               possible to use load-time dynamic linking through the use of
+               LoadLibrary() and GetProcAddress()). The OpenDoors package
+               includes a COFF-format import library for use Microsoft
+               compilers, named ODOORW.LIB. If you are using a compiler that
+               uses OMF-format object files, such as a Borland compiler, you
+               will need to create your own version of the odoorw.lib import
+               library, by using the implib utility provided with your
+               compiler.
+
+               When compiling an OpenDoors program with a Windows compiler, be
+               sure that either the WIN32 or __WIN32__ constant is defined.
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 24
+
+               Microsoft and Borland compilers define one of these constants by
+               default. However, if you are using a compiler from another
+               company, you may need to explicitly configure your compiler to
+               define one of these preprocessor constants.
+
+               If you are using Microsoft Visual C++ 2.0 or later, you can
+               setup your compiler to link with the OpenDoors import library by
+               creating a makefile (choose File|New|Project) and adding both
+               your program's .C/.CPP source file(s) and the odoorw.lib import
+               library to the project. When prompted for the Project type,
+               choose "Application", not a "MFC AppWizard". If you are using
+               Visual C++ 2.0, then you must manually edit the .mak file using
+               a text editor. In this file, replace all occurrences of
+               "/SUBSYSTEM:windows" with "/SUBSYSTEM:windows,4.0". This
+               instructs the linker to create an executable file that is
+               targeted for Windows 95. If you do not do this, some of the
+               OpenDoors visual elements will not appear correctly. Later
+               versions of Microsoft's compiler default to using
+               "/SUBSYSTEM:windows,4.0", and so this step is no longer
+               necessary with those compilers.
+
+               If you are using Borland C++ 4.50 or later, you must create an
+               OpenDoors import library for ODOORS60.DLL before you can compile
+               your first OpenDoors program. To do this, go to the directory
+               where ODOORS60.DLL is located, move the original odoorw.lib to a
+               backup location, and issue the command:
+
+                    IMPLIB ODOORW.LIB ODOORS60.DLL
+
+               This will create a new import library (ODOORW.LIB) which you can
+               then use with your compiler. To compile an OpenDoors program
+               from the command line, issue the command:
+
+                    BCC32 -tW your_program.c ODOORW.LIB
+
+               To compile an OpenDoors program from within the IDE, create a
+               new project file, and add both your program's source file(s) and
+               the OpenDoors import library to that project. If you are
+               compiling from within the IDE, check the TargetExpert and be
+               sure that you are using the multithreaded version of the the
+               runtime libraries. By default, the Borland IDE compiles single-
+               threaded, which will not work with OpenDoors.
+
+               Additional information on the Win32 version of OpenDoors is
+               provided in chapter 6.
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 25
+
+RUNNING A DOOR PROGRAM WRITTEN WITH OPENDOORS
+-------------------------------------------------------------------------------
+
+               This section provides information on how to run a BBS door
+               program that has been written with OpenDoors. If you are using
+               OpenDoors to write some other form of online software, the
+               information provided here will apply to different degrees,
+               depending on the nature of your program.
+
+               OpenDoors supports both local and remote modes. In the normal
+               mode of operation, remote mode, your program's output will be
+               displayed to both the local screen and the remote user's screen.
+               To run your program in remote mode, you will usually set it up
+               to run under some BBS package. However, for testing purposes, it
+               is often convenient to run your program in local mode.
+
+               There are several ways to start your program in local mode. The
+               first method is to place the example DORINFO1.DEF file in the
+               same directory as your program. If your program uses the
+               OpenDoors command line processing function, od_parse_cmd_line(),
+               then you can start your program in local mode by simply
+               specifying -local on your program's command line. For example,
+               you can try the example program include with OpenDoors by
+               issuing the command VOTEDOS -LOCAL (for the DOS version) or
+               VOTEWIN -LOCAL (for the Windows 95/NT version). OpenDoors will
+               also run in local mode if you set it up to run under a BBS
+               package, and log into the BBS in local mode. When the BBS runs
+               your door program, OpenDoors will automatically run in local
+               mode.
+
+               To run your program in remote mode, you will probably want to
+               run it under a BBS system. If you don't have a BBS package for
+               testing purposes, you might want to obtain a popular BBS package
+               such as Wildcat!, Maximus (which is free) or RemoteAccess.
+
+
+
+RUNNING DOS-BASED DOOR PROGRAMS
+-------------------------------------------------------------------------------
+
+               DOS BBS packages typically run door programs using one of two
+               methods. Either the BBS package directly loads and executes the
+               program, or it exits to a DOS batch file, which in turn executes
+               the door program. In either case, the BBS package produces a
+               door information file, common called a "drop file", which
+               provides information to the door program such as the name of the
+               current user. OpenDoors automatically supports the common drop
+               file formats, including DORINFOx.DEF and DOOR.SYS.
+
+
+
+RUNNING WINDOWS 95/NT DOOR PROGRAMS
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 26
+
+-------------------------------------------------------------------------------
+
+               This section provides information specific to running door
+               programs that are compiled with the Win32 version of OpenDoors.
+               Please feel free to include this information in your program's
+               manual.
+
+               Since the Win32 version of OpenDoors resides in a DLL,
+               ODOORS60.DLL, this file must be present on any system where your
+               program will be run. Although Windows 95/NT will find this file
+               if it is located in the same directory as your program's
+               executable file, it is a good idea to install this DLL into the
+               Windows system directory. This way, all programs using the Win32
+               version of OpenDoors can share the same copy of the DLL,
+               reducing the amount of disk space that is used.
+
+               The required setup for a Windows 95/NT door will depend upon
+               what BBS system it is being run under. If you the program is
+               being run under a native Windows 95/NT BBS system, then ideally
+               that BBS system will provide the ability to pass a live serial
+               port handle to the door program, on the program's command line.
+               Otherwise, you should run the door from a batch file, following
+               the instructions provided below for running the program under a
+               DOS-based BBS system. If the BBS system is able to pass a live
+               Window communications handle on the door's command line, and you
+               are using the OpenDoors standard command-line processing
+               function (od_parse_cmd_line()), then you can just setup the BBS
+               to run the program directly, using the command line:
+
+                    YourProgramName.exe -handle xxxxxxxxxx
+
+               where xxxxxxxxx is the serial port handle, in decimal format.
+               You do not need to use the start command, nor the DTRON utility,
+               and you do not have to change the COM<n>AutoAssign setting in
+               the system.ini file.
+
+               If you are running the Win32 door program under a DOS-based BBS
+               system, or a Windows-based BBS system that is unable to pass a
+               live serial port handle to the door program, then follow these
+               steps:
+
+               1.Add a line of the form "COM<n>AutoAssign=<x>" to the [386Enh]
+                 section of your system.ini file. Here, <n> specifies the
+                 serial port number that the BBS's modem is connected to, and
+                 <x> will usually be 0. For example, if your modem is
+                 connected to COM1, you would add a line such as
+                 "COM1AutoAssign=0" (sans quotes). You will then have to re-
+                 start your computer for this change to take effect. If you do
+                 not do this, the Windows-based door program will not be able
+                 to access the modem.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 27
+
+               2.Setup the BBS software to run the Windows-based door program
+                 just as you would any other door program. You will probably
+                 want to do this from a batch file. The command line that runs
+                 the Windows program should be of the form:
+
+                    start /w /m YourProgramName.exe [any command line
+                 parameters]
+
+                 This will cause the Windows-based door program to start in
+                 minimized mode, and cause the calling MS-DOS session to wait
+                 until the Windows program exits before continuing. If you do
+                 not wish the program to be started in minimized mode, remove
+                 the /m from the command line. If you attempt to start the
+                 door program by calling it directly, rather than using the
+                 "start /w" command, the BBS software will immediately start
+                 again, cause it to attempt to run simultaneously with the
+                 door program.
+
+               3.After running the start command, use DTRON.EXE or a similar
+                 utility to re-enable DTR detection by the modem. Normally,
+                 this command line will be of the form:
+
+                    dtron /port x /bps y
+
+                 Where x is the serial port number (0 for COM1, 1 for COM2,
+                 etc.) and y is the locked bps rate. For example, if your
+                 serial port is locked at 38400 bps and is connected to COM2,
+                 you would use:
+
+                    dtron /port 1 /bps 38400
+
+                 For full information on the DTRON utility, simply type the
+                 command line:
+
+                    dtron /help
+
+                 You may freely redistribute the DTRON utility that is
+                 included in this package with your program.
+
+               Additional information on the Win32 version of OpenDoors, and
+               further explanation of some of these steps, is provided in
+               chapter 6.
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 28
+
+BASICS OF DOOR PROGRAMMING WITH OPENDOORS
+-------------------------------------------------------------------------------
+
+               This section provides a complete tutorial to the basics of
+               writing BBS door programs using OpenDoors. If you are using
+               OpenDoors to write other online software, much of this
+               information will still be relevant.
+
+               In addition to reading this section, I would encourage you to
+               look at the example programs included int the OpenDoors
+               packages. These programs, which are described beginning on page
+               38, will give you a much better idea of what an OpenDoors
+               program will look like. These programs can also serve as a great
+               starting point for writing your own programs using OpenDoors.
+
+               Probably the best means of introduction to door programming with
+               OpenDoors is by doing it yourself. As such, I strongly encourage
+               you to try compiling and running the simple introduction program
+               below. For instructions on compiling programs written with
+               OpenDoors, see page 22.
+
+               DOS version:
+
+                    #include "opendoor.h"
+
+                    main()
+                    {
+                       od_printf("Welcome to my first door program!\n\r");
+                       od_printf("Press a key to return to BBS!\n\r");
+                       od_get_key(TRUE);
+                       od_exit(0, FALSE);
+                    }
+
+               Win32 version:
+
+                    #include "opendoor.h"
+
+                    int WINAPI WinMain(HINSTANCE hInstance,
+                       HINSTANCE hPrevInstance,LPSTR lpszCmdLine,int nCmdShow)
+                    {
+                       od_printf("Welcome to my first door program!\n\r");
+                       od_printf("Press a key to return to BBS!\n\r");
+                       od_get_key(TRUE);
+                       od_exit(0, FALSE);
+                    }
+
+               Keep in mind that even this simple program will automatically
+               have all of the door capabilities we have already mentioned.
+               Notice the line that reads #include "opendoor.h". All programs
+               written with OpenDoors must include the OPENDOOR.H header file
+               in order to compile correctly. The first two lines in the
+               main/WinMain function simply call the OpenDoors od_printf()
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 29
+
+               function. od_printf() is similar to the printf() function that C
+               programmers will already be familiar with. However, unlike
+               printf(), the od_printf() function sends the output to both the
+               modem and the local screen. Notice that the lines of text
+               displayed by the od_printf() function end with a "\n\r"
+               sequence, instead of the normal "\n". This is because the
+               terminal emulation software that is running on the remote user's
+               system usually requires both a carriage return and a line feed
+               to correctly begin a new line. The next line in our example
+               program is the OpenDoors single-key input function,
+               od_get_key(). The TRUE value causes OpenDoors to wait for a key
+               to be pressed (again, either from remote or local keyboard)
+               before returning. The last line of the main/WinMain function is
+               a call to od_exit(). Any program using OpenDoors should call
+               this function. For the time being, you can always use the (0,
+               FALSE) parameters.
+
+               Once again, you are encouraged to try compiling and running this
+               program, as described above. Congratulations, you have written
+               your first door program! Feel free to make any changes to this
+               program, and see what effects your changes have.
+
+               To simplify this example, separate versions of this program are
+               shown for the DOS and Win32 versions of OpenDoors. However, you
+               would typically write your program so that it could be compiled
+               using either the DOS or Win32 versions of OpenDoors, by
+               beginning the mainline function as follows:
+
+                    #ifdef ODPLAT_WIN32
+                    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE
+                    hPrevInstance,
+                       LPSTR lpszCmdLine, int nCmdShow)
+                    #else
+                    int main(int argc, char *argv[])
+                    #endif
+
+               In case you are not entirely familiar with the operation of door
+               programs, we will now provide an introduction to the internals
+               of a door's operation. Keep in mind that OpenDoors automatically
+               carries out most of these tasks for you. When any door program
+               starts up, one of the first things it must do is to read the
+               door information file(s) (sometimes called a "drop file") passed
+               to it by the BBS. When a user is on-line, and wishes to run a
+               door, they will most likely select a command from a menu. At
+               this point, the BBS system (such as RemoteAccess, Maximus, PC-
+               Board or whatever), will create a file of information about the
+               system, who is currently on-line, and so on. Various BBS
+               packages produce various styles of door information files.
+               OpenDoors automatically recognizes and reads a wide variety of
+               door information file formats. As a result, your doors will be
+               able to run on a almost any BBS system.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 30
+
+               Fortunately, OpenDoors takes care of all the work involved in
+               detecting and reading the door information file, and then
+               initializing and communicating with the serial port for you. In
+               order to carry out these tasks, along with setting up the status
+               line, and so on, OpenDoors provides a function called od_init().
+               If you do not explicitly call this function, the first call to
+               any other OpenDoors function (such as the first time your door
+               program outputs anything) will automatically cause the od_init()
+               function to be called. As a result, upon the first call to an
+               OpenDoors function, all of the initialization tasks for the door
+               will automatically be carried out. However, there may be times
+               when you will want your program to have access information about
+               the user who is on-line, or carry out other actions which
+               require od_init() to have been executed - prior to the point
+               where you call any other OpenDoors functions. In this case, you
+               will have to call od_init() yourself before you do any of these
+               things.
+
+               OpenDoors provides you with a C/C++ structure, by the name of
+               od_control, which allows you to access all the available
+               information about the user who is on-line, the system your door
+               is running on, and also allows you to adjust various OpenDoors
+               parameters. Depending on what BBS system your door is running
+               under, the actual information available from the od_control
+               structure will vary. For more information on the od_control
+               structure, see the section on the control structure, beginning
+               on page 148.
+
+               Once the door has initialized itself, it will then begin
+               communications with the user who is online. OpenDoors takes care
+               of all communications, through its various input and display
+               functions. When the door has finished, it will then write any
+               information that has changed back to the door information file
+               (if applicable), finish communicating with the modem, and return
+               to the BBS. In OpenDoors, these shut-down operations are
+               automatically performed you call the od_exit() function. This
+               function will terminate the door's activity, OPTIONALLY hang up
+               on the user (allowing you to provide either return to BBS or
+               logoff options for exiting), and then exit with the specified
+               errorlevel.
+
+               One other important OpenDoors function that you should be aware
+               of is the od_kernel() function. od_kernel() is the central
+               OpenDoors control function, and is responsible for much of
+               OpenDoor's updating of the status line, monitoring the carrier
+               detect and user timeout status, responding to sysop function
+               keys, and so on. The od_kernel() function is called
+               automatically by OpenDoors, within the other OpenDoors
+               functions. As a result, since most door programs will call some
+               OpenDoors function on a regular basis, you will most often have
+               no need to call the od_kernel() function yourself. However, if
+               your door is going to perform some action, such as updating data
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 31
+
+               files, during which it will not call any OpenDoors function for
+               more than a few seconds, you should then call the od_kernel()
+               function yourself. For more information on the od_kernel()
+               function, see page 97.
+
+               For more information on the functions available from OpenDoors,
+               or the control structure, see the corresponding sections in this
+               manual.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 32
+
+TOUR OF A SAMPLE DOOR PROGRAM: "EX_VOTE"
+------------------------------------------------------------------------------
+
+               One of the best ways to see how OpenDoors works, and the
+               potential that it has, is to look at the example programs
+               included in the OpenDoors package. A brief description of each
+               of these programs can be found on page 38. This section takes a
+               closer look at one of the example programs, EX_VOTE.C. Unlike
+               our simple example in the previous section, EX_VOTE.C is a much
+               more complicated program, taking advantage of many of the
+               advanced features of OpenDoors. Even if you do not understand
+               everything that EX_VOTE.C does, you should be able to make use
+               of various elements demonstrated here, in your own programs.
+
+               The OpenDoors package includes a two compiled versions of
+               EX_VOTE. VOTEDOS.EXE is a plain-DOS program which can run under
+               DOS, Windows or OS/2. VOTEWIN.EXE was compiled using the Win32
+               version of OpenDoors, and so it runs only on Windows 95/NT. The
+               OpenDoors package also contains a sample door information file,
+               DORINFO1.DEF. You can use this file to test any doors in local
+               mode. If you wish to manually create your own DORINFO1.DEF file,
+               you can do so very easily. The DORINFO1.DEF door information
+               file is a simple text file which lists a different piece of
+               information on each line, in the following format:
+
+               +----------------------------------------------------------+
+               | LINE NUMBER | DESCRIPTION            | EXAMPLE           |
+               +-------------+------------------------+-------------------|
+               |     1       | Name of the BBS        | MY OWN BBS        |
+               |     2       | Sysop's first name     | BRIAN             |
+               |     3       | Sysop's last name      | PIRIE             |
+               |     4       | Com Port modem is on   | COM0              |
+               |     5       | Baud rate, etc.        | 0 BAUD,N,8,1      |
+               |     6       | Unused                 | 0                 |
+               |     7       | User's first name      | JOHN              |
+               |     8       | User's last name       | PUBLIC            |
+               |     9       | Caller's location      | OTTAWA, ON        |
+               |     10      | ANSI mode (0=off, 1=on)| 1                 |
+               |     11      | User's security level  | 32000             |
+               |     12      | User's time left       | 60                |
+               +----------------------------------------------------------+
+
+
+               Feel free to make any changes you wish to EX_VOTE.C, and
+               recompile it. One of the most effective and enjoyable ways to
+               learn OpenDoors is by experimenting. If you are a registered
+               owner of OpenDoors, you may even distribute your own versions of
+               this door. Also, you may find that EX_VOTE.C serves as a good
+               framework for building your own door programs.
+
+               The EX_VOTE.C door behaves similarly to most other door
+               programs, and will have a fair bit in common with any other door
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 33
+
+               you write in OpenDoors. What you see in the output window is
+               identical to what a remote user will be seeing. If the user has
+               ANSI, AVATAR or RIP mode turned on, you will see the same colors
+               as they do, and if they have screen clearing turned on, your
+               screen will be cleared when theirs is. The status line at the
+               bottom of the window will list the name of the user currently
+               on-line (if you are using the sample DORINFO1.DEF file, the
+               user's name will be "The Sysop"), the user's location, and the
+               user's baud rate (0 if the door is operating in local mode). The
+               local display also shows how much time the user has left,
+               whether the user has paged the system operator for a chat, and
+               other information.
+
+               There are a number of special commands that are only available
+               to the system operator on the local keyboard. These commands
+               allow the system operator to hang up on the user, adjust the
+               amount of time the user may remain online, enter chat mode with
+               the user, enter a DOS shell (in the DOS version), and so on. In
+               the DOS version, help on these commands is available on the
+               status line by pressing the [F9] key. In the Windows version,
+               these commands are listed on the menu that appears at the top of
+               the window.
+
+               Now, let us take a closer look at the actual source code for the
+               EX_VOTE.C door. If you have not already printed out a copy of
+               this manual, and possibly the EX_VOTE.C file as well, it would
+               probably be a good idea to do so now.
+
+               Notice that near the top of the program, along with all the
+               standard header files, the OPENDOOR.H file is included. This
+               file must be included in all programs written under OpenDoors.
+               If you are placing the OPENDOOR.H file in the same directory as
+               the door you are compiling, simply include the line:
+
+                                    #include "opendoor.h"
+
+               in your program.
+
+               The main()/WinMain() function of the EX_VOTE.C program has a
+               for(;;) loop that repeatedly displays the main menu, obtains a
+               choice from the user and responds to the command, until the user
+               chooses to exit the program. Before the main menu is displayed,
+               the screen is cleared by calling od_clr_scr(). The od_clr_scr()
+               function will clear both the local and remote screens, but only
+               if the user has screen clearing enabled. Refer to page 57 for
+               information on how to force the screen to be cleared, regardless
+               of the user's screen clearing setting. The main menu is
+               displayed using the od_printf() function, one of the most common
+               OpenDoors functions you will use. Next, od_get_answer() is used
+               to obtain a menu choice from the user from the specified set of
+               keys. Next, a switch() statement is used to respond to the
+               user's command appropriately. If the user presses the P key to
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 34
+
+               page the system operator, od_page() is called. If the user
+               chooses to return to the BBS, od_exit() is called to terminate
+               OpenDoor's activities and return control to the BBS. The FALSE
+               parameter passed to od_exit() indicates that OpenDoors should
+               not disconnect (hangup) before exiting. If the user chooses to
+               log off, EX_VOTE.C first confirms this action with the user, and
+               then calls od_exit() with the TRUE parameter. The numerical
+               parameter passed to od_exit() sets the errorlevel that OpenDoors
+               will exit with.
+
+               In its ChooseQuestion() function, EX_VOTE.C uses the OpenDoors
+               function od_get_key(). This function is similar to the
+               od_get_answer() function that we have already seen. However,
+               unlike od_get_answer() which will wait until the user presses
+               some key from the list of possibilities you provide,
+               od_get_key() will allow the user to press any key. od_get_key()
+               accepts a single parameter. If this parameter is TRUE,
+               od_get_key() will wait for the user to press a key before
+               returning. If this parameter is FALSE, od_get_key() will return
+               immediately with a value of 0 if there are no keys waiting in
+               the inbound buffer, and returning the next key if there are
+               characters waiting.
+
+               In a number of places, EX_VOTE.C also uses the od_input_str()
+               function. Unlike od_get_key() and od_get_answer() which return a
+               single character, od_input_str() allows the user to input and
+               edit a string of many characters. You will only receive the
+               string entered by the user after they press the enter key.
+               od_input_str() accepts four parameters: the string where the
+               user's input should be stored, the maximum number of characters
+               to input, the minimum character value to accept and the maximum
+               character value to accept.
+
+               Another new feature of OpenDoors that is used by EX_VOTE.C is
+               the OpenDoors control structure, od_control. This global
+               structure is documented in chapter 5 of this manual. The
+               OpenDoors control structure allows you to access a wide variety
+               of information about the user who is currently online, the BBS
+               system your program is running on, and also allows you to
+               control various OpenDoors settings. For example, EX_VOTE.C
+               compares the current user name (od_control.od_user_name) with
+               the name of the system operator (od_control.od_sysop_name) to
+               determine whether it is the system operator who using the
+               program.
+
+               EX_VOTE.C uses two data files, the first of which contains a
+               record for every user, and the second of which contains a record
+               for every question. EX_VOTE.C accesses these data files in a
+               controlled manner in order to permit the program to be running
+               simultaneously on multiple lines on a multi-node BBS system.
+               When EX_VOTE.C needs to update a data file, it opens it for
+               exclusive access, so that only one node can access the file at
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 35
+
+               any given time. Since the data file could have been changed by
+               another node since the time that EX_VOTE.C last read the file,
+               it always reads a record, makes changes to it and then re-writes
+               the record while it has the file open for exclusive access. It
+               then closes the file as soon as possible after opening the file,
+               in order to permit other nodes to once again access the file.
+               Because EX_VOTE.C keeps track of which questions each user has
+               voted on, along with the questions and results of voting on each
+               question, its data file format is more complex than many door
+               programs (although not as complex as others).
+
+               EX_VOTE.C also uses color. One of the easiest ways to use
+               different colors in an OpenDoors program is to use the
+               OpenDoor's print color-setting extensions. You can change the
+               color of text display at any point in an od_printf() format
+               string using by enclosing the name of new display color in back
+               quote characters (`, not '). For example:
+
+                    od_printf("`red`This is in red `green`This is green\n\r");
+
+               Would cause the words "This is in red" to be displayed in red,
+               and the words "This is in green" to be displayed in green.
+
+               EX_VOTE.C also takes advantage of a number of OpenDoors
+               capabilities that you can optionally choose to include in your
+               door programs. You will notice that there are a number of new
+               lines at the beginning of the main() function, all of which
+               change settings in the OpenDoors control structure. The line:
+
+                    od_control.od_config_file = INCLUDE_CONFIG_FILE;
+
+               causes the OpenDoors configuration file system to be included in
+               your program. Using this system, OpenDoors automatically reads a
+               configuration file that can be used by the system operator to
+               change various program settings. Refer to the included door.cfg
+               file for an example OpenDoors configuration file. In addition to
+               the configuration file settings automatically supported by the
+               configuration file system, you can also add your own
+               configuration file settings. To do this, you simply supply
+               OpenDoors with a callback function that it will call whenever it
+               encounters an unrecognized keyword in the configuration file.
+               The line:
+
+                    od_control.od_config_function = CustomConfigFunction;
+
+               Causes OpenDoors to call the function CustomConfigFunction() in
+               EX_VOTE.C for this purpose. You will notice that the
+               CustomConfigFunction() receives two parameters - the first is
+               the unrecognized keyword, and the second is any parameters that
+               follow the keyword in the configuration file. EX_VOTE.C checks
+               for two special configuration file lines - one to set whether or
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 36
+
+               not users can add questions, and one to set whether or not users
+               can view the results of a question before voting on it.
+
+               The next line in the main() function,
+
+                    od_control.od_mps = INCLUDE_MPS;
+
+               causes the OpenDoors "Multiple Personality System" to be
+               included in program. This allows the sysop to choose from a
+               number of status line / sysop function key "personalities" that
+               mimic a number of different BBS systems, using the Personality
+               setting in the configuration file.
+
+
+               The line:
+
+                    od_control.od_logfile = INCLUDE_LOGFILE;
+
+               causes the OpenDoors log file system to be included in the
+               program. The OpenDoors log file system automatically records the
+               date and time of program startup, exit and other major actions
+               in the specified file. EX_VOTE.C also writes its own log file
+               entries by calling the od_log_write() function.
+
+               EX_VOTE.C also provides the ability for the sysop to provide
+               their own ASCII/ANSI/AVATAR/RIP files to be displayed in place
+               of the normal main menu. EX_VOTE.C uses the od_hotkey_menu()
+               function to display a VOTE.ASC/.ANS/.AVT/.RIP file for the main
+               menu, if such a file exists. If the file is not available, the
+               normal EX_VOTE.C menu is used instead. The od_hotkey_menu()
+               function will automatically select the appropriate file
+               (.ASC/.ANS/.AVT/.RIP) for the current display mode, and the user
+               is able to make a menu choice at any time. If a menu choice is
+               made before the menu is entirely displayed, the function will
+               stop displaying the menu and return immediately.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 37
+
+OTHER EXAMPLE PROGRAMS INCLUDED WITH OPENDOORS
+------------------------------------------------------------------------------
+
+               In addition to the EX_VOTE.C program, which is discussed in
+               detail in the previous section, a number of other example
+               programs are included with OpenDoors. These programs help to
+               demonstrate what is possible with OpenDoors. They can also serve
+               as excellent tools to help you learn OpenDoors. In addition, you
+               are free to include any portions of any of these example
+               programs in your own programs. Below is a summary of each of
+               these example programs:
+
+
+-------------------------------------------------------------------------------
+EX_HELLO.C     This an example of a very simple door program that displays a
+               short message and prompts for the user to press a key. After the
+               user presses a key, the door exits and control is returned to
+               the main BBS software. Despite the fact that it only consists of
+               a few lines of code, EX_HELLO remains a fully functional door
+               program. For information on compiling an OpenDoors door program,
+               see the section that begins on page 22.
+
+
+-------------------------------------------------------------------------------
+EX_CHAT.C      This program is an example of a multi-window full-screen chat
+               door written with OpenDoors. EX_CHAT demonstrates the ease of
+               using sophisticated ANSI / AVATAR / RIP terminal features within
+               OpenDoors programs. For instructions on how to compile this
+               program, see the section that begins on page 22.
+
+               This program create two windows on the screen, separated by a
+               bar with user name / sysop name information. This program
+               permits communication between the local sysop and remote user by
+               displaying the text typed by the user in one window, and the
+               text typed by the sysop in the other window. When either
+               person's typing reaches the bottom of the window, the contents
+               of the window is scrolled up to provide more room for typing.
+               Words are also wrapped when either typist reaches the end of a
+               line. The advantage of a split-screen chat program is that it
+               permits both sysop and user to type at the same time without
+               difficulty. The chat function automatically invokes OpenDoor's
+               internal chat mode if ANSI, AVATAR or RIP modes are not
+               available. The display colors, window sizes and locations, and
+               distance to scroll a window's contents are configurable by
+               setting the appropriate variables, below. When the Sysop invokes
+               a DOS shell, a pop-up window is displayed to indicate to the
+               user that the door program has been suspended.
+
+               The chat feature of this program can also be easily integrated
+               into other doors you write, and may be used to replace the
+               existing OpenDoors line-oriented chat system.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 38
+
+
+-------------------------------------------------------------------------------
+EX_MUSIC.C     This example door demonstrates how to play "ANSI" music and
+               sound effects in an OpenDoors door. Included in this program is
+               a function to send "ANSI" music to the remote system, and a
+               function to text the remote system's ability to play "ANSI"
+               music. You may use both of these functions in your own doors, if
+               you wish to add music or sound effect capabilities. This program
+               can be compiled by following the instructions that begin on page
+               22.
+
+
+-------------------------------------------------------------------------------
+EX_SKI.C       This is a simple but addictive online game that is written using
+               OpenDoors. In this action game, the player must control a skier
+               through a downhill slalom course. The user may turn the skier
+               left or right, and the game ends as soon as the player skis
+               outside the marked course. The game begins at an easy level, but
+               quickly becomes more and more difficult as the course to be
+               navigated becomes more and more narrow. The game maintains a
+               list of players with high scores, and this list may be viewed
+               from the main menu.
+
+
+-------------------------------------------------------------------------------
+EX_VOTE.C      The EX_VOTE.C file contain the source code for the Vote example
+               door, as is described beginning on page 38. The Vote example
+               door allows users to vote on up to 200 different "polls", view
+               the results of voting on each question, and optionally add their
+               own questions for other users to answer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 39
+
+    444
+   4444
+  44 44
+ 44444444
+     44
+     44
+     44
+-------------------------------------------------------------------------------
+CHAPTER 4 - THE OPENDOORS API FUNCTIONS
+
+
+
+
+OVERVIEW
+------------------------------------------------------------------------------
+
+               OpenDoors provides a wide set of features that you can take
+               advantage of in your program. You control these features and
+               access OpenDoors from your program using two facilities - the
+               OpenDoors API functions, and the OpenDoors control structure. In
+               general, the API functions are used to actually accomplish a
+               task, such as displaying something to the user, or retrieving
+               input from the user. The OpenDoors control structure, on the
+               other hand, is used to alter OpenDoors settings or retrieve
+               specific information.
+
+               Any program written with OpenDoors makes use of the OpenDoors
+               API functions for all of its door-related input and output. In
+               addition to the common input and output tasks, the OpenDoors API
+               functions provide access to many special capabilities, such as
+               displaying ASCII/ANSI/AVATAR/RIP files, providing pop-up windows
+               and menus, and much more. Much of the information about the user
+               who is online, information about the system your door is running
+               on, and settings which customize OpenDoor's behavior are
+               controlled through the OpenDoors control structure. The control
+               structure is described in the section beginning on page 148.
+
+               This chapter is divided into the following sections:
+
+                    i.)   TABLE OF MOST COMMONLY USED FUNCTIONS (Page 41)
+                   ii.)  TABLE OF ALL OPENDOORS FUNCTIONS (Page 42)
+                  iii.) DETAILED INFORMATION ON EACH FUNCTION (Pages 47 - 147)
+
+               The two tables list the names of the OpenDoors functions, along
+               with a brief description of the task performed by each function,
+               and the page number on which the detailed description of that
+               function can be found. The first table lists only the most
+               commonly used OpenDoors functions, to allow you to quickly find
+               the function you are most likely looking for. The second table
+               lists all of the OpenDoors functions, grouped according to
+               general categories of functionality.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 40
+
+               The section containing detailed information lists all of the
+               functions in alphabetical order, with the information about each
+               function beginning on a new page. This section includes a brief
+               description of each function's purpose, a detailed description
+               of how to use the function, the function call format, a list of
+               related functions, and in many cases example source code showing
+               you a typical use of the function.
+
+
+
+
+TABLE OF MOST COMMONLY USED FUNCTIONS
+------------------------------------------------------------------------------
+
+               od_printf()         Displays text, with the ability to change
+                                   display color. (page 110)
+
+               od_clr_scr()        Clears the screen. (Page 57)
+
+               od_input_str()      Inputs a string of one or more characters
+                                   from the user. (Page 95)
+
+               od_get_answer()     Inputs a single key from a list of possible
+                                   choices ignoring upper/lower case. (Page 81)
+
+               od_get_key()        Inputs any single key from the user.
+                                   (Page 82)
+
+               od_set_cursor()     Positions the cursor in ANSI/AVATAR/RIP
+                                   modes. (Page 134)
+
+               od_hotkey_menu()    Displays an ASCII/ANSI/AVATAR/RIP file, with
+                                   the option of watching for a keypress from
+                                   the user. (Page 90)
+
+               od_popup_menu()     Displays a popup menu in ANSI/AVATAR/RIP
+                                   modes. (Page 105)
+
+               od_window_create()  Creates a popup window in ANSI/AVATAR/RIP
+                                   modes. (Page 145)
+
+               od_window_remove()  Removes a popup window in, restoring screen
+                                   contents "underneath" window. (Page 147)
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 41
+
+TABLE OF ALL FUNCTIONS
+-------------------------------------------------------------------------------
+OUTPUT         TEXT DISPLAY FUNCTIONS
+FUNCTIONS      ----------------------
+               od_disp_str()            Displays a normal, NULL-terminated
+                                        string. (page 63)
+
+               od_disp()                Sends the specified number of
+                                        characters to the modem, with or
+                                        without local echo. (page 60)
+
+               od_printf()              Performs formatted output, as the
+                                        printf() function does. Also allows
+                                        imbedded codes to change display color.
+                                        (page 110)
+
+               od_putch()               Displays a single character. (page 115)
+
+               od_disp_emu()            Displays a string, interpreting
+                                        imbedded ANSI/AVATAR terminal emulation
+                                        codes. (page 62)
+
+               od_repeat()              Displays the same character any number
+                                        of times, using AVATAR optimization, if
+                                        possible. (page 118)
+
+               COLOR AND CURSOR CONTROL
+               ------------------------
+               od_set_color()           Sets current color to specified
+                                        foreground and background settings.
+                                        (page 131)
+
+               od_set_attrib()          Sets current color to specified IBM-PC
+                                        display attribute. (page 128)
+
+               od_set_cursor()          Sets the position of the cursor, if
+                                        ANSI/AVATAR/RIP mode is enabled. (page
+                                        134)
+
+               SCREEN MANIPULATION
+               -------------------
+               od_clr_scr()             Clears the screen, if user has screen
+                                        clearing enabled. (page 57)
+
+               od_save_screen()         Stores the current contents of the
+                                        screen, to be later redisplayed using
+                                        od_restore_screen(). Works in all
+                                        display modes. (page 121)
+
+               od_restore_screen()      Restores the contents of the screen, as
+                                        previously stored using
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 42
+
+                                        od_save_screen(). Works in all display
+                                        modes. (page 120)
+
+               BLOCK MANIPULATION
+               ------------------
+               od_clr_line()            Clears the remainder of current line.
+                                        (page 55)
+
+               od_gettext()             Stores any area of the screen, to later
+                                        be displayed by od_puttext(). Requires
+                                        ANSI/AVATAR/RIP graphics mode. (page
+                                        89)
+
+               od_puttext()             Displays text with color information,
+                                        as previously stored using
+                                        od_gettext(). Requires ANSI/AVATAR/RIP
+                                        graphics mode. (page 116)
+
+               od_scroll()              Scrolls a portion of the screen in
+                                        ANSI/AVATAR/RIP graphics modes. (page
+                                        123)
+
+               POPUP WINDOWS AND MENUS
+               -----------------------
+               od_draw_box()            Draws a box on the screen in
+                                        ANSI/AVATAR/RIP graphics mode. (page
+                                        65)
+
+               od_window_create()       Displays a popup window, storing the
+                                        screen contents "under" the window.
+                                        Requires ANSI/AVATAR/RIP graphics mode.
+                                        (page 145)
+
+               od_window_remove()       Removes a popup window displayed with
+                                        od_window_create(), restoring the
+                                        original screen contents "under" the
+                                        window. Requires ANSI/AVATAR/RIP
+                                        graphics mode. (page 147)
+
+               od_popup_menu()          Displays a menu in a popup window,
+                                        allowing the user to choose menu items
+                                        either by pressing a "hot" key, or
+                                        moving a highlighted selection bar.
+                                        After menu selection, the menu may be
+                                        removed, restoring the original screen
+                                        contents "under" the window. Requires
+                                        ANSI/AVATAR/RIP graphics mode. (page
+                                        105)
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 43
+
+               FILE DISPLAY FUNCTIONS
+               ----------------------
+               od_send_file()           Displays an ASCII/ANSI/AVATAR/RIP file
+                                        (for instance, an .ANS file created by
+                                        a program such as "TheDraw" (page 124)
+
+               od_hotkey_menu()         Displays an ASCII/ANSI/AVATAR/RIP menu
+                                        file, with hotkeys active. (page 90)
+
+               od_list_files()          Lists the files available for download
+                                        in an area, using a FILES.BBS file.
+                                        (page 98)
+
+
+-------------------------------------------------------------------------------
+INPUT          od_get_answer()          Inputs a single key from the keyboard,
+FUNCTIONS                               allowing only particular responses.
+                                        (page 81)
+
+               od_get_input()           A more flexible version of
+                                        od_get_key(), that also supports
+                                        extended keys such as arrow keys,
+                                        insert, etc. (page 82)
+
+               od_get_key()             Inputs a single key from the keyboard,
+                                        optionally waiting if a key is not
+                                        available. (page 82)
+
+               od_input_str()           Inputs a string of specified length,
+                                        from the keyboard. (page 95)
+
+               od_edit_str()            Formatted string editing function,
+                                        requiring ANSI/AVATAR/RIP graphics.
+                                        (page 68)
+
+               od_multiline_edit()      Provides a text editor that allows the
+                                        user to enter or edit text that spans
+                                        multiple lines, such as email messages
+                                        or text files. (page 101)
+
+               od_clear_keybuffer()     Removes any waiting keys from the
+                                        keyboard input queue. (page 53)
+
+
+-------------------------------------------------------------------------------
+COMMON         od_page()                Allows the user to page the sysop.
+DOOR                                    (page 101)
+ACTIVITY
+FUNCTIONS      od_spawn()                    OpenDoors "quick" spawn function.
+                                        Executes an external program (eg. file
+                                        compressor, external protocol, etc.) on
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 44
+
+                                        a separate screen, restoring the
+                                        OpenDoors screen afterwards. (page 139)
+
+               od_spawnvpe()            OpenDoors full-featured spawn function.
+                                        Executes an external program on a
+                                        separate screen, searching the path for
+                                        the program, allowing you to specify an
+                                        environment to pass to the child
+                                        process, and returning the errorlevel
+                                        returned by the child process. (page
+                                        143)
+
+               od_log_write()           Adds an entry to the end of the log
+                                        file. (page 100)
+
+               od_parse_cmd_line()      Handle standard command line options.
+                                        (page 105)
+
+
+-------------------------------------------------------------------------------
+SPECIAL        od_init()                Begins door operation by setting up
+CONTROL                                 the OpenDoors control structure,
+FUNCTIONS                               setting up the local screen,
+                                        initializing the serial port (if
+                                        applicable), and reading the door
+                                        information file. (page 92)
+
+               od_color_config()        Transfers a color configuration line to
+                                        a color attribute value. (page 59)
+
+               od_add_personality()     Adds a custom status line/control key
+                                        personality to OpenDoors. (page 47)
+
+               od_set_statusline()      Temporarily alters the setting of the
+                                        current OpenDoors status line. (page
+                                        137)
+
+               od_autodetect()          Automatically determines the remote
+                                        terminal software's graphical
+                                        capabilities. (page 48)
+
+               od_kernel()              The central OpenDoors control function,
+                                        which should be executed every few
+                                        seconds. (page 97)
+
+               od_exit()                Ends door operations, closing the
+                                        serial port driver, re-writing the door
+                                        information file, and optionally
+                                        returning control to the BBS. (page 79)
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 45
+
+               od_carrier()             Allows detection of carrier signal in
+                                        programs that have disabled OpenDoors
+                                        internal checking. (page 51)
+
+               od_set_dtr()             Controls the DTR signal to the modem.
+                                        Can be used to manually disconnect a
+                                        remote user, in order to perform
+                                        activities such as call back
+                                        verification. (page 135)
+
+               od_chat()                Forces OpenDoors to enter chat mode,
+                                        even if sysop did not press the "chat"
+                                        key. (page 50)
+
+               od_sleep()               Suspends program execution, yielding
+                                        control to other tasks in a
+                                        multitasking environment. (page 139)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 46
+
+OD_ADD_PERSONALITY()
+-------------------------------------------------------------------------------
+
+PURPOSE        Installs a custom status line / sysop function key personality
+               into OpenDoors.
+
+
+FORMAT         BOOL od_add_personality(char *pszName, BYTE btOutputTop,
+                   BYTE btOutputBottom, OD_PERSONALITY_PROC *pfPerFunc);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    If used, this function should be called before any other
+               OpenDoors API functions. This function installs a new
+               personality into OpenDoors. The first parameter specifies the
+               string that will be used to identify the personality. This is
+               the string that the user will be able to supply in the
+               configuration file to select this personality, and is also the
+               string that can be passed to od_set_personality() to manually
+               switch to this personality. The second and third parameters
+               specify the 1-based to and bottom line numbers of the output
+               window to be used with this personality. For instance, a top
+               value of 1 and bottom value of 23 would cause all door output to
+               be displayed on the first 23 lines of the screen, leaving the
+               bottom two lines for use by the personality's status line. The
+               last parameter is a pointer to the personality function, which
+               OpenDoors will call to perform various operations with that
+               involve the personality. For more information on personalities
+               and the OpenDoors Multiple Personality System, see the section
+               which begins on page 233.
+
+               This function only has an effect under the DOS version of
+               OpenDoors.
+
+SEE ALSO       od_set_personality(), od_set_statusline()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 47
+
+OD_AUTODETECT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Attempts to automatically determine the terminal capabilities of
+               the remote system.
+
+
+FORMAT         void od_autodetect(int nFlags);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function can be used to determine whether or not the remote
+               terminal supports ANSI and/or RIP (Remote Imaging Protocol)
+               graphics modes. This information is usually supplied to the door
+               by the BBS software, through the door information file. For this
+               reason, most door programs do not need to make used of this
+               function. However, if your door will be running under any BBS
+               software that does not report the ANSI or RIP capabilities of
+               the remote system, you may wish to use this function.
+               od_autodetect() will set either of the following OpenDoors
+               control structure variables to TRUE if the corresponding
+               graphics mode is detected:
+
+                    od_control.user_ansi     - TRUE if ANSI mode is available
+                    od_control.user_rip      - TRUE if RIP mode is available
+
+               However, if either of these variables have previously been set
+               to TRUE (either explicitly by your program, or due to the
+               corresponding modes being enabled in the door information file),
+               and od_autodetect() does not detect the corresponding graphics
+               mode, they will not be set to FALSE. Not all terminal software
+               that supports ANSI or RIP graphics mode will necessarily have
+               the ability to report their graphics mode capabilities to the
+               door. For this reason, failure to detect either of these modes
+               does not necessarily indicate that they are not available.
+               However, if these modes are detected by od_autodetect(), it is
+               safe to assume that the remote system does support the detected
+               mode.
+
+               The nFlags parameter is reserved for future use, and should
+               always be set to DETECT_NORMAL.
+
+               This function cannot auto-detect AVATAR mode, because there is
+               no standard means of determining whether a remote system
+               supports AVATAR mode.
+
+
+EXAMPLE        Below is an example of using od_autodetect() in determining the
+               remote terminal's graphics capabilities. Since not all terminal
+               software supports auto-detection, this example will also prompt
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 48
+
+               the user to determine their software's capabilities if
+               od_autodetect() fails to detect ANSI mode. This code assumes
+               that if the terminal software supports the autodetection of ANSI
+               mode, that it will also support the autodetection of RIP mode.
+               OpenDoors assumes that ANSI mode is always available in
+               conjunction with RIP mode.
+
+                    /* Call the automatic terminal detection function */
+                    od_autodetect();
+
+                    /* If ANSI mode was not detected, ask the user about
+                    if(!od_control.user_ansi)
+                    {
+                       /* Prompt the user for ANSI capabilities */
+                       od_clr_scr();
+                       od_printf("Does your system support ANSI graphics?");
+                       od_printf(" (Y/N)");
+
+                       /* If the user chooses [Y]es */
+                       if(od_get_answer("YN") == 'Y')
+                       {
+                          /* Turn on ANSI mode */
+                          od_control.user_ansi = TRUE;
+
+                          /* Since ANSI mode is present, RIP mode may also */
+                          /* be available. Prompt the user for RIP. */
+                          od_printf("\r\n\n");
+                          od_printf("Does your system support RIP graphics?");
+                          od_printf(" (Y/N)");
+
+                          /* If the user chooses [Y]es */
+                          if(od_get_answer("YN") == 'Y')
+                             /* Turn on RIP mode */
+                             od_control.user_rip = TRUE;
+
+                          /* Since ANSI mode is present, AVATAR mode may  */
+                          /* also be available. Prompt the user for AVATAR. */
+                          od_printf("\r\n\n");
+                          od_printf("Does your system support AVATAR ");
+                          od_printf("graphics? (Y/N)");
+
+                          /* If the user chooses [Y]es */
+                          if(od_get_answer("YN") == 'Y')
+                             /* Turn on AVATAR mode */
+                             od_control.user_avatar = TRUE;
+                       }
+
+                       od_printf("\r\n\n");
+                    }
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 49
+
+OD_CHAT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Manually invokes sysop chat mode.
+
+
+FORMAT         void od_chat(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    Normally, the OpenDoors sysop chat mode will only be invoked
+               when the sysop explicitly requests it using the sysop chat key.
+               However, there may be some cases where you wish to manually
+               invoke the sysop chat mode. One example is when you are
+               replacing the OpenDoors built-in chat mode with your own, but
+               still wish to use the OpenDoors chat mode under some
+               circumstances. For instance, you may wish to use your own split-
+               screen chat routine if ANSI, AVATAR or RIP graphics mode is
+               available, and use the OpenDoors line-oriented chat mode if only
+               ASCII mode is available.
+
+
+SEE ALSO       od_page()
+
+
+EXAMPLE        For an example of using the od_chat() function, see the
+               ex_chat.c example door, which is described on page 38.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 50
+
+OD_CARRIER()
+-------------------------------------------------------------------------------
+
+PURPOSE        To determine the status of the carrier detect signal, in
+               programs where OpenDoors' internal carrier detection has been
+               disabled.
+
+
+FORMAT         BOOL od_carrier(void);
+
+
+RETURNS        TRUE if a carrier is present, or
+               FALSE if no carrier is present, or in local mode.
+
+
+DESCRIPTION    Usually, you will not have any use for the od_carrier()
+               function, as OpenDoors automatically monitor's the carrier
+               detect signal, and will correctly recover if the carrier detect
+               signal is lost while the door is operating in remote mode.
+               However, in some programs, you may wish to disable OpenDoors'
+               internal carrier detection routines, using the
+               od_control.od_disable variable. Two such cases in which you
+               might want to do this, are a call-back verification door, which
+               disconnects the user and attempts to call them back, or in a
+               terminal program, which is in fact not a door at all (and as
+               such you would not want to have OpenDoors exit when the carrier
+               detect signal is lost). In cases like these, you will then be
+               able to use the od_carrier() function in order to determine the
+               state of the carrier detect signal.
+
+               This function will return a Boolean value (for more information
+               on Boolean values, see the Glossary which begins on page 256),
+               of either TRUE or FALSE. If a carrier detect signal is present
+               when the function is called, it will return TRUE, and if no
+               carrier detect signal is detected, it will return FALSE. Since
+               there is no remote connection, and thus no carrier when
+               OpenDoors is operating in local mode, this function will always
+               return a value of FALSE in local mode.
+
+
+SEE ALSO       od_set_dtr()
+
+
+EXAMPLE        As an example of the use of this function, let us consider a
+               call back verification door, which hangs up on the user, and
+               then calls the user back at their entered phone number, in order
+               to verify the correctness of that number. This program would
+               probably contain a function that is responsible for
+               disconnecting the user, waiting for the connection to be broken,
+               and then phoning the user. At some point in this function,
+               likely just prior to the point where the function hangs up on
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 51
+
+               the user, you would disable OpenDoors' internal carrier
+               detection, using the line:
+
+                    od_control.od_disable |= DIS_CARRIERDETECT;
+
+               You would then want to have a piece of code which would simply
+               wait up to a given amount of time for the carrier signal to
+               drop. If this occurs, you would continue to place the call, and
+               if it does not occur, you would probably try your hangup
+               procedure one or two more times. In this example, the function
+               will return with a value of FALSE if the carrier signal does not
+               drop, and will return a value of TRUE if it does.
+
+                    char hangup(void)
+                    {
+                       clock_t timer;
+                       char to_return = FALSE;
+
+                       od_set_dtr(FALSE);                    /* Hangup modem */
+
+                                                        /* Wait up to 30secs */
+                       timer = clock() + CLOCKS_PER_SEC * 30;
+                       while(timer >= clock())
+                       {    /* If carrier has been lost, return with success */
+                          if(!od_carrier())
+                          {
+                             to_return = TRUE;
+                             break;
+                          }
+                       }
+
+                       od_set_dtr(TRUE);             /* Re-enable DTR signal */
+                       return(to_return);
+                    }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 52
+
+OD_CLEAR_KEYBUFFER()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to clear the input keyboard buffer
+
+
+FORMAT         void od_clear_keybuffer(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    OpenDoors maintains its own keyboard input buffer, in order to
+               permit the user to "type ahead" - to send input to the door
+               prior to the time when it is ready to process those key presses.
+               For example, the user could begin to type a command while a menu
+               is still being displayed, and when your door reaches the point
+               of inputting the menu command, the characters already typed by
+               the user will already be waiting for the OpenDoors input
+               functions. Note that the keyboard input buffer will include both
+               the keys hit by the user on-line, and the non-function keys (ie,
+               Alt-C will not appear in the OpenDoors keyboard buffer), hit by
+               the sysop. This allows both the user on-line and the sysop to
+               control the door at any time. If the sysop wishes to temporarily
+               prevent the user from having any control over the door, the
+               sysop may use the Alt-K (user-keyboard off) key. The key strokes
+               placed in the OpenDoors type-ahead buffer will be retrieved by
+               the od_get_key() and od_input_str() functions. The keyboard
+               buffer can contain a maximum of 64 user keystrokes in this
+               version of OpenDoors, after which any additional keystrokes will
+               simply be discarded by OpenDoors.
+
+               There are times, however, when you will want to erase any keys
+               that have been hit by the user, to prevent them from typing
+               ahead. For example, if your door has been busy doing some
+               processing for a few moments, they user may have been pressing
+               keys on their keyboard - perhaps in the hope that doing so will
+               speed things up. These keys will be waiting in the type-ahead
+               buffer, and if one of the keys the user entered was a valid
+               response to the next prompt in your door, the user may find that
+               they have accidentally made a choice they did not wish to. A
+               well designed door will simply erase the contents of the type-
+               ahead buffer after any long period of internal processing, etc.
+               Keep in mind that too much use of the od_clear_keybuffer()
+               function can be just as undesirable as not using it all, as
+               there are times when the presence of the keyboard buffer can
+               prove to be very useful for the user of a door.
+
+               To erase the contents of the type-ahead buffer, you simply call
+               the od_clear_keybuffer() function. This function takes no
+               parameters, and does not return any value.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 53
+
+
+
+SEE ALSO       od_get_key(), od_input_str(), od_edit_str()
+
+
+EXAMPLE        For one example of the use of the od_clear_keybuffer() function,
+               see the example program EX_VOTE.C, which is described beginning
+               on page 38. Below is another example of using this function. In
+               this case, we present a simple function, wait_for_return(),
+               which simply pauses for the user to press their [Enter]/[Return]
+               key. The function begins by displaying a prompt asking for the
+               [Enter] or [Return] key to be pressed. The function then clears
+               the keyboard input buffer, and waits until the user presses the
+               carriage return key, using the od_get_key() function. Note also
+               that this function will only continue if the user has pressed
+               the correct key. This is a good idea in all door programs, as it
+               allows your door to distinguish between a character pressed by
+               the user, and a "line noise" character.
+
+                    void wait_for_return(void)
+                    {                                      /* Display prompt */
+                       od_disp_str("Please Press [Enter] to continue...\n\r");
+                       od_clear_keybuffer();        /* Clear keyboard buffer */
+                       while(od_get_key(TRUE) != 13);  /* Wait for Enter key */
+                    }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 54
+
+OD_CLR_LINE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Clears the rest of the current display line
+
+
+FORMAT         void od_clr_line(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function clears the line that the cursor is on, from the
+               cursor position to the end of the line. After the rest of the
+               line is cleared, the cursor is automatically returned to the
+               position it was at prior to issuing the command. Hence, if the
+               display line the cursor was located on looked as follows, with
+               the underscore (_) character representing the cursor position:
+
+                        This is a_line of text!
+
+               With the cursor between the words "a" and "line", after the
+               od_clr_line command is issued, the line would appear as follows:
+
+                        This is a_
+
+               With the cursor directly following the word "a". Note that this
+               function places a space character at the cursor location, and
+               every location up to the end of the line.
+
+               When the door is running in plain ASCII mode, this command will
+               simply clear the rest of the line by manually sending a series
+               of space and backspace characters. When ANSI, AVATAR or RIP
+               modes are active, the corresponding ANSI/AVATAR control sequence
+               will be sent in order to accomplish the line clear. Since the
+               graphics mode sequences are much shorter than the sequence that
+               would be required to clear the line manually, the use of this
+               function will cause your door's graphics to display much more
+               quickly when ANSI, AVATAR or RIP modes are active. Also note
+               that in ANSI, AVATAR or RIP graphics modes, the line will be
+               cleared with the currently selected color attribute. Thus, if
+               you wanted to place a blue background on a particular line, you
+               would use the od_set_color() (or od_set_attrib()) function, then
+               use the od_set_cursor() function to locate the cursor at the
+               beginning of the desired line, followed by the od_clr_line()
+               function. Just such a procedure is demonstrated in the example,
+               below.
+
+
+SEE ALSO       od_clr_scr(), od_set_cursor()
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 55
+
+EXAMPLE        Below, is an example of a function that clears an entire line
+               with a specified color. Since this function performs operations
+               that require ANSI, AVATAR or RIP graphics mode, it should only
+               be used in a case where these modes are known to be available.
+               For example, this function would be useful in a full-screen
+               editor or viewer, or when performing ANSI animations. The
+               function accepts three parameters: the line to be cleared (where
+               1 is the first line, 2 the second, and so on), the foreground
+               color of this line, and the background color of this line.
+
+               This function differs from the od_clr_line() function itself in
+               several important manners. First of all, this function clears
+               the entire line, whereas the od_clr_line() function can be used
+               to clear only the remaining characters of the line, after any
+               particular location. Also, as mentioned before, this function
+               selects a color to clear the line to, and moves the cursor to
+               the line which is to be cleared - neither of which is done by
+               the od_clr_line() function.
+
+
+               void clear_line(char line_number,char foreground,char
+               background)
+               {
+                  od_set_cursor(line_number,1);      /* move to correct line */
+                  od_set_color(foreground,background);          /* set color */
+                  od_clr_line();                        /* clear entire line */
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 56
+
+OD_CLR_SCR()
+------------------------------------------------------------------------------
+
+PURPOSE        The OpenDoors clear screen function
+
+
+FORMAT         void od_clr_scr(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    The od_clr_scr() function can be used to clear the output
+               screen. (ie, the user's screen and local screen with the
+               exception of the status line are cleared.) This function will
+               only clear the screen if screen clearing is enabled. If your
+               program will be running under BBS systems that do not pass the
+               user's screen clearing setting to the door, you may wish to
+               determine yourself whether or not the user's system supports
+               screen clearing codes, during the first time the user uses the
+               door. You will then be able to store this setting in a data
+               file. The example below demonstrates how to detect whether or
+               not the user's system supports screen clearing.
+
+               You should note that the ability for the user's terminal to
+               support screen clearing codes is independent of the user's ANSI
+               / AVATAR / RIP graphics mode settings.
+
+               For more information on the user's screen clearing setting,
+               please refer to the user_attrib variable in the OpenDoors
+               Control Structure chapter of this manual. If you wish to force a
+               screen clear, regardless of the user's screen clearing setting,
+               simply use the function call:
+
+                         od_disp_emu("\xc", TRUE);
+
+
+SEE ALSO       od_clr_line()
+
+
+EXAMPLE        Below is an example of a function which determines whether or
+               not the user's system supports screen clearing. This function
+               will return a value of TRUE if screen clearing is supported, and
+               will return a value of FALSE if screen clearing is not
+               supported:
+
+               int user_supports_screen_clearing(void)
+               {
+                  char answer;
+                                             /* display instructions to user */
+                  od_disp_str("In order for this door to function\n\r");
+                  od_disp_str("correctly, we must know whether or not\n\r");
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 57
+
+                  od_disp_str("your system supports screen clearing.\n\r");
+                  od_disp_str("In a moment, we will attempt to clear\n\r");
+                  od_disp_str(
+                     "your screen in order to test your system's\n\r");
+                  od_disp_str("capabilities.\n\r\n\r");
+
+                  od_disp_str("Please press [Enter]/[Return] when you\n\r");
+                  od_disp_str("are ready to perform this test.\n\r");
+                  while(od_get_key(TRUE)!=13);      /* wait for [Return] key */
+
+                  od_clr_scr();                   /* attempt to clear screen */
+                                         /* ask user if their screen cleared */
+                  od_disp_str("Did your screen just clear? (Y/N)\n\r");
+                  for(;;)           /* loop until user chooses [Y]es or [N]o */
+                  {
+                     answer=od_get_key(TRUE);           /* Get user's answer */
+                     if(answer=='y' || answer=='Y') return(TRUE);
+                     if(answer=='n' || answer=='N') return(FALSE);
+                  }
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 58
+
+OD_COLOR_CONFIG()
+-------------------------------------------------------------------------------
+
+PURPOSE        Parses a color configuration line from the configuration file,
+               generating a color attribute value.
+
+
+FORMAT         BYTE od_color_config(char *pszColorDesc);
+
+
+RETURNS        Color attribute value
+
+
+DESCRIPTION    This function will be of use if you are using the configuration
+               file system of OpenDoors, and wish to allow the sysop to specify
+               text colors to be used in your door. While OpenDoors
+               automatically recognizes color configuration settings for things
+               such as sysop chat mode and FILES.BBS listings, you may wish to
+               add additional color configuration options. In this case, you
+               could call the od_color_config() function from your custom line
+               function. For more information on the custom line function, see
+               the section on the OpenDoors configuration file system, which
+               begins on page 224.
+
+               To use this function, simply pass the configuration file line
+               you wish to have parsed to the function in it's single
+               parameter. The function will then return a color attribute value
+               in the same format that is used but the od_set_attrib()
+               function. Colors are specified using a string of the format:
+
+                    {Flashing} {Bright} [foreground] on [background]
+
+               Where "Flashing" is an optional keyword indicating that the text
+               should be flashing. "Bright" is an optional keyword indicating
+               that the foreground color should be bright. Foreground is the
+               name of a foreground color, and background is the name of a
+               background color. Case (upper or lower) is not significant.
+
+               The color keywords are language configurable, using the array
+               od_control.od_color_names.
+
+
+EXAMPLE        See the example accompanying in the section on the OpenDoors
+               configuration file system, which begins on page 224.
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 59
+
+OD_DISP()
+------------------------------------------------------------------------------
+
+PURPOSE        Sends a buffer of text with optional local echo
+
+
+FORMAT         void od_disp(char *pachBuffer, INT nSize, BOOL bLocalEcho);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function allows you to send a buffer of text of any
+               specified length, with the option of enabling or disabling local
+               echo. You will probably have little use for this function -
+               instead you will most likely display strings using either the
+               od_disp_str() or od_printf() functions, depending on whether or
+               not you wish to use printf()'s formatting options. For a
+               breakdown of the uses of the various OpenDoors display
+               functions, see the description of the od_disp_str() function, on
+               page 63.
+
+               There are two cases when this function will come in useful:
+
+                    1.)If you wish to display a buffer of characters of known
+                       length, which may contain null (ASCII 0) characters.
+                       Since this character is used by the C language to
+                       indicate the end of a string, the other two string
+                       display functions (od_disp_str() and od_printf()) will
+                       not send this character to the remote system.
+
+                    2.)If you wish to send text to the remote system without
+                       having it displayed on the local screen, or if you wish
+                       to send strings to the modem when it is in command
+                       mode, without having these characters displayed on the
+                       local screen.
+
+               The od_disp() function is called with three parameters. The
+               first parameter, pachBuffer, is a pointer to a buffer of
+               characters you wish to have displayed. The second parameter,
+               nSize, is simply the number of characters in the buffer to be
+               displayed. If the third parameter, bLocalEcho, is set to TRUE,
+               then all characters sent to the modem will also be displayed on
+               the local screen. If the third parameter is set to FALSE, then
+               the buffer will be sent to the modem without being echoed to the
+               sysop's screen.
+
+
+SEE ALSO       od_disp_str(), od_printf(), od_putch(), od_repeat(),
+               od_disp_emu()
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 60
+
+EXAMPLES       The following are a few examples of the use of the od_disp()
+               function:
+
+               In order to display a single character, contained in the
+               variable "character", without echo to the local screen:
+
+                    od_disp(&character,1,FALSE);
+
+
+               In order to send a command to the modem (only if you know that
+               the modem is in command mode), with the command contained in the
+               null-terminated string "string":
+
+                    od_disp(string,strlen(string),FALSE);
+
+
+               In order to send exactly 5 characters from the buffer "buffer",
+               WITH echo to the local screen:
+
+                    od_disp(buffer,5,TRUE);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 61
+
+OD_DISP_EMU()
+-------------------------------------------------------------------------------
+
+PURPOSE        Displays a string with ANSI/AVATAR terminal emulation
+
+
+FORMAT         void od_disp_emu(char *pszToDisplay, BOOL bRemoteEcho);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    The od_disp_emu() function allows you to display your own ANSI /
+               AVATAR graphics sequences. This function passes the characters
+               you wish to display to the OpenDoors terminal emulator, which is
+               fully documented in the description of the od_send_file()
+               function, on page 124. This function can be used to send these
+               control sequences to the user's terminal, and also have them
+               displayed on the local screen as they will appear to the user.
+
+               The string passed to od_disp_emu() contains any stream of text
+               to display, and may include both normal text and terminal
+               emulation control sequences. If the bRemoteEcho parameter is set
+               to TRUE, the string passed to od_disp_emu() will be sent to the
+               remote terminal in addition to being displayed locally. If this
+               parameter is set to FALSE, the string will only be displayed
+               locally.
+
+               Note that if you wish to display an entire file containing
+               ANSI/AVATAR/RIP graphics sequences (perhaps as your program's
+               menu or title screen), you can use the od_send_file() function.
+
+
+SEE ALSO       od_send_file(), od_disp(), od_disp_str() od_printf().
+
+               For a breakdown of the uses of the various OpenDoors display
+               functions, see the od_disp_str() function, on page 63.
+
+
+EXAMPLE        For an example of the use of the od_disp_emu() function, see the
+               SpaceRight() and MoveLeft() functions included in the example
+               program ex_ski.c.
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 62
+
+OD_DISP_STR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Displays a string to the screen (remote and local)
+
+
+FORMAT         od_disp_str(char *pszToDisplay);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    The two functions most often used for displaying strings within
+               a door are the od_disp_str() and od_printf() functions. The
+               od_printf() function allows for formatted output, whereas the
+               od_disp_str function simply displays the actual contents of the
+               string passed to it. If you wish to display a single character,
+               use the od_putch() function. If you wish to send a string or
+               buffer to the modem without local echo, use the od_disp()
+               function. If you wish to send a sequence of the same character
+               to the modem, the od_repeat() function will use graphics control
+               codes, if available to display the sequence much faster than
+               simply sending the same character in repetition. Also, if you
+               wish to send ANSI, AVATAR or RIP graphics control codes, and
+               have them emulated on the local screen, use the od_disp_emu()
+               function.
+
+               The od_disp_str() function displays the contents of the null-
+               terminated string pointed to by *string. Display is sent to both
+               the local screen and modem (presuming the door is not running in
+               local mode).
+
+               An important thing to keep in mind when using the od_disp_str()
+               function, is that you should use "/n/r" instead of simply "/n"
+               for a new line. This is due to the fact that terminal programs
+               usually require a carriage-return line-feed sequence (/n/r),
+               instead of just a line-feed (/n). For example, instead of using:
+
+                              od_disp_str("Hello world!\n");
+
+               You should use:
+
+                              od_disp_str("Hello world!\n\r");
+
+               To change the cursor color or location of output with the
+               od_disp_str() function, refer to the od_set_cursor() and the
+               od_set_attrib() functions.
+
+
+SEE ALSO       od_disp(), od_printf(), od_putch(), od_repeat(), od_disp_emu()
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 63
+
+EXAMPLES       Below are a few examples of various uses of the od_disp_str()
+               function:
+
+               Displaying three string constants on separate lines:
+
+                         od_disp_str("This is an example\n\r");
+                         od_disp_str("of the OpenDoors\n\r");
+                         od_disp_str("od_disp_str() function\n\r");
+
+
+               Displaying three string constants on the same line:
+
+                         od_disp_str("Another ");
+                         od_disp_str("od_disp_str() ");
+                         od_disp_str("example\n\r");
+
+
+               Displaying a string variable:
+
+                         char string[80];
+
+                         strcpy(string,"This is a string!\n\r");
+                         od_disp_str(string);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 64
+
+OD_DRAW_BOX()
+-------------------------------------------------------------------------------
+
+PURPOSE        Draws a box on the screen in ANSI, AVATAR or RIP graphics modes.
+
+
+FORMAT         BOOL od_draw_box(BYTE btLeft, BYTE btTop, BYTE btRight, BYTE
+                  btBottom);
+
+
+RETURNS        TRUE on success, FALSE on failure
+
+
+DESCRIPTION    This function is for use in ANSI, AVATAR or RIP graphics modes.
+               This function will draw a box in the current display attribute,
+               at the specified location on the screen. The boarder of the box
+               is made up of the characters specified in the od_control.
+               od_box_chars[] array. If AVATAR graphics mode is available, this
+               function uses AVATAR control codes to display the box in less
+               than 1/10 the length of time required to display the box in ANSI
+               mode.
+
+               The first two parameters of this function, btLeft and btTop,
+               specify the coordinates of the top, left-hand corner of the box
+               to be draw. The third and fourth parameters, btRight and
+               btBottom, specify the coordinates of the bottom, left-hand
+               corner of the box. Like the values passed to the od_set_cursor()
+               function, these coordinates are relative to the upper left-hand
+               corner of the screen, with the position (1,1) being this corner.
+
+               As mentioned above, this function will display the window in the
+               current text color. Thus, before calling this function, you
+               should use either the od_set_color() or the od_set_attrib()
+               function to specify the color in which you would like to have
+               the window displayed.
+
+               Normally, the boarder of the window will be displayed using the
+               IBM extended ASCII characters which produce a single line
+               boarder. However, you may wish to have the boarder displayed
+               using different characters. In this case, the characters used to
+               display the boarder can be specified by the od_control.
+               od_box_chars variable, described in the OpenDoors control
+               structure section of this manual.
+
+SEE ALSO       od_set_color(), od_set_attrib(), od_clr_scr(), od_edit_str(),
+               od_set_cursor()
+
+
+EXAMPLE        As an example of the use of the od_draw_box() function in
+               conjunction with the od_edit_str() function, we show a portion
+               of a program which displays a window, and allows the user to
+               input the name of a file they would like to upload, a
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 65
+
+               description of the file, and whether they want it to be a
+               private upload. The user is able to move among fields using the
+               tab key, and select a "continue" button when they are finished.
+               The function returns TRUE if the user selects continue, and
+               FALSE if the user presses [ESCape].
+
+                                                  // Main "dialog box" function
+               int get_information(char *filename, char *description,
+                                    char *private)
+               {
+                  char current_field=1;             // Currently selected field
+                  int choice;                                  // User's choice
+
+                  od_set_color(L_WHITE,D_BLUE);               // Display window
+                  od_draw_box(10,5,70,13);
+
+                  od_set_cursor(5,25);                  // Display window title
+                  od_set_color(L_GREEN,D_BLUE);
+                  od_disp_str(" ENTER FILENAME INFORMATION ");
+
+                  od_set_color(L_CYAN,D_BLUE);     // Display fields and titles
+                  od_set_cursor(6,15);
+                  od_disp_str("FILENAME : ");
+                  od_repeat(176,13);
+                  od_set_cursor(7,12);
+                  od_disp_str("DESCRIPTION : ");
+                  od_repeat(176,43);
+                  od_set_cursor(8,16);
+                  od_disp_str("PRIVATE : ");
+                  od_repeat(176,2);
+                  draw_button();
+
+                  filename[0]='\0';    // Blank out contents of input variables
+                  description[0]='\0';
+                  private[0]='\0';
+
+                  for(;;)                               // Main dialog box loop
+                  {
+                     if(current_field==4)             // If field is the button
+                     {
+                        od_set_color(L_GREEN,D_BLUE);       // Highlight button
+                        draw_button();
+
+                        do  // Loop until user presses [TAB], [ENTER], or [ESC]
+                        {
+                           choice=od_get_key(TRUE);
+                        } while(choice!=9 && choice!=13 && choice!=27);
+
+                        od_set_color(L_CYAN,D_BLUE);     // Un-highlight button
+                        draw_button();
+
+                        if(choice==13) return(TRUE);  // If [ENTER] was pressed
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 66
+
+                        if(choice==27) return(FALSE);   // If [ESC] was pressed
+                        current_field=1;        // Otherwise, [TAB] was pressed
+                     }
+
+                    switch(current_field)        // According to selected field
+                     {                       // Input from the appropriate line
+                        case 1:
+                           choice=od_edit_str(filename,"FFFFFFFFFFFF",6,26,
+                                              0x1b,0x1a,176,
+                                              EDIT_FLAG_EDIT_STRING|
+                                              EDIT_FLAG_ALLOW_CANCEL|
+                                              EDIT_FLAG_FIELD_MODE|
+                                              EDIT_FLAG_KEEP_BLANK);
+                           break;
+                        case 2:
+                           choice=od_edit_str(description,
+                                              "*******************",
+                                              7,26,0x1b,0x1a,176,
+                                              EDIT_FLAG_EDIT_STRING|
+                                              EDIT_FLAG_ALLOW_CANCEL|
+                                              EDIT_FLAG_FIELD_MODE|
+                                              EDIT_FLAG_KEEP_BLANK);
+
+                           break;
+                        case 3:
+                           choice=od_edit_str(private,"Y",8,26,
+                                              0x1b,0x1a,176,
+                                              EDIT_FLAG_EDIT_STRING|
+                                              EDIT_FLAG_ALLOW_CANCEL|
+                                              EDIT_FLAG_FIELD_MODE);
+                     }
+                                                    // If user pressed [ESCape]
+                     if(choice==EDIT_RETURN_CANCEL) return(FALSE);
+                                      // If user choice to go to previous field
+                     if(choice==EDIT_RETURN_PREVIOUS)
+                     {
+                        if(current_field==1)               // If at first field
+                           current_field=4;                 // Go to last field
+                        else                           // If not at first field
+                           --current_field;             // Go to previous field
+                     }
+                     else                           // If user chose next field
+                        ++current_field;                    // Go to next field
+                  }
+               }
+
+               void draw_button(void)         // Function to display the button
+               {
+                  od_draw_box(12,10,23,12);              // Draw box for button
+                  od_set_cursor(11,14);
+                  od_disp_str("Continue");            // Display text in button
+               }
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 67
+
+OD_EDIT_STR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Allows you to perform formatted input with full line editing
+               features, etc., in ANSI/AVATAR/RIP graphics mode.
+
+
+FORMAT         WORD od_edit_str(char *pszInput, char *pszFormat, INT nRow,
+                  INT nColumn, BYTE btNormalColor, BYTE btHighlightColor,
+                  char chBlank, WORD nFlags);
+
+
+RETURNS        This function will return one of the following values:
+
+               EDIT_RETURN_ERROR        Indicates that an error has occurred,
+                                        and the edit function was unable to
+                                        run. This will occur if there is an
+                                        error in one of the parameters, or if
+                                        ANSI/AVATAR/RIP graphics is not
+                                        available
+
+               EDIT_RETURN_CANCEL       Indicates that the user pressed the
+                                        cancel key [ESC], and that the string
+                                        was left unaltered.
+
+               EDIT_RETURN_ACCEPT       Indicates that the user pressed the
+                                        accept key [Enter], or that the auto-
+                                        enter feature was activated.
+
+               EDIT_RETURN_PREVIOUS     Indicates that the user wishes to move
+                                        to the previous field, by pressing [UP
+                                        ARROW], [SHIFT]-[TAB], etc.
+
+               EDIT_RETURN_NEXT         Indicates that the user wishes to move
+                                        to the next field, by pressing [DOWN
+                                        ARROW], [TAB], etc.
+
+
+DESCRIPTION    To perform string input within OpenDoors, one of two functions
+               can be used, od_input_str() and od_edit_str(). The first
+               function, od_input_str(), allows simple line input and editing,
+               and can be used in ASCII, ANSI, AVATAR and RIP modes. The second
+               function, od_edit_str(), allows many formatted input options,
+               advanced line editing, and other features, but requires the use
+               of ANSI, AVATAR or RIP terminal modes.
+
+               As mentioned above, the od_edit_str() function allows for
+               advanced line editing, such as inputting and deleting text from
+               the middle of the string (whereas the od_input_str() function
+               only allows editing from the end of the string, such as
+               backspacing to erase a mistake). The edit functions available
+               from the od_edit_str() are listed below. Note that some of these
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 68
+
+               functions may or may not be available, depending upon the
+               capabilities of the user's terminal program. While there is no
+               single standard used for the transmission of special edit keys
+               such as the arrow keys, the od_edit_str() function makes as much
+               effort as possible to make all of the edit features available to
+               most terminal programs. Many of the edit functions can be
+               accesses using either [CONTROL]-key combinations or special keys
+               such as the arrow keys, delete key, and so on. OpenDoors will
+               recognize most of these special control keys when sent as either
+               an ANSI control sequence (which is sent by most terminal
+               programs), or as a DoorWay style scan code / ASCII code sequence
+               (which is also available from many terminal programs, but is not
+               usually required). The od_edit_str() edit functions are as
+               follows. Note that all edit functions are always available from
+               the local keyboard.
+
+               HOME - Moves the cursor to the beginning of the line being
+                         edited. Press the [HOME] key, either in DoorWay mode
+                         or from the local keyboard.
+
+               END - Moves the cursor to the end of the line being edited.
+                         Press the [END] key, either in DoorWay mode or from
+                         the local keyboard.
+
+               DELETE CHARACTER - Deletes the character under the cursor. Press
+                         [DELete] on the local keyboard, in DoorWay mode, and
+                         under many terminal programs without DoorWay mode.
+                         Alternatively, press [CONTROL]-[G].
+
+               BACKSPACE - Deletes the character left of the cursor. Press
+                         [BACKSPACE] or [CONTROL]-[H].
+
+               TOGGLE INSERT MODE - Switches the od_edit_str() function between
+                         insert mode and overwrite mode. Press [INSert], either
+                         in DoorWay mode, or from the local keyboard.
+                         Alternatively, press [CONTROL]-[V].
+
+               CURSOR LEFT - Moves the cursor left one character. Press [LEFT
+                         ARROW] on the local keyboard, in DoorWay mode, and
+                         under many terminal programs without DoorWay mode.
+                         Alternatively, press [CONTROL]-[S].
+
+               CURSOR RIGHT - Moves the cursor right one character. Press
+                         [RIGHT ARROW] on the local keyboard, in DoorWay mode,
+                         and under many terminal programs without DoorWay mode.
+                         Alternatively, press [CONTROL]-[D].
+
+               ERASE ENTIRE LINE - Press [CONTROL]-[Y].
+
+               ACCEPT INPUT - Press the [ENTER] / [RETURN] line to accept the
+                         input. Alternatively, press [CONTROL]-[Z]. Note that
+                         this key will only work when the current input is
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 69
+
+                         "valid" (ie, it conforms to the format string, which
+                         is described below)
+
+               CANCEL INPUT - Only available if specifically enabled on the
+                         od_edit_str() command line. Press [ESCape].
+
+               NEXT FIELD - If enabled, allows the user to move to the next
+                         field in a dialog box / form. Press [DOWN ARROW] in
+                         DoorWay mode and under many terminal programs without
+                         DoorWay mode. Alternatively, press [TAB]. Note that
+                         the [DOWN ARROW] key is NOT usually available from the
+                         local keyboard, as it is usually used to adjust the
+                         user's remaining time.
+
+               PREVIOUS FIELD - If enabled, allows the user to move to the
+                         previous field in a dialog box / form. Press [UP
+                         ARROW] in DoorWay mode and under many terminal
+                         programs without DoorWay mode. Alternatively, press
+                         [SHIFT]-[TAB] on the local keyboard or in DoorWay
+                         mode. Again, note that the [UP ARROW] key is NOT
+                         usually available from the local keyboard, as it is
+                         usually used to adjust the user's remaining time.
+
+
+               Let us now look at the parameters which the od_edit_str()
+               function accepts. The first parameter, pszInput, is a pointer to
+               the string where the user's input should be stored. It is
+               important that this string be long enough to accommodate the
+               longest input your format string will permit, including the '\0'
+               C string terminator (ie, the string should be one character
+               greater than the length of the format string, not including the
+               format string's ' and " characters).
+
+               The second parameter, pszFormat, is a pointer to a string which
+               specifies the format and maximum length of the input the
+               od_edit_str() function should accept. Using the format string,
+               not only do you specify the length of the input field, but you
+               can also force the user's input into certain formats. For
+               example, if you wished to input a North American style phone
+               number, you could use a format string of "###-###-####". Then
+               regardless of whether the user typed any dash character or not,
+               their input would be converted, as they type, to the format of
+               the phone number 613-599-5554. You could also specify a format
+               string such of "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM", which would
+               permit the user to enter a name of up to 30 characters. Note
+               that since the cursor can be moved to the position immediately
+               following the last character, a the input field for a 30
+               character string will occupy 31 columns on the screen. The
+               od_edit_str() function would then automatically capitalize the
+               name, so that the first character of each word is capitalized,
+               and the remain characters of the word is in lower case. Even if
+               the user were to move the cursor to the middle of the string
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 70
+
+               they had entered, and add or delete a space (and thus either
+               make one work two or two words one), od_edit_str() would re-
+               format the string to reflect the change. The valid characters
+               for the format sting, along with their meanings, are listed
+               below. Note that the format string is NOT case sensitive (except
+               for literal strings delimited by the '' or "" characters), and
+               space characters can be added at any point to increase
+               legibility.
+
+               #    Indicates that numeric characters from '0' to '9' are valid
+                    for this position
+
+               %    Indicates that numeric characters from '0' to '9', and the
+                    space character (' ') are valid for this position.
+
+               9    Indicates that numeric characters from '0' to '9', along
+                    with '.', '-' and '+' are valid for this position. This
+                    format style is intended for floating-point numeric input.
+
+               ?    Indicates that any character is valid for this position.
+
+               *    Indicates that any printable character, from ASCII 32 to
+                    ASCII 127, is valid for this position.
+
+               A    Indicates that alphabetical characters 'A' to 'Z', 'a' to
+                    'z' and space (' ') are valid for this position.
+
+               C    Indicates that city name characters are valid for this
+                    position. As with the 'M' format character, words are
+                    automatically capitalized so that the first letter is in
+                    upper case, and all subsequent letters are in lower case.
+                    In addition to permitting alphabetical characters and the
+                    space (' ') character, the ',' and '.' characters are also
+                    accepted in this position.
+
+               D    Indicates that date characters '0' to '9', '-' and '/' are
+                    valid for this position.
+
+               F    Indicates that MS-DOS filename characters are valid for
+                    this position.
+
+               H    Indicates that hexidecimal character '0' to '9', 'A' to 'F'
+                    and 'a' to 'f' are valid for this position.
+
+               L    Indicates that only lower case alphabetical characters 'a'
+                    to 'z', and the space (' ') character is valid for this
+                    position. However, if the user attempts to enter an upper
+                    case alphabetical character in this position, it will
+                    automatically be converted to the lower case equivalent.
+
+               M    Indicates that name characters are valid for this position.
+                    These characters are the alphabetical characters 'A' to
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 71
+
+                    'Z', 'a' to 'z', and the space character (' '). A
+                    character's case is converted such that the first character
+                    of a word is in upper case, and all other letters are in
+                    lower case.
+
+               T    Indicates that telephone number character '0' to '9', '(',
+                    ')', '-' and ' ' are valid for this position.
+
+               U    Indicates that only upper case alphabetical characters 'A'
+                    to 'Z', and the space (' ') character is valid for this
+                    position. However, if the user attempts to enter a lower
+                    case alphabetical character in this position, it will
+                    automatically be converted to the upper case equivalent.
+
+               W    Indicates that MS-DOS filename characters are permitted in
+                    this position, including the '*' and '?' wildcard
+                    characters.
+
+               X    Indicates that alphanumeric characters 'A' to 'Z', 'a' to
+                    'z', '0' to '9' and ' ' are valid for this position.
+
+               Y    Indicates that yes/no characters 'Y', 'N', 'y', 'n' are
+                    valid for this position. The characters are automatically
+                    converted to upper case.
+
+               '/"  Single or double quotes can be used to specify sequences of
+                    characters that should appear at the same location in the
+                    input string (referred to elsewhere as "literal strings").
+                    When the user is entering the string, these characters are
+                    automatically supplied, and the user is not required to
+                    type them. Literal strings must begin and end with the same
+                    quote character. Remember that the double quote (")
+                    character must be imbedded in C strings by preceding the
+                    quote character with a \ (backslash) character.
+
+               The third and fourth parameters, nRow and nColumn specify the
+               location on the screen where the first (left most) character of
+               the input field should be located. These parameters are
+               identical to the nRow and nColumn parameters passed to the
+               od_set_cursor() function. In other words, nRow specifies the
+               line number on the screen, where 1 is the first line, and
+               nColumn specifies the column across the screen, where 1 is the
+               first column.
+
+               The fifth and sixth parameters, btNormalColor and
+               btHighlightColor, allow you to specify the color of the input
+               field. The fifth parameter, btNormalColor, specifies the color
+               of the input field when input is not taking place and the sixth
+               parameter, btHighlightColor, specifies the color of the field
+               while input is taking place. Thus, if you had several input
+               fields on the screen at one time, you would be able to make is
+               easier for the user to identify the currently active field by
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 72
+
+               having the field currently accepting input highlighted in a
+               color distinct from the other fields. When the od_edit_str()
+               function begins, it will change the current color of the field
+               from the normal color to the highlighted color. Then, when the
+               od_edit_str() function exits, it will change the current color
+               of the field back to its normal color. If you do not wish to
+               have the field highlighted, you can set both of these parameters
+               to the same value, and disable field re-drawing by using the
+               eighth parameter, flags.
+
+               The seventh parameter accepted by the od_edit_str() function,
+               chBlank, will serve one of two purposes. Normally, this
+               parameter will specify a background character to display in the
+               unfilled portion at the end of the input field. This can be set
+               to a character, such as the ASCII 177 grey block character, to
+               produce a visual background to the field. Doing this will show
+               the user visually how long the field is, and how many character
+               they will be permitted to type into the field. Normally, this
+               field will be displayed during input, and removed when the
+               od_edit_str() function exits. However, you may cause the
+               background to remain in place using the eighth parameter, flags.
+               If you do not wish to have this "background" visual field
+               effect, simply set the character parameter to a space (ASCII
+               32). In password input mode, this parameter will instead specify
+               the character to display in place of characters typed by the
+               user. In this case, the background display character defaults to
+               the space (ASCII 32) character.
+
+               The eighth, and last, parameter accepted by the od_edit_str()
+               function is the nFlags parameter. This parameter is a bit-mapped
+               flags variable which allows you to control special features of
+               the od_edit_str() function. More than one of these settings may
+               be specified by listing a chain of the values, separated by the
+               bitwise-or (|) operator. If you do not wish to turn on any of
+               these modes, simply pass the EDIT_FLAG_NORMAL value as the flags
+               parameter.
+
+               EDIT_FLAG_NORMAL - Default setting, use this value of none of
+                         the other flags below are active.
+
+               EDIT_FLAG_NO_REDRAW - When set, prevents the od_edit_str()
+                         function from re-drawing the input string and field
+                         when it starts up and exits. If you set this flag, the
+                         normal color and highlight color should contain the
+                         same value. If background character (the character
+                         parameter) is not a space (ASCII 32) character, you
+                         must draw the field background prior to calling
+                         od_edit_str(). Also, if you are calling od_edit_str()
+                         with the EDIT_FLAG_EDIT_STRING flag set, you must
+                         display the existing string in the field prior to
+                         calling od_edit_str().
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 73
+
+               EDIT_FLAG_FIELD_MODE - Setting this flag specifies that
+                         od_edit_str() should operate in field input mode. In
+                         field input mode, the user may finish entering their
+                         input by pressing the previous field or next field
+                         button (arrow keys, tab keys, etc.), as described
+                         above. If the user chooses to finish and accept their
+                         input by pressing one of these keys, the od_edit_str()
+                         return value will reflect which choice they made. This
+                         will allow you to make it possible for the user to
+                         move between a number of input fields in a form /
+                         dialog box, as demonstrated in the example
+                         accompanying the od_draw_box() function.
+
+               EDIT_FLAG_EDIT_STRING - Setting this flag specifies that
+                         od_edit_str() should edit a pre-existing string,
+                         instead of starting with a blank string. In this case,
+                         the input_string parameter MUST point to an
+                         initialized string. This string may either contain
+                         some text, or be empty, but od_edit_str() will expect
+                         to find a string terminator ('\0') character, and will
+                         begin editing the contents of the string prior to that
+                         character. If you do not set the EDIT_FLAG_EDIT_STRING
+                         flag, the previous contents of the input_string
+                         parameter is not significant, as od_edit_str() will
+                         automatically start with a blank string.
+
+               EDIT_FLAG_STRICT_INPUT - Setting this flag causes the
+                         od_edit_str() function to operate in "strict" input
+                         mode, which may be desirable if your input format
+                         contains more than one type of input. Normally, if you
+                         were inputting such a string, the user would be able
+                         to move to the middle of the string, and insert any
+                         text. Doing so would cause the rest of the input line
+                         to shift right. However, in cases where your format
+                         string specifies different types of character to be
+                         permitted in different positions, this can cause the
+                         input to be changed so that it no longer conforms to
+                         the format string. In this case, the user's input will
+                         no longer be valid, and the user will not be able to
+                         exit the function by pressing [ENTER] (although
+                         [ESCAPE] will still be available, if you activated it)
+                         until they change their input. However, when strict
+                         input mode is turned on, od_edit_str() will restrict
+                         the ways in which the user is permitted to edit the
+                         string, to prevent just such a case from occurring.
+
+               EDIT_FLAG_PASSWORD_MODE - Setting this flag causes the
+                         od_edit_str() function to operate in "password" mode.
+                         In password mode, the characters typed by the user
+                         will be hidden, displayed instead as the blank
+                         character specified in the "character" parameter.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 74
+
+               EDIT_FLAG_ALLOW_CANCEL - When this flag is set, the user will be
+                         able to cancel their current input and abort the
+                         editing process by pressing their [ESCAPE] key. When
+                         they do so, any changes they have made to the input
+                         field will be canceled, and replaced by the original
+                         contents of the string. The od_edit_str() function
+                         will then exit, indicating that the user has canceled
+                         their input.
+
+               EDIT_FLAG_FILL_STRING - When set, this flag will force the user
+                         to enter a string that fills the entire length of the
+                         format string. Normally, the user will be able to
+                         enter a string of any length up to the maximum length
+                         specified by the format string. However in some cases,
+                         such as when inputting a date, you will want to have
+                         the input field filled. (Otherwise, the user would be
+                         able to enter only the first part of the date.)
+
+               EDIT_FLAG_AUTO_ENTER - When set, this flag will cause the
+                         od_edit_str() function to automatically simulate
+                         pressing of the [ENTER] key when the string is filled.
+                         This can be used to cause the od_edit_str() function
+                         to finish inputting as soon as a valid string is
+                         entered, instead of having to wait for the user to
+                         press [ENTER] / [RETURN].
+
+               EDIT_FLAG_AUTO_DELETE - When set, along with the
+                         EDIT_FLAG_EDIT_STRING flag, this flag will activate
+                         the auto-delete feature of the od_edit_str() function.
+                         When auto-delete is active, if the first key pressed
+                         by the user is not an edit control key, the existing
+                         text will automatically be deleted, and a totally new
+                         string accepted from the user. This could be useful
+                         when you are allowing the user to go back to edit a
+                         previous input. If the user wishes to only change part
+                         of the old string, they can move the cursor to the
+                         location where they wish to make the change, and
+                         perform their editing. However, if the user wishes to
+                         completely replace the old string with a new one, they
+                         can simply begin to type, and the old string will
+                         automatically be deleted, and the new string accepted.
+
+               EDIT_FLAG_KEEP_BLANK - Normally, OpenDoors will only display the
+                         input field background (as passed in the "character"
+                         parameter) while the user is editing the string, and
+                         will remove it when the od_edit_str() function exits.
+                         However, you may wish to continue having this field
+                         displayed after input has taken place, and the
+                         od_edit_str() function has exited. In this case,
+                         setting this flag will cause the background characters
+                         to remain visible after input has finished.
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 75
+
+               EDIT_FLAG_PERMALITERAL - When the format string contains literal
+                         characters (such as forcing a ':' character to be
+                         added to a time input by using the format string
+                         "##':'##':'##"), the od_edit_str() function can
+                         operate in one of two modes. In the default mode, the
+                         literal characters will only be displayed when they
+                         have been automatically added to the string. For
+                         instance, if you were inputting the current time using
+                         the above format string, this mode would result in the
+                         input field initially being blank. When the user types
+                         the first digit of the time, that number would appear.
+                         When the user types the second digit of the time, that
+                         number will appear, and then the colon character will
+                         automatically be added by OpenDoors. However, you can
+                         also set the od_edit_str() function to operate in
+                         "PermaLiteral" mode, by setting this flag. When the
+                         EDIT_FLAG_PERMALITERAL flag is set, the input field
+                         will initially contain the literal characters (ie, the
+                         colons in our example), with the cursor still located
+                         at the leftmost position in the input field. In this
+                         mode, the literal character become a permanent part of
+                         the input field, and can not be moved or deleted by
+                         the user - instead the cursor simply skips over the
+                         literal character's position.
+
+               EDIT_FLAG_LEAVE_BLANK - This flag applies to the special case
+                         where the first character or characters of the format
+                         string are literals. By default, the od_edit_str()
+                         function will always return a string containing at
+                         least these first literal characters. However, you can
+                         alter this behaviors by setting this flag. When set,
+                         if no non-literal characters have been entered in the
+                         string, od_edit_str() will return an empty string.
+
+               EDIT_FLAG_SHOW_SIZE - Normally, od_edit() adds an extra blank to
+                         the end of the input field, to give the cursor a space
+                         to move into when the field is full. However, you may
+                         prefer to have the input field be shown as exactly the
+                         maximum size of input that is permitted. Setting
+                         EDIT_FLAG_SHOW_SIZE does just this. In this case, the
+                         cursor will be positioned immediately past the end of
+                         the input field when the maximum number of characters
+                         have been entered.
+
+
+SEE ALSO       od_input_str(), od_get_char(), od_clear_keybuffer()
+
+
+EXAMPLE        Below are several examples of typical uses of the od_edit_str()
+               function. For the sake of simplicity, all of these examples
+               perform their input beginning at the top, left hand corner of
+               the screen, and store the user's input in the string variable
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 76
+
+               named "string". For an example of the user of the od_edit_str()
+               function in a dialog-box / form entry application, see the
+               example accompanying the od_draw_box() function.
+
+               To input a name with a maximum of 25 characters, having the
+               first letter of each word automatically capitalized:
+
+                        od_edit_str(string, "MMMMMMMMMMMMMMMMMMMMMMMMM", 1, 1,
+                                    0x03, 0x21, 176, EDIT_FLAG_NORMAL);
+
+               To input a North American style phone number, requiring that all
+               digits be filled, and running in "strict input" mode:
+
+                        od_edit_str(string, "###'-'###'-'####",
+                                    1, 1, 0x03, 0x21, 176,
+                                    EDIT_FLAG_FILL_STRING|
+                                    EDIT_FLAG_STRICT_INPUT);
+
+               To allow the user to edit a previously entered 20 character
+               string, with auto-delete mode on. Any characters will be
+               permitted in the string. Remember that when the
+               EDIT_FLAG_EDIT_STRING flag is set, the string must be
+               initialized prior to calling the od_edit_str() function.
+
+                        od_edit_str(string, "????????????????????",
+                                    1, 1, 0x03, 0x21, 176,
+                                    EDIT_FLAG_EDIT_STRING|
+                                    EDIT_FLAG_AUTO_DELETE);
+
+
+               To input a password of up to 16 characters from the user. Here,
+               the password will only be permitted to contain upper case
+               characters, and the od_edit_str() password mode is used, with a
+               small block displayed in place of any characters typed:
+
+                         od_edit_str(string, "UUUUUUUUUUUUUUUU",
+                                     1, 1, 0x03, 0x21, 254,
+                                     EDIT_FLAG_PASSWORD_MODE);
+
+               To input a two-digit number from the user, requiring that both
+               digits be filled, and automatically accepting the input after
+               the two digits have been entered (not requiring the user to
+               press [ENTER]):
+
+                        od_edit_str(string, "##", 1, 1, 0x03, 0x21, 176,
+                                    EDIT_FLAG_FILL_STRING|
+                                    EDIT_FLAG_AUTO_ENTER);
+
+               To input a filename to download, as a field in a dialog box.
+               Here, the filename will be permitted to contain valid filename
+               characters, and the od_input_str() function will operate in
+               field mode, with the cancel [ESCape] key enabled. Also, string
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 77
+
+               edit mode will be enabled, allowing the user to edit a
+               previously entered line, and the EDIT_FLAG_KEEP_BLANK flag will
+               be set, causing the field background to remain displayed after
+               the user exits. This time, however, auto-delete mode will not be
+               used. Note that this combination of parameters expects that the
+               field and it's contents will have already been displayed, prior
+               to calling the od_edit_str() function.
+
+                        od_edit_str(string, "WWWWWWWWWWWW",
+                                    1, 1, 0x03, 0x21, 176,
+                                    EDIT_FLAG_EDIT_STRING|
+                                    EDIT_FLAG_FIELD_MODE|
+                                    EDIT_FLAG_ALLOW_CANCEL|
+                                    EDIT_FLAG_KEEP_BLANK);
+
+               To input a string without the field background and line
+               redrawing before and after input takes place:
+
+                        od_edit_str(string, "******************************",
+                                    1, 1, 0x07, 0x07, ' ',
+                                    EDIT_FLAG_NO_REDRAW);
+
+               To input a date, using PermaLiteral mode. Here, the month is
+               entered by a three digit short form ("JAN", "FEB", etc.), and
+               the literal characters such as the '-' and the "19" are a
+               permanent part of the input field:
+
+                        od_edit_str(string,"UUU'-'##'-19'##",
+                                    1, 1, 0x03, 0x21, 176,
+                                    EDIT_FLAG_PERMALITERAL|
+                                    EDIT_FLAG_FILL_STRING);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 78
+
+OD_EXIT()
+-------------------------------------------------------------------------------
+
+PURPOSE        The OpenDoors program termination function
+
+
+FORMAT         void od_exit(INT nErrorLevel, BOOL bTermCall);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    You MUST USE THIS FUNCTION when you want your program to exit.
+               This function will close the serial port, re-write changed
+               information to the door information (drop), call your end-of-
+               program function (if any), and then exit with the errorlevel
+               specified in the first parameter.
+
+               Also, if the second parameter, bTermCall, is set to TRUE,
+               od_exit() will also log the user off (for options such as
+               logging off within the door - as shown in the example below).
+               This is accomplished by lowering the DTR line to the modem,
+               causing the modem to hangup. When control is returned to the
+               BBS, it will then detect that the user is no longer online, and
+               will carry out its own logoff processing.
+
+               If you wish for your program to always perform any activities
+               prior to exiting, such as updating or closing data files, you
+               should set a function to be executed from within the od_exit()
+               function. This is accomplished by using the od_control.
+               od_before_exit variable, as described in the section on the
+               OpenDoors control structure in chapter 5. Use of this variable
+               will allow your program to always carry out these activates,
+               even if OpenDoors decides to call the od_exit() function itself,
+               such as when a user hangs up on the door.
+
+               Note that in special cases, you may use the
+               od_control.od_disable variable to prevent the od_exit() function
+               from re-writing the door information file. Also, you may use the
+               od_control.od_noexit variable to shutdown door operations
+               without actually exiting your program. Both of these variables
+               are described in chapter 5.
+
+
+SEE ALSO       od_init()
+
+
+EXAMPLE        The example below demonstrates a function which a door could
+               execute when the user chooses to exit the door. This function
+               will ask the user whether they wish to exit the door and return
+               to the BBS, simply logoff of the BBS, or continue using the
+               door. The example function will then call od_exit() if the user
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 79
+
+               wishes to exit the door, or return control to the function which
+               called it, if the user does not wish to exit:
+
+               void goodbye(void)
+               {
+                  char pressed;
+                                                  /* Display choices to user */
+                  od_disp_str("You have chosen to exit this door.\n\r");
+                  od_disp_str("Do you wish to:\n\r");
+                  od_disp_str("      [R]eturn to the BBS\n\r");
+                  od_disp_str("      [L]ogoff of the BBS\n\r");
+                  od_disp_str("      [C]ontinue using the door\n\r");
+
+                  for(;;)              /* loop until user makes valid choice */
+                  {
+                     pressed=od_get_key(TRUE);          /* Get key from user */
+
+                               /* If user selects R, exit without hanging up */
+                     if(pressed=='R' || pressed=='r') od_exit(40,FALSE);
+
+                                  /* If user selects L, hangup and then exit */
+                     if(pressed=='L' || pressed=='l') od_exit(41,TRUE);
+
+                     /* If user selects C, return and allow door to continue */
+                     if(pressed=='C' || pressed=='c') return;
+                  }
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 80
+
+OD_GET_ANSWER()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to allow the user to respond to a prompt using only
+               certain keys.
+
+
+FORMAT         char od_get_answer(char *pszOptions);
+
+
+RETURNS        Character that user entered
+
+
+DESCRIPTION    This function can be used to get a response from the user, when
+               only particular responses should be accepted. The parameter to
+               the od_get_answer() function is simply a string listing the
+               valid responses. The function will wait until the user selects
+               one of the valid responses, and then return that response. The
+               function is case insensitive, and will return the character in
+               the same case that was supplied to it in the string.
+
+
+SEE ALSO       od_get_key(), od_hotkey_menu()
+
+
+EXAMPLES       od_get_answer("YN");
+                    - If the user presses 'y', will return 'Y'.
+
+               od_get_answer("yn");
+                    - If the user presses 'y', will return 'y'.
+
+               od_get_answer("ABC 123\n\rZ");
+                    - Valid responses will be: [A], [B], [C], [SPACE],
+                      [1], [2], [3], [ENTER], [Z]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 81
+
+OD_GET_INPUT()
+-------------------------------------------------------------------------------
+
+PURPOSE        This function allows a single input event (e.g. keystroke) to be
+               retrieved, optionally translating extended key sequences such as
+               arrow keys and the insert key.
+
+
+FORMAT         BOOL od_get_input(tODInputEvent *pInputEvent,
+                  tODMilliSec TimeToWait, WORD wFlags);
+
+
+RETURNS        TRUE on success, FALSE if no input event was retrieved.
+
+
+DESCRIPTION    Like od_get_key(), od_get_input() can be used to retrieve a
+               single key of input from the user. However, od_get_input() has
+               been designed to be easily extended in future versions of
+               OpenDoors. The information retrieved by this new function is
+               placed in a structure, which contains information on whether the
+               input event was generated by the remote user or the local
+               console, and what type of input event it was. This function also
+               has built-in the ability to recognize and translate the multiple-
+               character sequences that are generated when the user presses
+               extended keys such as arrow keys, insert, delete, etc.
+
+               The first parameter points to a tODInputEvent structure, which is
+               defined as follows:
+
+                    typedef struct
+                    {
+                       tODInputEventType EventType;
+                       BOOL bFromRemote;
+                       char chKeyPress;
+                    } tODInputEvent;
+
+               When od_get_input() successfully retrieves an input event, this
+               structure is filled with information about the input. The
+               EventType member can be either EVENT_CHARACTER (indicating a
+               single character keystroke) or EVENT_EXTENDED_KEY (indicating an
+               extended key, such as an arrow key). In the case of
+               EVENT_CHARACTER, chKeyPress is set to the character that was
+               received. In the case of EVENT_EXTENDED_KEY, chKeyPress is set to
+               one of the following values:
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 82
+
+               +------------------+---------------+-------------------------+
+               | chKeyPress Value | Meaning       | Control Key Alternative |
+               +------------------+---------------+-------------------------+
+               | OD_KEY_F1        | [F1]          | None                    |
+               | OD_KEY_F2        | [F2]          | None                    |
+               | OD_KEY_F3        | [F3]          | None                    |
+               | OD_KEY_F4        | [F4]          | None                    |
+               | OD_KEY_F5        | [F5]          | None                    |
+               | OD_KEY_F6        | [F6]          | None                    |
+               | OD_KEY_F7        | [F7]          | None                    |
+               | OD_KEY_F8        | [F8]          | None                    |
+               | OD_KEY_F9        | [F9]          | None                    |
+               | OD_KEY_F10       | [F10]         | None                    |
+               | OD_KEY_UP        | [UP ARROW]    | [CTRL]-[E]              |
+               | OD_KEY_DOWN      | [DOWN ARROW]  | [CTRL]-[X]              |
+               | OD_KEY_LEFT      | [LEFT ARROW]  | [CTRL]-[S]              |
+               | OD_KEY_RIGHT     | [RIGHT ARROW] | [CTRL]-[D]              |
+               | OD_KEY_INSERT    | [INSERT]      | [CTRL]-[V]              |
+               | OD_KEY_DELETE    | [DELETE]      | [CTRL]-[G]              |
+               | OD_KEY_HOME      | [HOME]        | None                    |
+               | OD_KEY_END       | [END]         | None                    |
+               | OD_KEY_PGUP      | [PAGE UP]     | None                    |
+               | OD_KEY_PGDN      | [PAGE DOWN]   | None                    |
+               | OD_KEY_SHIFTTAB  | [SHIFT]-[TAB] | None                    |
+               +------------------+---------------+-------------------------+
+
+               The bFromRemote member of the tODInputEvent structure will be set
+               to TRUE if the input event originated from the remote system, or
+               FALSE if the event originated from the local system.
+
+               The second parameter, TimeToWait specifies how long the function
+               should wait for input before returning, in milliseconds. A value
+               of 0 causes the function to return immediately if no input is
+               waiting in OpenDoor's internal input buffer. The is equivalent to
+               a value of FALSE being passed to the od_get_key() function. A
+               value of OD_NO_TIMEOUT causes this function to wait and only
+               return after the next input event has been received. This is
+               equivalent to a value of TRUE being passed to the od_get_key()
+               function. An other value specifies the maximum number of
+               milliseconds that od_get_input() should wait for input. If input
+               is received before this time elapses, od_get_key() will return
+               immediately with a value of TRUE, and the tODInputEvent structure
+               will be fill accordingly. If no input is received before this
+               time elapses, od_get_key() will return FALSE. The number of
+               milliseconds to wait is rounded to the nearest 55 milliseconds in
+               the DOS version of OpenDoors.
+
+               The third parameter allows you to specify flags to further
+               control the behavior of od_get_input(). Normally, this parameter
+               will be set to GETIN_NORMAL. However, you can disable all
+               translation of extended keystrokes by setting this value to
+               GETIN_RAW. In this mode, od_get_input() works just like
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 83
+
+               od_get_key(), returning every individual character received from
+               the remote system.
+
+               Since extended keys are not directly supported by all terminal
+               programs, od_get_input() provides alternatives for some of the
+               extended keys, in the form of control-key combinations. The
+               control key combinations recognized by od_get_input() are listed
+               in the table above. However, these control key alternatives can
+               be ignored by setting the GETIN_RAWCTRL flag.
+
+               The od_get_input() function is used internally by
+               od_popup_menu(), od_edit_str() and od_multiline_edit().
+
+
+SEE ALSO       od_get_key(), od_clear_keybuffer()
+
+
+EXAMPLE        The following example shows the structure of how od_get_input()
+               might be used in your program:
+
+                    tODInputEvent InputEvent;
+                    od_get_input(&InputEvent, OD_NO_TIMEOUT, GETIN_NORMAL);
+                    if(InputEvent.EventType == EVENT_EXTENDED_KEY)
+                    {
+                       switch(InputEvent.chKeyPress)
+                       {
+                          case OD_KEY_UP:
+                             /* The up arrow key has been pressed. */
+                             break;
+                          case OD_KEY_DOWN:
+                             /* The down arrow key has been pressed. */
+                             break;
+                       }
+                    }
+                    else if(InputEvent.EventType == EVENT_CHARACTER)
+                    {
+                       /* A single character key has been pressed, and is  */
+                       /* stored in InputEvent.chKeyPress.                 */
+                    }
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 84
+
+OD_GET_KEY()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to input a key from the user
+
+
+FORMAT         char od_get_key(BOOL bWait);
+
+
+RETURNS        The next key waiting from the keyboard, or 0 if none.
+
+
+DESCRIPTION    This function retrieves the next key waiting in the OpenDoors
+               keyboard buffer (see the description of the od_clear_keybuffer()
+               function, on page 53, for more information on the OpenDoors
+               keyboard buffer). The od_get_key() function allows your door to
+               retrieve both those keystrokes pressed by the user, and the
+               keystrokes pressed on the sysop keyboard (other than the sysop
+               function keys), in the sequence they were pressed. Since input
+               is accepted from both sources, it is possible for the sysop, as
+               well as the remote user, to make selections and control the
+               door.
+
+               Door input with OpenDoors can be accomplished with this
+               function, with the od_input_str() function or with the
+               od_edit_str() function. The od_input_str() and od_edit_str()
+               functions is used to input an entire sequence of characters from
+               the user (a string), and requires the user to press the [Enter]
+               key when they are finished typing their input. On the other
+               hand, the od_get_key() function is used to input a single
+               keystroke (one character) from the user, and allows the user to
+               make choices without having to press the enter key.
+
+               The od_get_key() function accepts a single parameter, which
+               determines whether or not it should wait for the user to press a
+               key, if they have not already done so. If you pass a FALSE value
+               to od_get_key(), then the function will not wait for a key to be
+               pressed at the keyboard, but instead return a 0 if there are no
+               keys waiting in the buffer. If you pass a TRUE value to
+               od_get_key(), then this function will instead wait for a key to
+               be pressed. Also, while waiting for the user to press a key, the
+               od_get_key() function will give up the processor to other
+               waiting programs, if you door is running under DesqView.
+
+               If you are waiting for the user to make a choice from a menu or
+               list of options, you will most likely pass a TRUE to the
+               od_get_key() function, indicating that you wish for it to wait
+               until a key is pressed. However, if you wish to continue other
+               processing if no key is yet available from the keyboard, you
+               should pass a FALSE to the od_get_key() function. For example,
+               if you are displaying a screen of text, and wish to allow the
+               user to pause or abort the display, you would simply call the
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 85
+
+               od_get_key() function every few moments, passing it a value of
+               FALSE. You would then be able to check if any control keys have
+               been pressed, and if not, continue displaying text.
+
+               The od_get_key() function returns the ASCII value representing
+               the keystroke that was made. If you are waiting for the user to
+               make a particular choice, perhaps from a menu, you will most
+               likely store the value returned by od_get_key() in a variable of
+               type char. For example:
+
+                      char key;
+                      ...
+                      key=od_get_key(TRUE);
+
+               You would then be able to determine which key the user pressed
+               by testing the value of key, either by comparing it's numerical
+               ASCII value, or by comparing it to a character constant. If you
+               are testing for a non-character key, such as [ESCape], [Tab] or
+               [Return], you may wish to use the ASCII value of that key. For
+               example, if you wished to take some action in the case that the
+               user presses the [Enter]/[Return] key, who's ASCII value is 13,
+               you could do:
+
+                      key=od_get_key(TRUE);        /* Get keypress from user */
+                      if(key==13)             /* If key was [Enter]/[Return] */
+                      {
+                         ...                      /* Whatever you want to do */
+                      }
+
+               If you wish, instead, to respond to the user pressing a
+               character key (perhaps as a choice from a menu), you can do so
+               by using character constants, such as 'c', '6', or 'F'. Also,
+               when testing for an alphabetical character, you will probably
+               want to check for the user pressing either the upper or lower-
+               case version of the letter. For example, if you wished to have
+               the user press the [Y] key to continue, you could test for
+               either an upper or lower-case Y as follows:
+
+                       key=od_get_key(TRUE);       /* Get keypress from user */
+                       if(key=='y' || key=='Y')        /* If key was [y]/[Y] */
+                       {
+                          ...                     /* Whatever you want to do */
+                       }
+
+
+
+
+
+The charts on the following page lists the decimal value and corresponding
+keystroke(s) of each of the ASCII values from 0 to 127.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 86
+
+ASCII  KEYSTROKE                       |  ASCII   KEYSTROKE
+-----  ------------------------------  |  -----   ----------------------
+  0    [Control]-[@]                   |   15     [Control]-[O]
+  1    [Control]-[A]                   |   16     [Control]-[P]
+  2    [Control]-[B]                   |   17     [Control]-[Q]
+  3    [Control]-[C]                   |   18     [Control]-[R]
+  4    [Control]-[D]                   |   19     [Control]-[S]
+  5    [Control]-[E]                   |   20     [Control]-[T]
+  6    [Control]-[F]                   |   21     [Control]-[U]
+  7    [Control]-[G]                   |   22     [Control]-[V]
+  8    [Control]-[H]/[Backspace]       |   23     [Control]-[W]
+  9    [Control]-[I]/[Tab]             |   24     [Control]-[X]
+ 10    [Control]-[J]                   |   25     [Control]-[Y]
+ 11    [Control]-[K]                   |   26     [Control]-[Z]
+ 12    [Control]-[L]                   |   27     [ESCape]
+ 13    [Control]-[M]/[Enter]/[Return]  |   32     [SpaceBar]
+ 14    [Control]-[N]                   |
+
+
+
+ASCII  KEYSTROKE | ASCII  KEYSTROKE | ASCII  KEYSTROKE | ASCII  KEYSTROKE
+-----  --------- | -----  --------- | -----  --------- | -----  ---------
+ 33       '!'    |  57       '9'    |  80       'P'    |  104      'h'
+ 34       '"'    |  58       ':'    |  81       'Q'    |  105      'i'
+ 35       '#'    |  59       ';'    |  82       'R'    |  106      'j'
+ 36       '$'    |  60       '<'    |  83       'S'    |  107      'k'
+ 37       '%'    |  61       '='    |  84       'T'    |  108      'l'
+ 38       '&'    |  62       '>'    |  85       'U'    |  109      'm'
+ 39    '\''  (') |  63       '?'    |  86       'V'    |  110      'n'
+ 40       '('    |  64       '@'    |  87       'W'    |  111      'o'
+ 41       ')'    |  65       'A'    |  88       'X'    |  112      'p'
+ 42       '*'    |  66       'B'    |  89       'Y'    |  113      'q'
+ 43       '+'    |  67       'C'    |  90       'Z'    |  114      'r'
+ 44       ','    |  68       'D'    |  91       '['    |  115      's'
+ 45       '-'    |  69       'E'    |  92    '\\'  (\) |  116      't'
+ 46       '.'    |  70       'F'    |  93       ']'    |  117      'u'
+ 47       '/'    |  71       'G'    |  94       '^'    |  118      'v'
+ 48       '0'    |  72       'H'    |  95       '_'    |  119      'w'
+ 49       '1'    |  73       'I'    |  96       '`'    |  120      'x'
+ 50       '2'    |  74       'J'    |  98       'b'    |  121      'y'
+ 51       '3'    |  75       'K'    |  99       'c'    |  122      'z'
+ 52       '4'    |  76       'L'    |  100      'd'    |  123      '{'
+ 53       '5'    |  77       'M'    |  101      'e'    |  124      '|'
+ 54       '6'    |  78       'N'    |  102      'f'    |  125      '}'
+ 55       '7'    |  79       'O'    |  103      'g'    |  126      '~'
+ 56       '8'    |                  |                  |  127    [DELete]
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 87
+
+
+
+
+
+SEE ALSO       od_get_input(), od_input_str(), od_edit_str(),
+               od_clear_keybuffer()
+
+
+EXAMPLE        For examples of the use of the od_get_key() function, see the
+               examples in the description portion, above, and the examples for
+               the od_exit() and od_clear_keybuffer() functions. For further
+               examples of this function, see the example program EX_VOTE.C,
+               described in the section beginning on page 38.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 88
+
+OD_GETTEXT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Stores a rectangular region of the screen in an array, to later
+               be redrawn using od_puttext(). Requires ANSI, AVATAR or RIP
+               modes.
+
+
+FORMAT         BOOL od_gettext(INT nLeft, INT nTop, INT nRight, INT nBottom,
+                  void *pBlock);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    This function stores the contents (both text and color
+               information) of the rectangular portion of the screen denoted by
+               the variables nLeft, nTop, nRight and nBottom into the buffer
+               pointed to by pBlock. The saved portion of the screen may then
+               be restored using od_puttext(). The buffer must be large enough
+               to store two bytes for every character in the specified
+               rectangle. In other words, the required size of the buffer, in
+               bytes, is:
+
+                           length * width * 2
+
+               The parameters nLeft and nRight are column numbers from 1 to 80,
+               and the parameters nTop and nBottom are row numbers between 1
+               and 23.
+
+               This function has no effect on the current text color or cursor
+               position. ANSI, AVATAR or RIP mode is required for this
+               function. If you wish to save and restore the entire screen, you
+               may use the od_save_screen() and od_restore_screen() functions,
+               which can be used in all display modes.
+
+               If this function fails for any reason, a value of FALSE is
+               returned, and the od_control.od_error variable is set to
+               indicate the reason for the failure. For more information on the
+               od_control.od_error variable, see page 185.
+
+
+SEE ALSO       od_puttext(), od_save_screen(), od_restore_screen(),
+               od_scroll(), od_window_create(), od_window_remove()
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 89
+
+OD_HOTKEY_MENU()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to display a menu file with hotkeys
+
+
+FORMAT         char od_hotkey_menu(char *pszFileName, char *pszHotKeys, BOOL
+               bWait);
+
+
+RETURNS        Key pressed in response to menu, or '\0' if none.
+
+
+DESCRIPTION    This function can be used to display a menu from an ASCII, ANSI,
+               AVATAR or RIP file, allowing the user to select an option at any
+               time while the menu is being displayed. The od_hotkey_menu()
+               function is quite similar to the od_send_file() function, and
+               you should probably familiarize yourself with that function if
+               you are going to use od_hotkey_menu(). Like od_send_file(),
+               od_hotkey_menu() will display the file specified by pszFileName,
+               using the appropriate terminal emulation. If no extension is
+               provided for the filename, OpenDoors will automatically search
+               for matching files ending in .ASC, .ANS and .AVT extensions.
+               OpenDoors will the select the appropriate file to display, based
+               on the available files and available terminal emulation.
+
+               The second parameter, pszHotKeys, is a string specifying the
+               valid responses to the menu, in the same format as the string
+               passed to the od_get_answer() function. If any of the characters
+               listed in this string are pressed, either uppercase or lowercase
+               versions, OpenDoors will immediately stop displaying the menu,
+               and return with the value of the key pressed. The case (upper or
+               lower) returned will always be identical to the case used in the
+               hotkeys string. You can include the [ENTER] key as a valid hot
+               key by including the \n character in the hotkey string.
+
+               The third parameter passed to od_hotkey_menu(), bWait, specifies
+               whether OpenDoors should wait after displaying the menu for the
+               user to make a valid selection from the menu (TRUE), or if it
+               should exit immediately (FALSE). Normally, you will want to use
+               the TRUE value when calling this function. This will allow you
+               to use a single function call that will display the menu and
+               always return the user's selection. If you wish to gain control
+               as soon as OpenDoors has displayed the menu, you may specify
+               FALSE for this parameter. In this case, if the user does not
+               press any of the valid hot keys while the menu is being sent,
+               the function will return the character '\0'.
+
+
+SEE ALSO       od_send_file(), od_get_answer()
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 90
+
+EXAMPLE        As an example of the use of the od_hotkey_menu() function,
+               consider the following code fragment:
+
+
+                    for(;;)                             /* Main program loop */
+                    {                  /* Display menu and get user's choice */
+                         char choice=od_hotkey_menu("MAINMENU","123Q",TRUE");
+
+                         switch(choice)    /* Perform the appropriate action */
+                         {
+                              case '1':
+                                   od_printf("You selected one.\n\r");
+                                   break;
+
+                              case '2':
+                                   od_printf("You selected two.\n\r");
+                                   break;
+
+                              case '3':
+                                   od_printf("You selected three.\n\r");
+                                   break;
+
+                              case 'Q':
+                                   od_exit(FALSE,10);
+                         }
+                    }
+
+               This is an example of the main menu loop of a simple door that
+               uses the od_hotkey_menu() function. The program will continue
+               executing the for(;;) loop until the user chooses to exit the
+               door. On each iteration of the loop, the od_hotkey_menu()
+               function is called, to display the door's menu from the file
+               MAINMENU.A??. The appropriate .ASC/.ANS/.AVT file will be chosen
+               and displayed as the menu. The possible choices that may be made
+               from the menu are specified by the string "123Q". Thus, whenever
+               the user presses one of the keys [1], [2], [3] or [Q], the
+               od_hotkey_menu() function will return immediately with the value
+               of the key pressed. If the menu is still being displayed at the
+               time when the key was pressed, menu display will cease at that
+               moment. The program then executes a case statement, to respond
+               to the user's key appropriately. If the user presses [1], [2] or
+               [3] this door will output a simple message to the screen. If the
+               user presses the [Q] key, the door will pass control back to the
+               BBS.
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 91
+
+OD_INIT()
+-------------------------------------------------------------------------------
+
+PURPOSE        To initialize OpenDoors activities
+
+
+FORMAT         void od_init(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function initializes OpenDoors. This function must be
+               called manually if you wish to access data about the user, etc.,
+               before you call any other OpenDoors functions. However, if you
+               do not explicitly call the od_init() function, it will be called
+               automatically on the first call to most other OpenDoors
+               functions. The only functions that should be called before
+               od_init() are od_add_personality() and od_parse_cmd_line(). The
+               od_init() function reads information from the door information
+               file, initializes communications with the modem, displays the
+               status line, and sets up OpenDoors' internal data structures.
+               For more information on what data is and is not available before
+               od_init() has been called, please refer to the chapter on the
+               OpenDoors control structure, which begins on page 148.
+
+               The od_init() function will read the door information file which
+               is located in the directory specified by the variable
+               od_control.info_path. If this variable has not been set prior to
+               calling the od_init() function, OpenDoors will expect to find
+               the door information file in the current directory. Thus, if you
+               wish your door to be able to be run in a directory other than
+               the BBS system directory, it would be a good idea to allow the
+               sysop using your door to specify the location of the door
+               information file. For an example of setting the
+               od_control.info_path variable, please see the example program
+               located on page 150.
+
+               Also note that you can prevent the od_init() function from
+               carrying out some of it's normal activities, such as attempting
+               to read a door information file, by the use of the
+               od_control.od_disable variable, as described in the section on
+               the OpenDoors control structure, which begins on page 148.
+
+
+SEE ALSO       od_exit()
+
+
+EXAMPLE        At times, you may wish to write a door program which will
+               require a maintenance utility to be run on a regular basis. For
+               example, a game door may have to have its system files updated
+               on a daily basis, by having a utility program run in a system
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 92
+
+               event each day at midnight. One way of accomplishing this would
+               be to have your door package include two .EXE files, one being
+               the actual door program, and the other being a utility program.
+               However, another option would be to have both the door and
+               maintenance functions to be accessible from a single .EXE file,
+               in order to simplify use of the door for the sysop. In this
+               case, you would want to test the command line to determine
+               whether your program should run in door mode or maintenance
+               mode. You would then only execute the od_init() function, along
+               with the rest of your door code, if you program were running in
+               "door mode".
+
+               The program below demonstrates one method of doing just this. In
+               this case, the program would include two functions, door(),
+               which would carry out all of the door-related activities, and
+               maint(), which would carry out all of the maintenance-related
+               activities. In this simple example, if the command line includes
+               a "-M" or "/M", the program will run in maintenance mode,
+               otherwise it will run in door mode. Also, if it is running in
+               door mode, the program will take the first command-line
+               parameter, if any, as a path to the location of the door
+               information file.
+
+
+               #include "opendoor.h"
+
+               void door(void);
+               void maint(void);
+
+
+               int main(int argc, char *argv[])
+               {
+                  int counter;
+
+                           /* Check any command line parameters for /M or -M */
+                  for(counter=1;counter<argc;++counter)
+                  {
+                     if((argv[counter])[1]=='m' || (argv[counter])[1]=='M')
+                     {
+                        maint();               /* Then carry out maintenance */
+                        exit(20);                                /* And exit */
+                     }
+                  }
+                          /* If there was no -M or /M, then run in door mode */
+
+                  /* If there are any command-line parameters take the first */
+                                 /* as the path to the door information file */
+                  if(argc>1) strncpy(od_control.info_path,argv[1],59);
+
+                  od_init();                  /* call the od_init() function */
+                  door();             /* Run the door portion of the program */
+                  od_exit(30,FALSE);                    /* Shutdown the door */
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 93
+
+               }
+
+
+               void maint(void)
+               {
+                  ...               /* Carry out maintenance activities here */
+               }
+
+
+               void door(void)
+               {
+                  ...                      /* Carry out door activities here */
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 94
+
+OD_INPUT_STR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Inputs a string from the user
+
+
+FORMAT         void od_input_str(char *pszInput, INT nMaxLength,
+                  unsigned char chMin, unsigned char chMax);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    To perform string input within OpenDoors, one of two functions
+               can be used, od_input_str() and od_edit_str(). The first
+               function, od_input_str(), allows simple line input and editing,
+               and can be used in ASCII, ANSI, AVATAR and RIP modes. The second
+               function, od_edit_str(), allows many formatted input options,
+               advanced line editing, and other features, but requires the use
+               of ANSI, AVATAR or RIP graphics modes.
+
+               The od_input_str() function allows you to input a string from
+               the user. The string will be permitted to have up to the number
+               of characters specified by the max_len parameter, and all
+               characters must be between the values of the min_char and
+               max_char parameters. This function will wait until the user
+               presses the [Enter] key to finish inputting the string.
+
+               The first parameter passed to this function should be a pointer
+               to the string where the user's input should be stored. So, if
+               you wanted to store a string of up to 30 characters inputted by
+               the user, you might define this string as follows:
+
+                         char input_string[31];
+
+               Notice here than the string must be long enough to hold the
+               thirty characters which can be entered by the user, along with
+               the additional "null" character which is used to indicate the
+               end of a string in C. Hence, the length of the string should
+               always be at least one greater than the total number of
+               characters the user is permitted to enter, passed in the
+               nMaxLength parameter.
+
+               The second parameter passed to the od_input_str() function
+               should be an integer value indicating the maximum number of
+               characters which can be input by the user. For example, if this
+               parameter had a value of 10, the user would be able to enter a
+               string containing any number of characters up to and including
+               10 characters. If this parameter had a value of 1, the user
+               would only be able to enter a single character. However, the
+               user would be able to backspace, change the character, and press
+               [Enter] when they were satisfied with their entry. Note that
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 95
+
+               even if you only ask the od_input_str() function to input a
+               single character, it will still expect a STRING to be passed to
+               it, and will return a string with either zero or one character,
+               followed by a null (string terminator) character.
+
+               The third and fourth parameters passed to this function allow
+               you to control what characters the user will be permitted to
+               enter as part of the string. For example, you could set the
+               minimum character to the '0' character and the maximum character
+               to the '9' character, permitting the user to only enter numeric
+               characters. On the other hand, you could permit the user to
+               enter all ASCII characters in the range from 32 to 127. The
+               od_input_str() function will permit characters in the range
+               beginning with the character passed as minchar, up to and
+               including the character passed as maxchar.
+
+
+SEE ALSO       od_edit_str(), od_get_key(), od_clear_keybuffer()
+
+
+EXAMPLE        Below are a number of examples of the use of the od_input_str()
+               function in various applications:
+
+                 - To input a two character number (only digits from 0-9):
+
+                        od_input_str(string, 2, '0', '9');
+
+                 - To input a 35 character name (characters from Space to
+                   ASCII 127):
+
+                        od_input_str(string, 35, 32, 127);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 96
+
+OD_KERNEL()
+-------------------------------------------------------------------------------
+
+PURPOSE        The OpenDoors Central Control function.
+
+
+FORMAT         void od_kernel(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    In the DOS version of OpenDoors, the od_kernel() function is
+               responsible for many vital OpenDoors tasks, such as monitoring
+               the carrier detect signal, monitoring the amount of time that
+               the user has remaining, updating the status line, responding to
+               sysop hotkeys, and reading characters which are received from
+               the modem. The od_kernel() function is automatically called on a
+               frequent basis by the other OpenDoors functions, so most often
+               you will not need to be concerned with this function. However,
+               in order that OpenDoors can carry out the activities mentioned
+               above with a quick response, it is important that od_kernel(),
+               or some other OpenDoors function be called at least once every
+               second. Thus, if your program will be carrying out some
+               processing, in which it will not be calling any OpenDoors
+               functions for more than a second or so, you should call the
+               od_kernel() function yourself. The example below demonstrates
+               one method of doing just this.
+
+               Note that if for some reason or other, it is not possible for
+               your program to call the od_kernel() function, or any other
+               OpenDoors functions for a period of several seconds, this will
+               not cause your door to crash or fail in any way. The only
+               problem will be that OpenDoors will not be able to respond to
+               any action, such as the sysop pressing a function key, or the
+               user dropping carrier, until such time as you next call
+               od_kernel(), or some OpenDoors function. Hence, use of the
+               od_kernel() function will improve the quality and response time
+               of your program, but calling it or some OpenDoors function on a
+               regular basis is not absolutely vital.
+
+               This function has no effect in the Win32 version of OpenDoors.
+
+
+SEE ALSO       od_sleep()
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 97
+
+OD_LIST_FILES()
+-------------------------------------------------------------------------------
+
+PURPOSE        Lists files in a particular file area (using FILES.BBS)
+
+
+FORMAT         BOOL od_list_files(char *pszFileSpec);
+
+
+RETURNS        TRUE if successful, FALSE if unsuccessful
+
+
+DESCRIPTION    This function allows you to display a list of files available
+               for download from a particular file area, as any BBS system
+               would. The file names and descriptions are taken from the
+               FILES.BBS located in the directory pointed to by pszFileSpec.
+               Thus, to list the files available for download in
+               C:\BBS\FILES\UPLOADS, simply:
+
+                      od_list_files("C:\\BBS\\FILES\\UPLOADS");
+
+               OpenDoors uses a third-generation FILES.BBS format, that is
+               compatible with other FILES.BBS formats, but adds some
+               additional features. Each line in the FILES.BBS file lists a
+               filename, along with it's description. Thus, a typical FILES.BBS
+               file might look as follows:
+
+                        PKZ110.EXE    PKZip file compressor, version 1.10
+                        ODOORS60.ZIP  The newest version of OpenDoors
+                        REC*.ZIP      A Record file
+                        C:\BBS\*.*    All BBS files.
+
+               When displayed, OpenDoors will list the size of each file found
+               in the FILES.BBS file beside it's name, if the file is found. If
+               the file does not exist, then a "[OFFLINE]" string is displayed
+               in the file size column. Title lines may also be added to the
+               FILES.BBS, by indenting them one or more columns. Thus, you
+               could have something like:
+
+                           NEWEST UPLOADS
+                           ~~~~~~~~~~~~~~
+                        PKZ110.EXE    PKZip file compressor, version 1.10
+                        ODOORS60.ZIP  The newest version of OpenDoors
+                        REC*.ZIP      A Record file
+                        C:\BBS\*.*    All BBS files.
+
+               In addition to this standard FILES.BBS format, OpenDoors will
+               also permit wildcards to be used in FILES.BBS filenames (ie
+               FNEWS???.*), or full directory paths to allow files from several
+               different directories to be included in the same files area.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 98
+
+               You may alter the colors used to display the various portions of
+               the files list using the od_control variables:
+                         od_control.od_list_title_col
+                         od_control.od_list_name_col
+                         od_control.od_list_size_col
+                         od_control.od_list_comment_col
+                         od_control.od_list_offline_col
+
+               which are documented in the OpenDoors control structure section
+               on this manual, which begins on page 148.
+
+
+SEE ALSO       od_send_file()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 99
+
+OD_LOG_WRITE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to write an entry to the log file
+
+
+FORMAT         BOOL od_log_write(char *pszMessage);
+
+
+RETURNS        TRUE on success, or FALSE on failure
+
+
+DESCRIPTION    This function can be used to write entries to the log file. If
+               the logfile has not already been opened when you call this
+               function for the first time, OpenDoors will automatically open
+               the log file at that time.
+
+               To create an entry in the log file, simply call the
+               od_log_write() function, passing to it the string of the text
+               you wish to write. You should not include any control characters
+               in this string, simply the text that should appear on the line.
+               OpenDoors will automatically format the log file, adding the
+               time information and other control characters. It is recommended
+               that the length of the string passed to od_log_write() not
+               exceed 67 characters, in order that logfile lines will all be
+               less than 80 characters in length.
+
+               Log file entries do not usually contain periods or other
+               punctuation at the end of the line. Also, log file entries are
+               usually written in the present tense. The first character of the
+               entry is usually upper-case, with all other entries in lower
+               case. Also, since excessive numbers or lengths of log file
+               entries can quickly use a lot of disk space, it is best to think
+               carefully about what events should be recorded in the log file.
+               It is also a good idea to minimize the number of words used in
+               the entry, without being too cryptic. As an example, "User
+               entering options menu" should be used instead of "user entered
+               the options menu."
+
+
+SEE ALSO       Page 224.
+
+
+EXAMPLE        Calling the od_log_write() function is as simple as follows:
+
+                    od_log_write("Awarding user with 5 minutes more time");
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 100
+
+OD_MULTILINE_EDIT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Provides a multiple line text editor which can be used for
+               entering editing any text that spans more than one line, such as
+               messages or text files.
+
+
+FORMAT         INT od_multiline_edit(char *pszBufferToEdit, UINT unBufferSize,
+                  tODEditOptions *pEditOptions);
+
+
+RETURNS        OD_MULTIEDIT_SUCCESS on success, or OD_MULTIEDIT_ERROR on
+               failure
+
+
+DESCRIPTION    This function provides a text editor with optional word wrap
+               capabilities. This editor can be used for entering or editing
+               text files, messages or other information that spans multiple
+               lines. The editor can be configured to operate in full-screen
+               mode, or to occupy any smaller area of the screen that you
+               specify. It provides the navigation (home / end / page up / arrow
+               keys) features and editing features (insert / overwrite mode,
+               Ctrl-Y to delete a line, etc.) that you would expect.
+
+               The od_multiline_edit() function is designed to be both easy to
+               use and very flexible. To that end, the function only takes three
+               parameters. The first two parameters are required, and the third
+               parameter is an optional options structure. The first parameter,
+               pszBufferToEdit, is a pointer to the buffer of text to edit. This
+               buffer must always be a '\0'-terminated string. This buffer must
+               be initialized before calling od_multiline_edit(). The second
+               parameter, unBufferSize, indicates the size of the buffer that is
+               passed in pszBufferToEdit. Note that this should be the total
+               amount of space that is available in the buffer for text entered
+               by the user, not the length of data that is actually initially in
+               the buffer. If you do not wish to customize any of the
+               od_multiline_edit() options, then you may simply set the third
+               parameter to 0. Hence, a simple example of how to use
+               od_multiline_edit() is:
+
+                    char szMyEditBuffer[4000] = "";
+                    od_multiline_edit(szMyEditBuffer, sizeof(szMyEditBuffer),
+                    NULL);
+
+               If you wish to customize od_multiline_edit(), you should pass a
+               pointer to a tODEditOptions structure as the third parameter. You
+               should initialize this entire structure to zeros before
+               attempting to use it. You can then set any values of this
+               structure which you wish to change from their default. Any values
+               that are left at 0 will automatically revert to their defaults.
+               For example, if you wanted to specify a text format other than
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 101
+
+               the default, you could create, initialize and pass in a
+               tODEditOptions structure as follows:
+
+                    char szMyEditBuffer[4000] = "";
+                    tODEditOptions MyEditOptions;
+                    memset(&MyEditOptions, 0, sizeof(MyEditOptions));
+                    MyEditOptions.TextFormat = FORMAT_LINE_BREAKS;
+                    od_multiline_edit(szMyEditBuffer, sizeof(szMyEditBuffer),
+                         &MyEditOptions);
+
+               The definition of the tODEditOptions structure is as follows:
+
+                    typedef struct
+                    {
+                       INT nAreaLeft;
+                       INT nAreaTop;
+                       INT nAreaRight;
+                       INT nAreaBottom;
+                       tODEditTextFormat TextFormat;
+                       tODEditMenuResult (*pfMenuCallback)(void *pUnused);
+                       void * (*pfBufferRealloc)(void *pOriginalBuffer,
+                          UINT unNewSize);
+                       DWORD dwEditFlags;
+                       char *pszFinalBuffer;
+                       UINT unFinalBufferSize;
+                    } tODEditOptions;
+
+               nAreaLeft, nAreaTop, nAreaRight, nAreaBottom allows you to
+               specify the portion of the screen that the text editor should
+               use. This defaults to 1, 1 - 80, 23.
+
+               TextFormat allows you to specify what format the text should be
+               stored in the buffer using. The default is
+               FORMAT_PARAGRAPH_BREAKS, which specifies that a line break only
+               appears at the end of each paragraph, and that the contents of a
+               paragraph are word wrapped. FORMAT_LINE_BREAKS specifies that a
+               line break appears at the end of each line of text on the screen,
+               and that newly entered text is word wrapped. FORMAT_NO_WORDWRAP
+               is equivalent to FORMAT_LINE_BREAKS, except that newly entered
+               text is not word wrapped. Instead, lines may be arbitrarily long.
+               For each of these text formats, od_multiline_edit() automatically
+               decides whether line breaks should take the form of a carriage
+               return ('\r'), line feed ('\n'), or some combination of these,
+               based on what it sees in the buffer that you supply. If no line
+               breaks are found in the buffer, then the default is to use just a
+               line feed ('\n') character. FORMAT_FTSC_MESSAGE specifies a FTSC-
+               compliant message, such as is used in a *.MSG message file. Among
+               other things, this specifies that carriage returns ('\r') end
+               paragraphs, and that line feeds ('\n') should be ignored.
+
+               pfMenuCallback allows you to provide a callback function that
+               will be called when the user presses the escape (or control-Z)
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 102
+
+               key. This allows you to provide a menu that can be accessed from
+               within the text editor. This function should return
+               EDIT_MENU_DO_NOTHING if the editor should continue normally, or
+               EDIT_MENU_EXIT_EDITOR if the od_multiline_edit() should return.
+               If no menu callback function is provided, then
+               od_multiline_edit() always returns when the escape or control-z
+               key is pressed.
+
+               pfBufferRealloc allows you to provide a function which will
+               attempt to reallocate a larger buffer if the user enters more
+               text than will fit in the originally supplied buffer. You should
+               only do this if you have dynamically allocated the buffer that
+               you initially passed into od_multiline_edit(). If you allocated
+               the buffer using malloc() or calloc(), then pfBufferRealloc can
+               be set to point to the realloc() function. If you allocated the
+               buffer using the C++ new operator, then you must write a your own
+               reallocation function which obeys the same semantics as the C
+               realloc() function. If no buffer reallocation function is
+               provided, then od_multiline_edit() will never allow the user to
+               enter more text than will fit in the buffer that you initially
+               supply. If you are using the buffer reallocation option, you can
+               obtain a pointer to the final buffer, and the size of the final
+               buffer, from the pszFinalBuffer and unFinalBufferSize members.
+
+
+SEE ALSO       od_input_str(), od_edit_str(), od_get_input()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 103
+
+OD_PAGE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to allow user to page the sysop
+
+
+FORMAT         void od_page(void);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function can be called to allow the user to page the sysop.
+               This function will ask the user why they wish to chat with the
+               sysop, and then page the sysop. The sysop will then be free to
+               break into chat at any time. Sysop paging will also be aborted
+               by the user, simply by pressing [Enter] when asked for a reason
+               for chat. When the user pages the sysop, the [Wants-Chat]
+               indicator will begin to flash on the main status line, and the
+               status line will switch to show the user's reason for wanting to
+               chat. Also, the user's total number of pages will be
+               incremented.
+
+               Depending upon the setting of the od_control.od_okaytopage
+               variable, this function will also optionally check sysop paging
+               hours, and only allow the user to page the sysop during valid
+               paging hours. For information on the variables containing the
+               user's total number of pages, the user's want-chat status, valid
+               sysop paging hours, and the od_control.od_okaytopage variable,
+               see the section on the OpenDoors control structure, which begins
+               on page 148.
+
+
+EXAMPLE        For an example of the use of the od_page() function, see the
+               EX_VOTE.C example program, which is described beginning on page
+               38.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 104
+
+OD_PARSE_CMD_LINE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Handles standard command line options.
+
+
+FORMAT         Under DOS Version:
+               void od_parse_cmd_line(INT nArgCount, char *papszArguments[]);
+
+               Under Win32 Version:
+               void od_parse_cmd_line(LPSTR pszCmdLine);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This is the only OpenDoors function that uses a different
+               calling format in the DOS and Win32 versions of OpenDoors. The
+               reason for this is that od_parse_cmd_line() always allows you to
+               pass command line parameters in the same format that the
+               operating system passes them to you. Under the DOS version of
+               OpenDoors, you should pass the argc and argv values that were
+               passed to your main function as the two parameters to
+               od_parse_cmd_line(). Under the Win32 version of OpenDoors, you
+               should pass the pszCmdLine values that were passed to your
+               WinMain() function as the one parameter to od_parse_cmd_line().
+
+               The od_parse_cmd_line() function should be called before your
+               first call to any other OpenDoors function, with the possible
+               exception of the od_add_personality() function.
+
+               It is recommended that any program which uses OpenDoors call
+               od_parse_cmd_line() as part of its startup procedure. This
+               allows your program to automatically handle many common command
+               line options that will make it easier to setup and run your
+               program. Among the helpful command line options processed by
+               od_parse_cmd_line() are options to set serial port information
+               (including information on non-standard serial port setups),
+               specify the location of configuration and drop files, force the
+               program to run in silent mode (without no local display), pass
+               in user information, and the ability to start the program in
+               local mode without a drop file. For a complete list of the
+               options supported by od_parse_cmd_line(), run the example Vote
+               door that is included in the OpenDoors packages, specifying -
+               help on the command line.
+
+               If you wish to process your own command line parameters in
+               addition to those supported by OpenDoors, simply check the
+               command-line for your own parameters after calling
+               od_parse_cmd_line(). You can do this in the same way that you
+               would handle command line options if you weren't using
+               od_parse_cmd_line(). The od_parse_cmd_line() function does not
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 105
+
+               generate an error message if it encounters unrecognized command
+               line options. You can supply your own text to display when the
+               user chooses the /Help option by setting
+               od_control.od_cmd_line_help to point to your own string. Separate
+               lines in your string with the \n character, and align text using
+               the \t character.
+
+
+SEE ALSO       od_init()
+
+
+EXAMPLE        The following example shows how a program that uses
+               od_parse_cmd_line() should be structured in order to compile
+               under either DOS or Win32 versions of OpenDoors:
+
+               #include "opendoor.h"
+
+               /* main() or WinMain() function - Program begins here. */
+               #ifdef ODPLAT_WIN32
+               int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+                  LPSTR lpszCmdLine, int nCmdShow)
+               #else
+               int main(int argc, char *argv[])
+               #endif
+               {
+               #ifdef ODPLAT_WIN32
+               #endif
+
+                  /* Set program's name for use by OpenDoors. */
+               #ifdef ODPLAT_WIN32
+                  /* In Windows, pass in nCmdShow value to OpenDoors. */
+                  od_control.od_cmd_show = nCmdShow;
+
+                  /* Call od_parse_cmd_line. */
+                  od_parse_cmd_line(lpszCmdLine);
+               #else
+                  od_parse_cmd_line(argc, argv);
+               #endif
+
+
+                  /* Start the rest of your program here. */
+
+
+               }
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 106
+
+OD_POPUP_MENU()
+-------------------------------------------------------------------------------
+
+PURPOSE        Creates a popup menu which allows the user to make a selection
+               by pressing a single key, or selecting the item with a highlight
+               bar. After the user has made a selection, the menu may be
+               removed from the screen, restoring the original screen contents
+               "beneath" the window.
+
+
+FORMAT         INT od_popup_menu(char *pszTitle, char *pszText, INT nLeft,
+                  INT nTop, INT nLevel, WORD uFlags);
+
+
+RETURNS        POPUP_ERROR    On error (od_control.od_error is set to
+                              indicate type of error).
+               POPUP_ESCAPE   If user exited menu by pressing [ESCape].
+               POPUP_LEFT     If user exited menu by pressing the left arrow
+                              key.
+               POPUP_RIGHT    If user exited menu by pressing the right arrow
+                              key.
+
+               Or, a postive integer indicating the menu item that was chosen
+               if a selection was made.
+
+
+DESCRIPTION    od_popup_menu() creates a popup window with a menu of choices,
+               for use in ANSI/AVATAR/RIP modes. The user is able to choose an
+               item from the menu by moving the highlighted selection bar with
+               the arrow keys, or by pressing a key associated with a
+               particular menu item. The contents of the menu are defined by
+               the string pointed to by the pszText parameter. This menu
+               definition string contains each menu option, separated by a '|'
+               (pipe) character. Keys associated with each menu entry can be
+               defined by proceeding the letter with a '^' (carat) character.
+               For example, the string:
+
+                    "^Save|^Load|E^xit"
+
+               would produce a menu with three options: Save, Load and Exit.
+               The user would be able to select the Save option by pressing the
+               [S] key, the Load option by pressing the [L] key, and the Exit
+               option by pressing the [X] key. Furthermore, the characters
+               corresponding to each menu item would be displayed in a
+               highlighted color.
+
+               Menus displayed with od_popup_menu() may optionally have a
+               title, as specified by the pszTitle parameter. If this parameter
+               is set to NULL, no title will be displayed. If this parameter is
+               not NULL, the specified string will be displayed as a title on
+               the window.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 107
+
+               The nLeft and nTop parameters specify the left and top locations
+               of the menu window, were 1, 1 is the upper right corner of the
+               screen. The bottom and right corners of the menu are
+               automatically determined by the size and number of menu entries
+               in the menu definition string.
+
+               The nLevel parameter specifies the menu level, an integer from 0
+               to 10. Unless you are using the MENU_KEEP flag, this parameter
+               can always be 0.
+
+               The uFlags parameter specifies one or more of the following
+               options, joined by the bitwise-OR operator (|).
+
+                    MENU_NORMAL         Has no effect.
+                    MENU_ALLOW_CANCEL   Allow user to exit menu with [ESCape].
+                    MENU_PULLDOWN       Allow exit with arrow keys.
+                    MENU_KEEP           Leave menu active on selection.
+                    MENU_DESTROY        Remove a currently active menu.
+
+               If you are not using any of the other flags, you can use
+               MENU_NORMAL as a place-holder for this parameter. If you specify
+               MENU_ALLOW_CANCEL, the user will be able to exit the menu
+               without making a selection by pressing the [ESCape] key. If the
+               user presses [ESCape], od_popup_menu() returns POPUP_ESCAPE.
+
+               You can use the MENU_PULLDOWN option with od_popup_menu() to
+               implement a set of pulldown menus. In this case, if the user
+               presses the left arrow key or right arrow key while the menu is
+               being displayed, od_popup_menu() returns with POPUP_LEFT or
+               POPUP_RIGHT, allowing you to display a different menu.
+
+               Normally, od_popup_menu() will remove the menu from the screen
+               as soon as the user makes a selection. However, there may be
+               some cases when you want the menu to continue to be visible
+               after the user makes a selection. For example, you may want some
+               menu options to lead to further sub-menus, or you may wish to
+               display a popup window, and return to this menu after the user
+               has exited from the popup window. If the MENU_KEEP flag is
+               specified, the menu will remain active (on-screen) after the
+               user makes a selection. However, the menu will still be
+               destroyed if the user cancels out of the menu (this will only
+               happen if you have specified MENU_ALLOW_CANCEL), or if the user
+               moves to another menu by pressing the left or right arrow keys
+               (this will only happen if you have specified MENU_PULLDOWN). If
+               MENU_KEEP has been specified, and the user makes a selection,
+               you must eventually either return to the menu, or destroy it by
+               calling MENU_DESTROY. If you want to return to the menu, simply
+               call od_popup_menu() again with the same level value that was
+               used to originally create the menu. The user will now be able to
+               make another selection from the menu, and od_popup_menu() will
+               once again return that selection to you. If you want to destroy
+               the menu, simply call od_popup_menu() with the MENU_DESTROY flag
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 108
+
+               set, and the same level value that was used to create the
+               original menu. If you wish to create another popup menu while
+               the first one is still active, simply call od_popup_menu()
+               again, this time with a different level value. The colors used
+               by the od_popup_menu() function are set by the following
+               OpenDoors control structure settings:
+
+                    char od_control.od_menu_title_col;
+                    char od_control.od_menu_border_col;
+                    char od_control.od_menu_text_col;
+                    char od_control.od_menu_key_col;
+                    char od_control.od_menu_highlight_col;
+                    char od_control.od_menu_highkey_col;
+
+
+SEE ALSO       od_window_create(), od_window_remove(), od_draw_box(),
+               od_hotkey_menu()
+
+
+EXAMPLE        The following example shows the use of multiple-level menus:
+
+               #include <stdlib.h>
+               #include "opendoor.h"
+               main()
+               {
+                  for(;;)
+                  {
+                     switch(od_popup_menu("Main Menu",
+                        "^Files|^Electronic Mail|^News|E^xit",
+                        20, 5, 0, MENU_NORMAL | MENU_KEEP))
+                     {
+                        case 1:
+                           od_popup_menu("Files Menu",
+                              "^Search For Files|^Download|^Upload",
+                              30, 7, 2, MENU_NORMAL | MENU_ALLOW_CANCEL);
+                           break;
+                        case 2:
+                           od_popup_menu("EMail Menu",
+                              "Get ^New Mail|^Send Mail|Send ^Fax",
+                              30, 8, 1, MENU_NORMAL | MENU_ALLOW_CANCEL);
+                           break;
+                        case 3:
+                           od_popup_menu("News Menu",
+                              "Choose News^Group|^Read News|^Post News",
+                              30, 9, 1, MENU_NORMAL | MENU_ALLOW_CANCEL);
+                           break;
+                        case 4:
+                           od_popup_menu(NULL, NULL, 0, 0, 0, MENU_DESTROY);
+                           return(0);
+                     }
+                  }
+               }
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 109
+
+OD_PRINTF()
+-------------------------------------------------------------------------------
+
+PURPOSE        Performs formatted output (remote & local), with the ability to
+               change display colors.
+
+
+FORMAT         void od_printf(char * pszFormat,...);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This is one of two OpenDoors function which allows you to
+               display a string of characters, the other being the
+               od_disp_str() function. For a complete comparison of the various
+               OpenDoors display function, see the description of the
+               od_disp_str() function, on page 63. Like the od_disp_str()
+               function, the od_printf() function will display its output both
+               on the local screen, and on the remote user's screen (if the
+               door is not operating in local mode). However, the od_printf()
+               function also allows for formatted output, just as the printf()
+               function does. In addition to providing all of the features of
+               the normal C printf() function, the od_printf() function allows
+               you to include codes to change the color of the display of text.
+               This unique feature allows you to display multi-colored text,
+               without having to use chains of alternating od_disp_str() and
+               od_set_color() calls.
+
+               As with the printf() function, the od_printf() function accepts
+               one or more parameters, the first parameter being the format
+               string to be displayed, and the additional parameters being data
+               to be displayed within the string. The OpenDoors od_printf()
+               function recognizes all of the control characters and options
+               recognized by the normal printf() function. For example, to
+               display the amount of time that a user has left online, the
+               following line would be a valid use of the od_printf() function:
+
+                    od_printf("Time Left:%d\n\r", od_control.user_timelimit);
+
+               Note that a full discussion of the printf() function is beyond
+               the scope of this manual. For more information on using
+               printf(), please see your Turbo C(++) / Borland C++ manuals.
+
+               In addition to the normal control sequences, such as "%s", "%d",
+               or "%12.12s", the od_printf() function also allows you to
+               include special color-setting codes within the format string.
+               These color code sequences BEGIN and END with a delimiter
+               character, which is used to indicate that the sequence is a
+               color setting. Consider, for example, the following line of
+               code, which displays text in various colors:
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 110
+
+                       od_printf("`blue`Blue `green`Green `red`Red  \n\r");
+
+               In this case (assuming of course that a color monitor is being
+               used) the word "Blue" will be displayed in the color blue, the
+               word "Green" will be displayed in the color green, and the word
+               "Red" will be displayed in the color red. In this case, the
+               sequence `blue` sets the display color to dark blue on black.
+               Here, the back-quote (`) is the delimiter character which
+               indicates the beginning and end of the color sequence. Be sure
+               not to confuse the back-quote character (`) with the normal
+               forward quote ('). THIS IS THE MOST COMMON DIFFICULTY
+               EXPERIENCED WITH THE OD_PRINTF() FUNCTION. The text between the
+               back-quote characters indicates the color that should be set.
+               This text can include the name of the foreground color, the name
+               of the background color, the "bright" keyword and the "flashing"
+               keyword. The first color mentioned is taken to be the foreground
+               color, and the second the background color. Case is not
+               sensitive, additional words can be included for legibility.
+               Thus:
+
+                         `bright white cyan`
+
+               is equivalent to:
+
+                         `Bright white on a cyan background`.
+
+               The "bright" keyword indicates that the foreground color should
+               be displayed in high intensity, and the "flashing" keyword
+               indicates that the text should be flashing. If no background is
+               specified, the background color defaults to black. If no
+               foreground or background colors are specified, the color
+               defaults to white on black.
+
+               The od_printf() function will automatically determine whether
+               the user has ANSI, AVATAR or RIP graphics enabled, and will send
+               the appropriate color codes to change the color of displayed
+               text. If the user does not have either ANSI or AVATAR graphics
+               modes turned on, then the od_printf() function will not send any
+               color codes. Thus, a door program using color codes would work
+               just as well when ANSI/AVATAR/RIP graphics are not available,
+               except that all text will appear in the same color.
+
+               You may prefer to set colors by using the od_set_color() or
+               od_set_attrib() functions, instead of using these cryptic color
+               codes imbedded in od_printf() functions. In some cases, however,
+               it will be much more advantageous to place the color codes
+               within your od_printf() strings. As a case in point, consider
+               the single od_printf() statement in the example, above. To
+               accomplish the same result using the od_disp_str() and
+               od_set_color() functions, you would have to use the following
+               SIX function calls:
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 111
+
+                       od_set_color(D_BLUE,D_BLACK);
+                       od_disp_str("Blue ");
+                       od_set_color(D_GREEN,D_BLACK);
+                       od_disp_str("Green ");
+                       od_set_color(D_RED,D_BLACK);
+                       od_disp_str("Red  \n\r");
+
+               While this method MAY be easier understand, it certainly
+               requires many more line of code to accomplish. However, either
+               method will work, and the choice is up to you as to which method
+               you prefer. Keep in mind, however, that if the color to be set
+               is stored in a variable, instead of always being the same color,
+               you must use either the od_set_color() or od_set_attrib()
+               function to set the display color.
+
+               While the back-quote (`) character is normally used to delimit a
+               color sequence in the od_printf() function, you may wish to be
+               able to print a back-quote character using the od_printf()
+               function. In this case, you may configure OpenDoors to use a
+               different character to represent color code sequences. To do
+               this, simply use the od_control.od_color_delimiter variable,
+               which is described in the OpenDoors control structure section,
+               beginning on page 148. For example, if you wished to use the
+               tilde (~) character instead of the back-quote character to
+               change colors, simply place the following line within your
+               program, at some point after having called od_init() or some
+               OpenDoors function:
+
+                       od_control.od_color_delimiter='~';
+
+               Also, you may disable the color code interpretation within the
+               od_printf() function altogether, by setting the
+               od_control.od_color_delimiter variable to 0.
+
+               Note that the od_printf() function interprets the color codes
+               AFTER parsing the other control sequences, such as "%d" or "%s".
+               Thus, if you used the command:
+
+                       od_printf("%s",string);
+
+               Any color codes contained in the string "string" would also be
+               interpreted. If you did not wish to have any color code
+               characters which might be contained in the string "string"
+               treated as such, you could again disable od_printf()'s color
+               code interpretation, by setting the od_control.od_color_char
+               variable to 0.
+
+               Note that the string to be displayed by od_printf() should not
+               exceed 511 characters in size, including the size of color
+               sequences and expanded % fields.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 112
+
+SEE ALSO       od_disp_str(), od_disp(), od_putch(), od_repeat(), od_disp_emu()
+
+
+EXAMPLE        Below is a simple example of a user statistics door program,
+               which displays various pieces of information to the user, by
+               using the od_printf() function. Notice the use of color code
+               sequences in order to display the titles in a different color
+               from the information fields. Note that since the information
+               available to this door will depend on the BBS system under which
+               it is running, not all of the information displayed by this door
+               will be available under all BBS systems. For a description of
+               what information is available under what BBS systems, see the
+               OpenDoors control structure portion of this manual, which begins
+               on page 148.
+
+
+#include "opendoor.h"
+
+int main(int argc,char *argv[])
+   {
+   od_init();                                     /* Begin OpenDoors program */
+
+   od_printf("`bright white` YOUR STATISTICS\n\r");         /* Display title */
+   od_printf("---------------\n\r\n\r");
+
+                                                       /* Display statistics */
+   od_printf("`red`NAME :             `blue`%s\n\r",od_control.user_logintime);
+   od_printf("`red`LOCATION :         `blue`%s\n\r",od_control.user_location);
+   od_printf("`red`PHONE NUMBER :     `blue`%s\n\r",od_control.user_homephone);
+   od_printf("`red`LAST CALL :        `blue`%s\n\r",od_control.user_lastdate);
+   od_printf("`red`NUMBER OF CALLS :  `blue`%u\n\r",od_control.user_numcalls);
+   od_printf("`red`NUMBER OF PAGES :  `blue`%u\n\r",od_control.user_numpages);
+   od_printf("`red`REMAINING TIME :   `blue`%d\n\r",od_control.user_timelimit);
+   od_printf("`red`# OF DOWNLOADS :   `blue`%u\n\r",od_control.user_downloads);
+   od_printf("`red`# OF UPLOADS :     `blue`%u\n\r",od_control.user_uploads);
+   od_printf("`red`KBYTES DL TODAY :  `blue`%u\n\r",od_control.user_todayk);
+
+                                                /* Ask user to press [Enter] */
+   od_printf("`bright green on green`Press [Enter] to return to BBS...\n\r");
+
+   while(od_get_key(TRUE)!=13);            /* Wait for user to press [Enter] */
+
+   od_exit(20,FALSE);                                       /* Return to BBS */
+   }
+
+
+HELPFUL        This section demonstrates use of the od_printf() color
+HINT           sequences imbedded directly in the printf() format string, such
+               as:
+
+                         od_printf("Hello `bright green` there!");
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 113
+
+               However, there are also other ways that you can take advantage
+               of this feature. For example, the C programming language
+               concatenates string constants that are separated only by white
+               space or carriage returns. For instance,
+
+                         "Hello " "`bright green`" " there!"
+
+               is equivalent to:
+
+                         "Hello `bright green` there!"
+
+               For this reason, you can create macros for common color
+               sequences in your program, such as:
+
+                         #define HIGHLIGHT "`bright green`"
+
+               You can then use such constants when calling the od_printf()
+               function, as follows:
+
+                         od_printf("Hello " HIGHLIGHT " there!");
+
+               You may find this method of setting the display color to be
+               easier to read, and more easily configurable than including the
+               color sequence directly in the format string. Below another use
+               of the color sequences is describe, which allows the colors used
+               by od_printf() not be "hard-wired".
+
+
+               Since color control sequences are evaluated by od_printf() after
+               it evaluates all format sequences (such as "%d"). For this
+               reason, it is possible to change the display color using a
+               string variable, instead of using a fixed color in the string.
+               For example, if you program had the variable:
+
+                         char highlight[40];
+
+               which was set at some point to be equal to:
+
+                         "`bright green`"
+
+               you would be able to use od_printf() as follows:
+
+                         od_printf("Hello %s there!", highlight);
+
+               The display color would then be changed at the location where
+               the "%s" appears in the od_printf() format string. The advantage
+               of using this method to change display colors is that unlike
+               other methods, the value of the highlight variable can be
+               changed. This could be used, for example, to allow the sysop to
+               configure the colors they wish your door to use. You would only
+               need to change the value of the highlight variable in order to
+               change the color set by od_printf().
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 114
+
+OD_PUTCH()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to display a single character.
+
+
+FORMAT         void od_putch(int chToDisplay);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function performs a similar function to the other OpenDoors
+               display functions. For information on the uses of the various
+               OpenDoors display functions, see the description of the
+               od_disp_str() function, on page 63. This function is most
+               similar to the od_disp() and od_disp_str() functions, except
+               that it only displays a single character at a time.
+
+               This function will display the character passed to it at the
+               cursor position in the output window, and then advance the
+               cursor to the next display position. If OpenDoors is not
+               operating in local mode, the character will also be sent to the
+               modem, and thus displayed on the user's screen in the same
+               manner that it is displayed on the local screen. If ANSI, AVATAR
+               or RIP graphics mode is activated the character will be
+               displayed in the current color.
+
+
+SEE ALSO       od_disp_str(), od_disp(), od_printf(), od_repeat(),
+               od_disp_emu()
+
+
+EXAMPLE        Below is an example of the use of the od_putch() function. This
+               example is a function which you could use in place of the
+               od_get_key() function. This function inputs a single character
+               from the keyboard, just as the od_get_key() function does.
+               However, if the character entered is a printable character, the
+               function will also echo the character to the local screen, using
+               the od_putch() function.
+
+               char get_key_with_echo(int wait)
+               {
+                  char pressed=od_get_key(wait);        /* Get key from user */
+
+                  if(pressed>=32 && pressed<=126)     /* If key is printable */
+                  {
+                     od_putch(pressed);             /* Display the character */
+                  }
+
+                  return(pressed);             /* Return key pressed by user */
+               }
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 115
+
+OD_PUTTEXT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Displays a rectangular region of text and color information, as
+               previously stored using od_gettext()
+
+
+FORMAT         BOOL od_puttext(INT nLeft, INT nTop, INT nRight, INT nBottom,
+                  void *pBlock);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    This function "pastes" a rectangular block of text and color
+               information that has been previously retrieved using
+               od_gettext(). The block is placed at the screen location
+               indicated by the variables nLeft, nTop, nRight and nBottom,
+               where nLeft and nRight are column numbers from 1 - 80, and nTop
+               and nBottom are row numbers from 1 - 23. The length and width of
+               the rectangle specified by nLeft, nTop, nRight and nBottom must
+               be the same as the length and width of the rectangle passed to
+               od_gettext() when storing the block of text.
+
+               This function attempts to display the pasted block as quickly as
+               possible, only transmitting information on portions of the block
+               that are different than the original screen contents. When this
+               function returns, it leaves the cursor at its original position,
+               and the display color at its original setting. This function
+               requires ANSI or AVATAR mode.
+
+               The control structure variable od_control.od_full_put may be set
+               to TRUE to force od_puttext() to always send all characters in
+               the block to be displayed, instead of only displaying the
+               portions of the block that differ from the original screen
+               contents. If you wish to save and restore the entire screen, you
+               can use od_save_screen() and od_restore_screen(), which work in
+               all display modes.
+
+               You may also use the od_puttext() to display a rectangular block
+               of text that you generate manually, instead of retrieving using
+               od_gettext(). To do this, you pass an array which contains the
+               text and color information for the rectangular area to be
+               painted, in the pBlock parameter. The array passed to
+               od_puttext() contains a two-byte sequence of information for
+               each character in the rectangle. The first byte contains the
+               ASCII code of the character to be displayed. The second byte
+               contains the color attribute value of the character, in the same
+               format as used by the od_set_attrib() function (described on
+               page 128). These two byte sequences are stored in the order in
+               which English is written; the array begins with the two byte
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 116
+
+               sequences for all of the characters on the first line, from left
+               to right, followed by the characters for the second line, and so
+               on. The length of each line must be exactly equal to the width
+               of the rectangular region to be painted.
+
+
+SEE ALSO       od_gettext(), od_save_screen(), od_restore_screen(),
+               od_scroll(), od_window_create(), od_window_remove()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 117
+
+OD_REPEAT()
+-------------------------------------------------------------------------------
+
+PURPOSE        Repeatedly display the specified character any number of times,
+               using special graphics codes for greater speed, if possible.
+
+
+FORMAT         void od_repeat(char chValue, BYTE btTimes);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This display function will repeatedly display the character
+               chValue, btTimes times. For a complete breakdown of the various
+               OpenDoors display functions, see the description of the
+               od_disp_str() function, located on page 63.
+
+               The advantage of using this function to display a series of
+               identical characters is that this function will use special
+               graphics-mode control sequences to display the repeated
+               character very efficiently, if the required graphics mode is
+               available. For example, in AVATAR mode, this function can
+               display an entire line of one character, by sending a control
+               sequence to the modem that is only three characters long. If
+               graphics mode is not turned on, then the od_disp_str() function
+               will simply send the specified character the appropriate number
+               of times. As with the other display functions, the output of
+               this function is sent to both the local and remote screens.
+
+
+SEE ALSO       od_putch(), od_disp_str(), od_disp(), od_printf(), od_disp_emu()
+
+
+EXAMPLE        The example function below demonstrates the use of the
+               od_repeat() function in drawing a window (a square box) on the
+               screen. This function is essentially a simplified version of the
+               od_draw_box() function, which is described on page 65. Unlike
+               this function, the od_draw_box() function allows the
+               customization of the characters used to draw the box's boarder,
+               and if possible uses additional AVATAR graphics codes to display
+               the window even faster than this function does. Thus, the
+               function below is really provided for demonstration purposes
+               only.
+
+               This function accepts four parameters, which indicate the
+               location of the upper left and lower right corners of the window
+               to be displayed. The function then displays the window with the
+               current color attribute settings. Since this function uses the
+               od_repeat() function, if AVATAR graphics are available, it can
+               display the entire window in a fraction of a second, even if it
+               is displaying a window the size of the entire screen at slow
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 118
+
+               baud rates. Note that this window display function requires that
+               the user has ANSI, AVATAR or RIP graphics mode enabled.
+
+               void draw_window(char left, char top, char right, char bottom)
+               {
+                  char line_counter;   /* Number of current line being drawn */
+                  char between_size=(right-left)-1;      /* X size of window */
+
+                  od_set_cursor(top,left);             /* move to top corner */
+                  od_putch(218);                 /* display corner character */
+                  od_repeat(196,between_size);           /* display top line */
+                  od_putch(191);                 /* display corner character */
+                                      /* loop through middle lines of window */
+                  for(line_counter=top+1;line_counter<bottom;++line_counter)
+                  {
+                     od_set_cursor(line_counter,left); /* move to line start */
+                     od_putch(179);                /* display left line char */
+                     od_repeat(' ',between_size);      /* display blank area */
+                     od_putch(179);               /* display right line char */
+                  }
+
+                  od_set_cursor(bottom,left);       /* move to bottom corner */
+                  od_putch(192);                 /* display corner character */
+                  od_repeat(196,between_size);        /* display bottom line */
+                  od_putch(217);                 /* display corner character */
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 119
+
+OD_RESTORE_SCREEN()
+-------------------------------------------------------------------------------
+
+PURPOSE        Restores the screen contents as previous saved using the
+               od_save_screen() function. This function can be used in any
+               display mode.
+
+
+FORMAT         BOOL od_restore_screen(void *pBuffer);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    This function restores the entire contents of the screen, along
+               with the current cursor position and display color, which was
+               previously stored using the od_save_screen() function. Unlike
+               the od_get_text() and od_put_text() functions, the
+               od_save_screen() and od_restore_screen() functions will work in
+               all display modes (ASCII, ANSI, AVATAR and RIP).
+
+               The pBuffer parameter must point to the buffer previously passed
+               to od_save_screen(). Note that the od_save_screen() and
+               od_restore_screen() functions save the stored information in
+               different formats than the od_getttext() and od_puttext()
+               functions. For this reason, you cannot save the screen contents
+               with od_gettext() and restore them with od_restore_screen(), or
+               visa-versa.
+
+               If this function fails for any reason, a value of FALSE is
+               returned, and the od_control.od_error variable is set to
+               indicate the reason for the failure. For more information on the
+               od_control.od_error variable, see page 185.
+
+
+SEE ALSO       od_save_screen(), od_gettext(), od_puttext(), od_clr_scr()
+
+
+EXAMPLE        For an example of how to use the od_restore_screen() function,
+               see the example which accompanies the od_save_screen() function,
+               on page 121.
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 120
+
+OD_SAVE_SCREEN()
+-------------------------------------------------------------------------------
+
+PURPOSE        Saves the contents of the screen, to later be restored using
+               od_restore_screen(). Can be used in any display mode.
+
+
+FORMAT         BOOL od_save_screen(void *pBuffer);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    This function saves the entire contents of the screen, along
+               with the current cursor position and display color, to be later
+               restored using the od_restore_screen() function. Unlike the
+               od_get_text() and od_put_text() functions, the od_save_screen()
+               and od_restore_screen() functions will work in all display modes
+               (ASCII, ANSI, AVATAR and RIP).
+
+               The pBuffer parameter should point to a buffer where the current
+               screen information is to be stored. This buffer must be at least
+               4004 bytes in size.
+
+               Note that the od_save_screen() and od_restore_screen() functions
+               save the stored screen information in different formats than the
+               od_getttext() and od_puttext() functions. For this reason, you
+               cannot save the screen contents with od_save_screen() and
+               restore them with od_puttext(), or visa-versa.
+
+               Also, note that when used in RIP graphics mode, this function
+               only saves and restores textual information, and not bit-mapped
+               graphical information.
+
+               If this function fails for any reason, a value of FALSE is
+               returned, and the od_control.od_error variable is set to
+               indicate the reason for the failure. For more information on the
+               od_control.od_error variable, see page 185.
+
+
+SEE ALSO       od_restore_screen(), od_gettext(), od_puttext(), od_clr_scr()
+
+
+EXAMPLE        One case where you might wish to save and restore the contents
+               of the screen is when sysop chat mode is activated. This can be
+               accomplished by using the od_control.od_cbefore_chat and
+               od_control.od_cafter_chat variables. The following example
+               causes the screen contents to be saved and the entire screen
+               cleared, prior to entering sysop chat mode when the sysop
+               presses the "chat key". When the sysop ends chat mode, the
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 121
+
+               user's original screen is restored, and the user will be able to
+               continue working in the door as though nothing had happened.
+
+               Code to perform screen saving and restoring:
+
+                    /* Function prototypes */
+                    void before_chat_function(void);
+                    void after_chat_function(void);
+
+                    /* Buffer to hold contents of screen prior to chat */
+                    char before_chat_buffer[4004];
+                    /* Variable to store whether screen save was successful */
+                    char before_chat_saved = FALSE;
+
+                    /* Function which is called prior to entering chat mode */
+                    void before_chat_function(void)
+                       {
+                       /* Store current screen contents */
+                       before_chat_saved = od_save_screen(before_chat_buffer);
+
+                       /* Present a blank screen for chat mode */
+                       od_clr_scr();
+                       }
+
+                    /* Function which is called after exiting chat mode */
+                    void after_chat_function(void)
+                       {
+                       /* If screen was successfully saved prior to chat */
+                       if(before_chat_saved)
+                          {
+                          /* Restore original screen contents */
+                          od_restore_screen(before_chat_buffer);
+                          }
+                       }
+
+               Code included in main() function to enable screen saving and
+               restoring code:
+
+                    od_control.od_cbefore_chat = before_chat_function;
+                    od_control.od_cafter_chat = after_chat_function;
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 122
+
+OD_SCROLL()
+-------------------------------------------------------------------------------
+
+PURPOSE        Scrolls any rectangular area of the screen a specified number of
+               lines upwards or downwards. Requires ANSI/AVATAR/RIP graphics
+               mode to be active.
+
+
+FORMAT         BOOL od_scroll(INT nLeft, INT nTop, INT nRight, INT nBottom,
+                  INT nDistance, WORD nFlags);
+
+
+RETURNS        TRUE on success
+               FALSE on FAILURE
+
+
+DESCRIPTION    This function scrolls a rectangular area of the screen described
+               by the parameters nLeft, nTop, nRight and nBottom. The
+               parameters nLeft and nRight are column numbers from 1 - 80, and
+               the parameters nTop and nBottom are row numbers from 1 - 23.
+
+               The parameter nDistance indicates the direction and number of
+               lines to scroll the text in the specified area. Positive values
+               denote moving the text upwards, and negative values denote
+               moving the text downwards.
+
+               The new lines created by scrolling text will appear in the
+               current color. When this function returns, it leaves the cursor
+               at its original position, and the display color at its original
+               setting. This function requires ANSI or AVATAR modes. If ANSI
+               mode is active, this function is equivalent to calling
+               od_gettext(), od_puttext(), and then sending addition codes to
+               the modem clear the newly created lines. In ANSI mode, scrolling
+               can be accomplished more quickly if the area to be scrolled is
+               equal in width to the entire screen. This is because the
+               clearing of newly created lines is done by sending a simple
+               control sequence, instead of a line of space characters. If
+               AVATAR mode is active, scrolling of the window is accomplished
+               by sending a single 6-byte control sequence.
+
+               The last parameter to od_scroll(), nFlags, should normally be
+               set to SCROLL_NORMAL. However, if you set nFlags to
+               SCROLL_NO_CLEAR, the newly created lines at the top or bottom of
+               the screen are not cleared if it would take longer to do so.
+
+
+SEE ALSO       od_gettext(), od_puttext(), od_window_create(),
+               od_window_remove()
+
+
+EXAMPLE        For an example of a program which uses the od_scroll() function,
+               see the ex_chat.c example program, described on page 38.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 123
+
+OD_SEND_FILE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Sends an ASCII/ANSI/AVATAR/RIP file from disk, using terminal
+               emulation.
+
+
+FORMAT         BOOL od_send_file(char *pszFileName);
+
+
+RETURNS        TRUE if the file was successfully sent
+               FALSE if OpenDoors was unable to send the file
+
+
+DESCRIPTION:   This powerful function will display any ASCII, ANSI, AVATAR or
+               RIP file. The od_send_file() function can be used to display
+               existing BBS text files, such as a "logoff screen", before your
+               door hangs up on the user. You can also make use of the
+               od_send_file() function to build many of your door screens as
+               external files. This will allow you to easily create these
+               screens in an ANSI editor program, such as "TheDraw". It will
+               could also optionally allow sysops to customize your door for
+               use on their own BBS.
+
+               The od_send_file() function is called with the full path and
+               filename of the file you wish to have displayed. Thus, if you
+               wished to send the ANSI file MAINMENU.SCR, you would simply
+               call:
+
+                            od_send_file("MAINMENU.SCR");
+
+               In many cases, instead of having just one file that you want
+               displayed in particular, you will have several different files,
+               and will want a different one displayed according to the user's
+               graphics mode. For example, you might have the four files,
+               MAINMENU.ASC, MAINMENU.ANS, MAINMENU.AVT and MAINMENU.RIP; the
+               .ASC file containing no special control codes, the .ANS file
+               containing ANSI control codes, the .AVT file containing AVATAR
+               control codes, and the .RIP file containing RIP graphics control
+               codes. In this case, you can have the od_send_file() function
+               automatically select the appropriate file according to the
+               user's current display mode, by omitting the extension
+               altogether. Thus, a call to:
+
+                            od_send_file("MAINMENU");
+
+               would cause OpenDoors to automatically send the appropriate
+               file, according to the user's graphics mode settings. When the
+               od_send_file() function is used in this "automatic mode" (where
+               you do not specify a filename extension), it will look for one
+               of the four filename extensions listed below.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 124
+
+               +----------------------------------------------------------+
+               | Extension| File type                                     |
+               +----------+-----------------------------------------------|
+               |   .ASC   | Does not require any graphics mode to display |
+               |   .ANS   | Requires ANSI graphics mode to display        |
+               |   .AVT   | Requires AVATAR graphics mode to display      |
+               |   .RIP   | Requires RIP graphics mode to be displayed    |
+               +----------------------------------------------------------+
+
+
+               If the user has RIP graphics enabled, od_send_file() will first
+               search for the .RIP file. If no file exists with the specified
+               filename and a .RIP extension, od_send_file() will then search
+               for .AVT, then .ANS, and if not found .ASC. If the user has only
+               ANSI graphics enabled, od_send_file() will attempt first to
+               display the .ANS file, and if not found will search for .ASC. In
+               the case that the user is using plain-ASCII mode, this function
+               will attempt only to display the .ASC file.
+
+               When displaying a .RIP file to the remote system, OpenDoors will
+               attempt to locate and display a corresponding .AVT/.ANS/.ASC
+               file on the local system. If no such file can be found, a window
+               will be displayed, indicating the name of the .RIP file that is
+               being sent to the remote system. When a .RIP file is being
+               displayed, page pausing is disabled.
+
+               When displaying .AVT/.ANS/.ASC files, od_send_file() will send
+               any ANSI or AVATAR codes in the file directly to the remote
+               terminal, and interpret them to display on the local screen
+               (regardless of the actual filename extension). This
+               interpretation is accomplished by OpenDoor's built in terminal
+               emulator. The terminal emulator fully supports all ANSI and
+               AVATAR level 0 and level 0+ control codes. The terminal emulator
+               will also translate Remote Access/QuickBBS style control codes,
+               if enabled by setting od_control.od_no_ra_codes to FALSE. The
+               control codes supported by OpenDoors are listed in the chart on
+               the following pages. When these control codes are inserted into
+               the file, OpenDoors will replace them with various pieces of
+               user or system information.
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 125
+
+                 +-----------------------------------------------------+
+                 | CONTROL | ASCII |                                   |
+                 |  CODE   | VALUE | DESCRIPTION                       |
+                 +---------+-------+-----------------------------------|
+                 |   ^FA   | 06,65 | Displays the user's full name     |
+                 |   ^FB   | 06,66 | Location the user is calling from |
+                 |   ^FC   | 06,67 | Displays the user's password      |
+                 |   ^FD   | 06,68 | Business/data phone number        |
+                 |   ^FE   | 06,69 | Home/voice phone number           |
+                 |   ^FF   | 06,70 | Date of the user's last call      |
+                 |   ^FG   | 06,71 | Time of day of the last call      |
+                 |   ^FH   | 06,72 | The user's `A' flags settings     |
+                 |   ^FI   | 06,73 | The user's `B' flags settings     |
+                 |   ^FJ   | 06,74 | The user's `C' flags settings     |
+                 |   ^FK   | 06,75 | The user's `D' flags settings     |
+                 |   ^FL   | 06,76 | User's remaining netmail credit   |
+                 |   ^FM   | 06,77 | Number of messages posted by user |
+                 |   ^FN   | 06,78 | Last read message number by user  |
+                 |   ^FO   | 06,79 | Displays security level of user   |
+                 |   ^FP   | 06,80 | Number of times user has called   |
+                 |   ^FQ   | 06,81 | Total # of uploads by user        |
+                 |   ^FR   | 06,82 | Total KBytes uploaded by user     |
+                 |   ^FS   | 06,83 | Total # of downloads by user      |
+                 |   ^FT   | 06,84 | Total Kbytes downloaded by user   |
+                 |   ^FU   | 06,85 | # of minute user has used today   |
+                 |   ^FV   | 06,86 | User's screen length setting      |
+                 |   ^FW   | 06,87 | User's first name only            |
+                 |   ^FX   | 06,88 | User's ANSI setting               |
+                 |   ^FY   | 06,89 | User's "continue?" prompt setting |
+                 |   ^FZ   | 06,90 | Does user have screen clearing on |
+                 |   ^F0   | 06,48 | User's Full-screen editor setting |
+                 |   ^F1   | 06,49 | User's Quiet mode setting         |
+                 |   ^F2   | 06,50 | User's hot-keys setting           |
+                 |   ^F3   | 06,51 | Displays the user's alias         |
+                 |   ^F4   | 06,52 | The date of the User's first call |
+                 |   ^F5   | 06,53 | The user's date of birth          |
+                 |   ^F6   | 06,54 | User's subscription expiry date   |
+                 |   ^F7   | 06,55 | Number of days until expiry       |
+                 |   ^F8   | 06,56 | User's AVATAR setting             |
+                 |   ^F9   | 06,57 | The user's upload:download ratio  |
+                 |   ^F:   | 06,58 | User's Upload K:download K ratio  |
+                 +-----------------------------------------------------+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 126
+
+                 +-----------------------------------------------------+
+                 | CONTROL | ASCII |                                   |
+                 |  CODE   | VALUE | DESCRIPTION                       |
+                 +---------+-------+-----------------------------------|
+                 |   ^F;   | 06,59 | Full-screen message reader        |
+                 |   ^KA   | 11,65 | Total # of calls BBS has received |
+                 |   ^KB   | 11,66 | Name of the last caller to BBS    |
+                 |   ^KC   | 11,67 | Total # of active messages on BBS |
+                 |   ^KD   | 11,68 | Displays # of the first message   |
+                 |   ^KE   | 11,69 | Displays # of the last message    |
+                 |   ^KF   | 11,70 | # of times user has paged sysop   |
+                 |   ^KG   | 11,71 | Full name of the current weekday  |
+                 |   ^KH   | 11,72 | Displays total number of users    |
+                 |   ^KI   | 11,73 | Displays the current time         |
+                 |   ^KJ   | 11,74 | Displays the current date         |
+                 |   ^KK   | 11,75 | Minutes the user has been online  |
+                 |   ^KL   | 11,76 | Seconds the user has been online  |
+                 |   ^KM   | 11,77 | Minutes the user has used today   |
+                 |   ^KN   | 11,78 | Seconds the user has used today   |
+                 |   ^KO   | 11,79 | Minutes remaining for user today  |
+                 |   ^KP   | 11,80 | Seconds remaining for user today  |
+                 |   ^KQ   | 11,81 | The user's daily time limit       |
+                 |   ^KR   | 11,82 | Displays the current baud rate    |
+                 |   ^KS   | 11,83 | The current weekday in short-form |
+                 |   ^KT   | 11,84 | The user's daily download limit   |
+                 |   ^KU   | 11,85 | # of minutes until the next event |
+                 |   ^KV   | 11,86 | Time of the next system event     |
+                 |   ^KW   | 11,87 | # of node user is currently on    |
+                 |   ^KX   | 11,88 | Disconnects the user              |
+                 +-----------------------------------------------------+
+
+
+SEE ALSO       od_disp_emu(), od_list_files(), od_hotkey_menu()
+
+
+EXAMPLE        For an example of the use of the od_send_file() function in
+               displaying a custom door menu, see the EX_VOTE.C example
+               program, which is described beginning on page 38.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 127
+
+OD_SET_ATTRIB()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to change the text color in ANSI or AVATAR mode, using
+               a single IBM-PC color attribute value.
+
+
+FORMAT         void od_set_attrib(INT nColor);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    od_set_attrib() is one of two functions which change the color
+               of the currently displayed text. This function allows you to set
+               the text color using a single IBM-PC style color attribute. On
+               the other hand, the od_set_color() function allows you to set
+               the display color by specifying a foreground and background text
+               color. Generally speaking, which of these two functions you use
+               will be only a matter of personal preference. You will, however,
+               most likely find it more convenient to use the od_set_color()
+               function for changing display color. However the od_set_attrib()
+               offers the advantage of allowing you to manipulate the color to
+               be displayed as a single value, instead of two separate values.
+               This could be convenient, for example, when displaying text in a
+               user configured color. Using a single byte to represent the
+               color will likely be easier than using two. An alternative
+               method of setting the color of displayed text is to include the
+               color codes within a string displayed by the od_printf()
+               function. The benefits of doing this, along with instructions on
+               how to do this, are described in the section on the od_printf()
+               function, which begins on page 110.
+
+               This function will only have an effect if the user has ANSI,
+               AVATAR or RIP modes enabled. As a result, you can use this
+               function within your door program, and have your text
+               automatically displayed in multiple colors if graphics mode is
+               available, and displayed without colors if graphics mode is not
+               available.
+
+               Note that the color to be set is passed to this function as an
+               IBM-style screen attribute. Hence, you can set the color of text
+               to be displayed by a single hexidecimal value, encoded as
+               follows:
+
+                              +------------- Background color
+                              |
+                            0x7f
+                               |
+                               +------------ Foreground color
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 128
+
+               Where the left digit (most significant nibble) of the
+               hexidecimal number represents the background color, and the
+               right digit (least significant nibble) represents the foreground
+               color. Each of the possible colors, along with their
+               corresponding hexidecimal values, are listed in the charts,
+               below.
+
+               +-----------------------+  +--------------------------+
+               |   Foreground colors   |  |   Background  | Flashing |
+               +-----------------------|  +---------------+----------|
+               |  0  | Black           |  |  0  | Black   |   Off    |
+               |  1  | Blue            |  |  1  | Blue    |   Off    |
+               |  2  | Green           |  |  2  | Green   |   Off    |
+               |  3  | Cyan            |  |  3  | Cyan    |   Off    |
+               |  4  | Red             |  |  4  | Red     |   Off    |
+               |  5  | Magenta         |  |  5  | Magenta |   Off    |
+               |  6  | Brown           |  |  6  | Brown   |   Off    |
+               |  7  | White (grey)    |  |  7  | White   |   Off    |
+               |  8  | Bright Black    |  |  8  | Black   |    On    |
+               |  9  | Bright Blue     |  |  9  | Blue    |    On    |
+               |  a  | Bright Green    |  |  a  | Green   |    On    |
+               |  b  | Bright Cyan     |  |  b  | Cyan    |    On    |
+               |  c  | Bright Red      |  |  c  | Red     |    On    |
+               |  d  | Bright Magenta  |  |  d  | Magenta |    On    |
+               |  e  | Yellow          |  |  e  | Brown   |    On    |
+               |  f  | White (bright)  |  |  f  | White   |    On    |
+               +-----------------------+  +--------------------------+
+
+
+SEE ALSO       od_set_color(), od_disp_emu(), od_clr_scr(), od_clr_line(),
+               od_set_cursor()
+
+
+EXAMPLE        At times, you may wish to allow the user to select the color of
+               text they wish to have displayed, perhaps to configure your door
+               for the ideal colors to be displayed on their system. To
+               demonstrate the use of the od_set_attrib() function, we show
+               another function, which shows the user all 256 possible colors
+               that can be displayed, and allows the user to choose which color
+               they prefer. The function will then return the color attribute
+               value of the user's chosen color.
+
+               unsigned char choose_color(void)
+               {
+                  register unsigned char counter;   /* for displaying colors */
+                  char string[4];                    /* string input by user */
+
+                  od_set_attrib(0x07);                      /* display title */
+                  od_disp_str("Available colors:\n\r\n\r");
+
+                  for(counter=0;counter<=255;)    /* loop through all colors */
+                  {
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 129
+
+                     od_set_attrib(counter);        /* set appropriate color */
+                     od_printf("%03.3u",counter);  /* display color's number */
+                     if(((++counter)%16)==0)    /* after every 16 colors ... */
+                     {
+                        od_set_attrib(0x07);  /* ... reset display color ... */
+                        od_disp_str("\n\r");     /* ... and start a new line */
+                     }
+                  }
+
+                  od_set_attrib(0x07);         /* Allow user to choose color */
+                  od_disp_str("Which color do you prefer : ");
+                  od_input_str(string,3,'0','9');
+                  return(atoi(string));               /* Return chosen color */
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 130
+
+OD_SET_COLOR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to change the current display color in ANSI, AVATAR or
+               RIP modes, using foreground and background color values.
+
+
+FORMAT         void od_set_color(INT nForeground, INT nBackground);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    od_set_color() is one of two functions which change the color of
+               the currently displayed text. This function allows you to set
+               the text color using separate foreground an background text
+               colors, whereas od_set_attrib() allows you to set the text color
+               using a single IBM-PC style color attribute. Generally speaking,
+               which of these two functions you use is only a matter of
+               personal preference. An alternative method of setting the color
+               of displayed text is to include the color codes within a string
+               displayed by the od_printf() function. The benefits of doing
+               this, along with instructions on how to do this, are described
+               in the section on the od_printf() function, which begins on page
+               110.
+
+               This function will only have an effect if the user has ANSI,
+               AVATAR or RIP mode turned on. As a result, you can use this
+               function within your door program, and have your text
+               automatically displayed in multiple colors if graphics mode is
+               available, and displayed without colors if graphics mode is not
+               available.
+
+               The od_set_color() function accepts two parameters, the first
+               parameter being the foreground color to be used in displaying
+               text, and the second parameter being the background color to be
+               used in displaying text. For example,
+
+                               od_set_color(L_WHITE,D_BLACK);
+
+               would set the current color to Light White on Dark Black. The
+               foreground and background text colors can be any one of the
+               color values listed on the following page.
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 131
+
+
+
+               +-------------------+-----------+
+               | Foreground Color  | Value     |
+               +-------------------+-----------+
+               | Dark Black        | D_BLACK   |
+               | Dark Blue         | D_BLUE    |
+               | Dark Green        | D_GREEN   |
+               | Dark Cyan         | D_CYAN    |
+               | Dark Red          | D_RED     |
+               | Dark Magenta      | D_MAGENTA |
+               | Dark Brown        | D_BROWN   |
+               | Grey (Dark White) | D_GREY    |
+               | Light Black (Grey)| L_BLACK   |
+               | Light Blue        | L_BLUE    |
+               | Light Green       | L_GREEN   |
+               | Light Cyan        | L_CYAN    |
+               | Light Red         | L_RED     |
+               | Light Magenta     | L_MAGENTA |
+               | Yellow            | L_YELLOW  |
+               | White             | L_WHITE   |
+               +-------------------+-----------+
+
+
+               +-------------------+-----------+
+               | Background Color  | Value     |
+               +-------------------+-----------+
+               | Black             | D_BLACK   |
+               | Blue              | D_BLUE    |
+               | Green             | D_GREEN   |
+               | Cyan              | D_CYAN    |
+               | Red               | D_RED     |
+               | Magenta           | D_MAGENTA |
+               | Brown             | D_BROWN   |
+               | Grey              | D_GREY    |
+               | Blinking Black    | B_BLACK   |
+               | Blinking Blue     | B_BLUE    |
+               | Blinking Green    | B_GREEN   |
+               | Blinking Cyan     | B_CYAN    |
+               | Blinking Red      | B_RED     |
+               | Blinking Magenta  | B_MAGENTA |
+               | Blinking Brown    | B_BROWN   |
+               | Blinking Grey     | B_WHITE   |
+               +-------------------+-----------+
+
+
+SEE ALSO       od_set_attrib(), od_disp_emu(), od_clr_scr(), od_clr_line(),
+               od_set_cursor()
+
+EXAMPLE        As an example of using the od_set_color() function to set the
+               color of displayed text, we show a pair of two functions. These
+               functions will allow a program to set the foreground OR
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 132
+
+               background color of text, without setting the other. In
+               contrast, the od_set_color() function sets both the foreground
+               and background color at the same time. These function presume
+               that they are the only functions used within the door to set the
+               color of displayed text, and that the original text color prior
+               to calling either of these functions is dark white on black.
+               These function must also have access to the two global variables
+               "current_foreground" and "current_background", as defined below.
+
+
+               void set_foreground(char foreground);
+               void set_background(char background);
+               unsigned char current_foreground=D_BLACK;
+               unsigned char current_background=D_GREY;
+
+               void set_foreground(char foreground)
+               {                                       /* set new text color */
+                  od_set_color(foreground, current_background);
+                  current_foreground=foreground;      /* save new foreground */
+               }
+
+               void set_background(char background)
+               {                                       /* set new text color */
+                  od_set_color(current_foreground, background);
+                  current_background=background;      /* save new background */
+               }
+
+
+               Using these functions, you would then be able to set just the
+               foreground text color by a function call like:
+
+                         set_foreground(L_YELLOW);
+
+               Or set just the background text color by a function call like:
+
+                         set_background(D_GREY);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 133
+
+OD_SET_CURSOR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Function to reposition the text cursor in ANSI, AVATAR or RIP
+               mode
+
+
+FORMAT         void od_set_cursor(INT nRow, INT nColumn);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    This function repositions the cursor to the specified row and
+               column on the screen. nRow can have a value of 1 to 23, and
+               nColumn can have a value of 1 to 80. ANSI, AVATAR or RIP mode
+               must be active.
+
+
+SEE ALSO       od_disp_emu(), od_clr_scr(), od_clr_line(), od_set_color(),
+               od_set_attrib()
+
+
+EXAMPLE        Below is a simple example that demonstrates the use of the
+               od_set_cursor() function. Note that this example detects whether
+               or not graphics mode is available, and if it is not, will carry
+               out the same task without the use of od_set_cursor().
+
+                            od_init();         /* Initialize door operations */
+                            od_clr_scr();                /* Clear the screen */
+                            if(od_control.user_ansi || od_control.user_avatar)
+                            {               /* If graphics mode is available */
+                               od_set_cursor(1,1);           /* Display demo */
+                               od_disp_str("Top, Left Corner");
+
+                               od_set_cursor(1,70);
+                               od_disp_str("Top, Right Corner");
+
+                               od_set_cursor(15,1);
+                               od_disp_str("Fifteenth line\n\r");
+                            }
+                            else        /* If graphics mode is not available */
+                            {                                /* Display demo */
+                               od_disp_str("Top, Left Corner");
+                               od_repeat(' ', 54);
+                               od_disp_str("Top, Right Corner\n\r");
+                               od_disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n");
+                               od_disp_str("Fifteenth line\n\r");
+                            }
+                            od_get_key(TRUE);  /* Wait for user to press key */
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 134
+
+OD_SET_DTR()
+-------------------------------------------------------------------------------
+
+PURPOSE        Controls the DTR (Data Terminal Ready) signal to the modem. Used
+               primarily to cause the modem to "hang up".
+
+
+FORMAT         void od_set_dtr(BOOL bHigh);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    In certain circumstances (such as call back verification doors),
+               you may wish to "hang up" the modem without exiting your door
+               program. This can be accomplished with most modems by
+               controlling the DTR (Data Terminal Ready) signal to the modem.
+               Passing a FALSE value to od_set_dtr() causes the DTR signal to
+               be set low, and passing a TRUE value causes the DTR signal to be
+               set high. Normally, OpenDoors maintains the DTR signal in its
+               high state. Since most (but not all) modems are configured to
+               disconnect from the remote modem when the DTR signal is set low,
+               calling od_set_dtr(FALSE) can be used to hangup the modem. When
+               hanging up by this method, you should be sure to set the DTR
+               signal high again, after the carrier detect signal has
+               disappeared. For more information on determining the state of
+               the carrier detect signal, see the od_carrier() function, which
+               is described on page 51. Note that not all modems will
+               disconnect from the remote user in response to your lowering the
+               DTR signal. If your software may be used with such modems, you
+               may wish to also provide the option of disconnecting the modem
+               by sending a "hang up" command sequence to the modem.
+
+               Since OpenDoors normally monitors the carrier detect signal, and
+               exits when this signal disappears, you will have to disable
+               OpenDoor's carrier detection if you wish your program to
+               continue executing after hanging up the modem. OpenDoor's
+               automatic carrier detection can be disabled using the
+               od_control.od_disable OpenDoors control structure variable, as
+               follows:
+
+                    od_control.od_disable |= DIS_CARRIERDETECT;
+
+SEE ALSO       od_carrier(), od_exit()
+
+
+EXAMPLE        For an example of using the od_set_dtr() function to "hang up"
+               the modem, see the example that accompanies the od_carrier()
+               function, which is described on page 52.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 135
+
+OD_SET_PERSONALITY()
+-------------------------------------------------------------------------------
+
+PURPOSE        Sets the current status line / sysop function key personality to
+               be used.
+
+
+FORMAT         BOOL od_set_personality(char *pszName);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    This function changes the current status line / sysop function
+               key personality. The pszName parameter should contain the string
+               which uniquely identifies the personality to be set. This
+               function may only be used by OpenDoors programs which include
+               the OpenDoors "Multiple Personality System". To enable use of
+               the MPS, include the following line before your first call to
+               any OpenDoors function:
+
+                         od_control.od_mps=INCLUDE_MPS;
+
+               OpenDoors includes a number of built-in personalities.
+               Additional personalities may be added using the
+               od_add_personality() function, which is described on page 47.
+               The following personalities are included with this version of
+               OpenDoors:
+
+                    Name                Description
+                    -----------------------------------------------------------
+                    Standard            OpenDoors style, similar to RA 1.11
+                    PCBoard             Similar to PC-Board
+                    RemoteAccess        Similar to RemoteAccess 2.x
+                    Wildcat             Similar to Wildcat!
+
+               Personality names are not case sensitive. For more information
+               on the OpenDoors Multiple Personality System, see the section
+               which begins on page 233.
+
+               This function returns TRUE on success, or FALSE on failure. In
+               the case of a failure, the od_control.od_error variable is set
+               to indicate the nature of the failure. For more information on
+               the od_control.od_error variables, see page 185.
+
+
+SEE ALSO       od_add_personality(), od_set_statusline()
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 136
+
+OD_SET_STATUSLINE()
+-------------------------------------------------------------------------------
+
+PURPOSE        To set the currently displayed status line.
+
+
+FORMAT         void od_set_statusline(INT nSetting);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    If you have the OpenDoors status line enabled within your door
+               program (as is the default), the sysop will be able to control
+               the setting of the status line using the F1 - F10 keys on the
+               keyboard. These function keys are as follows:
+
+                         [F1] -  Display basic door and user information
+                         [F2] -  Display phone numbers and important dates
+                         [F3] -  Display security flags and up/download info
+                         [F4] -  Display system information and current time
+                         [F5] -  Display message info and user's settings
+                         [F6] -  Display chat reason and sysop's comment
+                         [F9] -  Display help information for sysop
+                         [F10] - Turn off the status line
+
+               Using the od_set_statusline() function, you can manually set
+               which of these status line settings is currently selected. The
+               od_set_statusline() accepts a single parameter, which should be
+               one of the values listed below, which indicates which status
+               line you would like to have selected:
+
+               +---------------+---------------+------------------------------+
+               |               | Corresponding |                              |
+               | Value         | Function Key  | Meaning                      |
+               +---------------+---------------+------------------------------+
+               | STATUS_NORMAL | [F1]          | Basic door and user info     |
+               | STATUS_NONE   | [F10]         | Turn off status line         |
+               | STATUS_HELP   | [F9]          | Displays help for the sysop  |
+               | STATUS_USER1  | [F2]          | Phone Numbers and dates      |
+               | STATUS_USER2  | [F3]          | Security flags & up/downloads|
+               | STATUS_USER3  | [F5]          | Message info & user settings |
+               | STATUS_USER4  | [F6]          | Chat reason and sysop comment|
+               | STATUS_SYSTEM | [F4]          | System info & current time   |
+               +---------------+---------------+------------------------------+
+               (Note that these keys may be customized using variables in the
+                OpenDoors control structure.)
+
+               Keep in mind that the od_set_statusline() function only
+               temporarily changes the current status line setting, and that
+               the sysop will still be able to change the status line to any of
+               the other settings using the function keys. For instance, if you
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 137
+
+               wished to allow the sysop to normally see all 25 lines of text
+               displayed by your door, but at the same time to still allow the
+               sysop to turn on the status line at any time, you could place
+               the line
+
+                         od_set_statusline(STATUS_NONE);
+
+               at the beginning of your program. Similarly, when the user pages
+               the sysop, OpenDoors itself calls
+
+                         od_set_statusline(STATUS_USER4);
+
+               in order to display the status line which shows the user's
+               reason for chat, while still allowing the sysop to switch back
+               to any of the other status lines.
+
+               If you wish to permanently turn off the OpenDoor's status line,
+               without allowing the sysop to be able to turn it back on using
+               the sysop function keys, simply set the
+               "od_control.od_status_on" variable to FALSE. This variable is
+               described in the OpenDoors control structure section of this
+               manual, which begins on page 148.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 138
+
+OD_SLEEP()
+-------------------------------------------------------------------------------
+
+PURPOSE        Suspends program execution, yielding control to other tasks in a
+               multitasking environment.
+
+
+FORMAT         void od_sleep(tODMilliSec Milliseconds);
+
+
+RETURNS        N/A
+
+
+DESCRIPTION    od_sleep() suspends execution of your program for the specified
+               number of milliseconds. Note that under the DOS version of
+               OpenDoors, this value is rounded to the nearest 55 milliseconds.
+               While the program's execution is suspended, od_sleep() yields
+               control of the processor to other tasks in a multitasking
+               environment.
+
+               Calling od_sleep() with a sleep time of 0 causes control to be
+               yielded to other waiting processes without imposing a minimum
+               sleep time. If no other processes are waiting to execute, the
+               function returns immediately. OpenDoors automatically calls
+               od_sleep(0) itself in most of the situations where there is a
+               need to do so. However, there may be circumstances under which
+               od_sleep(0) can be used to improve performance. In particular,
+               od_sleep(0) can be used to improve the performance of other
+               applications that are also running at the same time as yours. By
+               calling od_sleep(0), you are essentially telling the operating
+               system that your program doesn't currently need all of the
+               processing time that has been allocated to it. While appropriate
+               use of od_sleep(0) can improve overall system performance,
+               overusing od_sleep(0) can dramatically degrade the performance
+               of your own program. The only way to determine the optimal use
+               of od_sleep(0)  is by trial and error.
+
+               The most common situation where you might want to use
+               od_sleep(0) is when your program cannot do anything useful until
+               you receive input from the user, but for some reason you cannot
+               call od_get_key(TRUE). Rather than sitting in a tight loop,
+               repeatedly checking where the user has pressed a key yet, it is
+               better to call od_sleep(0) to let other tasks run for a while
+               before checking again. OpenDoors calls od_sleep(0) itself under
+               any of the following circumstances:
+
+                    - When transmitting characters, if the outbound serial
+                      buffer is full, OpenDoors yields while waiting for some
+                      of the characters in the buffer to be sent.
+                    - During od_get_key(), if called with the wait parameter
+                      set to TRUE.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 139
+
+                    - While waiting for input, during the execution of any of
+                      the following input functions: od_get_answer(),
+                      od_hotkey_menu() (after menu has been displayed),
+                      od_popup_menu(), od_edit_str(), od_input_str().
+                    - While pausing at the end of a screen during
+                      od_send_file(), od_list_files(), od_hotkey_menu().
+                    - During chat mode.
+
+
+SEE ALSO       od_kernel()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 140
+
+OD_SPAWN()
+-------------------------------------------------------------------------------
+
+PURPOSE        To facilitate easy execution of child tasks from doors.
+
+
+FORMAT         BOOL od_spawn(char *pszCommandLine);
+
+
+RETURNS        TRUE on success,
+               FALSE on failure
+
+
+DESCRIPTION    This function allows you to easily run other programs from
+               within your door programs, such as external file transfer
+               utilities, compression utilities, and so on.
+
+               This function will attempt to swap OpenDoors and your entire
+               door to expanded memory or disk. OpenDoors swapping can be
+               controlled by the OpenDoors control structure variables,
+               od_swapping_disable, od_swapping_ems and od_swap_path. The
+               od_spawn...() functions first attempt to swap OpenDoors to EMS
+               memory. If enough EMS 3.2 or later memory is available, it will
+               be used. If not, OpenDoors will swap to a disk file in the
+               directory specified by the od_control.od_swap_path variable.
+
+               Unlike the other Turbo C(++) / Borland C++ library functions
+               such as system() or spawnf(), this function will automatically
+               store the door screen prior to executing the sub-program, and
+               will restore the screen upon return. This function will also
+               store the current drive and directory settings prior to
+               executing the program, and restore them after the program has
+               returned.
+
+               Normally, the user's time will continue to be decreased during
+               the execution of the od_spawn() function. However, you can
+               freeze the user's time during the spawn process by using the
+               OpenDoors control structure variable od_spawn_freeze_time.
+
+
+SEE ALSO       od_spawnvpe()
+
+
+EXAMPLE        Below are a few examples of various uses of the od_spawn()
+               function:
+
+               To run the command processor from within your door program, to
+               allow the sysop access to the DOS shell, simply use the
+               following line of code:
+
+                              od_spawn(getenv("COMSPEC"));
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 141
+
+               The following function is an example of using the od_spawn()
+               function to call DSZ, allowing the user to download a file. You
+               pass the name of the file that you wish to send to the user.
+               This function will then ask the user what transfer protocol they
+               would like to use, generate the appropriate DSZ command line,
+               and then transmit the file to the user. Note that in order to
+               use a door which implements this function, the external file
+               transfer program "DSZ" must be available in the current search
+               path. As an alternative, you may want to allow the sysop to
+               specify the location of the DSZ file from within a configuration
+               program. If you wish to receive a file (allow the user to
+               upload), instead of sending one, simply change the "s" in the
+               command line to a "r".
+
+               char download(char *filename)
+               {
+                  char commandline[80];/* string containing DSZ command line */
+                  char protocol;   /* character representing chosen protocol */
+
+                                                    /* display protocol menu */
+                  od_printf("Select File Transfer Protocol:\n\r");
+                  od_printf("   [X] XModem\n\r");
+                  od_printf("   [Y] YModem\n\r");
+                  od_printf("   [Z] ZModem\n\r");
+                  od_printf("or press [A] to abort transfer\n\r");
+
+                  do            /* loop until valid protocol has been chosen */
+                  {
+                     protocol=od_get_key();                       /* get key */
+                                              /* abort if [A] key is pressed */
+                     if(protocol=='a' || protocol=='A') return(FALSE);
+                  } while(protocol!='x' && protocol!='y' && protocol!='z' &&
+                          protocol!='X' && protocol!='Y' && protocol!='Z');
+
+                  od_printf("Begin receiving file now or press [CTRL]-[X] to
+                             abort\n\r");
+                                                /* generate DSZ command line */
+                  sprintf(commandline,"dsz port %d s%c %s",
+                          od_control.port+1,
+                          protocol,
+                          filename);
+
+                  return(od_spawn(commandline));             /* spawn to DSZ */
+               }
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 142
+
+
+OD_SPAWNVPE()
+-------------------------------------------------------------------------------
+
+PURPOSE        To facilitate easy execution of child tasks from doors. Allows
+               specification of child's environment, returns errorlevel
+               returned by child task, and searches path for the executable
+               file.
+
+
+FORMAT         INT16 od_spawnvpe(INT16 nModeFlag, char *pszPath,
+                  char *papszArg[], char *papszEnv[]);
+
+
+RETURNS        -1 on failure
+               errorlevel returned by child process on success
+
+
+DESCRIPTION    This function behaves very similarly to the od_spawn() function.
+               Thus, to save space in the manual, I will not recapitulate what
+               is already said in the description of the od_spawn() function.
+               Instead, this description concentrates on the additional
+               features available through the od_spawnvpe() function. If you
+               are not already familiar with the od_spawn() function, take a
+               moment now to review the description of that function.
+
+               The od_spawn() function (the OpenDoors "quick-spawn" function)
+               is designed to be quick and easy to use, but does not have all
+               of the features available in the od_spawnvpe() function. In
+               addition to the features of the od_spawn() function, the
+               od_spawnvpe() function also provides the following features:
+
+                         - od_spawnvpe() will search the "path" for the file
+                           to be executed.
+
+                         - od_spawnvpe() allows you to pass an altered
+                           environment to the child process.
+
+                         - od_spawnvpe() returns the errorlevel returned by
+                           the child process.
+
+               The parameters passed to the od_spawnvpe() function are
+               identical to those passed to the C spawnvpe() function. The
+               first parameter should usually the be P_WAIT flag. The second
+               parameter is the name of the child program to execute. If a full
+               path to the child program is not specified, and the child
+               program does not exist in the current directory, OpenDoors will
+               search the directories listed by the PATH environment variable.
+               Also, if the .EXE or .COM extension is not provide, OpenDoors
+               will look first for a .COM file, and if not found, for a .EXE
+               file. The third parameter is an array of arguments to pass to
+               the child process, or NULL if no arguments are to be passed. The
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 143
+
+               fourth parameter is the environment to be passed to the child
+               process, or NULL if the a copy of the current environment should
+               be used.
+
+
+SEE ALSO       od_spawn()
+
+
+EXAMPLE        For an example of the use of the od_spawn...() functions, see
+               the example accompanying the od_spawn() function. As a specific
+               example of the od_spawnvpe function, consider the following code
+               which executes the "TEST.EXE" program.
+
+                         od_spawnvpe(P_WAIT,"TEST.EXE",NULL,NULL);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 144
+
+OD_WINDOW_CREATE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Creates a popup window of the specified size and color, storing
+               the contents of the screen "under" the window. The window can
+               later be removed from the screen, restoring the original
+               contents of the screen "under" the window, using the
+               od_window_remove() function. Requires ANSI, AVATAR or RIP mode.
+
+
+FORMAT         void *od_window_create(INT nLeft, INT nTop, INT nRight, INT
+               nBottom,
+                    char *pszTitle, BYTE btBorderCol, BYTE btTitleCol,
+                    BYTE btInsideCol, INT nReserved);
+
+
+RETURNS        Pointer to newly allocated window structure on success
+               NULL on failure
+
+
+DESCRIPTION    This function creates a pop-up window on the remote and local
+               screens. The contents of the screen beneath the window are
+               stored, to allow the window to later be removed from the screen
+               using the od_window_remove() function. The window is drawn using
+               the boarder characters defined in the already existing
+               od_control.od_box_chars[] array. The boarder is displayed using
+               the color attribute specified in btBorderCol. The working area
+               of the window is created in the color specified in btInsideCol.
+               A title may optionally be displayed on the window by specifying
+               a string in pszTitle. This title will be displayed in the color
+               specified by btTitleCol. If you do not wish a title to be
+               displayed, pass an empty string or NULL pointer in pszTitle. On
+               success, od_window_create() will return a pointer to a buffer
+               which was allocated to store information on the window and the
+               contents of the screen "under" the window. This pointer should
+               at some point be passed in a call to od_window_remove().
+
+               This function requires ANSI, AVATAR or RIP graphics mode. If
+               AVATAR mode is active, this function will take advantage of
+               special AVATAR control sequences to display the window much
+               faster than is possible in ANSI mode. In ANSI mode, window
+               display will be slightly faster if btBorderCol and btTitleCol
+               are equal. Note that the nReserved parameter of this function is
+               not currently used. To preserve compatibility with future
+               versions of OpenDoors, this parameter should always be set to 0.
+               Currently, the size of the buffer allocated to store the window
+               information will be (length*width*2) + 4 bytes in size.
+
+               If od_window_create() fails for any reason, a value of NULL is
+               returned, and the od_control.od_error variable is set to
+               indicate the reason for the failure. For more information on the
+               od_control.od_error variable, see page 185.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 145
+
+
+
+SEE ALSO       od_window_remove(), od_draw_box(), od_gettext(), od_puttext(),
+               od_save_screen(), od_restore_screen(), od_scroll()
+
+
+EXAMPLE        For an example of the use of the od_window_create() function,
+               see the included ex_chat.c example program.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 146
+
+OD_WINDOW_REMOVE()
+-------------------------------------------------------------------------------
+
+PURPOSE        Removes a window previously created using od_window_create(),
+               restoring the original screen background.
+
+
+FORMAT         BOOL od_window_remove(void *pWinInfo);
+
+
+RETURNS        TRUE on success
+               FALSE on failure
+
+
+DESCRIPTION    The od_window_remove() function removes a window from the screen
+               which was previously created by od_window_create(), and
+               deallocates the memory which was allocated to store the window
+               information. The contents of the screen beneath the window is
+               restored to appear as it did prior to the call to
+               od_window_create(). pWinInfo must point to the value returned by
+               od_window_create().
+
+               Note that overlapping windows must be removed in the reverse
+               order from which they were created for proper display results.
+               The last window to be created must be the first window to be
+               removed.
+
+               If od_window_remove() fails for any reason, a value of FALSE is
+               returned, and the od_control.od_error variable is set to
+               indicate the reason for the failure. For more information on the
+               od_control.od_error variable, see page 185.
+
+
+SEE ALSO       od_window_create(), od_draw_box(), od_gettext(), od_puttext(),
+               od_save_screen(), od_restore_screen(), od_scroll()
+
+
+EXAMPLE        For an example of the use of the od_window_remove() function,
+               see the included ex_chat.c example program.
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 147
+
+ 555555
+ 55
+ 55
+ 55555
+     55
+     55
+ 55555
+-------------------------------------------------------------------------------
+CHAPTER 5 - THE OPENDOORS CONTROL STRUCTURE
+
+
+
+
+INTRODUCTION TO THE CONTROL STRUCTURE
+-------------------------------------------------------------------------------
+
+               The OpenDoors "Control Structure" is used by OpenDoors in order
+               to provide you with a wide range of information about the system
+               on which you door is running, and the user who is currently
+               using the door, along with providing you a means by which to
+               customize much of OpenDoor's behavior. Using the OpenDoors
+               control structure, you can access or alter information about the
+               user who is online, information about the system on which your
+               door is running, and information about OpenDoors itself. You can
+               also use the control structure to customize all of the text
+               displayed by OpenDoors, the function keys to which it responds,
+               and many other aspects of OpenDoor's behavior.
+
+               The OpenDoors control structure is quite simply a normal C
+               "struct", named od_control, and is defined in the OPENDOOR.H
+               file. This "struct" contains many different variables, which
+               provide you access to the information provided by the control
+               structure. Hence, to access the contents of a control structure
+               variable, for example the variable "system_name" which contains
+               the name of the BBS the door is running under, you would use:
+
+                                  od_control.system_name
+
+               The following section of this chapter contains a complete
+               reference to all of the variables which make up the OpenDoors
+               control structure. This reference includes the name, type and
+               complete description of the use of each variable. The reference
+               is divided into the following categories of variables, with the
+               reference to the variables in each section beginning on the
+               listed page.
+
+                         Door Information File Statistics..................150
+                         Modem Settings....................................153
+                         BBS and Caller Information........................158
+                         Door Settings.....................................182
+                         OpenDoors Behavior Customization..................187
+                         Function Keys Customization.......................212
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 148
+
+                         Color Customization...............................237
+                         Text Customization................................217
+
+               Within each section, variables are listed alphabetically by
+               name.
+
+               Also, in order to make use of some of the variables in the
+               OpenDoors control structure, it is important to understand the
+               concepts of Boolean (TRUE/FALSE), and bit-mapped flag variables.
+               If you are not familiar with these two terms, they are described
+               in detail in the glossary, located towards the end of this
+               manual.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 149
+
+CONTROL STRUCTURE - DOOR INFO FILE STATS
+-------------------------------------------------------------------------------
+
+               The following OpenDoors control structure variables provide your
+               program with information concerning the door information file
+               from which OpenDoors obtained the BBS and caller information
+               that is found elsewhere in the control structure. The following
+               control structure items are listed in this section:
+
+               info_path                Sets the location and, optionally, the
+                                        name of the door information file
+
+               od_info_type             Type of door information file that was
+                                        found
+
+               od_node                  Node number the door is running under
+
+               user_timeofcreation      The time at which the door information
+                                        file was created
+
+
+
+
+-------------------------------------------------------------------------------
+info_path      char od_control.info_path[60];
+
+               If used, this variable should be set prior to calling od_init()
+               or any other OpenDoors function. This variable allows you to
+               control where OpenDoors will look for the door information (drop
+               file). By default, OpenDoors searches for the door information
+               file in the current directory. If this variable is set to the
+               name of some other directory, OpenDoors will first search for
+               any door information files in that directory. If you only wish
+               OpenDoors to look for a particular type of door information file
+               (for instance, you want OpenDoors to only read a DORINFO1.DEF,
+               and ignore any DOOR.SYS file), you can specify the full path and
+               filename of the file you wish OpenDoors to use.
+
+               It is usually a good idea to design your door to allow the
+               system operator to set the location of the door information
+               file. This will allow the sysop to place your door in its own
+               directory, and will facilitate the use of your door on multi-
+               line BBS systems. If you are using the OpenDoors configuration
+               file system, then the system operator can set the door
+               information file location and/or name using the BBSDir keyword.
+               However, you may also wish to allow the location of the door
+               information file to be set on the command line. The following
+               example illustrates a method of reading and setting the location
+               of the door information file from the door's command line:
+
+               #include "opendoor.h"
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 150
+
+               main(int argc, char *argv[])
+                  {
+                  if(argc>1) strncpy(od_control.info_path,argv[1],59);
+
+                  od_disp_str("This is a sample OpenDoors door.\n\r");
+                  od_disp_str("Press any key to continue...\n\r");
+                  od_get_key(TRUE);
+                  od_exit(20);
+                  }
+
+
+
+-------------------------------------------------------------------------------
+od_info_type   char od_control.od_info_type;
+
+               This variable indicates the type of information file from which
+               OpenDoors has obtained the BBS and caller information that is
+               found elsewhere in the OpenDoors control structure. This
+               variable will have one of the following values, indicating that
+               the door information file was of the corresponding type:
+
+                     +----------------+----------------------------+
+                     |  od_info_type  | Door Information File Type |
+                     |      Value     |                            |
+                     +----------------+----------------------------+
+                     | DORINFO1       | DORINFO?.DEF               |
+                     | EXITINFO       | EXITINFO.BBS (Normal)      |
+                     | RA1EXITINFO    | EXITINFO.BBS (Extended)    |
+                     | RA2EXITINFO    | EXITINFO.BBS (RA 2.x)      |
+                     | QBBS275EXITINFO| EXITINFO.BBS (QuickBBS)    |
+                     | CHAINTXT       | CHAIN.TXT                  |
+                     | SFDOORSDAT     | SFDOORS.DAT                |
+                     | CALLINFO       | CALLINFO.BBS               |
+                     | DOORSYS_GAP    | DOOR.SYS (GAP/PC-Board)    |
+                     | DOORSYS_DRWY   | DOOR.SYS (Doorway style)   |
+                     | DOORSYS_WILDCAT| DOOR.SYS (WildCat standard)|
+                     | CUSTOM         | Custom door information    |
+                     |                | file, defined in config    |
+                     |                | file.                      |
+                     | NO_DOOR_FILE   | No drop file was found.    |
+                     +----------------+----------------------------+
+
+               The value of this variable is only valid AFTER od_init() or some
+               OpenDoors function has been called.
+
+               Note that this variable should be treated as a read-only
+               variable, and should not normally be altered by your program.
+               Altering this variable may cause OpenDoors to re-write a
+               different type of door information file upon exiting, than was
+               read upon startup.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 151
+
+
+-------------------------------------------------------------------------------
+od_node        char od_control.od_node;
+
+               This variable indicates the node number that the door is running
+               under. If this information is supplied by the BBS in the door
+               information file, the node number will be automatically by
+               OpenDoors. Specifically, the node number can be determined
+               automatically from systems that produce an SFDOORS.DAT, PC-
+               Board/GAP style DOOR.SYS or Wildcat style DOOR.SYS door
+               information file. If this information is not supplied in the
+               door information file, but is provided by the sysop in the
+               door's configuration file, OpenDoors will use the value found
+               there. Alternatively, you can set this variable manually.
+
+               On systems that produce a DORINFO?.DEF file, OpenDoors will use
+               this variable to determine which DORINFO?.DEF file to search
+               for. For instance, if od_control.od_node is set to 3, OpenDoors
+               will first search for a DORINFO3.DEF file. If this file is not
+               found, OpenDoors will then default to the DORINFO1.DEF filename.
+
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_timeofcreation[6];
+_timeof
+creation       This variable contains the time of day at which the door
+               information file was created. This variable is available only
+               when the door is running under a system that produces an
+               EXITINFO.BBS file. To determine what type of door information
+               file your door is running under, see the od_control.od_info_type
+               variable, below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 152
+
+CONTROL STRUCTURE - SERIAL PORT SETTINGS
+-------------------------------------------------------------------------------
+
+               The following OpenDoors control structure items store the
+               communications settings that OpenDoors uses to communicate with
+               the modem. These values are normally set upon the first call to
+               an OpenDoors function, during the od_init() procedure. However,
+               you may need to manual set this variables if:
+
+                    - you wish to allow greater configurability of your door
+                    - you are reading the door information file yourself
+                    - you are using the OpenDoors to write a non-door
+                     program
+
+               Some of these variables are always used by OpenDoors, while
+               others are only relevant if OpenDoor's built-in serial
+               communications code is being used instead of a FOSSIL driver.
+               Those that are only used when no FOSSIL driver is present are
+               denoted by an [*] in the list below.
+
+               The control structure variables controlling OpenDoor's serial
+               port settings are as follows:
+
+               od_control.baud            Serial Port BPS rate
+
+               od_control.od_connect_sppedThe modem connection BPS rate
+
+               od_control.od_com_address  Serial Port address [*]
+
+                " " .od_com_fifo_trigger  16550A FIFO trigger size
+
+                " " .od_com_flow_control  Type of flow control to use.
+
+               od_control.od_com_irq      Serial Port IRQ number [*}
+
+               od_control.od_com_method   Is FOSSIL or built-in serial I/O
+                                          being used
+
+               od_control.od_com_no_fifo  Disables use of 16550A FIFOs [*]
+
+               od_control.od_com_rx_buf   Size of receive buffer [*]
+
+               od_control.od_com_tx_buf   Size of transmit buffer [*]
+
+               od_control.od_no_fossil    Prevents OpenDoors from using a
+                                          FOSSIL driver, even if one is
+                                          available.
+
+               od_control.od_open_handle  Allows a live serial port handle to
+                                          be passed to OpenDoors.
+
+               od_control.port            Serial port number, 0 based.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 153
+
+
+
+
+-------------------------------------------------------------------------------
+baud           unsigned long od_control.baud;
+
+               This variable contains the BPS rate at which the computer is
+               communicating with the modem, not to be confused with the BPS
+               rate at which the local modem is communicating with the remote
+               modem.
+
+               A value of 0 indicates that the program is operating in local
+               mode.
+
+               If a FOSSIL driver is being used for serial I/O, this value is
+               ignored if it does not correspond to one of the baud rates that
+               an application can directly set a FOSSIL driver to. The BPS
+               rates recognized by FOSSIL drivers are: 300, 600, 1200, 2400,
+               4800, 9600, 19200, 38400. If any other BPS rate is to be used,
+               the FOSSIL driver must be locked at that BPS from the FOSSIL
+               driver command-line. When locked, FOSSIL drivers ignore any
+               attempt by an application to change the BPS rate of the locked
+               port. For this reason, the od_control.baud setting has no effect
+               on the FOSSIL driver if it is locked.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        int od_control.od_com_address;
+address
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the serial port address can be set from the FOSSIL driver
+               command line).
+
+               This variable may optionally be set to specify the base address
+               of the serial port to be used. For ports COM1: through COM4:,
+               OpenDoors can normally determine the serial port address
+               automatically. However, for other serial ports, the port address
+               must be specified using this variable. If you are not specifying
+               a serial port address with this variable, do not change it's
+               default value of 0.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        char od_control.od_com_fifo_trigger;
+fifo_trigger
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the IRQ line can be set from the FOSSIL driver command
+               line).
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 154
+
+
+               This variable sets the number of bytes that will be placed in
+               the 16550A UART FIFO buffers before an interrupt is triggered,
+               if the 16550A UART FIFOs are used. Valid values are 1, 4, 8 and
+               14.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        unsigned char od_control.od_com_flow_control;
+flow_control
+               This variable sets the type of serial I/O flow control to use.
+               By default, this variable is set to COM_DEFAULT_FLOW, which
+               specifies the default mode of flow control. Most often, this
+               will be RTS/CTS flow control. A value of COM_RTSCTS_FLOW
+               explicitly enables RTS/CTS flow control. A value of COM_NO_FLOW
+               disables all flow control. If you are going to change the value
+               of this variable, it should be set prior to your first call to
+               any OpenDoors function.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        unsigned char od_control.od_com_irq;
+irq
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the IRQ line can be set from the FOSSIL driver command
+               line).
+
+               This variable may optionally be set to specify the IRQ line to
+               be used for the serial port. By default, OpenDoors uses the
+               normal IRQ 4 line for ports COM1: and COM3:, and IRQ 3 for ports
+               COM2: and COM4:. To override this default, the IRQ line can be
+               set using this variable. If you are not specifying an IRQ line
+               with this variable, do not change it's default value of 0.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        char od_control.od_com_method;
+method
+               This read-only variable reports the method that OpenDoors is
+               using for serial I/O. This variable is set during od_init() or
+               the first call to an OpenDoors function. This variable can be
+               one of the following values:
+
+               COM_FOSSIL          - Indicates that a FOSSIL driver is being
+               used
+               COM_INTERNAL   - Indicates that OpenDoor's internal serial I/O
+                                code is being used.
+               COM_WIN32      - Indicates that the Win32 communication system
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 155
+
+                                is being used.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        char od_control.od_com_no_fifo;
+no_fifo
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the receive buffer size can be set from the FOSSIL driver
+               command line).
+
+               Normally, OpenDoors will use a 16550A FIFO buffer if a 16550A
+               UART is installed. You can disable the use of the 16550A FIFO
+               buffer by setting this variable to TRUE.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        unsigned int od_control.od_com_rx_buf;
+rx_buf
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the receive buffer size can be set from the FOSSIL driver
+               command line).
+
+               This variable allows you to set the size of OpenDoor's serial
+               I/O receive buffer. If you do not set this buffer size, a
+               default value of 256 characters is used. Normally, this buffer
+               size is more than large enough for door programs. However, if
+               you find that inbound characters are lost before they can be
+               processed by your program, you may wish to increase the size of
+               this buffer.
+
+               This variable should only be changed before your first call to
+               od_init() or any other OpenDoors function.
+
+
+
+-------------------------------------------------------------------------------
+od_com_        unsigned int od_control.od_com_tx_buf;
+tx_buf
+               This variable is only used when OpenDoors is NOT performing
+               serial I/O using a FOSSIL driver. (When a FOSSIL driver is being
+               used, the receive buffer size can be set from the FOSSIL driver
+               command line).
+
+               This variable allows you to set the size of OpenDoor's serial
+               I/O transmit buffer. If you do not set this buffer size, a
+               default value of 1024 characters is used.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 156
+
+               This variable should only be changed before your first call to
+               od_init() or any other OpenDoors function.
+
+
+
+-------------------------------------------------------------------------------
+od_connect_    DWORD od_control.od_connect_speed;
+speed
+               This variable contains the best guess at the current modem
+               connection speed. This information is currently only accurate if
+               a DOOR.SYS file is being used. In other situations, it will
+               always be set to be equal to od_control.baud.
+
+
+
+-------------------------------------------------------------------------------
+od_open_       DWORD od_control.od_open_handle;
+handle
+               Under platforms where this is supported (currently only the
+               Win32 version of OpenDoors), this variable can be used to pass a
+               live serial port handle to OpenDoors, which OpenDoors will use.
+               OpenDoors will not close this handle when it exits. If this
+               value is set to 0, OpenDoors will open and close the serial port
+               itself.
+
+
+
+-------------------------------------------------------------------------------
+port           char od_control.port;
+
+               This variable contains the serial port number that the modem is
+               connected. This number is 0 based, so that a value of 0
+               corresponds to COM1:, a value of 1 corresponds to COM2:, and so
+               on. This value will normally be set by the od_init() function,
+               when the door information file is read, and should not be
+               changed after modem initialization has been carried out by the
+               od_init() function.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 157
+
+CONTROL STRUCTURE - BBS AND CALLER INFORMATION
+-------------------------------------------------------------------------------
+
+               As we have already described, there are two types of variables
+               in the OpenDoors control structure. Some of the variables are
+               simply used to allow you to customize OpenDoor's various
+               features, such as altering colors, prompts, timeouts, etc. Other
+               variables in the OpenDoors control structure serve to provide
+               you with information about the user who is online and the BBS
+               system your door is running under. This section deals with those
+               variables that provide you with information about the BBS and
+               the user.
+
+               The information in these variables is read from the door
+               information file, a small file created by the BBS specifically
+               for the purpose of communicating with door programs. Depending
+               on what BBS system your door is running under, the type of door
+               information file will vary. Since different door information
+               files do not all provide the same pieces of information, some
+               variables in this section will only be available when your door
+               is running under particular BBS systems.  Other variables will
+               be available with many or all BBS systems. In the description of
+               each variable in this section, we indicate under which door
+               information files the particular variable will be . So, if you
+               wish to access a variable that is only under certain door
+               information files, your program should test whether or not the
+               required information is available under the particular door
+               information file that was found. In order to determine which
+               door information file your door is running under, you should use
+               the od_control.od_info_type variable. This variable is described
+               in the section which begins on page 150. If you test the value
+               of the od_control.od_info_type variable, and find that the
+               required information is not available, you may wish to simply
+               use some sort of default value for the variable, or
+               alternatively, not allow your door to run under certain BBS
+               systems. Another possibility, if the required information is not
+               available, is imply to obtain this information from the user
+               yourself. For example, if you wished to know the length of the
+               user's screen, when this information is not available from the
+               door information file, you could simply prompt the user for
+               their screen length the first time they use your door. This
+               information could then be stored in your door's data files for
+               future reference.
+
+               As an example of testing what door information file your door is
+               running under, consider the case where you wanted to display the
+               user's birthday. The example below will display the user's
+               birthday if it is known, and otherwise, print the string
+               "unknown".
+
+                         if(od_control.od_info_type == RA1EXITINFO
+                            od_control.od_info_type == RA2EXITINFO)
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 158
+
+                         {
+                            od_disp_str(od_control.user_birthday);
+                         }
+                         else
+                         {
+                            od_disp_str("Unknown");
+                         }
+
+               The chart below lists the door information file formats that
+               OpenDoors recognizes, along with example BBS systems that
+               produce these files and a reference letter for each type. Thus,
+               an OpenDoors door can run DIRECTLY under ANY BBS SYSTEM that
+               produces one of these files formats, and under ANY OTHER BBS
+               system when used in conjunction with a door information file
+               conversion utility.
+
++--------------------------+----------------------------------------+
+| FILE FORMAT              | EXAMPLE BBS SYSTEMS                    |
++--------------------------+----------------------------------------+
+| CHAIN.TXT                | WWIV                                   |
++--------------------------+----------------------------------------+
+| DORINFO1.DEF             | RBBS-PC                                |
++--------------------------+----------------------------------------+
+| DORINFO1.DEF             | QuickBBS                               |
+|      &                   | Remote Access (versions 0.01-0.04)     |
+| EXITINFO.BBS (Std. Ver.) |                                        |
++--------------------------+----------------------------------------+
+| DOOR.SYS (DoorWay Style) | Remote Access                          |
++--------------------------+----------------------------------------+
+| DOOR.SYS (PCB/GAP Style) | PC-Board                               |
+|                          | GAP                                    |
++--------------------------+----------------------------------------+
+| DOOR.SYS (WildCat Style) | Wildcat 3.00 and above                 |
+|                          | Telegard                               |
++--------------------------+----------------------------------------+
+| SFDOORS.DAT              | Spitfire                               |
+|                          | TriBBS                                 |
++--------------------------+----------------------------------------+
+| CALLINFO.BBS             | WildCat 2.xx                           |
++--------------------------+----------------------------------------+
+| DORINFO1.DEF             | Remote Access (versions 1.00 and later)|
+|      &                   |                                        |
+| EXITINFO.BBS (Ext. Ver.) |                                        |
++--------------------------+----------------------------------------+
+
+
+
+               The chart on the following page lists all of the OpenDoors
+               control structure variables in this section, along with a brief
+               description of their use. The variables are then described in
+               detail, below.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 159
+
++-----------------------+-----------------------------------------------+
+| VARIABLE NAME         | VARIABLE CONTENTS                             |
++-----------------------+-----------------------------------------------+
+| EMSI INFORMATION      | Information on current IEMSI session          |
+| event_status          | The status of the next system event           |
+| event_starttime       | The start time of the next system event       |
+| event_errorlevel      | The errorlevel of the next system event       |
+| event_days            | The days of the week to execute the event     |
+| event_force           | Whether the next system event is forced       |
+| event_last_run        | When the next system event was last run       |
+| sysop_name            | The name of the BBS's sysop                   |
+| system_calls          | Total number of calls BBS has received        |
+| system_last_caller    | The name of the last caller to the BBS        |
+| system_last_handle    | The handle (alias) of the last caller         |
+| system_name           | The name of the BBS                           |
+| TIMELOG VARIABLES     | The times at which the BBS has been most busy |
+| user_ansi             | Whether the user has ANSI graphics mode on    |
+| user_attribute        | User attribute bit-mapped flags               |
+| user_attrib2          | Second set of user attribute bit-mapped flags |
+| user_attrib3          | Third set of user attribute flags             |
+| user_avatar           | Whether the user has AVATAR graphics mode on  |
+| user_birthday         | The date the user was born                    |
+| user_callsign         | The user's amateur radio call sign            |
+| user_combinedrecord   | The user's combined message areas settings    |
+| user_comment          | Sysop's comment about the user                |
+| user_credit           | Amount of NetMail credit the user has         |
+| user_dataphone        | The user's data phone number                  |
+| user_date_format      | Format user wishes to have dates displayed in |
+| user_deducted_time    | Total time that has been subtracted from user |
+| user_downk            | Total Kilobytes downloaded by the user        |
+| user_downlimit        | User's daily download limit                   |
+| user_downloads        | Total number of files downloaded by the user  |
+| user_echomailentered  | Whether or not the user has entered EchoMail  |
+| user_error_free       | Whether or not connection is error-free       |
+| user_file_area        | The user's current file area                  |
+| user_firstcall        | Date of the user's first call to the BBS      |
+| user_flags            | User's sysop-defined flag settings            |
+| user_forward_to       | Name to forward user's mail to                |
+| user_group            | User's group number                           |
+| user_handle           | User's alias                                  |
+| user_homephone        | User's home telephone number                  |
+| user_language         | User's language setting                       |
+| user_last_pwdchange   | Total calls since last password change        |
+| user_lastdate         | Date of the user's last call                  |
+| user_lastread         | Highest message number read by user           |
+| user_lasttime         | Time of the user's last call                  |
+| user_location         | Name of the city where the user lives         |
+| user_logindate        | Date on which the current call began          |
++-----------------------+-----------------------------------------------+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 160
+
+
+
+
++-----------------------+-----------------------------------------------+
+| VARIABLE NAME         | VARIABLE CONTENTS                             |
++-----------------------+-----------------------------------------------+
+| user_loginsec         | User's security at the beginning of this call |
+| user_logintime        | Time at which the current call began          |
+| user_logonpassword    | User's password at the beginning of this call |
+| user_menustack        | Contents of the user's current menu stack     |
+| user_menustackpointer | Pointer to the top of the menu stack          |
+| user_messages         | Total number of messages written by the user  |
+| user_msg_area         | The user's current message area               |
+| user_name             | The user's name                               |
+| user_net_credit       | The user's remaining netmail credit           |
+| user_netmailentered   | Whether or not the user has entered NetMail   |
+| user_num              | The user's record number in the user file     |
+| user_numcalls         | Number of calls the user has made to the BBS  |
+| user_numpages         | Number of times the user has paged the sysop  |
+| user_password         | The user's current password                   |
+| user_pending          | The value of unsent NetMail written by user   |
+| user_reasonforchat    | The reason the user wishes to chat with sysop |
+| user_rip_ver          | RIP protocol version being used               |
+| user_screen_length    | The length of the user's screen               |
+| user_screenwidth      | The width of the user's screen                |
+| user_security         | The user's security access level              |
+| user_sex              | The user's gender                             |
+| user_subdate          | The date the user's subscription expires      |
+| user_timelimit        | The user's daily time limit                   |
+| user_todayk           | Kilobytes downloaded by the user today        |
+| user_upk              | Total Kilobytes uploaded by the user          |
+| user_uploads          | Total number of files uploaded by the user    |
+| user_wantchat         | Whether or not the user wishes to chat        |
+| user_xi_record        | The user's record in the USERSXI.BBS file     |
++-----------------------+-----------------------------------------------+
+
+
+
+
+-------------------------------------------------------------------------------
+EMSI           char od_control.ra_emsi_session;
+INFORMATION    char od_control.ra_emsi_crtdef[41];
+               char od_control.ra_emsi_protocols[41];
+               char od_control.ra_emsi_capabilities[41];
+               char od_control.ra_emsi_requests[41];
+               char od_control.ra_emsi_software[41];
+               char od_control.ra_hold_attr1;
+               char od_control.ra_hold_attr2;
+               char od_control.ra_hold_len;
+
+               These variables provide your door with information pertaining to
+               an interactive EMSI session that has been established. Note that
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 161
+
+               these variables are only available under systems that produce an
+               RA 1.00 and later style extended EXITINFO.BBS door information
+               file.
+
+               If an IEMSI session has been established, the Boolean variable
+               od_control.ra_emsi_session will be TRUE, and if no session has
+               not been established, this variable will be FALSE.
+
+               A full discussion of the IEMSI protocol is beyond the scope of
+               this manual. Specifications for the IEMSI protocol are available
+               from the OpenDoors support BBS.
+
+
+
+-------------------------------------------------------------------------------
+event_days     unsigned char od_control.event_days;
+
+               This variable is a bit-mapped flag of the days of the week on
+               which the next system event is run. The bit-map bits are as
+               follows:
+
+                       +-----+------+-----------+
+                       | BIT | MASK | MEANING   |
+                       +-----+------+-----------+
+                       |  0  | 0x01 | Sunday    |
+                       |  1  | 0x02 | Monday    |
+                       |  2  | 0x04 | Tuesday   |
+                       |  3  | 0x08 | Wednesday |
+                       |  4  | 0x10 | Thursday  |
+                       |  5  | 0x20 | Friday    |
+                       |  6  | 0x40 | Saturday  |
+                       |  7  | 0x80 | All Days  |
+                       +-----+------+-----------+
+
+               For more information on bit-mapped flags, see the glossary item
+               entitled "BIT-MAPPED FLAGS".
+
+               This variable is only available under systems that produce an
+               EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+event_         unsigned char od_control.event_errorlevel;
+errorlevel
+               This variable contains the ErrorLevel associated with the next
+               system event. This variable is only available under systems that
+               produce an EXITINFO.BBS door information file.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 162
+
+-------------------------------------------------------------------------------
+event          char od_control.event_force;
+_force
+               This variable indicates whether the next system event should be
+               forced to run at a particular time. If this variable contains a
+               value of TRUE, then the user should be forced off-line in order
+               to accommodate the event, and if this variable is false, then
+               the event can wait until after the user logs off normally. This
+               variable is only available under systems that produce an
+               EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+event          char od_control.event_last_run[9];
+_last_run
+               This variable contains a string representing the date on which
+               the next system event was last run, and is in the same format as
+               the user_lastdate variable. This variable is only available
+               under systems that produce an EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+event          char od_control.event_starttime[6];
+_starttime
+               This variable contains a string representing the time at which
+               the next system event is scheduled to start, in the same format
+               as the user_lasttime variable. This variable is only available
+               under systems that produce an EXITINFO.BBS or Wildcat style
+               DOOR.SYS door information file.
+
+
+
+-------------------------------------------------------------------------------
+event          unsigned char od_control.event_status;
+_status
+               This variable represents the status of the next system event,
+               and will be equal to the value
+
+                            ES_ENABLED
+
+               if and only if the other event information contained in the
+               control structure is valid. This variable is only available
+               under systems that produce an EXITINFO.BBS file.
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 163
+
+-------------------------------------------------------------------------------
+sysop_name     char od_control.sysop_name[40];
+
+               The od_control.sysop_name variable contains the name of the
+               sysop of the BBS under which your door is running. This variable
+               is available under any BBS system that produces a DORINFO?.DEF
+               (including RA & QBBS which process both DORINFO1.DEF and
+               EXITINFO.BBS files), or Wildcat style DOOR.SYS file.
+
+
+
+-------------------------------------------------------------------------------
+system_calls   long od_control.system_calls;
+
+               This variable contains the total number of calls that have been
+               placed to the BBS, and is available under any BBS which produces
+               an EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+system_last    char od_control.system_last_caller[36];
+_caller
+               This string contains the name of the previous caller to the BBS,
+               on any line, and is available under EXITINFO.BBS.
+
+
+
+-------------------------------------------------------------------------------
+system_last    char od_control.system_last_handle[36];
+_handle
+               This string contains the handle (alias) of the previous caller
+               to the BBS, on any line, and is available under EXITINFO.BBS.
+
+
+
+-------------------------------------------------------------------------------
+system_name    char od_control.system_name[40];
+
+               The od_control.system_name variable contains the name of the BBS
+               under which your door is running. This variable is available
+               under any BBS system that produces a DORINFO?.DEF (including RA
+               & QBBS which process both DORINFO1.DEF and EXITINFO.BBS files).
+
+
+
+-------------------------------------------------------------------------------
+TIMELOG        char od_control.timelog_start_date[9];
+VARIABLES
+               This string contains the date of the beginning of the time
+               period for which the time log is recorded. This variable is
+               available under any system that produces an EXITINFO.BBS file.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 164
+
+
+
+               int od_control.timelog_busyperhour[24];
+
+               This variable is an array of 24 elements, with each element
+               indicating the total number of times the BBS was in use during
+               each of the 24 hours of the day. Element 0 corresponds to the
+               time period of 0:00-1:00, element 1 corresponds to the time
+               period of 1:00-2:00, and so on. In order to determine the
+               frequency of system use during any hour as a percentage, simply
+               calculate the total of all 24 entries in the array, and divide
+               any given entry by the total, in order to come up with an
+               average. This variable is available under any system that
+               produces an EXITINFO.BBS file.
+
+
+               int od_control.timelog_busyperday[7];
+
+               This variable is an array of 7 elements, with each element
+               indicating the total number of times the BBS was in use during
+               each of the 7 days of the week. Here, elements 0 corresponds to
+               Sunday, element 1 to Monday, and so on. In order to calculate
+               the frequency of system use during any day of the week, use the
+               same method as for calculating the frequency of calls during
+               each hour, as described above. This is only available under
+               systems that produces an EXITINFO.BBS file. Note that at least
+               some, if not all, versions of RemoteAccess do not maintain this
+               variable correctly, and thus even with the presence of an
+               EXITINFO.BBS file, this array may contain all zero entries.
+
+
+
+-------------------------------------------------------------------------------
+user_ansi      char od_control.user_ansi;
+
+               This variable contains a Boolean value, indicating whether or
+               not the user has ANSI mode turned on. If ANSI graphics mode is
+               enabled, this variable will contain a value of TRUE, and if ANSI
+               graphics mode is disabled, this variable will contain a value of
+               FALSE. Many of the OpenDoors functions test the setting of this
+               variable in order to determine whether or not they should send
+               ANSI-graphics control characters. Also, if this variable
+               contains a TRUE value, OpenDoors will display an "[ANSI]"
+               indicator on the status line.
+
+               You may change the value of this variable at any time after the
+               first call to od_init() or any other OpenDoors functions.
+               Depending upon what BBS system your door is running under,
+               changes to this variable may or may not result in changes to the
+               user's ANSI setting upon return to the BBS.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 165
+
+               This variable is available under all door information file
+               formats.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned char od_control.user_attribute;
+attribute
+               This variable is a bitmap of eight flags, each of which
+               represent individual pieces of information pertaining to the
+               user that is currently online. These flags are as follows:
+
+                       +-----+------+-----------------------+
+                       | BIT | MASK | DESCRIPTION           |
+                       +-----+------+-----------------------+
+                       |  0  | 0x01 | Is the user deleted   |
+                       |  1  | 0x02 | Is screen clearing on |
+                       |  2  | 0x04 | Is "more" prompt on   |
+                       |  3  | 0x08 | Is ANSI mode on       |
+                       |  4  | 0x10 | User no-kill setting  |
+                       |  5  | 0x20 | Transfer-priority     |
+                       |  6  | 0x40 | Full screen editor    |
+                       |  7  | 0x80 | Quiet mode            |
+                       +-----+------+-----------------------+
+
+               For more information on using and setting bit-mapped flags,
+               please see the entry entitled "BITMAPED FLAGS" in the glossary
+               of this manual.
+
+               Note that this variable is only available under systems that
+               produce and EXITINFO.BBS format door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned char od_control.user_attrib2;
+attrib2
+               See the user_attrib variable for more information. This variable
+               is like the user_attrib variable, except that it contains
+               different information. The bit-mapped flags for the
+               od_control.user_attrib2 variable are as follows:
+
+                       +-----+------+-----------------------+
+                       | BIT | MASK | DESCRIPTION           |
+                       +-----+------+-----------------------+
+                       |  0  | 0x01 | User hot-keys setting |
+                       |  1  | 0x02 | Is AVATAR graphics on |
+                       |  2  | 0x04 | Full screen reader    |
+                       |  3  | 0x08 | Hidden from userlist  |
+                       +-----+------+-----------------------+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 166
+
+               Note that this variable is only available under systems that
+               produce an EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned char od_control.user_attrib3;
+attrib3
+               This variable contains user attribute flags when a RA 2.50 or
+               later EXITINFO.BBS file is used.
+
+
+
+-------------------------------------------------------------------------------
+user_avatar    char od_control.user_avatar;
+
+               This variable is a Boolean value indicating whether or not
+               AVATAR graphics mode is on. If AVATAR graphics is available,
+               then many of the OpenDoors functions will make use of AVATAR
+               graphics codes for greater display speed. If AVATAR graphics
+               mode is on, a [AVT] indicator will appear on the status line. If
+               your door is running under a system which produces an RA 1.00+
+               style extended EXITINFO.BBS door information file, the
+               user_avatar variable is set automatically. If the extended
+               EXITINFO.BBS file is not available, this value will default to
+               FALSE. In this case, you may wish to ask the user whether or not
+               they wish to use AVATAR graphics, and thus set this variable
+               yourself.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_birthday[9];
+_birthday
+               This variable is a string, in the same format as the
+               od_control.user_lastcall variable, which stores the date of the
+               user's birthday, if it is available. This variable is only
+               available under systems that produce an RA 1.00 and later style
+               extended EXITINFO.BBS or Wildcat style DOOR.SYS file.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_callsign[12];
+_callsign
+               This variable is a string which contains the user's amateur
+               radio call sign, if any. This variable is only available under
+               systems that produce a CHAIN.TXT file.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 167
+
+-------------------------------------------------------------------------------
+user_combined  unsigned char od_control.user_combinedrecord[25];
+record
+               This variable is an array of bit-mapped flags, with each flag
+               corresponding to an individual message area. In this case, the
+               first bit of od_control.ra_combinedrecord[0] corresponds to the
+               first message area, the second bit to the second message area,
+               and so on. If any given bit-flag is turned on, then the user has
+               corresponding message area enabled for combined access, and if
+               the bit is turned off, the user does not have the area enabled
+               for combined access. A detailed description of the combined
+               message access is beyond the scope of this manual. This variable
+               is only available under systems that produce an RA 1.00 or later
+               style extended EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_comment   char od_control.user_comment[81];
+
+               This variable is a string which contains the sysop's comment
+               about the user that is currently online. This comment may be
+               displayed on the OpenDoors status line, if this variable is
+               available. This variable is available under systems that produce
+               an RA 1.00 and later style extended EXITINFO.BBS or Wildcat
+               style DOOR.SYS file.
+
+
+
+-------------------------------------------------------------------------------
+user_credit    unsigned int od_control.user_credit;
+
+               This variable contains the total amount of NetMail credit that
+               the caller has left. Changes to this variable will be by the BBS
+               when your door exits and control is returned to the BBS. This
+               variable is only available under systems that produce an
+               EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_          char od_control.user_dataphone[13];
+dataphone
+               This string contains the user's data or business phone number,
+               if available. This value is only available under system that
+               produce EXITINFO.BBS, PC-Board/GAP style DOOR.SYS and WildCat
+               DOOR.SYS format door information files.
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 168
+
+-------------------------------------------------------------------------------
+user           int od_control.user_deducted_time;
+_deducted
+_time          This variable contains a signed integer value, which indicates
+               the total amount of time that has been deducted from the user
+               during this call. This variable is only available under systems
+               that produce an RA 1.00 and later style extended EXITINFO.BBS
+               door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_downk     unsigned int od_control.user_downk;
+
+               This variable contains the total kilobytes of files that the
+               current user has downloaded from the BBS, and is available under
+               systems that produce EXITINFO.BBS, Wildcat style DOOR.SYS or
+               SFDOORS.DAT format door information files.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_downlimit;
+_downlimit
+               This variable contains the total number of kilobytes that the
+               caller is permitted to download during this call. If your door
+               allows files do be downloaded, you will probably want to compare
+               the value of this variable to the size of any file to be
+               transferred and the total kilobytes already downloaded, as
+               stored in the od_control.user_todayk variable. This variable is
+               only available under systems that produce an EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_downloads;
+_downloads
+               This variable contains the total number of files that the
+               current user has downloaded from the BBS, and is available under
+               systems that produce EXITINFO.BBS, PC-Board/GAP style DOOR.SYS,
+               WildCat style DOOR.SYS or SFDOORS.DAT format door information
+               files.
+
+
+
+-------------------------------------------------------------------------------
+user_echo      char od_control.user_echomailentered;
+mailentered
+               This variable is a Boolean value, indicating whether or not the
+               user has entered new EchoMail during this call. If this variable
+               has a value of TRUE, then EchoMail has been entered, and if it
+               has a value of FALSE, then EchoMail has not been entered. This
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 169
+
+               variable will contain a valid value only after od_init() or some
+               OpenDoors function has been called. Any changes made to this
+               variable will be reflected within the BBS software when control
+               is returned to the BBS. This variable is accessible only under
+               systems which produce an EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_error          char od_control.user_error_free;
+_free
+               This variable contains a Boolean value indicating whether or not
+               the user is connected to the BBS via an error free connection
+               (eg. a V.42/MNP or similar modem protocol). This variable is
+               only available under systems that produce an SFDOORS.DAT,
+               Wildcat style DOOR.SYS or RA 1.00 or later style extended
+               EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_first     char od_control.user_firstcall[9];
+call
+               This variable is a string which contains the date of the user's
+               first call, in the same format as the od_control. user_lastcall
+               variable. This variable is only available under systems which
+               produce an RA 1.00 and later style extended EXITINFO.BBS door
+               information file.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned char od_control.user_flags[4];
+flags
+               The od_control.user_flags variable is an array of four sysop
+               defined bit-mapped flags, which represent some sort of
+               information about the user. od_control.user_flags[0] stores
+               flags A1 - A8 in bits 0 through 7, respectively. Likewise,
+               od_control.user_flags[1] stores flags B1 - B8, and so on. This
+               variable is only available under systems that produce
+               EXITINFO.BBS format door information files.
+
+
+
+-------------------------------------------------------------------------------
+user_handle    char od_control.user_handle[36];
+
+               This variable contains the user's alias or handle name, if any.
+               If the user does not have and alias or handle, this variable
+               will be blank. This variable is only available under systems
+               that produce a CHAIN.TXT, RA 1.00 and later extended
+               EXITINFO.BBS or Wildcat style DOOR.SYS door information file.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 170
+
+
+
+
+-------------------------------------------------------------------------------
+user_          char od_control.user_homephone[13];
+homephone
+               This string contains the user's home or data phone number, if
+               available. This value is only available under system that
+               produce one of the following door information files:
+               EXITINFO.BBS, PC-Board/GAP style DOOR.SYS, WildCat style
+               DOOR.SYS or SFDOORS.DAT.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned char od_control.user_last_pwdchange;
+_last
+_pwdchange     This variable contains the number of calls that the user has
+               made since they last changed their password. This variable is
+               only available under EXITINFO.BBS files.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_lastdate[9];
+_lastdate
+               This variable is a string containing the date of the user's last
+               call to the BBS, and should always be of the format:
+
+                                  "MM-DD-YY"
+
+               Where MM is two digits representing the number of the month of
+               the user's call, with 1 being January, 2 being February, and so
+               on. DD should be two digits representing the day of the month of
+               the user's last call, beginning with 1, and MM should be the
+               last two digits of the year of the user's last call.
+
+               This variable is only available under systems that produce one
+               of the following door information files: CHAIN.TXT,
+               EXITINFO.BBS, PC-Board/GAP style DOOR.SYS or WildCat style
+               DOOR.SYS files.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned int od_control.user_lastread;
+lastread
+               This variable contains the number of the highest message number
+               that the user has read, and is only available under EXITINFO.BBS
+               format door information files.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 171
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_lasttime[6];
+_lasttime
+               This variable contains a string representing the time of the
+               user's last call to the BBS, and should always be of the format:
+
+                                  "HH:MM"
+
+               Where HH is two digits representing the 24-hour format hour of
+               the user's last call, and MM is two digits representing the
+               minute of the user's last call. Thus, the following strings
+               would be valid entries for this string:
+
+                    "00:01"    (12:01 am)
+                    "03:47"    (3:47 am)
+                    "18:20"    (6:20 pm)
+
+               This variable is only available under systems that produce an
+               EXITINFO.BBS or Wildcat style DOOR.SYS format door information
+               file.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_location[26];
+_location
+               This string contains the name of the location from which the
+               current user is calling from. This will usually be the name of
+               the city, region (province, state, etc.) and sometimes country
+               where the user lives. The contents of this variable are
+               displayed on the OpenDoors status line. The value of this
+               variable is valid after od_init() or any other OpenDoors
+               function has been called. Also, you may change the value of this
+               variable if you wish. However, not that these changes may not
+               immediately be reflected in the status line, and may or may not
+               cause the setting to be changed after the user returns to the
+               BBS. This variable is available under systems that produce one
+               of the following door information files: DORINFO?.DEF,
+               EXITINFO.BBS, PC-Board/GAP style DOOR.SYS, WildCat style
+               DOOR.SYS SFDOORS.DAT and CALLINFO.BBS, but is not available
+               under CHAIN.TXT or DoorWay style DOOR.SYS files.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.caller_logindate[9];
+_logindate
+               This variable contains a string representing the date on which
+               the current call to the BBS began. This variable is in the same
+               format as the od_control.user_lastdate variable, described
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 172
+
+               below. This variable is only available under systems which
+               produce an EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+user           long od_control.user_loginsec;
+_loginsec
+               This variable contains the user's security at login, and can be
+               used to detect changes by the sysop or other programs during the
+               course of the call, by comparing it's value with the
+               od_control.user_security variable. This variable is only
+               available under systems which produce an EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_logintime[6];
+_logintime
+               This variable contains a string representing the time of day at
+               which the current call to the BBS began. This variable is in the
+               same format as the od_control.user_lasttime variable, which is
+               also described below. This variable is available under systems
+               which produce an EXITINFO.BBS, a Wildcat style DOOR.SYS, or an
+               SFDOORS.DAT file.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_logonpassword[16];
+_logon
+password       This variable is a string which contains the user's password
+               at the time at which the current call to the BBS began. This
+               variable can be used to detect changes by the sysop or other
+               programs to the user's password, which have taken place during
+               the course of the call. In order to detect such changes, simply
+               compare the contents of this string with the contents of the
+               od_control.user_password variable. This variable is only
+               available under systems which produce an EXITINFO.BBS format
+               door information file.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_menustack[50][9];
+_menustack
+               This variable is an array of 50 strings, containing the stack of
+               BBS menus that have been executed, and is used to record the
+               current position of the user within the BBS's menu system. Each
+               string contains just the base portion of the filename of the
+               menu, without the extension. The od_control.ra_menustackpointer
+               variable points to the top of the menu stack. However, a
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 173
+
+               complete discussion of the menu stack is beyond the scope of
+               this manual. This variable is only available under systems that
+               produce an RA 1.00 and later style extended EXITINFO.BBS door
+               information file.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned char od_control.user_menustackpointer;
+_menustack
+pointer        This variable points to the top of the current menu stack. For
+               more information on the menu stack, please refer to the
+               od_control.ra_menustack variable, above. This variable is only
+               available under systems that produce an RA 1.00 and later style
+               extended EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_messages;
+_messages
+               This variable contains a value representing the total number of
+               messages that have been written by the user, and is available
+               under EXITINFO.BBS or Wildcat style DOOR.SYS format door
+               information files.
+
+
+
+-------------------------------------------------------------------------------
+user_name      char od_control.user_name[36];
+
+               This string contains the name of the user that is currently on-
+               line, and is used by OpenDoors to display the current user name
+               on the status line, and will most likely be used by your door
+               for differentiating among different users. In most cases, you
+               should probably not change the value of this variable, as a
+               user's name does not usually change, and doing so could results
+               in problems when returning to some BBS systems. For an example
+               of using this variable, see the EX_VOTE.C example program. This
+               variable is available under all BBS systems.
+
+
+
+-------------------------------------------------------------------------------
+user_net_      unsigned int od_control.user_net_credit;
+credit
+               This variable contains the amount of NetMail credit that the
+               current user has to his or her name. This variable is only
+               available under systems that produce an EXITINFO.BBS file.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 174
+
+               Note that if you wish to change the value of the user's
+               remaining NetMail credit, you should use the od_control.
+               user_credit variable, instead of this variable.
+
+
+
+-------------------------------------------------------------------------------
+user_net       char od_control.user_netmailentered;
+mailentered
+               This variable is a Boolean value, indicating whether or not the
+               user has entered new NetMail or GroupMail during this call. If
+               this variable has a value of TRUE, then NetMail/GroupMail has
+               been entered, and if it has a value of FALSE, then
+               NetMail/GroupMail has not been entered. This variable will
+               contain a valid value only after od_init() or some OpenDoors
+               function has been called. Any changes made to this variable will
+               be reflected within the BBS software when control is returned to
+               the BBS. This variable is accessible only under systems which
+               produce an EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_num       unsigned int od_control.user_num;
+
+               This variable contains the number of the user's record in the
+               user database file, where 0 is the first record. This can be
+               useful for changing user settings that are not re-read by the
+               BBS, such as the user's phone number or security level which
+               might be altered by a call back verification door. However, the
+               value of this variable itself should not be altered.
+
+               This variable is available under systems which produce any of
+               the following door information file formats: CHAIN.TXT, PC-
+               Board/GAP style DOOR.SYS, Wildcat style DOOR.SYS SFDOORS.DAT and
+               EXITINFO.BBS.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned int od_control.user_numcalls;
+numcalls
+               This variable contains the total number of calls that the
+               current user has placed to the BBS, and is available under
+               systems that produce EXITINFO.BBS or PC-Board/GAP and Wildcat
+               style DOOR.SYS door information files.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 175
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_numpages;
+_numpages
+               The value of this variable contains the total number of times
+               that the user has paged the sysop, and can be used to limit the
+               number of times that the user is permitted to page the sysop.
+               OpenDoors increments this variable every time that the user
+               pages the sysop, via the od_page() function. This variable is
+               used with all types of door information files. However, this
+               variable will only reflect the value within the BBS if an
+               EXITINFO.BBS file is produced. Otherwise, the variable will only
+               contain the number of times that the user has paged within the
+               door, but not the total number of times the user has paged.
+               Under EXITINFO.BBS systems, changes to the value of this
+               variable will be reflected within the BBS upon return by the
+               DOOR.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_password[16];
+_password
+               This variable contains the user's password for accessing the
+               BBS. OpenDoors does not use this value itself. This variable
+               will contain a valid value only after od_init() or some
+               OpenDoors function has been called. You may change the value of
+               this variable. Note, however, that changes in this variable may
+               or may not cause the setting to be changed when control returns
+               to the BBS - this will depend upon the particular BBS system
+               your door is running under. This variable is only available
+               under systems that produce one of the following door information
+               files: EXITINFO.BBS, PC-Board/GAP and Wildcat style DOOR.SYS,
+               SFDOORS.DAT, and CALLINFO.BBS.
+
+
+
+-------------------------------------------------------------------------------
+user_pending   unsigned int od_control.user_pending;
+
+               This variable represents the total value of NetMail that has
+               been written by the current user, but not yet exported from the
+               message base. This variable is only available under systems that
+               produce an EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user_reason    char od_control.user_reasonforchat[78];
+forchat
+               This variable is a string, containing the reason for which the
+               user wishes to chat with the sysop, as they entered at the time
+               of paging the sysop. This variable will contain an empty string
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 176
+
+               if the user has not paged the sysop, or if the reason the user
+               wishes to chat is unknown. See also the od_control.user_wantchat
+               variable. This variable is available under all BBS systems,
+               regardless of what style of door information file they produce.
+               However, this variable will not be passed between the door and
+               BBS, and thus the user's reason for chat within the door will
+               not necessarily correspond to their reason for chat outside the
+               door.
+
+
+
+-------------------------------------------------------------------------------
+user_rip       char user_rip;
+
+               This variable is set to TRUE if the user has RIP (Remote Imaging
+               Protocol) graphics enabled, and FALSE if they do not. This
+               setting can be determined from the door information (drop) file
+               in many cases. In other cases, you can automatically determine
+               whether or not the user's system supports RIP graphics using the
+               od_autodetect() function (see page 48).
+
+
+
+-------------------------------------------------------------------------------
+user_rip_ver   BYTE user_rip_ver;
+
+               This variable contains the version of the RIP protocol that is
+               in use. This variable is only available under a RemoteAccess
+               2.50 EXITINFO.BBS file.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_screen_length;
+_screen
+_length        This value of this variable represents the total number of
+               lines that can be displayed on the user's screen at once, and is
+               usually either 24 or 25. You may wish to make use of this
+               variable to allow your door to pause the display of long pieces
+               of text after every screen length, in order to allow the user to
+               read this information before it passes off of their screen. In
+               this case, you would simply maintain a counter of the total
+               number of lines displayed, and when this value reaches one less
+               than the length of the user screen, display a prompt asking the
+               user to whether or not they wish to continue.
+
+               This variable is set to the user's setting within the BBS under
+               systems that produce any of the following door information file
+               formats: CHAIN.TXT, EXITINFO.BBS, PC-Board/GAP and Wildcat style
+               DOOR.SYS and CALLINFO.BBS files.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 177
+
+               This variable is used by the OpenDoors function,
+               od_list_files(). If this variable contains a valid value,
+               OpenDoors will pause the listing of files after every screen,
+               and give the user the option of continuing, aborting, or
+               disabling the "Continue?" prompt for the rest of the file
+               listing. Thus, if you are using the od_list_files() under a
+               system that does not produce one of the door information files
+               listed above, you may wish to obtain the user's screen length
+               from the user themselves. If the screen length is not available
+               from the particular type of door information file that is found,
+               and you do not set this value yourself, this variable will
+               default to 23. If you are going to set the value of this
+               variable yourself, you should do so after having called
+               od_init() or some OpenDoors function.
+
+
+
+-------------------------------------------------------------------------------
+user_          unsigned char od_control.user_screenwidth;
+screenwidth
+               This variable contains a value representing the width of the
+               user's screen, and will most often be equal to 80. This variable
+               is only available under systems that produce a CHAIN.TXT or RA
+               1.00 and later style extended EXITINFO.BBS door information
+               file.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_security;
+_security
+               This variable contains a numerical value representing the user's
+               security access level on the BBS. You may wish to use this value
+               to determine whether or not the current user of your door should
+               have access to certain sysop-only functions. In this case, you
+               may wish to have a configuration file used by your door, in
+               which the sysop may define the minimum security level for sysop
+               access. You would then be able to compare this configuration
+               setting to the security level stored in this variable, in order
+               to determine whether or not sysop function should be available.
+               An alternative method, used by the EX_VOTE.C sample door, of
+               determining whether or not the current user is the sysop is to
+               compare the user's name with the value of the
+               od_control.sysop_name variable. This method has the advantage of
+               not requiring a configuration program, but the disadvantage that
+               the door will not function correctly under all BBS systems, as
+               the od_control.sysop_name variable is not available under all
+               BBS systems.
+
+               The od_control.user_security variable is available under BBS
+               systems that produce any of the following door information file
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 178
+
+               formats: CHAIN.TXT, EXITINFO.BBS, PC-Board/GAP and Wildcat style
+               DOOR.SYS, SFDOORS.DAT or CALLINFO.BBS.
+
+
+
+-------------------------------------------------------------------------------
+user_sex       char od_control.user_sex;
+
+               This variable contains a single character representing the
+               gender of the user that is currently online. This variable will
+               contain an upper-case 'F' if the user is female, and an upper-
+               case 'M' if the user is male. This variable is available under
+               systems that produce a CHAIN.TXT or RA 2.x style EXITINFO.BBS
+               file.
+
+
+
+-------------------------------------------------------------------------------
+user_subdate   char od_control.user_subdate[9];
+
+               This variable is a string, in the same format as the
+               od_control.user_lastdate variable, which stores the date of
+               expiry of the user's subscription to the BBS. This variable is
+               only available under systems which produce a PC-Board/GAP and
+               Wildcat style DOOR.SYS or RA 1.00 and later style extended
+               EXITINFO.BBS door information file.
+
+
+
+-------------------------------------------------------------------------------
+user           int od_control.user_timelimit;
+_timelimit
+               This variable contains the amount of time, in minutes, that the
+               user has left in the door. Note that this value may or may not
+               be equal to the total amount of time that the user has left on
+               the BBS, depending upon whether the BBS or a third-party door
+               manager program only allows a limited amount of time in this
+               door. This variable contains a valid value after od_init() or
+               some OpenDoors function has been called. OpenDoors uses this
+               variable to keep track of how much time the user has left in the
+               door, and will automatically warn the user when nearly all of
+               his or her time has been used up. OpenDoors will also force the
+               user out of the door when their time in the door has expired.
+               OpenDoors automatically subtracts one minute from this variable
+               every minute that OpenDoors is active, unless chat mode has been
+               activated (in which case the user's time will freeze), and also
+               adjusts the value of this variable when the sysop uses the time
+               adjustment function keys. Hence, you will not normally have any
+               need to alter the value of this variable yourself. However,
+               there may be some cases in which you wish to subtract a penalty
+               or add a bonus to the user's time, such as in a "timebank" door
+               or a door game that permits the user to "gamble time".
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 179
+
+
+               Depending on which BBS system your door is running under, the
+               value of this variable may or may not effect the user's time
+               left upon return to the BBS. The BBS system will either reset
+               the user's time to the value re-written to the door information
+               file (this variable), or will always subtract the amount of time
+               spent in the door from the user's remaining time.
+
+               This variable is available under all door information file
+               formats.
+
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_todayk;
+_todayk
+               This variable contains the total kilobytes of files that the
+               current user has downloaded from the BBS during the current day,
+               and is available under systems that produce EXITINFO.BBS, PC-
+               Board/GAP and Wildcat style DOOR.SYS, or SFDOORS.DAT format door
+               information files.
+
+
+
+-------------------------------------------------------------------------------
+user_upk       unsigned int od_control.user_upk;
+
+               This variable contains the total kilobytes of files that the
+               current user has uploaded to the BBS, and is available under
+               systems that produce EXITINFO.BBS, Wildcat style DOOR.SYS or
+               SFDOORS.DAT files.
+
+
+
+-------------------------------------------------------------------------------
+user_uploads   unsigned int od_control.user_uploads;
+
+               This variable contains the total number of files that the
+               current user has uploaded to the BBS, and is available under
+               systems that produce EXITINFO.BBS, PC-Board/GAP and Wildcat
+               style DOOR.SYS, or SFDOORS.DAT format door information files.
+
+
+
+-------------------------------------------------------------------------------
+user           char od_control.user_wantchat;
+_wantchat
+               This variable is a Boolean value which indicates whether or not
+               the user wishes to chat with the sysop (ie, the user has paged
+               the sysop, but has yet to receive a chat with the sysop). This
+               variable is used under all door information file formats.
+               However, changes to this variable are only reflected on the BBS
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 180
+
+               when the door is running under a system that produces an
+               EXITINFO.BBS door information file.
+
+               This variable is automatically turned on (ie., set to TRUE),
+               when the user begins to page the sysop for chat, within the
+               od_page() function, and is automatically turned off (ie., set to
+               FALSE), when the sysop breaks in for chat via the chat function
+               key. Also, setting this variable to TRUE will turn on the
+               flashing want-chat indicator on the OpenDoors status line.
+
+
+-------------------------------------------------------------------------------
+user           unsigned int od_control.user_xi_record;
+_xi_record
+               This variable contains the number of the user's record in the
+               USERXI.BBS file, if any. This variable is only available under
+               system that produce a Remote Access 1.00 and later style
+               extended door information file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 181
+
+CONTROL STRUCTURE - DOOR SETTINGS
+-------------------------------------------------------------------------------
+
+               This section deals with those variables in the OpenDoors control
+               structure which reflect the current door settings. These
+               variables are as follows:
+
+               od_cur_attrib            The current display attribute, or -1 if
+                                        unknown.
+
+               od_okaytopage            Controls whether the user is currently
+                                        permitted to page the sysop.
+
+               od_pageendmin            End of valid paging hours.
+
+               od_pagestartmin          Start of valid paging hours.
+
+               od_silent_mode           Turns off local user interface.
+
+               od_user_keyboard_on      Controls whether OpenDoors will
+                                        currently accept input from the remote
+                                        user's keyboard.
+
+               od_update_status_now     Forces immediate update of the status
+                                        line.
+
+               sysop_next               Indicates whether or not the sysop has
+                                        reserved use of the system after the
+                                        current calls.
+
+
+
+-------------------------------------------------------------------------------
+od_cur         int od_control.od_cur_attrib;
+_attrib
+               This read-only values stores the current display color
+               attribute, or the value -1 if the current display color is
+               unknown (such as when the door first begins execution).
+
+
+
+-------------------------------------------------------------------------------
+od             char od_control.od_okaytopage;
+_okaytopage
+               This variable allows you to control whether or not the user is
+               currently permitted to page the sysop via the od_page()
+               function. A value of PAGE_ENABLE indicates that paging is
+               currently permitted, regardless of the sysop page hours setting.
+               A value of PAGE_DISABLE indicates that paging is not current
+               permitted. A value of PAGE_USE_HOURS indicates that the
+               od_page() function should check the values of the
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 182
+
+               od_pagestartmin and od_pageendmin variables in order to
+               determine whether or not paging should be permitted.
+               The od_okaytopage variable should only be set after you call
+               od_init() or some other OpenDoors function. The default value is
+               PAGE_USE_HOURS. For more information on the od_page() function
+               itself, see page 101.
+
+
+
+-------------------------------------------------------------------------------
+od             unsigned int od_control.od_pageendmin;
+_pageendmin
+               This variable can be used to set the beginning of valid sysop
+               paging hours within the od_page() function. If the
+               od_control.od_okaytopage variable (which is described above) is
+               set to MAYBE, then OpenDoors will check the value of this
+               variable prior to paging the sysop via the od_page() function.
+               This variable should contain the time at which the valid sysop
+               paging hours end, represented as the a number of minutes since
+               midnight. For more information on the od_page() function itself,
+               see page 101.
+
+
+
+-------------------------------------------------------------------------------
+od             unsigned int od_control.od_pagestartmin;
+_pagestartmin
+               This variable can be used to set the beginning of valid sysop
+               paging hours within the od_page() function. If the
+               od_control.od_okaytopage variable (which is described above) is
+               set to MAYBE, then OpenDoors will check the value of this
+               variable prior to paging the sysop via the od_page() function.
+               This variable should contain the time at which the valid sysop
+               paging hours begin, represented as the a number of minutes since
+               midnight. For more information on the od_page() function itself,
+               see page 101.
+
+
+
+-------------------------------------------------------------------------------
+od_silent      BOOL od_control.od_silent_mode;
+_mode
+               If this variable is set to TRUE prior to the first call to any
+               OpenDoors function, OpenDoors will operate in silent mode, where
+               the local display and sysop commands are not used. Silent mode
+               is automatically disabled if the program is running in local
+               mode.
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 183
+
+-------------------------------------------------------------------------------
+od_update      char od_control.od_update_status_now;
+_status_now
+               Setting this variable to TRUE forces OpenDoors to update the
+               status line during the next od_kernel() execution. When the
+               status line is updated, this variable is reset to its default
+               value of FALSE.
+
+
+
+-------------------------------------------------------------------------------
+od_user        char od_control.od_user_keyboard_on;
+_keyboard_on
+               This variable is a Boolean value, indicating whether OpenDoors
+               will currently accept input from a remote user. OpenDoors
+               provides a function key (usually [ALT]-[K], unless you have
+               changed the default), which will allow the sysop to temporarily
+               prevent the user from having any control over the door. When the
+               sysop activates this feature, a flashing [Keyboard-Off]
+               indicator will appear on the status line, and this variable will
+               be set to FALSE. When the sysop presses the [ALT]-[K]
+               combination a second time, to toggle the user's keyboard back
+               on, the flashing indicator will disappear, and this variable
+               will be set back to TRUE.
+
+
+
+-------------------------------------------------------------------------------
+sysop_next     char od_control.sysop_next;
+
+               This variable is a Boolean value, indicating whether or not the
+               "sysop next" feature has been activated. The "sysop next"
+               feature, which reserves the system for the sysop after the call
+               has ended, can be toggled on and off within OpenDoors by use of
+               a function key (Alt-N by default). Also, when the "sysop next"
+               feature has been activated, an indicator will appear on the
+               OpenDoors status line. This variable is only available under
+               systems that produce an SFDOORS.DAT or RA 1.00 and later style
+               extended EXITINFO.BBS door information file. For more
+               information on testing the type of door information file
+               available, please see page 158.
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 184
+
+CONTROL STRUCTURE - DIAGNOSTICS
+-------------------------------------------------------------------------------
+
+               To help in diagnosing problems in your OpenDoors programs,
+               OpenDoors stores information on the most recent error which
+               occurred. When any of the OpenDoors functions return an "error"
+               or "failure" state, the reason for this failure is recorded.
+
+               The following OpenDoors control structure variable provides
+               diagnostics information:
+
+               od_error                 Stores a "reason code" for the last
+                                        failed OpenDoors API function call.
+
+
+
+
+-------------------------------------------------------------------------------
+od_error       int od_control.od_error;
+
+               When any of the OpenDoors API functions return an "error" or
+               "failure" state (usually denoted by either of the values FALSE
+               or NULL), the reason for the failure is recorded in this
+               variable. Since successful function calls do not alter the value
+               of the od_control.od_error variable, you must be careful not
+               only to check the value of the od_control.od_error variable, but
+               also to check the OpenDoors function return codes, in order to
+               determine which function failed.
+
+               This variable will always store the reason for the most recent
+               function call failure, or ERR_NONE if no functions have failed.
+               od_error may take on any of the following values:
+
+                    ERR_NONE            Indicates that no error has occurred
+                                        yet.
+
+                    ERR_MEMORY          Function was unable to allocate
+                                        required memory. This usually indicates
+                                        that there is not enough available
+                                        memory. This failure may also be due to
+                                        memory corruption caused by your
+                                        program inadvertently overwriting heap
+                                        structures. If your program has been
+                                        compiled in either the small or the
+                                        medium memory model, try recompiling it
+                                        in the compact, large, or huge memory
+                                        models. If your program is already
+                                        compiled in the compact, large, or huge
+                                        memory models, try making more system
+                                        memory available to your program.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 185
+
+                    ERR_NOGRAPHICS      This setting indicates that the
+                                        function called requires ANSI, AVATAR
+                                        or RIP graphics mode, but none of these
+                                        modes are active.
+
+                    ERR_PARAMETER       An invalid parameter was passed to an
+                                        OpenDoors functions. Check the
+                                        function's description in chapter four,
+                                        to determine the required values for
+                                        each function parameter.
+
+                    ERR_FILEOPEN        OpenDoors was unable to open a file.
+                                        This can be due to the specified
+                                        filename not existing, due to the file
+                                        being locked for exclusive access by
+                                        another process, or due to a hardware
+                                        failure.
+
+                    ERR_FILEREAD        OpenDoors was able to open the
+                                        specified file, but unable to read the
+                                        required data from the file. This error
+                                        may be due to an invalid file format,
+                                        due to a portion of the file being
+                                        locked by another process, or due to a
+                                        hardware failure.
+
+                    ERR_LIMIT           An internal function limit has been
+                                        exceeded. Refer to the function's
+                                        description in chapter four for
+                                        information on the function's
+                                        limitations.
+
+                    ERR_NOREMOTE        Indicates that a function has been
+                                        called which is not valid in local
+                                        mode, such as od_carrier() or
+                                        od_set_dtr().
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 186
+
+CONTROL STRUCTURE - OPENDOORS CUSTOMIZATION
+-------------------------------------------------------------------------------
+
+               The OpenDoors control structure provides many variables which
+               allow you to customize OpenDoor's behavior and appearance. These
+               customization variables fit into one of the following
+               categories:
+
+                         General Behavior Customization Variables
+                         Sysop Function Keys Customization Variables
+                         Color Customization Variables
+                         Language-Specific Prompts Customization Variables
+
+               This section deals with those variables that fit into the first
+               category, "General Behavior Customization Variables". The other
+               categories are dealt with in the following sections of this
+               chapter.
+
+               Below is a brief overview of the variables grouped into this
+               section of the OpenDoors control structure. Following the
+               overview is a detailed description of each of these variables.
+
+
+               od_app_icon              Program icon for Win32 version.
+
+               od_box_chars             Array of characters used by the
+                                        od_draw_box() function.
+
+               od_before_exit           Function to call prior to exiting.
+
+               od_cafter_chat           Function to call after sysop chat.
+
+               od_cafter_shell          Function to call after DOS shell.
+
+               od_cbefore_chat          Function to call prior to sysop chat.
+
+               od_cbefore_shell         Function to call prior to DOS shell.
+
+               od_cfg_lines             Sets the configuration file's custom
+                                        door information file line keywords.
+
+               od_cfg_text              Sets the built-in configuration file
+                                        keywords that OpenDoors will recognize.
+
+               od_chat_active           Controls whether or not sysop chat mode
+                                        is active.
+
+               od_clear_on_exit         Controls whether the screen is cleared
+                                        upon door exit.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 187
+
+               od_color_delimiter       Indicates what character should delimit
+                                        imbedded color codes for the
+                                        od_printf() function.
+
+               od_color_names           Strings which OpenDoors recognizes as
+                                        the names of various text colors.
+
+               od_config_file           Used to enable or disable the OpenDoors
+                                        configuration file system.
+
+               od_config_filename       Sets the filename that will be read by
+                                        the configuration file system.
+
+               od_config_function       The callback function that OpenDoors
+                                        will call to allow your program to
+                                        process custom configuration file
+                                        entries.
+
+               od_default_personality   Sets the default personality to be used
+                                        with the OpenDoors Multiple Personality
+                                        System, and also sets the personality
+                                        to use when the MPS is not active.
+
+               od_default_rip_win       Whether OpenDoors should use the
+                                        default 43-line RIP window for ANSI
+                                        text (TRUE), or a 23-line window
+                                        (FALSE).
+
+               od_disable               Disable OpenDoors activities such as
+                                        reading door information file and
+                                        monitoring carrier detect / remaining
+                                        time.
+
+               od_disable_dtr           Specifies the string that will be sent
+                                        to the modem to prevent the modem from
+                                        hanging up when DTR is lowered.
+
+               od_emu_simluate_modem    Simulates modem display speed for
+                                        emulation functions such as
+                                        od_send_file(), od_disp_emu() and
+                                        od_hotkey_menu().
+
+               od_errorlevel            Sets the errorlevel OpenDoors exits
+                                        with under various conditions.
+
+               od_force_local           Forces door to operate in local mode,
+                                        ignoring any door information file and
+                                        using default user settings.
+
+               od_help_callback         Allows you to provide a help menu item
+                                        under the Win32 version of OpenDoors
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 188
+
+               od_in_buf_size           Sets size of OpenDoor's internal
+                                        local/remote inbound buffer.
+
+               od_inactive_warning      Number of seconds before hanging up
+                                        that OpenDoors displays the inactivity
+                                        timeout warning.
+
+               od_inactivity            Controls user inactivity timeout.
+
+               od_ker_exec              Is called whenever od_kernel()
+                                        executes.
+
+               od_last_input            Indicates whether the last input came
+                                        from the remote user (==0) or the local
+                                        sysop (==1).
+
+               od_list_pause            Controls whether or not the user may
+                                        pause display within the
+                                        od_list_files() and od_send_file()
+                                        functions by using the [P] key.
+
+               od_list_stop             Controls whether or not the user may
+                                        terminate display within the
+                                        od_list_files() and od_send_file()
+                                        functions using [S], [CTRL]-[K], etc.
+
+               od_logfile               Enables or disables the OpenDoors log
+                                        file system.
+
+               od_logfile_disable       Prevents the logfile from being opened,
+                                        even if the logfile is enabled by
+                                        od_logfile.
+
+               od_logfile_messages      Array of message strings that OpenDoors
+                                        will use when writing log file entries.
+
+               od_logfile_name          Contains the filename and possibly path
+                                        of the logfile.
+
+               od_maxtime               Indicates the maximum length of time
+                                        any user is permitted to use the door.
+
+               od_maxtime_deduction     Indicates the amount of time that has
+                                        temporarily been taken away from the
+                                        user's remaining time, as a result of
+                                        the maximum door time setting.
+
+               od_mps                   Enables or disables the OpenDoors
+                                        Multiple Personality System.
+
+               od_no_file_func          Called when no door information file
+                                        can be read.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 189
+
+
+               od_no_ra_codes           Disables translation of RA/QBBS control
+                                        codes.
+
+               od_nocopyright           Prevents OpenDoors from displaying it's
+                                        name and version number when a door
+                                        program begins execution.
+
+               od_noexit                Prevents OpenDoors from exiting when
+                                        the od_exit() function is called.
+
+               od_page_len              Controls length of the sysop page beep.
+
+               od_page_pausing          Enables or disables page pausing in
+                                        od_send_file(), od_hotkey_menu() and
+                                        od_list_files() functions.
+
+               od_page_startmin         Indicates the time of day at which
+                                        sysop paging is first enabled.
+
+               od_page_statusline       Which status line (if any) is activated
+                                        when the user pages the sysop.
+
+               od_page_endmin           Indicates the time of day at which
+                                        sysop paging is disabled.
+
+               od_prog_name             Stores the name of your program.
+
+               od_prog_version          Stores the version number of your
+                                        program.
+
+               od_prog_copyright        Place your copyright information here.
+
+               od_reg_key               Stores the registration key that you
+                                        receive when purchasing OpenDoors.
+
+               od_reg_name              Stores your name or your companies name
+                                        when you have purchased an OpenDoors
+                                        license (registration).
+
+               od_spawn_freeze_time     Indicates whether the user's time
+                                        remaining continues to be decreased
+                                        during the execution of the
+                                        od_spawn...() functions (FALSE), or if
+                                        the timer should be "frozen" (TRUE).
+
+               od_swapping_disable      Disables swapping during DOS shell and
+                                        od_spawn...() functions.
+
+               od_swapping_noems        Prevents swapping form being done to
+                                        EMS expanded memory.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 190
+
+               od_swapping_path         Location where disk swap file should be
+                                        created.
+
+               od_status_on             Controls whether the status line sub-
+                                        system is active.
+
+               od_time_msg_func         Called instead of displaying time limit
+                                        warning messages.
+
+
+
+
+-------------------------------------------------------------------------------
+od_app         HICON od_control.od_app_icon;
+_icon
+               Normally, the Win32 version of OpenDoors displays its own icon
+               on the application title bar, on the Windows taskbar, and in the
+               help|about dialog box. You can supply your own icon by setting
+               this variable to point to the handle of the icon, as returned by
+               LoadIcon();
+
+
+
+-------------------------------------------------------------------------------
+od_box         char od_control.od_box_chars[8];
+_chars
+               This variable allows you to specify which character the
+               od_draw_box() function uses in drawing the boarder of a window.
+               The elements of this array are as follows:
+
+               od_box_chars[BOX_UPPERLEFT]  - Upper left corner of box
+               od_box_chars[BOX_TOP]        - Top horizontal line
+               od_box_chars[BOX_UPPERRIGHT] - Upper right corner of box
+               od_box_chars[BOX_LEFT]       - Left Vertical line
+               od_box_chars[BOX_LOWERLEFT]  - Lower left corner of box
+               od_box_chars[BOX_LOWERRIGHT] - Lower right corner of box
+               od_box_chars[BOX_BOTTOM]     - Bottom horizontal line
+               od_box_chars[BOX_RIGHT]      - Right horizontal line
+
+
+
+-------------------------------------------------------------------------------
+od_before      void (*od_control.od_before_exit)();
+_exit
+               This variable contains a pointer to a function which OpenDoors
+               should call prior to exiting, or NULL if you do not wish to have
+               any function called at exit time. For an example of the use of
+               this variable, see the description of the EX_VOTE.C example
+               program, which begins on page 38.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 191
+
+-------------------------------------------------------------------------------
+od_cafter      void (*od_control.od_cafter_chat)();
+_chat
+               The function pointed to by this variable will be called after
+               sysop chat mode has ended. This may be useful for allowing you
+               to save the user's screen contents prior to chat, and restoring
+               the afterwards. If this variable contains its default value of
+               NULL, no function will be called. To alter the string of text
+               which is displayed after sysop chat, see the
+               od_control.od_after_chat variable, which is described in the
+               section on the prompts customization portion of the control
+               structure.
+
+
+
+-------------------------------------------------------------------------------
+od_cafter      void (*od_control.od_cafter_shell)();
+_shell
+               The function pointed to by this variable will be called after
+               the sysop has returned from a DOS shell. If this variable
+               contains its default value of NULL, no function will be called.
+               To alter the string of text which is displayed after a DOS
+               shell, see the od_control.od_after_shell variable, which is
+               described in the section on the prompts customization portion of
+               the control structure.
+
+
+
+-------------------------------------------------------------------------------
+od_cbefore     void (*od_control.od_cbefore_chat)();
+_chat
+               The function pointed to by this variable will be called prior to
+               entering sysop chat mode. This may be useful for allowing you to
+               save the user's screen contents prior to chat, and restoring the
+               afterwards. If this variable contains its default value of NULL,
+               no function will be called. To alter the string of text which is
+               displayed prior to sysop chat, see the od_control.od_before_chat
+               variable, which is described in the section on the prompts
+               customization portion of the control structure. To replace the
+               OpenDoors sysop chat facility with your own, simply activate
+               your chat mode when this function is called. Your chat mode
+               facility should remain active until OpenDoors sets the
+               od_control.od_chat_active variable to FALSE. If you wish to
+               terminate chat mode prior to this variable being set to FALSE,
+               you should set this variable to FALSE yourself if you do not
+               wish OpenDoors to activate its own chat mode.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 192
+
+-------------------------------------------------------------------------------
+od_cbefore     void (*od_control.od_cbefore_shell)();
+_shell
+               The function pointed to by this variable will be called prior to
+               executing a sysop DOS shell. If this variable contains its
+               default value of NULL, no function will be called. To alter the
+               string of text which is displayed before a DOS shell, see the
+               od_control.od_before_shell variable, which is described in the
+               section on the prompts customization portion of the control
+               structure.
+
+
+
+-------------------------------------------------------------------------------
+od_cfg_lines   char od_control.cfg_lines[25][33];
+
+               This array contains the strings for the keywords that represent
+               various lines in the definition of a custom door information
+               file. Each keyword must be 32 character or less in length. These
+               keywords are not case sensitive. See page 230 for more
+               information on defining custom door information (drop) file
+               formats. The default values for this array are as follows:
+
+                    [0]  "Ignore"
+                    [1]  "ComPort"
+                    [2]  "FossilPort"
+                    [3]  "ModemBPS"
+                    [4]  "LocalMode"
+                    [5]  "UserName"
+                    [6]  "UserFirstName"
+                    [7]  "UserLastName"
+                    [8]  "Alias"
+                    [9]  "HoursLeft"
+                    [10] "MinutesLeft"
+                    [11] "SecondsLeft"
+                    [12] "ANSI"
+                    [13] "AVATAR"
+                    [14] "PagePausing"
+                    [15] "ScreenLength"
+                    [16] "ScreenClearing"
+                    [17] "Security"
+                    [18] "City"
+                    [19] "Node"
+                    [20] "SysopName"
+                    [21] "SysopFirstName"
+                    [22] "SysopLastName"
+                    [23] "SystemName"
+                    [24] "RIP"
+
+               If you wish to change any of these variable, you must do so
+               before calling any OpenDoors functions.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 193
+
+
+
+-------------------------------------------------------------------------------
+od_cfg_text    char od_control.od_cfg_text[47][33];
+
+               This array of strings contains the built-in configuration file
+               keywords that are recognized by OpenDoors. These keywords may be
+               up to 32 characters in size, and are not case sensitive. If you
+               wish to change any of these settings, you must do so before
+               calling any OpenDoors functions. The default values for this
+               array are as follows:
+
+                    [0]  "Node"
+                    [1]  "BBSDir"
+                    [2]  "DoorDir"
+                    [3]  "LogFileName"
+                    [4]  "DisableLogging"
+                    [5]  "SundayPagingHours"
+                    [6]  "MondayPagingHours"
+                    [7]  "TuesdayPagingHours"
+                    [8]  "WednesdayPagingHours"
+                    [9]  "ThursdayPagingHours"
+                    [10] "FridayPagingHours"
+                    [11] "SaturdayPagingHours"
+                    [12] "MaximumDoorTime"
+                    [13] "SysopName"
+                    [14] "SystemName"
+                    [15] "SwappingDisable"
+                    [16] "SwappingDir"
+                    [17] "SwappingNoEMS"
+                    [18] "LockedBPS"
+                    [19] "SerialPort"
+                    [20] "CustomFileName"
+                    [21] "CustomFileLine"
+                    [22] "InactivityTimeout"
+                    [23] "PageDuration"
+                    [24] "ChatUserColor"
+                    [25] "ChatSysopColor"
+                    [26] "FileListTitleColor"
+                    [27] "FileListNameColor"
+                    [28] "FileListSizeColor"
+                    [29] "FileListDescriptionColor"
+                    [30] "FileListOfflineColor"
+                    [31] "Personality"
+                    [32] "NoFossil"
+                    [33] "PortAddress"
+                    [34] "PortIRQ"
+                    [35] "ReceiveBuffer"
+                    [36] "TransmitBuffer"
+                    [37] "PagePromptColor"
+                    [38] "LocalMode"
+                    [39] "PopupMenuTitleColor"
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 194
+
+                    [40] "PopupMenuBorderColor"
+                    [41] "PopupMenuTextColor"
+                    [42] "PopupMenuKeyColor"
+                    [43] "PopupMenuHighlightColor"
+                    [44] "PopupMenuHighKeyColor"
+                    [45] "NoFIFO"
+                    [46] "FIFOTriggerSize"
+                    [47] "DiableDTR"
+                    [48] "NoDTRDisable"
+
+
+
+-------------------------------------------------------------------------------
+od_chat        char od_control.od_chat_active;
+_active
+               This variable is set to TRUE when sysop chat mode is active, and
+               is set to FALSE when sysop chat mode is not active. This
+               variable can be used to determine whether or not chat mode is
+               active, and to force chat mode to end. When the sysop presses
+               the chat mode key ([ALT]-[C] if the default personality is being
+               used) while chat mode is active, this variable is set to FALSE.
+
+
+
+-------------------------------------------------------------------------------
+od_clear       char od_control.od_clear_on_exit;
+_on_exit
+               This variable contains a Boolean value, which indicates whether
+               or not you wish OpenDoors to clear the screen prior to exiting.
+               This variable defaults to a value of TRUE, which causes the
+               screen to be cleared when a door program exits. However, you may
+               wish to set this variable to a value of FALSE, which will cause
+               the contents of the screen to remain unchanged when the door
+               exits. While setting this variable to FALSE will probably result
+               in a messy display if the door is to return control to a batch
+               file, if the door returns directly to the BBS, it will result in
+               a smoother transition from the door back to the BBS (as the
+               sysop is not left with a blank screen). If your door has a
+               configuration file or configuration program, you may wish to
+               have an option which will allow the individual sysop to
+               determine whether or not the screen should be cleared when the
+               door exits.
+
+
+
+-------------------------------------------------------------------------------
+od_color       char od_control.od_color_delimiter;
+_delimiter
+               This variable sets the character that is used to delimit color
+               codes in the od_printf() function, and defaults to the back-
+               quote (`) character. If you wish to be able to display the back-
+               quote (`) character using the od_printf() function, and thus
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 195
+
+               wish to use a different character to delimit color codes in the
+               od_printf() function, simply set this variable to the
+               alternative character you wish to use. If you wish to disable
+               the imbedded color codes feature of the od_printf() function,
+               simply set this variable to a value of zero. For more
+               information on od_printf() imbedded color codes, see the
+               description of the od_printf() function, which begins on page
+               110.
+
+
+
+-------------------------------------------------------------------------------
+od_color       char od_control.od_color_names[12][33];
+_names
+               This array sets the strings that OpenDoors will recognize as
+               color description keywords. These are the keywords that can be
+               imbedded in od_printf() format strings, and are also the
+               keywords that can be used to change color settings in the
+               OpenDoors configuration file. If you wish to change these
+               keywords, you will normally do so before calling any OpenDoors
+               functions. These keywords should always be supplied in upper-
+               case characters. The defaults values for this array are as
+               follows:
+
+                    [0]  "BLACK"
+                    [1]  "BLUE"
+                    [2]  "GREEN"
+                    [3]  "CYAN"
+                    [4]  "RED"
+                    [5]  "MAGENTA"
+                    [6]  "YELLOW"
+                    [7]  "WHITE"
+                    [8]  "BROWN"
+                    [9]  "GREY"
+                    [10] "BRIGHT"
+                    [11] "FLASHING"
+
+
+
+-------------------------------------------------------------------------------
+od_config      void (*od_control.od_config_file)(void);
+_file
+               Set this variable to INCLUDE_CONFIG_FILE to enable the OpenDoors
+               configuration file system, or set it to NO_CONFIG_FILE to
+               disable the configuration file system. This variable should only
+               be set prior to your first call to an OpenDoors function. For
+               more information on the OpenDoors configuration file system, see
+               page 224.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 196
+
+-------------------------------------------------------------------------------
+od_config      char *od_control.od_config_filename;
+_filename
+               If set, this variable should point to a string containing the
+               filename that you wish the OpenDoors configuration file system
+               to read. If this variable has its default value of NULL, the
+               filename DOOR.CFG will be used by default.
+
+
+
+-------------------------------------------------------------------------------
+od_config      void (*od_control.od_config_function)(char *keyword, char
+_function      *options);
+
+               If set, this variable should point to the function that
+               OpenDoors should call when lines with unrecognized keywords are
+               encountered in the configuration file. This allows you to add
+               your own configuration file keywords. The first parameter to
+               this function will be a pointer to a string containing the
+               unrecognized keywords, and the second parameter will be a
+               pointer to a string containing any options that were specified
+               after the keyword. If no options were specified after the
+               keyword, this string will have a length of 0.
+
+
+
+-------------------------------------------------------------------------------
+od_default     void (*od_control.od_default_personality)(unsigned char
+_personality   operation);
+
+               This variable sets the default personality that OpenDoors will
+               use if the multiple personality system is active. If the
+               multiple personality system is not active, the personality set
+               by this variable will be the only personality available. This
+               variable should only be set prior to calling an OpenDoors
+               function. This variable can be set to point to your own
+               personality function, or it can be set to one of the manifest
+               constants that represent one of the built-in personalities:
+
+                    PER_OPENDOORS
+                    PER_PCBOARD
+                    PER_RA
+                    PER_WILDCAT
+
+               For more information on the OpenDoors Multiple Personality
+               System, see page 230.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 197
+
+-------------------------------------------------------------------------------
+od_default     char od_control.od_default_rip_win;
+_rip_win
+               This variable defaults to FALSE. When set to FALSE, OpenDoors
+               resets the RIP text window to a 23-line window that is most
+               appropriate for doors that support both RIP-graphics and non-RIP
+               mode. When this variable is set to TRUE, OpenDoors will use the
+               default sized text output window, 43 lines in size.
+
+
+
+-------------------------------------------------------------------------------
+od_disable          unsigned int od_control.od_disable;
+
+               This variable is a bit-mapped flag which can be used to disable
+               certain OpenDoors features which are normally active, in order
+               to allow for maximum customization of OpenDoors. Each bit of
+               this variable represents a different feature that can be
+               disabled. To DISABLE a feature, you set the bit that corresponds
+               to the particular feature. To ENABLE the feature, the bit is
+               reset. Each bit is represented by a keyword, as follows:
+
+               DIS_INFOFILE - Setting the DIS_INFOFILE bit of the
+                    od_control.od_disable variable allows you to prevent
+                    OpenDoors from reading or re-writing a door information
+                    file. If you wish to disable OpenDoors' reading of the door
+                    information file, you must  do so prior to calling
+                    od_init() or any other OpenDoors door-driver functions. At
+                    the same time, you must also manually set any required
+                    variables that are normally set by the information obtained
+                    from the door information file, such as the comm port
+                    number, baud rate, user name, and so on. You may wish to
+                    disable reading of the door information file in a number of
+                    cases. For example, you may wish to manually read another
+                    format of door information file not supported by OpenDoors,
+                    or to obtain the necessary door information from your
+                    program's command line. Also, if you are using OpenDoors to
+                    write a non-door communications program, such as a terminal
+                    program, you want to prevent OpenDoors from attempting to
+                    read a door information file on startup.
+
+               DIS_CARRIERDETECT - Setting this bit allows you to prevent
+                    OpenDoors from exiting when it the carrier detect signal
+                    from the modem disappears. This bit may be set or rest at
+                    any time. If you use this bit to disable OpenDoors' carrier
+                    detection, you will probably want to monitor the state of
+                    the carrier detect signal yourself, using the od_carrier()
+                    function, which is described on page 51.
+
+               DIS_TIMEOUT - This flag allows you to prevent OpenDoors from
+                    exiting when the user runs out of time. As with the
+                    DIS_CARRIERDETECT flag, you may set or reset this bit at
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 198
+
+                    any time. You will most often want to use this setting when
+                    writing a non-door program, which you would not want to
+                    have exit after a particular amount of time has elapsed. Be
+                    sure that you do not confuse this flag with the user's
+                    inactivity timeout. To disable the inactivity timeout, set
+                    the do_control.od_inactivity variable to 0.
+
+               DIS_LOCAL_OVERRIDE - This setting affects OpenDoors' behavior
+                    when a locked BPS rate is specified in the configuration
+                    file, and another BPS rate is specified in the door
+                    information file. By default, OpenDoors will initialize the
+                    modem at the BPS rate specified in the configuration file,
+                    unless the BPS rate specified in the door information file
+                    is 0. In this case, the 0 BPS rate is used to indicate that
+                    the door is operating in local mode, and will override the
+                    BPS rate specified in the configuration file. Setting this
+                    flag disables the local mode override, causing the modem to
+                    always be initialized at the locked BPS rate, even when the
+                    door information file specifies that local mode should be
+                    used.
+
+               DIS_BPS_SETTING - When used with a FOSSIL driver, OpenDoors
+                    normally changes the BPS rate to that passed from the BBS
+                    (if the BBS passes a valid FOSSIL BPS rate). Setting the
+                    DIS_BPS_SETTING flag disables this BPS rate setting.
+
+               DIS_LOCAL_INPUT -  The local keyboard may be disabled by setting
+                    this bit. This only affects the sysop's input in
+                    circumstances that input is also accepted from the remote
+                    user; this setting has no effect on the sysop function
+                    keys.
+
+               DIS_SYSOP_KEYS - This setting also disables the local keyboard.
+                    However, unlike the DIS_LOCAL_INPUT, this function disables
+                    both sysop function keys and door input from the local
+                    keyboard.
+
+               DIS_DTR_DISABLE - This setting prevents OpenDoors from
+                    disabiling DTR response from the modem. Even if not
+                    specified, OpenDoors only disables DTR response in the when
+                    exiting under the Win32 version if an open serial port
+                    handle was not provided to OpenDoors at startup.
+
+               DIS_NAME_PROMPT - Prevents OpenDoors from prompting for a user
+                    name when operating in automatic local mode (by setting
+                    od_force_local to TRUE or specifying -local on the command
+                    line).
+
+               Note that in order to disable the OpenDoors status line, the
+               od_control.od_status_on variable is used, instead of the
+               od_disable variable. You may also disable the user's inactivity
+               timeout by setting the od_control.od_inactivity variable to 0.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 199
+
+               The od_control.od_status_on variable is described later in this
+               section.
+
+
+
+-------------------------------------------------------------------------------
+od_disable_    char od_control.od_disable_dtr[40];
+dtr
+               Unles the DIS_DTR_DISABLE od_disable flag is set, the Win32
+               version of OpenDoors will attempt to disable DTR response by the
+               modem when closing the serial port, if the serial port was
+               opened by OpenDoors. This is done by sending a series of
+               commands to the modem, and possibly waiting for responses to the
+               command. The string format specifies each command, followed by
+               the required response. The command and response is separated by
+               a single space character. If no response is required between two
+               commands, then those commands may be separated by two space
+               characters. A '|' character is translated into a carriage
+               return, and a '~' character is translated into a one second
+               pause. The default value of this string is "~+++~  AT&D0  ATO".
+
+
+
+-------------------------------------------------------------------------------
+od_emu_        BOOL od_control.od_emu_simulate_modem;
+simulate_modem
+               When this flag is set to its default value of FALSE, the
+               OpenDoors terminal emulator displays text at full speed. When
+               this flag is set to TRUE, the emulation functions will display
+               text at approximately the same speed as it would be displayed
+               when sent over the modem, based on the current connect speed. In
+               local mode, an average modem speed of 9600bps is assumed. This
+               allows animations to be displayed locally at the same speed as
+               they would appear on the remote system. This switch affects the
+               following functions:
+                    od_disp_emu()
+                    od_send_file()
+                    od_hotkey_menu()
+
+
+
+-------------------------------------------------------------------------------
+od             unsigned char od_control.od_errorlevel[8];
+_errorlevel
+               Allows you to configure the errorlevel (program exit code) which
+               OpenDoors exits with under various circumstances. The elements
+               of this array are as follows:
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 200
+
+               [ERRORLEVEL_ENABLE]     Enables or disables custom errorlevels
+               [ERRORLEVEL_CRITICAL]   Critical error errorlevel
+               [ERRORLEVEL_NOCARRIER]  Carrier lost errorlevel
+               [ERRORLEVEL_HANGUP]     Sysop manually terminated call
+               [ERRORLEVEL_TIMEOUT]    User time expired errorlevel
+               [ERRORLEVEL_INACTIVITY] Keyboard inactivity timeout errorlevel
+               [ERRORLEVEL_DROPTOBBS]  Sysop returned user to BBS errorlevel
+               [ERRORLEVEL_NORMAL]     Door has exited normally
+
+               If you wish to override the default errorlevels used by
+               OpenDoors, you should set element [ERRORLEVEL_ENABLE] of this
+               array to TRUE, and set the remaining array elements to the
+               appropriate errorlevels. Note that the settings in this array
+               only affect the errorlevels which OpenDoors uses when it causes
+               the door to exit for one of the reasons listed above. This
+               setting has no effect on the errorlevel returned when your
+               program explicitly exits by calling the od_exit() function, or
+               your program returns by calling exit() or returning from the
+               main() function.
+
+
+
+-------------------------------------------------------------------------------
+od             char od_control.od_force_local;
+_force_local
+               This variable defaults to FALSE, which causes OpenDoors to
+               behave normally. When this variable is set to TRUE prior to
+               calling od_init() or any other OpenDoors functions, OpenDoors
+               will operate in local mode. In this case, no door information
+               file will be read. Also, the user name will be used if
+               od_control.user_name has not been set prior to calling od_init()
+               or the first OpenDoors function.
+
+               The default OpenDoors settings when od_control.od_force_local is
+               set are as follows:
+
+               - ANSI mode is on
+               - Time limit is 60 minutes
+               - User's location is the name of the BBS, or "Unknown Location"
+               otherwise if BBS name is not known.
+               - User name is set to sysop's name ("Sysop" if no sysop name is
+               specified in the configuration file).
+
+               You may wish to add a "-local" type parameter to your program's
+               command line, which will permit the sysop to easily operate the
+               door in local mode, as an interface to the
+               od_control.od_force_local setting.
+
+
+
+-------------------------------------------------------------------------------
+od_help        void (*od_control.od_help_callback)(void);
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 201
+
+_callback
+               If this variable is set to a non-NULL value, the Win32 version
+               of OpenDoors will provide a Contents item on the help menu, and
+               call the function pointed to by this variable when the user
+               chooses the Contents menu item.
+
+
+
+-------------------------------------------------------------------------------
+od_in_buf      unsigned int od_control.od_in_buf_size;
+_size
+               Specifies the size, in characters, of the OpenDoor's internal
+               local/remote inbound buffer size. Two bytes of storage are
+               required for each character in this buffer. This variable should
+               only be changed prior to calling od_init() or the first
+               OpenDoors function. If not set, this variable defaults to a
+               value of 256.
+
+               The buffer corresponding to this variable should not be confused
+               with the FOSSIL or internal communications receive buffer (which
+               is set by od_control.od_com_rx_buf). Unlike the serial I/O
+               receive buffer, which is used only for characters received from
+               the remote system, this buffer serves as a queue for input from
+               both the remote system and the local keyboard. If you find that
+               characters are lost when information is being set to your door
+               from the user, you may wish to increase the size of this buffer.
+
+
+
+-------------------------------------------------------------------------------
+od             unsigned int od_control.od_inactivity;
+_inactivity
+               OpenDoors has a built in user-inactivity timeout facility, which
+               will automatically disconnect a user who appears .to be sleeping
+               at the keyboard. If the user has not pressed any keys on their
+               keyboard for to great a length of time, they will be warned that
+               they are about to be disconnected due to inactivity. If they
+               still do not respond after another few seconds, OpenDoors will
+               automatically disconnect the user and return control to the BBS
+               software. The od_control.od_inactivity variable allows you to
+               set the maximum length of time, in seconds, after which the user
+               will be disconnected for inactivity. This variable defaults to a
+               value of 200 seconds. You may disable OpenDoors' inactivity
+               timeout altogether, by setting the od_control.od_inactivity
+               variable to a value of 0.
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 202
+
+-------------------------------------------------------------------------------
+od_inactive    int od_control.od_inactive_warning.
+_warning
+               This variable sets the number of seconds prior to hanging up
+               that OpenDoors displays the inactivity timeout warning. This
+               variable should only be changed after your first call to an
+               OpenDoors API function. If not explicitly set by your program,
+               this setting defaults to 10 seconds.
+
+
+
+-------------------------------------------------------------------------------
+od_ker_exec    void (*od_control.od_ker_exec)(void);
+
+               When od_control.od_ker_exec is set to point to a function,
+               OpenDoors will call this function whenever od_kernel() executes.
+               This provides any easy way for you to perform your own
+               processing on a regular basis during door execution. The
+               od_control.od_ker_exec variable defaults to NULL.
+
+
+
+-------------------------------------------------------------------------------
+od_last        char od_control.od_last_input;
+_input
+               Indicates whether the last key retrieved using the od_get_key()
+               function originated from the remote user, or the local sysop. If
+               the input originated from the remote, this variable is set to 0.
+               If the input originated from the local keyboard, this variables
+               is set to 1.
+
+
+
+-------------------------------------------------------------------------------
+od_list        char od_control.od_list_pause;
+_pause
+               This variable contains a Boolean value, which allows you to
+               control whether or not the user may pause displaying within the
+               od_list_files() and od_send_file() function. When this variable
+               is set to its default value of TRUE, the user will be able to
+               pause the display by pressing the [P] key, and resume display by
+               pressing any other key. However, the pause feature may be
+               disabled by setting this variable to FALSE.
+
+
+
+-------------------------------------------------------------------------------
+od_list        char od_control.od_list_stop;
+_stop
+               This variable contains a Boolean value, which allows you to
+               control whether or not the user may abort displaying within the
+               od_list_files() and od_send_file() function. When this variable
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 203
+
+               is set to its default value of TRUE, the user will be able to
+               pause the display by pressing the [S], [CTRL]-[K] or [CTRL]-[C]
+               keys. However, the stop feature may be disabled by setting this
+               variable to FALSE.
+
+
+
+-------------------------------------------------------------------------------
+od_local       void (*od_control.od_local_input)(int);
+_input
+               If set, this function is called whenever the sysop presses a
+               non-sysop-function key on the local keyboard. The key pressed is
+               passed to the function in the single int parameter that it
+               accepts.
+
+
+
+-------------------------------------------------------------------------------
+od_logfile     void *(od_control.od_logfile)(void);
+
+               To make the OpenDoors log file system available in your program,
+               set this variable to INCLUDE_LOGFILE, prior to calling any
+               OpenDoors functions. If not set, or if set to NO_LOGFILE, the
+               OpenDoors log file system will not automatically be enabled.
+
+
+
+-------------------------------------------------------------------------------
+od_logfile     char od_control.od_logfile_disable;
+_disable
+               This variable defaults to the value of FALSE, unless the
+               "LogfileDisable" option is specified in the configuration file,
+               in which case the variable will be set to TRUE. If this variable
+               is set to TRUE, OpenDoors will not write to a logfile, even if
+               the logfile system is enabled using od_control.od_logfile.
+
+
+
+-------------------------------------------------------------------------------
+od_logfile     char *od_control.od_logfile_messages[14];
+_messages
+               This array of pointers to strings contains the messages that
+               OpenDoors will automatically write to the log file, if the log
+               file system is enabled. If you wish to change the settings of
+               this array, you should do so before calling any OpenDoors
+               functions. The default strings for this array are as follows:
+
+               [0] "Carrier lost, exiting door"
+               [1] "System operator terminating call, exiting door"
+               [2] "User's time limit expired, exiting door"
+               [3] "User keyboard inactivity time limit exceeded, exiting door"
+               [4] "System operator returning user to BBS, exiting door"
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 204
+
+               [5] "Exiting door with errorlevel %d,
+               [6] "Invoking operating system shell"
+               [7] "Returning from operating system shell"
+               [8] "User paging system operator"
+               [9] "Entering sysop chat mode"
+               [10] "Terminating sysop chat mode"
+               [11] "%s entering door"
+               [12] "Reason for chat: %s"
+               [13] "Exiting door"
+
+
+
+-------------------------------------------------------------------------------
+od_logfile     char od_control.od_logfile_name[80];
+_name
+               This variable specifies the filename, and optionally the full
+               path of the logfile where OpenDoors should perform logging. This
+               variable only has an effect when set prior to calling any
+               OpenDoors functions. If the log file name is specified in the
+               configuration file, that name will be stored in this variable.
+               If you do not set this variable, and the log file name is not
+               specified in the configuration file, the default name "DOOR.LOG"
+               will be used. If you wish to set this variable, you should do so
+               prior to calling od_init() or any OpenDoors function.
+
+
+
+-------------------------------------------------------------------------------
+od_            unsigned int od_control.od_maxtime;
+maxtime
+               This variable specifies the maximum length of time that any user
+               is permitted to use the door, and is normally set from a
+               configuration file option. If upon entering the door, the user's
+               time remaining online is greater than the od_maxtime setting,
+               their time remaining is temporarily decreased to the maximum
+               value. Then upon exit of the door, the number of subtracted
+               minutes is added back onto the user's remaining time. If the
+               user's remaining time is less than this value, then the setting
+               has no effect. A value of 0 disables the maximum time setting
+               altogether.
+
+
+
+-------------------------------------------------------------------------------
+od_maxtime     int od_control.od_maxtime_deduction;
+_deduction
+               This variable store the amount of time that should be added to
+               the user's time upon exit of the door, as a result of the
+               maximum time deduction, described above. If the maximum time
+               feature is not used, this variable will be given a value of 0.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 205
+
+
+-------------------------------------------------------------------------------
+od_mps         void (*od_control.od_mps)(void);
+
+               To make the OpenDoors Multiple Personality system available in
+               your program, set this variable to INCLUDE_MPS before calling
+               any OpenDoors functions. If this variable is not set, or is set
+               to NO_MPS, the Multiple Personality System will be disabled. For
+               more information on the OpenDoors Multiple Personality System,
+               see page 233.
+
+
+
+-------------------------------------------------------------------------------
+od_no_         void (*od_control.od_no_file_func)();
+file_func
+               If od_no_file_func is set to point to a function, that function
+               will be called whenever a door information (drop) file cannot be
+               located or read. This provides an easy mechanism to add your own
+               door information file reader, or to provide a local login prompt
+               when no drop file is present. If you wish the door to operate in
+               local mode, you should set od_control.od_force_local to TRUE
+               prior to returning from your function. If you have successfully
+               read your own door information file format, you should set
+               od_control.od_info_type to CUSTOM. If neither of these variables
+               are set by the od_no_file_function, OpenDoors will report that
+               it is unable to find or read a door information file and will
+               exit immediately.
+
+
+
+-------------------------------------------------------------------------------
+od_no_ra       char od_control.od_no_ra_codes;
+_codes
+               This variable defaults to FALSE. When set to TRUE, the
+               translation of the RemoteAccess/QuickBBS control codes by the
+               functions od_send_file(), od_hotkey_menu() and od_disp_emu() is
+               disabled.
+
+
+
+-------------------------------------------------------------------------------
+od_            char od_control.od_nocopyright;
+nocopyright
+               This variable is a Boolean value that allows you to prevent
+               OpenDoors from displaying its name, version number, copyright
+               notice and registration information when the program begins
+               execution. Set this variable to TRUE to disable the display of
+               copyright and associated information. When this variable is set
+               to TRUE, OpenDoors also does not change the initial display
+               color on startup. For obvious reasons, this variable does not
+               take effect when OpenDoors is operating in unregistered mode.
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 206
+
+
+
+
+-------------------------------------------------------------------------------
+od_noexit      char od_control.od_noexit;
+
+               This variable contains a Boolean value, which allows you to
+               prevent OpenDoors from exiting when shutting down. This may be
+               useful when you want to have your program to do more processing
+               after you have called the od_exit() function, or if you do not
+               wish to have your program exit automatically when the user drops
+               carrier. Normally, this variable will default to a value of
+               FALSE, indicating that OpenDoors will exit normally when the
+               od_exit() function is called. However, you may optionally set
+               this variable to TRUE after od_init() or some OpenDoors function
+               has been called. In this case, when the od_exit() function is
+               called, either by your program manually, or automatically by
+               OpenDoors in response to the user dropping carrier, etc.,
+               OpenDoors will not exit. However, the normal operations of
+               closing the serial port and re-writing the door information file
+               will be carried out. If you set the od_noexit variable to TRUE,
+               you will probably have to provide some mechanism to allow your
+               program to detect when OpenDoors shutdowns due to the loss of
+               carrier, etc. The best way of doing this is to provide a
+               function which is to be called at the beginning of the od_exit()
+               function, by setting the od_control.od_before_exit pointer,
+               described above.
+
+
+
+-------------------------------------------------------------------------------
+od_page        char od_control.od_page_len;
+_len
+               This variable allows you to control the length, in seconds, of
+               the sysop page beep produced when the user pages the sysop via
+               the od_page() function.
+
+
+
+-------------------------------------------------------------------------------
+od_page        char od_control.od_page_pausing;
+_pausing
+               This variable contains a Boolean value that indicates whether or
+               not page pausing is enabled in the od_send_file(),
+               od_hotkey_menu() and od_list_files() functions. The default
+               value of TRUE indicates that page pausing is enabled. A value of
+               FALSE indicates that page pausing is disabled.
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 207
+
+-------------------------------------------------------------------------------
+od_page        int od_control.od_pagestartmin;
+startmin       int od_control.od_pageendmin;
+
+od_page        These variables indicate the start and end times for sysop
+endmin         paging, expressed as the number of minutes past midnight.
+               Sysop paging will be available through the od_page() function
+               from the start time, up to but not including the end time.
+
+
+
+-------------------------------------------------------------------------------
+od_page        char od_control.od_page_statusline;
+_statusline
+               This variable controls which status line, if any, is activated
+               when the user pages the system operator (via the od_page()
+               function). A value between 0 and 9 causes the corresponding
+               status line to be activated. A value of -1 prevents any change
+               from being made to the current status line setting. This
+               variable will normally be set by personality functions (see page
+               233).
+
+
+
+-------------------------------------------------------------------------------
+od_prog_       char od_control.od_prog_copyright[40];
+copyright
+               This variable should contain your program's copyright notice,
+               such as "(C) Copyright 1996 by Your Name". This information is
+               used in the Help|about dialog box under the Win32 version of
+               OpenDoors, and may be used in other places in future versions of
+               OpenDoors.
+
+
+
+-------------------------------------------------------------------------------
+od_prog_name   char od_control.od_prog_name[40];
+
+               This variable should contain the full name of your program, up
+               to 39 characters. If not set, OpenDoors will use the string
+               "OpenDoors" in place of this variable. If used, this variable
+               should be set prior to calling any OpenDoors functions, and
+               should not include your program's version number. This
+               information is used to write your program's name in the log file
+               and to indicate your program's name on various windows, among
+               other places.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 208
+
+
+
+
+-------------------------------------------------------------------------------
+od_prog_version     char od_control.od_prog_version[40];
+
+               This variable should contain the version information of your
+               program. If used, this variable should be set prior to calling
+               any OpenDoors functions. This information is used in the
+               Help|About dialog box under the Win32 version of OpenDoors,
+               among other places.
+
+
+
+-------------------------------------------------------------------------------
+od_reg_key     unsigned log od_control.od_reg_key;
+
+               When you purchase an OpenDoors licence (registration), this
+               variable should be set to your registration key, prior to
+               calling any OpenDoors functions.
+
+
+
+-------------------------------------------------------------------------------
+od_reg_name    char od_control.od_reg_name[36];
+
+               When you purchase an OpenDoors licence (registration), this
+               variable should be set to your name, or your company's name, as
+               is listed in your OpenDoors registration record.
+
+
+
+-------------------------------------------------------------------------------
+od_spawn       char od_control.od_spawn_freeze_time;
+_freeze_time
+               This variable is a Boolean value which indicates whether or not
+               the user's time remaining is frozen during the execution of one
+               of the od_spawn...() functions. If this variable is set to TRUE,
+               the user's time remaining will not decrease during the time that
+               the od_spawn...() function is executing. However, if this
+               variable is set to FALSE, the user's time remaining will
+               continue to be subtracted during the execution of the
+               od_spawn...() function. The default value of this variable is
+               FALSE.
+
+
+
+-------------------------------------------------------------------------------
+od_swapping    char od_control.od_swapping_disable;
+_disable
+               This variable is a Boolean value which specifies whether or not
+               OpenDoors will attempt to swap itself and your entire door upon
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 209
+
+               DOS shell or a call to one of the od_spawn...() functions. This
+               variable defaults to FALSE. If set to TRUE, OpenDoors will not
+               attempt to perform swapping activities.
+
+
+
+-------------------------------------------------------------------------------
+od_swapping    char od_control.od_swapping_noems;
+_noems
+               This variable is a Boolean value which can be used to prevent
+               OpenDoors from swapping to EMS memory. This variable defaults to
+               the value FALSE. If set to TRUE, OpenDoors will not attempt to
+               use EMS memory for swapping, and will only swap to disk.
+
+
+
+-------------------------------------------------------------------------------
+od_swapping    char od_control.od_swapping_path;
+_path
+               This variable specifies the drive and directory where OpenDoors
+               should create its disk swapping file, if applicable. More than
+               one path can be specified, by separating the paths with a semi-
+               colon (;) character.
+
+
+
+-------------------------------------------------------------------------------
+od_status      char od_control.od_status_on;
+_on
+               This variable is a Boolean value which allows your program to
+               completely disable the OpenDoors status line. The variable
+               defaults to a value of TRUE, which causes the OpenDoors status
+               line to be controllable by function keys, displayed and updated
+               as it would normally be. However, if this variable is set to
+               FALSE, then OpenDoors will not update the status line, nor will
+               it allow the status line to be re-displayed as a result of one
+               of the status line ([F1] through [F10]) keys being pressed. When
+               you change the value of this variable from FALSE to TRUE,
+               OpenDoors will automatically redisplay the status line. Note,
+               however, that the status line isn't automatically removed when
+               the value of this variable is changed from TRUE to FALSE. In
+               order to erase the status line after resetting the value of this
+               variable, you should reset the output window to the full screen,
+               by calling the function window(1,1,25,80). Then manually erase
+               the old status line either by clearing the bottom two lines of
+               the screen, or by clearing the entire screen.
+
+               It is important that you do not confuse the use of this variable
+               with the od_set_statusline() function, which is described on
+               page 137. When the status line is enabled, the sysop can change
+               which status line, if any, is being displayed, using the
+               function keys [F1] through [F10]. The od_set_statusline()
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 210
+
+               function allows your program to make the same changes to the
+               status line setting which the sysop can make by pressing one of
+               the function keys. The status line can be removed from the
+               screen, allowing a full 25 lines of text to be displayed, by
+               pressing the [F10] key, or by making the appropriate call to the
+               od_set_statusline() function. Note, however, than when this is
+               done, the status line is still enabled, and can be turned on by
+               pressing any of the other function keys. On the other hand, if
+               the status line is turned off using this variable
+               (od_control.od_status_on), the status line sub-system will be
+               disabled, and pressing function keys will not "bring it back".
+               So, if you were writing a program where a status line would be
+               undesirable - such as a non-door communications program, you
+               would use the od_control.od_status_on variable. On the other
+               hand, if you only wanted to temporarily remove the status line -
+               say in order that all 25 lines of a door program's output could
+               be viewed - while still allowing the status line to be turned on
+               with the sysop function keys, you would use the
+               od_set_statusline() function. For more information on the
+               od_set_statusline() function, see page 137.
+
+
+
+-------------------------------------------------------------------------------
+od_time        void (*od_control.od_time_msg_func)(char *string)
+_msg_func
+               This variable defaults to a value of NULL. If set to point to a
+               function, OpenDoors will call this function INSTEAD OF
+               displaying time limit warning messages to the user. The messages
+               redirected to this function are:
+
+                    - Inactivity timeout warning
+                    - Inactivity timeout expired
+                    - Less than 4 minutes left today
+                    - Daily time limit expired
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 211
+
+CONTROL STRUCTURE - FUNCTION KEYS
+-------------------------------------------------------------------------------
+
+               Within OpenDoors, as with most BBS software and doors, the sysop
+               has access to a number of function keys, which permits the sysop
+               to carry out various functions such as entering chat mode,
+               hanging up on the user, shelling to DOS, and so on. The
+               variables in this section allow you to customize which keys
+               carry out the standard sysop functions, allowing you to
+               customize your door's interface to mimic any BBS package. By
+               default, OpenDoors emulates the function keys used by the Remote
+               Access BBS package, but you may choose, for example, to have
+               your door use the key combinations used by PC-Board. In
+               addition, OpenDoors provides an interface which allows you to
+               add your own function keys which will be accepted by the door.
+               This could allow you to add additional features, such as giving
+               the sysop access to a status screen which displays information
+               about your door.
+
+               Many of the variables in this section are unsigned ints, which
+               represent a sysop key combination such as [ALT]-[H], [F8], or
+               [CTRL]-[P]. These values are in the same format as is returned
+               by the Turbo C(++) / Borland C++ bioskey() function. The high-
+               order byte represents the scan code of the key, and the low-
+               order byte represents the ASCII value, if any, of the key
+               combination. Note that a complete tutorial on these key codes is
+               beyond the scope of this manual. For more information on these
+               key codes, you should see the documentation on the bioskey()
+               function, which accompanies your compiler. If you wish to
+               determine the key code which corresponds to a particular
+               keystroke, there is a simple program, listed below, which you
+               can compile and use. This program will simply display the key
+               code for any key pressed, until you press the [ESCape] key. So,
+               in order to determine the code for [SHIFT]-[F8], you would
+               simply run this program, press the [SHIFT]-[F8] key combination
+               on your keyboard, and record the value displayed on your screen.
+
+                         #include <stdio.h>
+                         #include <bios.h>
+                         main()
+                         {
+                            int nKey;
+
+                            do
+                               {
+                               nKey = bioskey(0);
+                               printf("%d (from: %x, %x)\n",
+                                  nKey, nKey>>8, nKey&0xff);
+                               } while((nKey & 0xff) != 27);
+                         }
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 212
+
+
+-------------------------------------------------------------------------------
+BUILT IN       These variable allow you to customize the sysop function keys
+FUNCTION       which control functions such as hanging up on the user, shelling
+KEYS           to DOS, and so on. All of these variable will be assigned
+               default values, which correspond to the same function keys used
+               by the RemoteAccess BBS package. However, you may change the
+               values of these variables in order to customize the key
+               combinations which carry out these functions in your own door
+               program. Remember that if you wish to change the value of any of
+               these variables, you must do so after having called od_init() or
+               some OpenDoors function. Each of these variables contain a scan-
+               code / ASCII-code combination representing a keystroke, as is
+               described above. These variables are as follows:
+
+               +---------------------+----------------------------------------+
+               | VARIABLE            | CORRESPONDING FUNCTION                 |
+               +---------------------+----------------------------------------+
+               | od_control.         | Enter sysop chat mode                  |
+               | key_chat            | (Normally [ALT]-[C]                    |
+               |                     |                                        |
+               | od_control.         | Invoke sysop DOS shell                 |
+               | key_dosshell        | (Normally [ALT]-[J]                    |
+               |                     |                                        |
+               | od_control.         | Return to the BBS without hanging up   |
+               | key_drop2bbs        | (Normally [ALT]-[D])                   |
+               |                     |                                        |
+               | od_control.         | Hangup on the user                     |
+               | key_hangup          | (Normally [ALT]-[H])                   |
+               |                     |                                        |
+               | od_control.         | Turn off the user's keyboard           |
+               | key_keyboardoff     | (Normally [ALT]-[K])                   |
+               |                     |                                        |
+               | od_control.         | Decreases the user's remaining time    |
+               | key_lesstime        | (Normally [DOWN-ARROW])                |
+               |                     |                                        |
+               | od_control.         | Lock the user out of the BBS system    |
+               | key_lockout         | (Normally [ALT]-[L])                   |
+               |                     |                                        |
+               | od_control.         | Increases the user's remaining time    |
+               | key_moretime        | (Normally [UP-ARROW])                  |
+               |                     |                                        |
+               | od_control.         | Array of eight function keys to set the|
+               | key_status[8]       | current status line.                   |
+               |                     | (Normally [F1], [F2], [F3], [F4], [F5],|
+               |                     |  [F6], [F9], [F10])                    |
+               |                     |                                        |
+               | od_control.         | "Sysop next" toggle key                |
+               | key_sysopnext       | (Normally [ALT]-[N])                   |
+               +---------------------+----------------------------------------+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 213
+
+
+-------------------------------------------------------------------------------
+CUSTOM         In addition to the sysop function keys built into OpenDoors, you
+FUNCTION       may wish to add your own function keys to your door. For
+KEYS           example, you might wish to have the [ALT]-[Z] combination
+               display a window of information about your door, or you may wish
+               to add your own user editor to your door, accessible through the
+               [ALT]-[E] combination. The four variables:
+
+                         unsigned char od_control.od_num_keys;
+                         unsigned int od_control.od_hot_key[16];
+                         unsigned int od_control.od_last_hot;
+                         void (*od_control.od_hot_function[16])(void);
+
+               provide your program with an interface to add your own sysop
+               function keys (not accessible by the remote user) to the door
+               you have written.
+
+               OpenDoors allows you to define up to sixteen custom sysop
+               function keys. The key codes (as described at the beginning of
+               this section) are stored in the od_control.od_hot_key[] array,
+               and the od_control.od_num_keys variable records the number of
+               keys which have been defined. The od_control.od_num_keys
+               variable defaults to a value of 0. So, in order to add your own
+               function keys, simply place the key codes for these keys in the
+               first n elements of the od_control.od_hot_key[] array, and set
+               the od_control.od_num_keys variable to the number of keys you
+               have defined. OpenDoors will then watch the keyboard for any of
+               your predefined sysop function keys being pressed. If one of
+               these keys is pressed, OpenDoors will place the key code of the
+               pressed key in the od_control.od_last_hot variable. Your program
+               will then be able to respond to one of your custom function keys
+               being pressed by checking the value of the
+               od_control.od_last_hot variable. At any time this variable
+               contains a non-zero value. If this is the case, you will then be
+               able to determine which of your function keys has been pressed
+               by checking the key code contained in this variable. After
+               taking the appropriate action for the key pressed, you should be
+               sure to reset the value of the od_control.od_last_hot variable
+               back to zero, which will indicate to OpenDoors that your program
+               has received and responded to the function key which was
+               pressed.
+
+               As an alternative to testing the contents of the
+               od_control.od_last_hot variable, you  can also have your program
+               respond to custom sysop function keys by providing a callback
+               function in the array: void
+               (*od_control.od_hot_function[16])(void);
+
+               The Nth element in this array corresponds to the Nth element in
+               the od_control.od_hot_key array. To use this mechanism, simply
+               set the appropriate element of this array to point to the
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 214
+
+               function that you wish to have OpenDoors call when the sysop
+               presses the corresponding function key. For instance, assume
+               that the following function is included in your program's source
+               code:
+
+                    void addPoints(void)
+                    {
+                       /* add ten points to the user's score */
+                       currentUser->points += 10;
+                    }
+
+               If you wanted to have this function called when the sysop
+               presses the [Page Up] key, you could do the following:
+
+                    /* get number of new sysop function key, and increment */
+                    /* total number of keys */
+                    int new_key = od_control.od_num_keys++;
+
+                    /* Set next sysop hotkey to Page Up */
+                    od_control.od_hot_key[new_key] = 0x4900;
+
+                    /* Set corresponding function to addPoints() */
+                    od_control.od_hot_function[new_key] = addPoints;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 215
+
+CONTROL STRUCTURE - COLOR CUSTOMIZATION
+-------------------------------------------------------------------------------
+
+               These variables allow you to customize the color of text
+               displayed by OpenDoors. Each of these variables are assigned
+               color attributes, in the format used by od_set_attrib()
+               (described on page 128). These variables are as follows:
+
+               +---------------------+----------------------------------------+
+               | VARIABLE            | WHERE COLOR IS USED                    |
+               +---------------------+----------------------------------------+
+               | od_control.         | Text typed by the sysop and user in    |
+               | od_chat_color1 & 2  | chat mode.                             |
+               |                     |                                        |
+               | od_control.         | File description fields in FILES.BBS   |
+               | od_list_comment_col | listings                               |
+               |                     |                                        |
+               | od_control.         | Color of page pausing prompt that is   |
+               | od_continue_col     | displayed at the end of each page      |
+               |                     |                                        |
+               | od_control.         | Filename fields in FILES.BBS listings  |
+               | od_list_name_col    |                                        |
+               |                     |                                        |
+               | od_control.         | "Missing" string in FILES.BBS listings |
+               | od_list_offline_col |                                        |
+               |                     |                                        |
+               | od_control.         | File size fields in FILES.BBS listings |
+               | od_list_size_col    |                                        |
+               |                     |                                        |
+               | od_control.         | Title fields in FILES.BBS listings     |
+               | od_list_title_col   |                                        |
+               |                     |                                        |
+               | od_control.         | Color of the window title as displayed |
+               | od_menu_title_col   | by od_popup_menu()                     |
+               |                     |                                        |
+               | od_control.         | Color of the window border as          |
+               | od_menu_border_col  | displayed by od_popup_menu()           |
+               |                     |                                        |
+               | od_control.         | Color of the normal text displayed     |
+               | od_menu_text_col    | by od_popup_menu()                     |
+               |                     |                                        |
+               | od_control.         | Color of the shortcut keys displayed   |
+               | od_menu_key_col     | by od_popup_menu()                     |
+               |                     |                                        |
+               | od_control.         | Color of the selection bar as          |
+               | od_menu_highlight_  | displayed by od_popup_menu()           |
+               | col                 |                                        |
+               |                     |                                        |
+               | od_control.         | Color of the shortcut keys displayed   |
+               | od_menu_highkey_col | on the selected line by od_popup_menu()|
+               +---------------------+----------------------------------------+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 216
+
+CONTROL STRUCTURE - TEXT CUSTOMIZATION
+-------------------------------------------------------------------------------
+
+               In addition to the other aspects of OpenDoors which may be
+               customized by use of the OpenDoors control structure, all of the
+               text displayed by OpenDoors may also be customized. This may be
+               done either to create doors with OpenDoors that use languages
+               other than English, or to simply give your doors a "personal
+               touch". The variables described in this section allow you to
+               define what text you want to have displayed by OpenDoors at any
+               time. All of these variables are pointers to strings, and are
+               set to default values in the od_init() function. Thus, if you
+               wish to change the string pointed to by any of these variables,
+               you must do so after od_init() or some OpenDoors API function
+               has been called. To set any of these variables, you can simply
+               set them to point to a string-constant in your program. For
+               example, to set the text displayed by OpenDoors prior to a DOS
+               shell, you could:
+
+               od_control.od_before_shell=(char *)"\n\rJust a moment...\n\r";
+
+               The chart below lists each of the text customization variables
+               (without the "od_control." prefix, for the sake of brevity),
+               along with their default strings.
+
+               Note that some of these strings MUST always be the same length
+               as their default string. You may not display longer text within
+               these strings, and if you wish to display shorter text, you must
+               pad the remaining space in the string with spaces, in order to
+               preserve its length. Those string which must be of fixed length
+               also have their length listed in the chart below. Any strings
+               which have an asterisk (*) in their length column may be any
+               length.
+
+               Also keep in mind that any string with "printf-style" formatting
+               sequences, such as "%s", must retain the same sequences in the
+               same order.
+
+               In addition, four of these pointers - od_after_chat,
+               od_after_shell, od_before_chat and od_before_shell - can be set
+               to a value of NULL. In this case, OpenDoors will not display any
+               string where this variable's string is normally displayed.
+
++-----------------------+-----+----------------------------------------------+
+| VARIABLE NAME         | LEN | DEFAULT VALUE                                |
++-----------------------+-----+----------------------------------------------+
+| od_after_chat         |  *  | "\n\rChat mode ended...\n\r\n\r"             |
+|                       |     |                                              |
+| od_after_shell        |  *  | "\n\r...Thanks for waiting\n\r\n\r"          |
+|                       |     |                                              |
+| od_before_chat        |  *  | "\n\rSysop breaking in for chat...\n\r\n\r"  |
+|                       |     |                                              |
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 217
+
+| od_before_shell       |  *  | "\n\rPlease wait a moment...\n\r"            |
+|                       |     |                                              |
+| od_chat_reason        |  *  | "                          Why would you "   |
+|                       |     | "like to chat?\n\r"                          |
+|                       |     |                                              |
+| od_continue           |  *  | "Continue? [Y/n/=]"                          |
+|                       |     |                                              |
+| od_continue_no        | char| 'N'                                          |
+|                       |     |                                              |
+| od_continue_nonstop   | char| '='                                          |
+|                       |     |                                              |
+| od_continue_yes       | char| 'Y'                                          |
+|                       |     |                                              |
+| od_day[0]             |  3  | "Sun"                                        |
+|                       |     |                                              |
+| od_day[1]             |  3  | "Mon"                                        |
+|                       |     |                                              |
+| od_day[2]             |  3  | "Tue"                                        |
+|                       |     |                                              |
+| od_day[3]             |  3  | "Wed"                                        |
+|                       |     |                                              |
+| od_day[4]             |  3  | "Thu"                                        |
+|                       |     |                                              |
+| od_day[5]             |  3  | "Fri"                                        |
+|                       |     |                                              |
+| od_day[6]             |  3  | "Sat"                                        |
+|                       |     |                                              |
+| od_hanging_up         |  *  | "Terminating Call"                           |
+|                       |     |                                              |
+| od_help_text          |  80 | "  Alt: [C]hat [H]angup [L]ockout [J]Dos "   |
+|                       |     | "[K]eyboard-Off [D]rop to BBS            "   |
+|                       |     |                                              |
+| od_help_text2         |  79 | "  OpenDoors 6.00 - (C)Copyright 1992, "     |
+|                       |     | "Brian Pirie - Registered Version         "  |
+|                       |     |                                              |
+| od_inactivity_timeout |  *  | "User sleeping at keyboard, inactivity "     |
+|                       |     | "timeout...\n\r\n\r"                         |
+|                       |     |                                              |
+| od_inactivity_warning |  *  | "Warning, only %d minute(s) remaining "      |
+|                       |     | "today...\n\r\n\r"                           |
+|                       |     |                                              |
+| od_month[0]           |  3  | "Jan"                                        |
+|                       |     |                                              |
+| od_month[1]           |  3  | "Feb"                                        |
+|                       |     |                                              |
+| od_month[2]           |  3  | "Mar"                                        |
+|                       |     |                                              |
+| od_month[3]           |  3  | "Apr"                                        |
+|                       |     |                                              |
+| od_month[4]           |  3  | "May"                                        |
+|                       |     |                                              |
+| od_month[5]           |  3  | "Jun"                                        |
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 218
+
+|                       |     |                                              |
+| od_month[6]           |  3  | "Jul"                                        |
+|                       |     |                                              |
+| od_month[7]           |  3  | "Aug"                                        |
+|                       |     |                                              |
+| od_month[8]           |  3  | "Sep"                                        |
+|                       |     |                                              |
+| od_month[9]           |  3  | "Oct"                                        |
+|                       |     |                                              |
+| od_month[10]          |  3  | "Nov"                                        |
+|                       |     |                                              |
+| od_month[11]          |  3  | "Dec"                                        |
+|                       |     |                                              |
+| od_no_keyboard        |  10 | "[Keyboard]"                                 |
+|                       |     |                                              |
+| od_no_sysop           |  *  | "\n\rI'm afraid the sysop is not available " |
+|                       |     | "at this time.\n\r"                          |
+|                       |     |                                              |
+| od_no_response        |  *  | " No response.\n\r\n\r"                      |
+|                       |     |                                              |
+| od_no_time            |  *  | "Sorry, you have used up your time for "     |
+|                       |     | "today...\n\r\n\r"                           |
+|                       |     |                                              |
+| od_offline            |  10 | "[OFFLINE] "                                 |
+|                       |     |                                              |
+| od_paging             |  *  | "\n\rPaging Sysop for Chat"                  |
+|                       |     |                                              |
+| od_press_key          |  *  | "Press [Enter] to continue..."               |
+|                       |     |                                              |
+| od_sending_rip        |  *  | "\xb4 Sending RIP File \xc3"                 |
+|                       |     |                                              |
+| od_status_line[0]     |  80 | "                                        "   |
+|                       |     | "                             [Node:     "   |
+|                       |     |                                              |
+| od_status_line[1]     |  *  | "%s of %s at %u BPS"                         |
+|                       |     |                                              |
+| od_status_line[2]     |  79 | "Security:        Time:                  "   |
+|                       |     | "                             [F9]=Help "    |
+|                       |     |                                              |
+| od_sysop_next         |  5  | "[SN] "                                      |
+|                       |     |                                              |
+| od_time_left          |  10 | "%d mins   "                                 |
+|                       |     |                                              |
+| od_time_warning       |  *  | "Warning, only %d minute(s) remaining tod"   |
+|                       |     | "ay...\n\r\n\r"                              |
+|                       |     |                                              |
+| od_want_chat          |  11 | "[Want-Chat]"                                |
++-----------------------+-----+----------------------------------------------+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 219
+
+    66
+   66
+  66
+ 66666
+ 66  66
+ 66  66
+  6666
+-------------------------------------------------------------------------------
+CHAPTER 6 - SPECIAL TOPICS
+
+
+
+
+ADDITIONAL INFORMATION ON THE WIN32 VERSION
+-------------------------------------------------------------------------------
+
+               This section provides additional information on the Win32
+               version of OpenDoors that isn't found elsewhere in this manual.
+               If you are working with the Win32 version of OpenDoors, you
+               should take the time to read this entire section. You should
+               also read the sections in chapter 3 that describe how to compile
+               and run Win32 programs that use OpenDoors.
+
+               The Win32 version of OpenDoors has been designed to be as
+               similar as possible to the DOS version of OpenDoors. This means
+               that where possible, you can compile the same source code to
+               produce both a DOS and a Windows program. However, if you are
+               porting an existing DOS OpenDoors-based program to the Win32
+               platform, there are some important things to keep in mind.
+
+               The first thing to note is that under DOS, the program's
+               execution begins in the main() function, whereas under Windows,
+               it begins in the WinMain() function. To allow the same source
+               file to build both DOS and Windows versions you can use
+               conditional compilation. OpenDoor.h defines a constant of the
+               form ODPLAT_xxx, indicating which version of OpenDoors is being
+               used. Currently, this will be either ODPLAT_DOS, or
+               ODPLAT_WIN32. However, if a OS/2 or Unix version of OpenDoors
+               were created, they would use definitions such as ODPLAT_OS2, or
+               ODPLAT_UNIX. Under the Win32 version, you should pass the
+               nCmdShow parameter that is passed to WinMain into OpenDoors,
+               through od_control.od_cmd_show. If you do not do this, the
+               program will always start with the main window maximized,
+               regardless of what the user has requested. Also, you will
+               probably want to use the new od_parse_cmd_line() function in
+               both DOS and Windows programs, to allow standard command-line
+               options to be processed. The od_parse_cmd_line() function
+               accepts command line information in the same format as it is
+               passed to the main or WinMain() function. So, the general
+               structure of an OpenDoors program that can be compiled under
+               either DOS or Win32 now becomes:
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 220
+
+               /* Add your own #includes here. */
+
+               #include "opendoor.h"
+
+               #ifdef ODPLAT_WIN32
+               int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+                  LPSTR lpszCmdLine, int nCmdShow)
+               #else
+               int main(int argc, char *argv[])
+               #endif
+               {
+                    /* Add local variables here. */
+
+               #ifdef ODPLAT_WIN32
+                  od_control.od_cmd_show = nCmdShow;
+
+                  od_parse_cmd_line(lpszCmdLine);
+               #else
+                  od_parse_cmd_line(argc, argv);
+               #endif
+
+                    /* Add the rest of your program after this point. */
+               }
+
+               If you are porting existing OpenDoors programs over to the Win32
+               version of OpenDoors, another issue that you will have to pay
+               careful attention to is the fact that you are now working in the
+               32-bit world. While 32-bit programming under a flat memory model
+               has many advantages (no more 64K segments and related
+               limitations, for example), you must be aware that the size of
+               basic data types that you are used to using may have changed.
+               For example, an int is now 32-bits wide instead of 16-bits wide.
+               One of the places where this difference becomes very important
+               is if you are performing file-I/O by directly dumping a
+               structure to or from disk using functions such as fread() and
+               fwrite(). In this case, you must declare your structures using
+               types that are of the same size between the 16-bit and 32-bit
+               worlds, in order for your file formats to be compatible between
+               the DOS and Win32 versions of your program. For example, the
+               EX_VOTE.C example program declares its structure using fixed-
+               sized types that are always available to any program including
+               "opendoor.h". These types are the following size, regardless of
+               what platform you are compiling under:
+
+               INT8     - 8-bit signed integer.
+               INT16    - 16-bit signed integer.
+               INT32    - 32-bit signed integer.
+               BYTE     - 8-bit unsigned integer.
+               WORD     - 16-bit unsigned integer.
+               DWORD    - 32-bit unsigned integer.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 221
+
+               (NOTE: Obviously, the many details of 32-bit programming and
+               Windows programming are beyond the scope of this document. For
+               more information on the issues discussed here, you will probably
+               wish to consult other sources of information on Win32
+               programming.)
+
+               As you are probably aware, the Win32 edition of OpenDoors makes
+               extensive use of multithreading. The number of threads will
+               depend on what mode OpenDoors is operating in. In some
+               situations, all of the following threads may exist:
+
+               - The client thread(s), which executes the code that you write
+                 in your program, along with the OpenDoors API functions.
+               - The local screen thread, which is responsible for drawing
+                 your program's output on the screen, and receiving input from
+                 the local keyboard.
+               - The frame window thread, which handles the OpenDoors menus,
+                 toolbar, status bar and sysop function keys.
+               - The remote input thread, which receives input from the serial
+                 port and adds it to OpenDoors common local/remote input
+                 queue.
+               - The carrier detection thread, which blocks and only executes
+                 if the carrier detect signal goes low.
+               - The time update thread, which updates the user's time
+                 remaining online, and monitors user timeouts.
+
+               Since most of these threads only execute when the operating
+               system determines that there is actually something for them to
+               do, the Win32 version of OpenDoors provides very high
+               performance and responsiveness.
+
+               You may also want to make use of multithreading directly within
+               your program. If you do this, please note that while you may use
+               threads to perform background processing, OpenDoors requires
+               that you only call OpenDoors API functions from one thread.
+
+               If you wish to customize the information that is displayed in
+               the Help|About dialog box (including your program's name and
+               copyright information), provide your own application icon, or
+               add online help to the help menu, refer to the sections in the
+               manual on the following od_control variables:
+
+                    od_control.od_app_icon
+                    od_control.od_help_callback
+                    od_control.od_prog_name
+                    od_control.od_prog_version
+                    od_control.od_prog_copyright
+
+               The section that describes how to run Windows based door
+               programs under DOS-based BBS package indicates that
+               COM<n>AutoAssign=0 should be set in the system.ini file. The
+               explanation for this is as follows: The default value for this
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 222
+
+               setting in Windows 95 is -1, which prevents any Windows-based
+               program from accessing a serial port which has previously been
+               used by a non-Windows-based program, until the window that
+               program was running in is closed. By setting this value to 0,
+               you are allowing the Windows-based door program to immediately
+               use the modem, even while the MS-DOS session (VM) is still
+               active. A value of <x> greater than 0 will allow Windows-based
+               programs to access the serial port, only if the DOS-based
+               program has not accessed the serial port for at least <x>
+               seconds. For example, the default setting in Windows 3.1 was
+               COM1AutoAssign=2, which allowed Windows-based programs to access
+               the serial port if no DOS program had used it for at least 2
+               seconds.
+
+               The section that describes how to run Windows based door
+               programs under DOS-based BBS package also indicates that the
+               DTRON utility should be run after the start command returns. The
+               reason for this is that when a Windows program exits and closes
+               the serial port (by calling the CloseHandle() function), Windows
+               95 lowers the DTR line on the serial port. Most modems are
+               configured to respond to this by hanging up on the remote user.
+               From talking to other people, it seems that this "feature" (or
+               fundamental design flaw, depending on how you want to look at
+               it) is unique to Windows 95, and won't effect OpenDoors when
+               running under Windows NT. However, the majority of people will
+               undoubtedly be using the Win32 version of OpenDoors under
+               Windows 95. This is unfortunate, since the Win32 communications
+               facilities are otherwise _very_ well designed. There is a rumor
+               that Microsoft's next upgrade to Windows 95 will fix this
+               problem. However, I must stress that this is only a rumor, and
+               that I haven't received any confirmation about this from
+               Microsoft.
+
+               OpenDoors currently provides two solutions to this problem.
+
+               First of all, OpenDoors has the ability to use an already open
+               serial port handle, if that information is supplied to it.
+               Hopefully, all Windows 95-based BBS software will provide the
+               option of running a door program with the serial port still
+               open, and allow you to pass that serial port handle on the door
+               program's command line. OpenDoors allows the serial port handle
+               to be passed on the command line, or set directly in the
+               od_control structure, as is described later in this manual. On
+               BBS systems where this form of hot sharing of the serial port is
+               supported, the serial port can remain open at all times, and so
+               the CloseHandle() problem is avoided.
+
+               This means that the only situation where the CloseHandle()
+               problem still has to be dealt with is when OpenDoors is running
+               on a Windows 95 system and OpenDoors has to open the serial port
+               itself (and so must close the serial port before exiting). This
+               would be the case for most MS-DOS based BBS systems running
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 223
+
+               under Windows 95, unless some intermediate layer is provided. By
+               default, in this situation OpenDoors will disable DTR response
+               by the modem just before it closes the serial port, by sending
+               the AT&D0 command to the modem. The exact sequence of commands
+               used by OpenDoors to do this is specified by the
+               od_control.od_disable_dtr string. This DTR response disabling
+               may be turned off by setting the DIS_DTR_DISABLE
+               od_control.od_disable flag. Since many programs (OpenDoors
+               included) assume that they can hangup the modem by lowering the
+               DTR signal, a small utility will usually be run after the door,
+               which first raises the DTR signal again, and then re-enables DTR
+               response by the modem. Such a utility is included in this
+               package, named DTRON.EXE. I wrote the DTRON utility so that you
+               can freely redistributed it with your programs.
+
+               So, to summarize, the DTR disabling by OpenDoors and subsequent
+               reenabling by DTRON is only required for the Win32 version of
+               OpenDoors running under Windows 95 when the modem is configured
+               to hangup if the DTR signal is lowered, and the BBS software
+               does not have the ability to pass a live serial port handle to a
+               door program. Setting COM<n>AutoAssign in system.ini is only
+               required for the Win32 version of OpenDoors when it is being
+               called from an MS-DOS session that has previously accessed the
+               serial port.
+
+               Note that the Win32 version of OpenDoors requires Windows 95 or
+               Windows NT. It will not run under Windows 3.x, even with Win32s.
+               This is because OpenDoors makes use of the Windows 95/NT
+               multitasking and multithreading services that are not available
+               under Win32s.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 224
+
+CONFIGURATION FILE SYSTEM
+-------------------------------------------------------------------------------
+
+               One of the most useful OpenDoors features that you can
+               optionally choose to include in your programs is the OpenDoors
+               configuration file system. All that is required to enable the
+               configuration file system is to include the following line
+               before your first call to any OpenDoors function:
+
+                    od_control.od_config_file = INCLUDE_CONFIG_FILE;
+
+               OpenDoors will now search for and read an OpenDoors
+               configuration file. If you do not specify the name of this file,
+               the default name of DOOR.CFG will be used. Using this
+               configuration file, the sysop can set a wide variety of options,
+               such as modem and system configuration information, maximum time
+               limits for the door, and even define custom door information
+               (drop) file formats. The example DOOR.CFG file included in your
+               OpenDoors package shows the format and all options that are
+               automatically supported by the configuration file system. This
+               configuration file format is designed to be easy to use, and the
+               example configuration file contains comments which provide a
+               complete description of each option. Feel free to redistribute
+               DOOR.CFG or a modified version of this file with your door
+               programs. In addition to the many configuration file settings
+               already supported, you can add your own settings that are
+               specific to your particular program.
+
+               To specify your own filename for the configuration file, use the
+               od_config_filename control structure variable. For example, the
+               following line:
+
+                    od_control.od_config_filename = "MYDOOR.CFG"
+
+               causes OpenDoors to look for the configuration file MYDOOR.CFG
+               instead of the default DOOR.CFG.
+
+               OpenDoors fill first search for the configuration file in the
+               directory specified in the od_config_filename variable, if a
+               specific directory name was supplied. If not found, it will then
+               search the current directory. If the configuration file system
+               is unable to locate a configuration file, or if any settings are
+               omitted from the file, the default values for these settings
+               will be used automatically. This means that the configuration
+               file is always optional, unless your program has custom settings
+               that it requires in order to run.
+
+               The format for the configuration file is as follows. Blank lines
+               and any text following the semi-colon (;) character are ignored.
+               Configuration options are specified using a keyword, possibly
+               followed by one or more options. The keywords are not case
+               sensitive, but some of the options are. The order of options in
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 225
+
+               the configuration file is not significant, with the exception of
+               the "CustomFileLine" option. For more information on the
+               "CustomFileLine" setting, see the section that begins on page
+               230. The built-in configuration options are as follow:
+
+               BBSDir - BBS System directory. Indicates where the door
+                    information file (drop file) can be found.
+
+               DoorDir - The door's working directory. This is where the door's
+                    system files are located. OpenDoors will automatically
+                    perform a chdir into this directory at initialization, and
+                    will return to the original directory on exit.
+
+               LogFileName - Specifies the filename (path optional) where the
+                    door should record log information.
+
+               DisableLogging - Prevents door from writing to a log file.
+
+               Node - BBS node number that the door is running on. Only used if
+                    OpenDoors is unable to determine the node number by some
+                    other means.
+
+               ???dayPagingHours - Specifies sysop paging hours. Sysop paging
+                    will be permitted beginning at the start time, up until,
+                    but not including, the end time. Times should be in the 24-
+                    hour format. To disable paging on a particular day, set the
+                    paging start and end times to the same time. ???day can be
+                    one of Sunday, Monday, Tuesday, Wednesday, Thursday, Friday
+                    or Saturday.
+
+               PageDuration - Duration of sysop page. Value indicates the
+                    number of beeps that compose the sysop page alarm.
+
+               MaximumDoorTime - Maximum length of time a user is permitted to
+                    access the door. If the user's total remaining time on the
+                    BBS is less than this value, the user will only be
+                    permitted to access the door for this shorter length of
+                    time. This option is disabled by commenting out the line.
+
+               InactivityTimeout - Specifies the maximum number of seconds that
+                    may elapse without the user pressing a key, before the user
+                    will automatically be disconnected. A value of 0 disables
+                    inactivity timeouts.
+
+               SysopName - Name of the sysop. OpenDoors can usually determine
+                    the sysop's name from the door information (drop) file.
+                    How3ever, some BBS packages do not supply this information.
+                    In such cases, if the sysop's name is required by the door,
+                    it may be supplied here.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 226
+
+               SystemName - Like the sysop's name, this option can usually be
+                    determined from the door information file. If it is not
+                    available, the sysop my supply the information here.
+
+               ChatUserColor - Specifies the color of text typed by the user in
+                    sysop chat mode. The format of the color name is included
+                    in the description of the od_color_config() function.
+
+               ChatSysopColor - Specifies the color of test typed by the sysop
+                    in chat mode.
+
+               FileListTitleColor - Files.BBS listing colors.
+               FileListNameColor
+               FileListSizeColor
+               FileListDescriptionColor
+               FileListOfflineColor
+
+               SwappingDir - Directory where disk swapping will be done.
+
+               SwappingNoEMS - Disables swapping to EMS memory.
+
+               SwappingDisable - Disables swapping entirely.
+
+               LockedBPS -  BPS rate at which door should communicate with the
+                    modem.  Valid rates are 300, 600, 1200, 2400, 4800, 9600,
+                    19200 and 38400. A value of 0 forces the door to always
+                    operate in local mode. This option is not normally needed,
+                    as the information is usually available from the door
+                    information file.
+
+               FossilPort - Specifies the FOSSIL driver port number that the
+                    modem is connected to. FOSSIL port 0 usually corresponds to
+                    COM1, port 1 to COM2, and so on. This option is not
+                    normally needed, as the information is usually available
+                    from the door information file.
+
+               CustomFileName - Specifies the filename used by the custom door
+                    information file format. Described in more detail below.
+
+               CustomFileLine - Specifies the contents of a particular line in
+                    the custom door information file format.
+
+               The last two configuration file options, "CustomFileName" and
+               "CustomFileLine" allow you or the system operator using your
+               program to define your own door information (drop) file formats.
+               For more information on this topic, see the section which begins
+               on page 230.
+
+               You can also extend OpenDoor's configuration file format to add
+               your own options, by supplying a callback function that will be
+               called whenever OpenDoors encounters an unrecognized
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 227
+
+               configuration file keyword. The prototype of this function
+               should be as follows:
+
+                    custom_line_function(char *keyword, char *options)
+
+               To cause OpenDoors to use your function, you would include the
+               following line before your first call to any OpenDoors function:
+
+                    od_control.od_config_function = custom_line_function;
+
+               (You can use a different function name if you wish.) When
+               OpenDoors encounters unrecognized keyword, it will now call your
+               function, passing a pointer to an upper case version the keyword
+               string in the first parameter, and a pointer to any options that
+               follow the keyword in the second parameter. For instance, if the
+               following line were encountered in the configuration file:
+
+                    RegisteredTo    John Smith      ; Sysop's name
+
+               The parameters passed to your function would be:
+
+                    char *keyword = "REGISTEREDTO"
+                    char *options = "John Smith"
+
+               Your custom line function should be written in such a way that
+               if OpenDoors passes a configuration option to your function that
+               your function does not recognize, that option would simply be
+               ignored.
+
+               The example program below demonstrates how to use the custom
+               line function to add your own configuration file options. This
+               program looks for three custom configuration file options,
+               "RegistrationKey", "DefaultColor" and "DisplayWinners". If the
+               "RegistrationKey" option is present, the numerical value
+               following this option is stored in the global variable "key". If
+               the "DefaultColor" option is present, the color description
+               (such as "Bright Red on Black") is translated to an
+               od_set_attr() color code using od_color_config(). This color
+               setting is stored in the global variable default_color. Since
+               this variable is initialized to 0x07 (the value for dark white
+               on black), if this option is omitted, that color is used by
+               default. If the "DisplayWinners" option is included in the
+               configuration file, the global variable display_winners is set
+               to TRUE, regardless of any options that may follow this keyword.
+
+
+               #include "opendoor.h"                  /* Include opendooor.h */
+                                       /* Prototype for custom line function */
+               void custom_line_function(char *keyword, char *options);
+
+               unsigned long key=0L; /* Variables for our own config option */
+               unsigned char default_color=0x07;
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 228
+
+               char display_winners=FALSE;
+
+               main()                     /* Program's execution begins here */
+               {               /* Begin door operations, reading config file */
+                  od_control.od_config_file = INCLUDE_CONFIG_FILE;
+                               /* Tell OpenDoors to use custom line function */
+                  od_control.od_config_function = custom_line_function;
+                  od_init();
+                  /* Main program's operations go here */
+                  od_exit(10, FALSE);                        /* Exit program */
+               }
+                                            /* Code for custom line function */
+               void custom_line_function(char *keyword, char *options)
+               {                            /* If option is registration key */
+                  if(stricmp(keyword,"REGISTRATIONKEY")==0)
+                  {
+                     key=atol(options);             /* Store key in variable */
+                  }                               /* If option is text color */
+                  else if(stricmp(keyword,"DEFAULTCOLOR")==0)
+                  {               /* Get color value using od_color_config() */
+                     default_color=od_color_config(options);
+                  }         /* Example of option enabled by just the keyword */
+                  else if(stricmp(keyword,"DISPLAYWINNERS")==0)
+                  {                 /* If keyword is present, turn on option */
+                     display_winners=TRUE;
+                  }
+               }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 229
+
+DEFINING CUSTOM DOOR INFORMATION FILE FORMATS
+-------------------------------------------------------------------------------
+
+               As is mentioned in the previous section, the OpenDoors
+               configuration file system provides two settings which allow the
+               sysop to define a custom door information file format. This
+               permits OpenDoors doors to operate directly on any BBS system
+               that produces a door information file format not directly
+               supported by OpenDoors. A custom door information file format is
+               defined using the "CustomFileName" option, followed by one or
+               more lines beginning with the "CustomFileLine" option.
+
+               The "CustomFileName" option specifies the filename used to
+               distinguish this file format from other file formats. This
+               filename should not include a path. To specify the path where
+               the door information file is located, the sysop should use the
+               BBSDir configuration file setting. If the filename of the custom
+               format is the same as that of one of the built-in formats, the
+               custom format will override the built-in format.
+
+               The actual format of the custom file is specified using a number
+               of lines that begin with the keyword "CustomFileLine". Each of
+               these lines will correspond to a single line in the door
+               information file, with the option following the "CustomFileLine"
+               keyword specifying the information that can be found on that
+               line. This can be one of the following keywords:
+
+                    Ignore - Causes the next line in the door information file
+                         to be ignored. Use on lines for which none of the
+                         options below apply.
+
+                    COMPORT - COM? port the modem is connected to (0 indicates
+                         local mode)
+
+                    FOSSILPORT - Fossil port number the modem is connected to
+
+                    MODEMBPS - BPS rate at which to communicate with modem (0
+                         or non-numerical value indicates local mode)
+
+                    LOCALMODE - 1, T or Y if door is operating in local mode
+
+                    USERNAME - Full name of the user
+
+                    USERFIRSTNAME - First name(s) of the user
+
+                    USERLASTNAME - Last name of the user
+
+                    ALIAS - The user's pseudonym / handle
+
+                    HOURSLEFT - Hours user has left online
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 230
+
+                    MINUTESLEFT - Minutes user has left online, or time left
+                         online in format hh:mm
+
+                    SECONDSLEFT - Seconds user has left online, or time left
+                         online in format hh:mm:ss or format mm:ss (If more
+                         than one of the above time options are used, the user
+                         time left is taken to be the total of all of these
+                         values.)
+
+                    ANSI - 1, T, Y or G for ANSI graphics mode
+
+                    AVATAR - 1, T or Y for AVATAR graphics mode
+
+                    PAGEPAUSING - 1, T or Y if user wishes a pause at end of
+                         screen
+
+                    SCREENLENGTH - Number of lines on user's screen
+
+                    SCREENCLEARING - 1, T or Y if screen clearing mode is on
+
+                    SECURITY - The user's security level / access level
+
+                    CITY - City the user is calling from
+
+                    NODE - Node number user is connected to
+
+                    SYSOPNAME - Full name of the sysop
+
+                    SYSOPFIRSTNAME - The sysop's first name(s)
+
+                    SYSOPLASTNAME - The sysop's last name
+
+                    SYSTEMNAME - Name of the BBS
+
+               As an example of how to define custom door information file
+               formats, consider the following imaginary file format, which we
+               will name DROPINFO.TXT:
+
+                    Brian Pirie         <-- User name
+                    0                   <-- Local mode
+                    COM1:               <-- Serial port to use
+                    9600                <-- BPS rate
+                    22:30:15 05-08-95   <-- File creation time
+                    35                  <-- Time remaining (in minutes)
+                    1                   <-- ANSI mode
+                    Ottawa, Canada      <-- Location
+
+               This format would be defined in an OpenDoors configuration file
+               as follows:
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 231
+
+                    CustomFileName DROPINFO.TXT
+                    CustomFileLine USERNAME
+                    CustomFileLine LOCALMODE
+                    CustomFileLine COMPORT
+                    CustomFileLine MODEMBPS
+                    CustomFileLine IGNORE
+                    CustomFileLine MINUTESLEFT
+                    CustomFileLine ANSI
+                    CustomFileLine CITY
+
+               Notice that the first "CustomFileLine" keyword in the
+               configuration file corresponds to the first line in our
+               DROPINFO.TXT file, the second "CustomFileLine" to the second
+               line, and so on. Also notice that the keyword "IGNORE" is used
+               for the line that contains the file creation time, since there
+               is no CustomFileLine keyword that allows you to read this
+               information.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 232
+
+MULTIPLE PERSONALITY SYSTEM
+-------------------------------------------------------------------------------
+
+               The OpenDoors Multiple Personality System allows  the DOS
+               version of OpenDoors to support multiple sysop function key /
+               status line "personalities". Most commonly, you will use this
+               feature in conjunction with the "Personality" setting in the
+               OpenDoors configuration file, to allow the sysop to choose one
+               of the built-in personalities that most closely mimics the BBS
+               software they are using. OpenDoors includes the following
+               personalities:
+
+                    Configuration Keyword         Manifest constant
+                    -----------------------------------------------------------
+                    Standard                      PER_OPENDOORS
+                    PCBoard                       PER_PCBOARD
+                    RemoteAccess                  PER_RA
+                    Wildcat                       PER_WILDCAT
+
+               The PCBoard, RemoteAccess and Wildcat personalities mimic the
+               status lines and function keys used by the BBS packages with
+               those names. The Standard personality, which is the personality
+               used by default, is a trimmed down version of the status lines
+               provided by OpenDoors 4.10 and earlier.
+
+               In addition to using the personalities supplied with OpenDoors,
+               you can create your own personalities. This simply involves
+               writing a function which OpenDoors will call to setup the sysop
+               function keys and to display the status line.
+
+               Include the following line before your first call to any
+               OpenDoors function:
+
+                    od_control.od_mps = INCLUDE_MPS;
+
+               to include the multiple personality system in your program. This
+               also enables the Personality setting in the configuration file,
+               if you are using the configuration file system.
+
+               You can set the default personality to be used by OpenDoors by
+               setting od_control.od_default_personality to one of the manifest
+               constants listed in the table above. If you have included the
+               multiple personality system in your program, this setting will
+               determine the personality to use if the "Personality" option is
+               not set in the configuration file, and your program does not
+               later change the personality using the od_set_personality()
+               function. If you do not include the multiple personality system
+               in your program, this setting will determine the personality
+               that will always be used.
+
+               Creating your own personality involves writing a single
+               function.. Whenever OpenDoors needs to perform an operation that
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 233
+
+               involves the personality, it will call this function, passing
+               one of the following message values:
+
+               PEROP_INITIALIZE    Initialize the personality, installing any
+                                   custom function keys.
+               PEROP_DEINITIALIZE  Deinitialize the personality, returning any
+                                   changed settings to their original values.
+               PEROP_CUSTOMKEY     Indicates that a custom function key has
+                                   been pressed.
+               PEROP_DISPLAYx      Where x is a number from 1 to 10. Indicates
+                                   that the specified status line should be
+                                   drawn from scratch.
+               PEROP_UPDATEx       Where x is a number from 1 to 10. Indicates
+                                   that the specified status line should be
+                                   updated to reflect any changes.
+
+               If you have enabled the multiple personality system by setting
+               od_control.od_mps to INCLUDE_MPS, you can install your
+               personality function into OpenDoors by calling
+               od_add_personality(). When you call od_add_personality(), you
+               supply a string containing the name of the personality, along
+               with the top and bottom output line numbers to use. These line
+               numbers specify the portion of the screen to use for door
+               output, leaving the remainder of the screen available for
+               displaying the personality's status line. Once the personality
+               has been installed into OpenDoors, it can be selected by the
+               sysop using the "Personality" configuration file option, or
+               manually activated using the od_set_personality() function. For
+               more information on the od_add_personality() function, see page
+               47.
+
+               You can make your personality function the default personality
+               by setting od_control.od_default_personality to point to your
+               personality function. As is the case with the built-in
+               personalities, this setting will be used as the default
+               personality if you have enabled the multiple personality system
+               by setting od_control.od_mps to INCLUDE_MPS. If you have not
+               enabled the multiple personality system in this manner, your
+               personality function will become the one and only personality
+               used within your program. When creating your own personality,
+               you can use the od_control.od_page_statusline variable to set
+               which status line (if any) will be activated when the user pages
+               the system operator.
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 234
+
+LOG FILE SYSTEM
+-------------------------------------------------------------------------------
+
+               In order for the system operator to monitor system activity and
+               diagnose problems that have occurred while the system was
+               unattended, it is common for BBS software and door programs to
+               record major events in a log file. This log file typically
+               records the date and time of evens such as a user logging on or
+               off, transferring files, sending messages, paging the system
+               operator, and similar activities. Sometimes the system operator
+               will configure all of the pieces of software running on a
+               particular node to write to a single log file. In other cases,
+               the system operator will prefer to have each program write to
+               its own log file. However, software serving one line of a multi-
+               node BBS system should never attempt to write to the same log
+               file that is used by another node.
+
+               OpenDoors uses the "FrontDoor format" log file standard. This
+               was chosen as it is a clearly documented format that is quickly
+               becoming the standard for bulletin board software log files. A
+               segment from a log file produced by OpenDoors is listed below.
+
+                         ----------  Thu 25 Feb 93, Vote 6.00
+                         > 19:42:23  Brian Pirie entering door
+                         > 19:50:55  User paging system operator
+                         > 19:51:02  Entering sysop chat mode
+                         > 20:05:41  Terminating sysop chat mode
+                         > 20:18:32  User time expired, exiting door
+
+               To enable the OpenDoors log file system, simply include the
+               following line before your first call to any OpenDoors function:
+
+                    od_control.od_logfile = INCLUDE_LOGFILE;
+
+               When OpenDoors is initialized, it will open the log file and
+               begin logging activities, unless logging has been disabled with
+               the od_control.od_logfile_disable variable. The log file name
+               will be taken from the od_control.od_logfile_name variable,
+               which is usually set by the configuration file. If no logfile
+               name has been set, OpenDoors will use the logfile named
+               DOOR.LOG. Upon opening the log file, OpenDoors will write an
+               entry indicating the time at which the use entered the door.
+
+               The od_control.od_prog_name variable sets the program name that
+               is written to the log file immediately after the current date
+               information. If this variable is not set, OpenDoors will write
+               its own name and version information in this place.
+
+               When the OpenDoors log file system is enabled, OpenDoors will
+               automatically produce logfile entries for the following events:
+
+                         - User paging sysop, beginning of chat, end of chat
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 235
+
+                         - Sysop entering or returning from DOS shell
+                         - User inactivity timeout or user time expired
+                         - Sysop dropping user back to BBS
+                         - Sysop hanging up on user, sysop locking out user
+                         - User hanging up on BBS
+                         - Your program calling the od_exit() function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 236
+
+MAKING DOORS MULTI-NODE-AWARE
+-------------------------------------------------------------------------------
+
+               While the majority of BBS systems have only a single phone line,
+               allowing only one user to access the system at a time, there are
+               also many multi-node BBS systems. On such systems, it is quite
+               possible that more than one user may be using your door program
+               simultaneously. OpenDoors itself is designed for both single-
+               node and multi-node operation. However, if you want your program
+               to operate correctly on multi-node systems, there are a number
+               of concurrency issues that you must keep in mind when writing
+               your own code.
+
+               Some door programs are designed to behave on multi-node systems
+               just as they would on single-line BBSes. Others add special
+               features only possible in multi-node environments. For instance,
+               you may want to permit users to interact or chat with one
+               another in "real time". Many simple doors may not require any
+               special attention to multi-node capabilities. However, if your
+               door must access any data files or other resources that are to
+               be shared among nodes, it is necessary to carefully coordinate
+               access to these resources.
+
+               There are two primary issues that are often of concern when
+               creating door programs for multi-node systems. The first issue
+               discussed below is how to coordinate concurrent file access
+               between multiple node. The second topic we will deal with is the
+               installation of door programs on multi-node systems.
+
+
+
+CONCURRENT FILE ACCESS
+-------------------------------------------------------------------------------
+
+               One of the most important issues that arises when writing door
+               programs for multi-node systems is how to coordinate
+               simultaneous access to a single data file by multiple instances
+               of your program. While it is generally safe to have multiple
+               nodes reading simultaneously from a single file, having multiple
+               nodes updating a file without any coordination can lead to lost
+               updates and other problems. Consider, for example, the EX_VOTE.C
+               example program that is included in your OpenDoors package. When
+               the user votes on a poll, EX_VOTE.C must update the total number
+               of votes for the user's answer. Such a program that is only
+               intended for single node operation could do this by simply
+               reading the current number of votes for the appropriate option,
+               adding one to this total, and writing the updated total back to
+               the file. However, if this approach where to be used on a multi-
+               node system, it is quite possible that two users would vote on
+               the same poll after both nodes have read the poll record into
+               memory. In this situation, one node would add one to the total
+               number of votes for the poll record that it has in memory, and
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 237
+
+               write the updated information to the file. The second node would
+               then add one to its total, without reading the updated
+               information written by the first node. When the second node then
+               writes this information to the file, it overwrites the first
+               node's total with its own. The final effect is that the second
+               user's vote overwrites the first, and so the first user's vote
+               is lost.
+
+               The solution to this problem is to lock a file unit for the
+               entire update operation, to prevent other nodes from accessing
+               the unit at the same time. This unit could be the entire file,
+               or only a single record in the file. EX_VOTE.C locks its entire
+               file when performing an update operation, but in other cases it
+               may be more appropriate to only lock a single record in the
+               file. The important thing to understand is that when one node
+               locks a file unit, other nodes much wait until the first node is
+               finished the update operation. This means that if one node is
+               updating information that other nodes could possibly need access
+               to, it should always perform the lock, read, write and unlock
+               cycle as quickly as possible.
+
+               Let's look again at the approach taken by EX_VOTE.C. After the
+               user has indicated which option he/she wishes to vote on, Vote
+               attempts to open the file for exclusive access. By doing this,
+               EX_VOTE.C in effect locks the entire file for the duration that
+               it has the file open. If another node attempts to open the file
+               while one node has it locked, the open operation will fail, and
+               the C runtime library will set the errno variable to EACCES.
+               This, in effect, tells you that another node is currently
+               working on the file, and that you must wait your turn. In this
+               case, EX_VOTE.C continues to retry the open operation until the
+               other node is finished its update, at which time the open
+               operation will succeed. This approach will even work when there
+               are many nodes that are attempting to update the file at the
+               same time. Whichever node first attempts to open the file will
+               gain exclusive access to the file, and any additional nodes are
+               forced to wait for access to the file. When one node finishes
+               with the file, another node will gain access to the file
+               (whichever happens to be the next node to re-attempt the open
+               operation). This process continues until all waiting nodes have
+               had a chance to perform their update. EX_VOTE.C will repeatedly
+               try to open the file for up to 20 seconds, after which time it
+               will give up, reporting an error which indicate that it is
+               unable to access the file. During this waiting process,
+               EX_VOTE.C repeatedly calls od_kernel(), so that sysop function
+               keys, carrier detection and other essential door operations can
+               continue to be performed.
+
+               After EX_VOTE.C has successfully secured exclusive access to the
+               file, it first reads the record that it is going to update. It
+               is important that this be done after the file unit is locked, in
+               order to ensure that the copy of the record in memory matches
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 238
+
+               what is stored in the file. EX_VOTE.C then updates the record
+               based on the question on which the user has voted, writes this
+               information back to the file. EX_VOTE.C then immediately closes
+               the file, allowing other nodes to also access the file.
+               EX_VOTE.C is very carefully designed so that the file update
+               operation can never be interrupted (for instance, no OpenDoors
+               functions are called, which could detect a time-out and
+               terminate the program while a file update operation is in
+               progress), or delayed until the user makes a response. As such,
+               the file unit is always unlocked (in this case, closed) within a
+               fraction of a second after it was locked, or order that other
+               nodes will never have to wait long for access to the file.
+
+               Here I have presented a detailed account of how EX_VOTE.C
+               handles multi-node file access. While all of the details
+               involved in coordinating multiple file access can be
+               overwhelming at first, they will begin to come naturally to you,
+               as you begin to always think in terms of multi-node scenarios.
+               To summarize, the important elements that are typically involved
+               in multi-node file access are:
+
+               A. Decide on an appropriate file unit to lock for your
+                  application. In simple cases, this can be the entire file.
+                  In other cases, you may wish to lock individual file
+                  records, using the appropriate runtime library functions.
+
+               B. Always perform update operations in lock, read, update,
+                  write, unlock cycles on individual file units. If there is a
+                  chance that other nodes will also need to access the file
+                  unit, ensure that the update operation cannot be interrupted
+                  or delayed until a user makes a response.
+
+               After you have designed your program for concurrent file access,
+               how can you test it? If you don't have a multi-node BBS system
+               that you have access to, you can perform most of your testing
+               under a multitasking environment, with multiple copies of your
+               program running in different windows.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 239
+
+MULTI-NODE CONFIGURATION
+-------------------------------------------------------------------------------
+
+               A second issue that you may want to bear in mind is how door
+               programs are typically setup on multi-node systems.
+               Unfortunately, this may differ considerable depending upon which
+               BBS software is being used. However, some of the issues that you
+               may have to consider discussed below:
+
+               A. Your program must be able to locate the correct door
+                  information file for the appropriate node. Most BBS systems
+                  make separate door information files available to each node
+                  by one of the following means:
+
+                         - By naming each node's door information file
+                           uniquely. (e.g. DORINFO1.DEF, DORINFO2.DEF.)
+
+                         - By having a separate directory for each node's door
+                           information file. (e.g. \NODE1\DOOR.SYS,
+                           \NODE2\DOOR.SYS, etc.)
+
+                  In the first case, OpenDoors can automatically select the
+                  correct door information file, assuming that it knows which
+                  node it is running on (see item C, below). In the later
+                  case, you must tell OpenDoors which directory it must look
+                  in to find the appropriate door information file. You may do
+                  this by any of the following means:
+
+                         - By specifying the location of the file on the
+                           command line, if od_parse_cmd_line() is used.
+
+                         - By providing a configuration file keyword to set
+                           the door information file location for each node.
+
+                         - By providing a different configuration file for
+                           each node (See item B, below).
+
+               B. If you are using the OpenDoors configuration file system,
+                  node-specific options should not be used if each node is
+                  accessing the same configuration file. While it is possible
+                  to have a different configuration file for each node (the
+                  filename can be specified on the command line if
+                  od_parse_cmd_line() is used), in most cases the same
+                  configuration file will be used for all nodes. In this case,
+                  the node number, serial port information, and possible door
+                  information file location operations should not be used. If
+                  you are basing your configuration file on the example
+                  DOOR.CFG file that is included in the OpenDoors package, you
+                  may want to remove these options from the file.
+
+               C. In many cases, your program must also be able to determine
+                  which node it is running under. If this information is
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 240
+
+                  available in the door information file, or is stored in a
+                  TASK environment variable, OpenDoors will automatically set
+                  the appropriate node number in od_control.od_node.
+                  Otherwise, if your program requires this information, it
+                  should be specified on the program's command line. The
+                  od_parse_cmd_line() function supports this option. Reasons
+                  that your program might need to know the current node number
+                  include:
+
+                         - In order for OpenDoors to display this information
+                           correctly on the status line.
+
+                         - In order to determine which configuration file to
+                           read or which node directory in which to look for
+                           the door information file.
+
+                         - In order for OpenDoors to know which door
+                           information file to read (e.g. DORINFO1.DEF,
+                           DORINFO2.DEF. etc.)
+
+                         - In order to provide any form of real-time
+                           interaction between nodes, such as inter-node chat.
+
+               D. If your program is running under MS-DOS, and multi-node file
+                  access is being coordinated by locking part or all of a
+                  file, the SHARE.EXE utility must be installed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 241
+
+ 7777777
+      77
+     77
+    77
+   77
+  77
+ 77
+-------------------------------------------------------------------------------
+CHAPTER 7 - TROUBLESHOOTING AND GETTING ASSISTANCE WITH OPENDOORS
+
+
+
+
+ABOUT THIS CHAPTER
+-------------------------------------------------------------------------------
+
+               This chapter is perhaps the most important section of this
+               entire manual. Here, we provide detailed instructions to help
+               you in tracing the source of problems in programs written with
+               OpenDoors. Included in this chapter is a step-by-step OpenDoors
+               troubleshooting guide and a chart listing common problems and
+               their solutions. Also included in this chapter is information on
+               the many means available to you for getting more help with
+               OpenDoors, including the OpenDoors support BBS, the OpenDoors
+               EchoMail conference, and how to get in touch with me. It is
+               strongly encouraged that you take the time to read through this
+               chapter.
+
+
+
+TROUBLESHOOTING PROBLEMS
+-------------------------------------------------------------------------------
+
+               If you are experiencing difficulty with a program that you are
+               writing using OpenDoors, it is suggested that you follow the
+               steps listed below in order to quickly solve your problem. Also,
+               be sure to check to "solutions to common problems" section of
+               this manual. There are many common difficulties which people
+               have with OpenDoors, that can easily be fixed using the
+               instructions in the "common solutions" section. Also, if you are
+               having difficulty solving a problem yourself, do not hesitate to
+               get in touch with me, as I am always happy to help with any
+               problems. In addition, you may find the other means of OpenDoors
+               support (described latter in this chapter), invaluable in
+               solving difficulties with OpenDoors.
+
+               Keep in mind that most programs you write will have some "bugs"
+               to begin with, and you should expect to spend at least 50% of
+               any programming project tracing down and solving errors and
+               bugs. While it would be nice if every program written worked
+               correctly the first time, it is a fact of life that debugging is
+               and always has been an important part of the software life-
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 242
+
+               cycle. In fact, what most often separates the good programs from
+               the bad is the amount of time their programmer's spend debugging
+               and improving them. Unfortunately, it is difficult, if not
+               impossible, to come up with a "magic formula" for debugging
+               software. Debugging software is really more of an art than a
+               science. However, there are some basic guidelines, which if
+               followed, can greatly ease the task of software debugging.
+
+               As with all problem solving, the secret to software debugging
+               lies in obtaining as much information about the problem as
+               possible. While it is sometimes possible to solve a bug by
+               making intuitive changes in your program, or in re-writing a
+               piece of code to solve the problem by a different means,
+               debugging most often requires more of a "planned attack". This
+               planned attack generally involves little more than learning as
+               much about what is going wrong as possible. The first step in
+               solving a bug usually lies in locating the source of the
+               problem. Once you have located the problem, solving it is often
+               a relatively simple procedure. In locating the source of your
+               bug, the use of a software debugger, such as the one built into
+               the Turbo C(++) / Borland C++ integrated development
+               environment, can be invaluable.
+
+               When debugging programs written with OpenDoors, you should also
+               follow the steps listed below, in order to obtain more
+               information related to the problem you are trying to solve:
+
+               1.)  Re-read the section(s) of this manual, your Turbo C(++) /
+                    Borland C++ manuals and your program's source code, which
+                    apply to the problem you are experiencing.
+
+               2.)  Check the solutions to common problems section below. The
+                    most common problems with OpenDoors can be solved using
+                    this simple chart.
+
+               3.)  Check the value in the od_errno variable, which will often
+                    provide vital clues as to the source of the problem. Use of
+                    the od_errno variable is described in the section below.
+
+               4.)  If you are still stuck, please feel more than free to get
+                    in touch with me! (see the end of this chapter for
+                    information on reaching me) I am always more than happy to
+                    help with any OpenDoors or general programming problems!
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 243
+
+SOLUTIONS TO COMMON PROBLEMS
+-------------------------------------------------------------------------------
+
+               Below, several common difficulties with OpenDoors are listed,
+               along with suggested solutions to these problems. If you are
+               experiencing any difficulty with a program that you have written
+               with OpenDoors, we would suggest that you read this section
+               thoroughly. Here, the common problem is listed in the left
+               margin, and the solutions listed on the right portion of the
+               page.
+
+
+-------------------------------------------------------------------------------
+PROGRAM        1.) Check that the compiler is able to locate the OpenDoors
+WON'T          header file, "OPENDOOR.H". This can be accomplished either by
+COMPILE        placing this header file in the same directory as your other
+               header files (such as STDIO.H, etc.), or by placing the header
+               file in the current directory.
+
+               2.) Be sure that you are linking your program with the correct
+               library for the memory model you are using. (See the section on
+               compiling with OpenDoors). Also be sure that both the source
+               code file for your program (such as EX_VOTE.C) and the library
+               file are listed in your project file, and that the project file
+               is loaded. For more information on compiling programs written
+               with OpenDoors, see page 22.
+
+               3.) If you have tried the above solutions, and your program
+               still won't compile, then the problem is most likely an error in
+               your source code file. If you are unable to resolve your
+               problem, feel free to get in touch with me.
+
+
+-------------------------------------------------------------------------------
+SCREEN         If you are using the od_clr_scr() function to clear the screen,
+WILL NOT       but are not getting any results, this is likely because the user
+CLEAR          online has screen clearing turned off. If you wish to force
+               screen clearing regardless of the user's screen clearing
+               settings (this is probably not a good idea), use the function
+               call od_disp_emu("\xc", TRUE);
+
+
+-------------------------------------------------------------------------------
+FIXUP          This problem was probably caused by a mismatch between your
+OVERFLOW       memory model selection in your compiler, and the memory model
+ERROR          library you are using. See the section on compiling programs
+               with OpenDoors for more information on the correct library you
+               should be using for your memory model selection.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 244
+
+OPENDOORS SUPPORT
+-------------------------------------------------------------------------------
+
+               The powerful and easy to use door toolkit and this comprehensive
+               manual are only two portions of how OpenDoors helps you to write
+               BBS door and similar programs. The third element of OpenDoors is
+               the extensive OpenDoors support mechanisms. The OpenDoors email
+               conference and support BBS each give you a chance to share ideas
+               and source code with other OpenDoors programmers. A lot of
+               information concerning OpenDoors, along with the newest version
+               and online registration, is available through the OpenDoors
+               World Wide Web site. In addition to these sources, I am also
+               more than happy to answer any of your questions, or hear any
+               suggestions for future versions of OpenDoors. The remainder of
+               this chapter provides more information on the various sources of
+               OpenDoors support. Also keep your eyes open for the "OpenDoors
+               Tech Journal", that is produced on a regular basis by the users
+               of OpenDoors. Included in this newsletter is information on
+               OpenDoors and future versions, questions and answers about
+               OpenDoors and BBS door / utility programming in general, sample
+               source code, and much more.
+
+
+
+THE OPENDOORS SUPPORT BBS
+-------------------------------------------------------------------------------
+
+               One means of receiving OpenDoors support is via the OpenDoors
+               BBS. Below is an outline of some of what is available from the
+               OpenDoors BBS:
+
+                    -  The newest version of this package is always available
+                       for download.
+
+                    -  Also available for download is example source code and
+                       other files which you may find helpful when writing
+                       programs with OpenDoors.
+
+                    -  Access to the OpenDoors conference where OpenDoors
+                       programmers can share ideas, source code, and receive
+                       help with difficulties or with learning OpenDoors.
+
+                    -  Get in touch with me with any questions, comments,
+                       suggestions or bug reports.
+
+                    -  Other files by yours truly, which may be of use in you
+                       programming, such as a registration key system, and so
+                       on.
+
+               All users receive full access upon their first call to the
+               OpenDoors BBS. The North American phone number for the support
+               BBS is:
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 245
+
+
+                                   +1 (613) 599-5554
+
+               The OpenDoors support BBS also has a FidoNet address, 1:243/8.
+               If you are interested in a list of files available from the
+               support BBS, simply file-request "FILES". To receive the newest
+               version of OpenDoors, you can file-request "ODOORS".
+
+
+
+THE OPENDOORS WORD WIDE WEB SITE
+-------------------------------------------------------------------------------
+
+               The OpenDoors World Wide Web site has been setup to provide up-
+               to-date information on OpenDoors. This includes news concerning
+               OpenDoors, OpenDoors tips and techniques, pointers to other
+               sources of OpenDoors support, online registration and access to
+               the newest version of OpenDoors.
+
+               The current URL (address) of the OpenDoors Web site is:
+
+                    http://omega.scs.carleton.ca/~ug930227/opendoor.html
+
+               However, I plan on moving this to a new location some time this
+               year. If you are unable to reach the OpenDoors Web site through
+               the above URL, please get in touch with me, and I will be able
+               to tell you where it has moved to.
+
+
+
+THE OPENDOORS CONFERENCE
+-------------------------------------------------------------------------------
+
+               The OpenDoors conference is devoted to OpenDoors and BBS / door
+               / BBS utility programming in general. The OpenDoors conference
+               serves as a place where people working with OpenDoors can share
+               ideas, source code examples, and other tricks and techniques.
+               Through the OpenDoors conference you can receive help with
+               OpenDoors and programming in general. Also available through the
+               OpenDoors conference is information on future versions of
+               OpenDoors and recent developments of concern to BBS door and
+               utility programmers. The OpenDoors conference is also a place
+               for suggestions for future versions of OpenDoors, OpenDoors bug
+               reports, a place to announce the availability of your programs,
+               and much more information of interest to programmers working
+               with OpenDoors.
+
+               You can become involved in the OpenDoors Conference by the
+               following means:
+
+               - The OpenDoors conference is available as an Internet mailing
+                 list. to subscribe, send email to [email protected] and put
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 246
+
+                 SUBSCRIBE OPENDOOR in the message body. For help on using the
+                 listserver you can send email to [email protected] and put
+                 HELP in the message body.
+
+               - The OpenDoors conference is available on the FidoNet North
+                 America echo backbone, and so is available to a large number
+                 of BBS systems. FidoNet capable systems may also obtain an
+                 OpenDoors feed directly from the moderator, Brian Pirie.
+
+
+GETTING IN TOUCH WITH ME
+-------------------------------------------------------------------------------
+
+               If you have any questions about OpenDoors, would like help with
+               any programs that your are writing, or have any suggestions for
+               future versions of OpenDoors, please feel free to get in touch
+               with me. You can get in touch with me by any of the following
+               means:
+
+               - The best way to contact me is by Internet email. My primary
+                 email address is:
+
+                              [email protected]
+
+                 If you have difficulty contacting me through this address, I
+                 may also be reached through the address:
+
+                              [email protected]
+
+               - By writing a message to me in the OpenDoors support
+                 conference. For more information on the OpenDoors conference,
+                 see the previous section of this chapter.
+
+               - By calling the OpenDoors support BBS. Information on the
+                 support BBS is provided earlier on in this chapter.
+
+               - By sending your question or comment by Fax. My fax number is:
+
+                              +1 (613) 599-5554
+
+               - By FidoNet NetMail. My address is:
+
+                              1:243/8
+
+                 While I would like to be able to reply to all NetMail
+                 messages by CrashMail, I am afraid I simply can not afford to
+                 do this. So, if you choose to send NetMail, please indicate
+                 whether you would like me to reply by routed NetMail (this
+                 may not work, if routed NetMail is not available in your
+                 area), or to place the message on hold for you to poll and
+                 pick up.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 247
+
+               - By conventional mail. My postal address is:
+
+                              Brian Pirie
+                              117 Cedarock Drive
+                              Kanata ON  K2M 2H5
+                              Canada
+
+
+               I try to respond to all correspondences as soon as possible.
+
+               If you are having some sort of difficulty with OpenDoors, the
+               more detailed information you supply (such as source code to the
+               program that is causing the problem, how to duplicate the
+               problem, and so on), the more quickly I will be able to
+               determine the source of your problem. Also, before you write
+               about a problem with OpenDoors, you may wish to be sure that you
+               have read and followed the instructions in the section on
+               troubleshooting, found on page 242. While I do not mind taking
+               the time to answer any questions related to OpenDoors, you may
+               be able to save yourself the time of writing and waiting for a
+               response - simply by following the instructions in the
+               troubleshooting section. More often than not, the answer to
+               questions I receive is already in this manual.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 248
+
+    A
+   AAA
+  AA AA
+ AAAAAAA
+ AA   AA
+ AA   AA
+ AA   AA
+-------------------------------------------------------------------------------
+APPENDIX A - CONTENTS OF PACKAGE
+
+
+
+
+               The main OpenDoors package is distributed in the form of a
+               single, compressed archive file. Thus, you should have received
+               this version of OpenDoors in a file whose name began with
+               ODOORS60. The files listed below should be included in your
+               OpenDoors package. If any of these files are missing, you will
+               probably want to look for the most recent version of OpenDoors
+               from another source. Note that the medium and compact memory
+               model libraries are now distributed separately from the main
+               OpenDoors package.
+
+               MISCALENEOUS FILES
+                    FILE_ID.DIZ    Description of the OpenDoors package
+                    DORINFO1.DEF   Sample door info file for testing programs
+                    DOOR.CFG       Sample OpenDoors configuration file
+
+               EXAMPLE PROGRAMS
+                    EX_HELLO.C     A simple program using OpenDoors
+                    EX_CHAT.C      Split-screen sysop chat program
+                    EX_MUSIC.C     Example of ANSI music in OpenDoors
+                    EX_SKI.C       Simple slalom skiing action game
+                    EX_VOTE.C      Example of an online voting program
+                    VOTEDOS.EXE    Compiled version of EX_VOTE.C - DOS version
+                    VOTEWIN.EXE    EX_VOTE.C compiled - Windows version
+                    DTRON.EXE      Free utility for running Win 95 doors
+
+               THE LIBRARY FILES
+                    ODOORS.LIB     DOS, Small memory model library
+                    ODOORL.LIB     DOS, Large memory model library
+                    ODOORH.LIB     DOS, Huge memory model library
+                    ODOORW.LIB     Win32 import library (Microsoft version)
+                    ODOORS60.DLL   The OpenDoors Win32 DLL
+
+               THE HEADER FILE
+                    OPENDOOR.H     OpenDoors header file
+
+               OPENDOORS DOCUMENATION
+                    ORDER.TXT      Easy-to-print order form
+                    OPENDOOR.TXT   OpenDoors programmer's manual (this file)
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 249
+
+ BBBBBB
+ BB   BB
+ BB   BB
+ BBBBBB
+ BB   BB
+ BB   BB
+ BBBBBB
+-------------------------------------------------------------------------------
+APPENDIX B - CHANGES FOR THIS VERSION
+
+
+
+
+               Since version 5.00, a lot of work has gone into OpenDoors. Many
+               months were spent cleaning up and restructuring the OpenDoors
+               code in a process that has touched nearly every line of the
+               OpenDoors code. As well as making the OpenDoors source code
+               easier to maintain, this has also involved making OpenDoors more
+               easily portable to 32-bit multithreaded platforms.
+
+               After this effort was completed, I began work on a Win32 port of
+               OpenDoors. The OpenDoors package now includes both DOS and Win32
+               versions of the library, giving you the option of developing for
+               one, the other, or both platforms. The Win32 version of
+               OpenDoors is highly multithreaded to give you the best possible
+               performance. With some exciting new BBS packages that are
+               designed specifically for Windows 95/NT in the works, the Win32
+               version of OpenDoors gives you a head start on developing
+               applications that will integrate smoothly with these new BBS
+               packages. Even if your programs will only be used with DOS-based
+               BBS packages, the Win32 version of OpenDoors allows you to take
+               advantage of the Windows 95 GUI, multithreading capabilities and
+               flat 32-bit memory model (no more DOS 64K limitations and
+               different memory models to worry about!). It also allows you to
+               access services that are only available to Windows based
+               programs, such as ODBC for easy database access, and MAPI for
+               email, fax and other messaging.
+
+               While this internal restructuring of OpenDoors and the Win32
+               port of the package would be enough alone to count as a major
+               new version, version 6.00 also includes many exciting new
+               features, enhancements and bug fixes:
+
+               - A new option, "silent mode", has been added. When operating
+                 in silent mode, OpenDoor's local sysop interface is disabled,
+                 so that no output is displayed on the local screen, and no
+                 input is accepted from the local keyboard. Silent mode can be
+                 activated by setting od_control.od_silent_mode = TRUE prior
+                 to the first call to any OpenDoors function, or by specifying
+                 -SILENT on the command-line (if od_parse_cmd_line() is used).
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 250
+
+               - OpenDoors now fully supports RTS/CTS flow control, which is
+                 enabled by default. To disable the use of the RTS/CTS lines
+                 for flow control, use od_control.od_com_flow_control.
+
+               - In version 5.00, the built-in serial I/O module would be
+                 unable to initialize the serial port if the "Uses Serial
+                 Ports" option was turned off in DesqView or other
+                 multitasking environments. When this option is turned off,
+                 multitasking environments typically remove the serial port
+                 information from the BIOS data segment. However, it seems
+                 that other serial I/O software commonly uses the default
+                 address for each port if this information is not available
+                 from the BIOS data area. OpenDoors 6.00 has been changed to
+                 do the same thing.
+
+               - OpenDoors now provides a standard command-line processing
+                 function, od_parse_cmd_line(). This function provides a one-
+                 step method of adding support for many common command-line
+                 options to your program. This function handles options such
+                 as serial port information (including non-standard serial
+                 port configurations), node number information, user
+                 information, drop file and configuration file locations,
+                 silent mode (turns off the local interface for efficiency and
+                 privacy), one step local login without a drop file, and more.
+                 For details, run the included example program (votedos.exe or
+                 votewin.exe) with the -help command line option. The
+                 od_parse_cmd_line() function is particularly helpful in
+                 several situations:
+
+                         - When your program is being used on a multi-node
+                           system.
+                         - Allows potential users to try your program in local
+                           mode by just specifying -local on the command line.
+                         - Allows door information to be specified on the
+                           command line, rather than through a drop file, as
+                           supported by some BBS systems
+                         - Allows serial port handle to be directly passed to
+                           the program under Windows-based BBS systems that
+                           support this.
+                         - Provides customizable command-line help (in a
+                           window in the Win32 version of OpenDoors).
+
+               - A new function, od_sleep(), allows you to suspend program
+                 execution for the specified number of milliseconds, releasing
+                 time to other processes in a multitasking environment. A call
+                 of od_sleep(0) will yield control to any other processes
+                 without forcing program execution to be suspended if no other
+                 processes are waiting.
+
+               - The sysop page timing mechanism has been reworked. By
+                 default, od_control.od_okaytopage is set to PAGE_USE_HOURS,
+                 only allowing the sysop to be paged during the paging hours
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 251
+
+                 set by od_pagestartmin and od_pageendmin. Rather than
+                 allowing the sysop to be paged at any time of the day or
+                 night, the default now only permits sysop paging from 8:00am
+                 to 10:00pm. Setting paging start and end time to the same
+                 value now correctly permits paging 24 hours a day.
+
+               - OpenDoors now provides a multi-line editor function,
+                 od_multiline_edit(). This editor allows the user to enter or
+                 edit any information that spans multiple lines, such as email
+                 messages or text files. The editor can be configured to
+                 operate in full screen mode, or in any smaller area of the
+                 screen that you specify. The editor is designed to be both
+                 easy to use, and highly customizable.
+
+               - One new feature of OpenDoors 5.00 was somehow omitted from
+                 the manual. Since version 5.00, it has been possible to set
+                 the name of the drop file to use by including both a path and
+                 filename in the od_control.info_path variable.
+
+               - All known inaccuracies and missing information from the
+                 version 5.00 manual have been corrected.
+
+               - The example program, ex_chat.c, has been expanded to
+                 demonstrate how OpenDoor's standard chat mode can be replaced
+                 with the split-screen chat mode within your own programs.
+
+               - The multiple ex_vote?.c example files have been combined and
+                 simplified into a single example program, as it was prior to
+                 version 5.00.
+
+               - A memory leak in the od_popup_menu() function has been fixed.
+
+               - od_control.od_open_handle can be used to provide OpenDoors
+                 with an already open serial port handle on platforms that
+                 support it. Currently, this only applies to the Win32 version
+                 of OpenDoors. If this variable is not set to a non-zero value
+                 prior to calling the first OpenDoors function (other than
+                 od_parse_cmd_line()), then OpenDoors proceeds normally,
+                 opening the serial port itself, and closing the serial port
+                 before exiting.
+
+               - A new switch, od_emu_simulate_modem, has been added to
+                 od_control. When this is set to its default value of FALSE,
+                 the OpenDoors terminal emulator displays at full speed, as it
+                 has always done. However, when this flag is set to TRUE, the
+                 emulation functions will display text at approximately the
+                 same speed as it would be displayed when sent over the modem,
+                 based on the current connect speed. This allows animations to
+                 be displayed locally at the same speed as they would appear
+                 on the remote system. This switch affects the following
+                 functions:
+                    od_disp_emu()
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 252
+
+                    od_send_file()
+                    od_hotkey_menu()
+
+               - OpenDoors now distinguishes between the serial port BPS rate
+                 (od_control.baud) and the connection BPS (a new variable,
+                 od_control.od_connect_speed). In situations where a separate
+                 value is available for the connect speed (e.g., this caller
+                 has connected at 14400 bps), OpenDoors will now display that
+                 value on the status line. Currently, a separate connect speed
+                 is only obtained from the DOOR.SYS drop file format.
+
+               - The latest versions of the QBBS EXITINFO.BBS drop file is now
+                 supported.
+
+               - A new od_edit_str() flag, EDIT_FLAG_SHOW_SIZE, has been
+                 added. By default, the fields shown by od_edit_str() are one
+                 character larger than the number of characters that may be
+                 entered. This is for esthetic reasons, so that the cursor
+                 remains within the highlighted field, even after a full
+                 string has been entered. However, you may prefer that the
+                 highlighted area reflect the exact number of characters that
+                 are permitted. This can be accomplished by setting
+                 EDIT_FLAG_SHOW_SIZE.
+
+               - Tab characters ('\t') are now expanded on the local display.
+
+               - The new RemoteAccess 2.50 EXITINFO.BBS fields are now
+                 supported. This has included the addition of the following
+                 new od_control members: user_rip_ver, user_attrib3 and
+                 system_last_handle.
+
+               - When operating in "forced automatic local" mode (where no
+                 door information file used), OpenDoors now displays a window
+                 in which in prompts for the user's name at startup time. This
+                 new feature can be disabled by specifying the DIS_NAME_PROMPT
+                 od_control.od_disable flag.
+
+               - The latest version of the SFDOORS.DAT drop file is now
+                 supported. SFMAIN.DAT, SFSYSOP.DAT, SFFILE.DAT and
+                 SFMESS.DAT, which are in the same format as SFDOORS.DAT, are
+                 also now recognized.
+
+               - A new function, od_get_input(), allows you to easily handle
+                 extended keys in your program, such as arrow keys, insert and
+                 function keys.
+
+               - The OpenDoors configuration file system will now display an
+                 error and exit the program if a particular configuration file
+                 name has been specified, and that configuration file cannot
+                 be found.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 253
+
+   CCCC
+  CC  CC
+ CC
+ CC
+ CC
+  CC  CC
+   CCCC
+-------------------------------------------------------------------------------
+ APPENDIX C - FUTURE VERSIONS
+
+
+
+
+               While I cannot make any promises about what features and changes
+               will be seen in future versions of OpenDoors, I would like to
+               take a moment to tell you a bit about some of the features you
+               can expect to see in future versions of OpenDoors
+
+               As you are probably already aware, OpenDoors is a constantly
+               evolving package. To help meet the needs of programmers working
+               with OpenDoors, nearly every idea and change that is made to the
+               package results from the suggestions and comments I receive from
+               the people using OpenDoors. For this reason, I will most likely
+               continue to produce new versions of OpenDoors for as long as
+               there is a demand and ideas for upgrades. There certainly is no
+               shortage of either of this right now.
+
+               Some of the features that I am considering for upcoming versions
+               of OpenDoors include:
+
+                    -Telnet support.
+
+                    - HTML support.
+
+                    - Direct interfacing with new Windows 95/NT BBS packages.
+
+                    - Further features to help writing multi-node door
+                     programs.
+
+                    -Direct interfacing with more BBS systems.
+
+                    -Additional RIP graphics support, including possible
+                     display of RIP images on local screen.
+
+                    -Possible support for additional programming languages and
+                     operating systems.
+
+                    - Improvements to existing OpenDoors features, such as more
+                     configuration file options, multiple log file formats,
+                     and many smaller changes.
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 254
+
+ DDDDD
+ DD  DD
+ DD   DD
+ DD   DD
+ DD   DD
+ DD  DD
+ DDDDD
+-------------------------------------------------------------------------------
+ APPENDIX D - SPECIAL THANKS
+
+
+
+
+               There are many people who I would like to thank, for their
+               suggestions, ideas and assistance in making OpenDoors what it is
+               today. Among those I would like to thank are:
+
+                    - Everyone who has registered OpenDoors.
+
+                    - All those who participate in the OpenDoors conference,
+                      who provide many suggestions, bug reports and words of
+                      encouragement.
+
+                    - Those who on the OpenDoors beta team. Thank-you for
+                      putting up with the problems along the way. You have
+                      certainly helped to make OpenDoors a better package. The
+                      people who have helped to beta test OpenDoors 6.00 are:
+
+                              Robert Bouman
+                              Doug Crone
+                              Greg Diener
+                              Patrick Dixon
+                              Joel Downer
+                              Mike Fenton
+                              Les Howie
+                              Vince Jacobs
+                              Scott Jibben
+                              Dean Mills
+                              Jimmy Rose
+                              Jim Woodward
+                              Timothy Ward
+                              Mark Williams
+
+                    - Last but not least, I would like to thank my wife, Pearl,
+                      for her support during the long hours that it has taken
+                      to put OpenDoors together.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 255
+
+  GGGG
+ GG  GG
+ GG
+ GG GGGG
+ GG  GG
+ GG  GG
+  GGGG
+-------------------------------------------------------------------------------
+GLOSSARY
+
+ANSI           ANSI is an acronym for "American National Standards Institute".
+               One of the standards approved by ANSI is a terminal display
+               protocol which allows (in this case), BBS software to perform
+               certain display functions such as changing the color of
+               displayed text, or moving the location of the cursor on the
+               screen. The majority, though not all, BBS users use terminal
+               software with ANSI capabilities. Any users that do not have
+               graphics display capabilities, will be using ASCII mode,
+               instead. The ANSI terminal protocol is sometimes referred to as
+               "ANSI graphics". It is graphic in the sense that it provides
+               more visual control than an ASCII TTY terminal does, but does
+               not imply any support for bit-mapped nor vector graphics.
+               Compare ASCII and AVATAR.
+
+
+API            API is an acronym for "Application Program(er) Interface". An
+               API is a set of well documented functions, variables and data
+               types that you can use to access certain services from your
+               program. When you write any C program that uses standard C
+               library functions such as fopen() or strcpy(), you are using a
+               sort of API. When you use OpenDoors functions such as
+               od_printf() or od_get_key(), you are using functions that are
+               part of the OpenDoors API. Operating systems provide their own
+               APIs that allow programs to gain access to operating system
+               features such as screen display, file I/O and communications.
+               The API provided by Microsoft Windows 95 and Windows NT is
+               called the Win32 API.
+
+
+ASCII          ASCII (pronounced "ass-key") is an acronym for "American
+               Standard Code for Information Interchange", and is a definition
+               of a set of 128 letters, number and symbols, which can be
+               displayed by computer systems. Also, when used within the domain
+               of BBS software, ASCII mode is often used to refer to the lack
+               of any more advanced display capabilities, such as ANSI or
+               AVATAR. When ASCII mode is used, characters can only be
+               displayed in standard Teletype (TTY) fashion, one after another.
+               Also, color and cursor positioning functions are not available
+               in ASCII mode. Compare ANSI and AVATAR.
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 256
+
+AVATAR         AVATAR is an acronym for "Advanced Video Attribute Terminal
+               Assembler and Recreator". AVATAR is a graphics display protocol,
+               similar to ANSI. Like ANSI-graphics, AVATAR graphics allow
+               functions such as cursor positioning, and color changing.
+               However, AVATAR also offers many capabilities not available from
+               ANSI, and performs the same functions as ANSI much more quickly.
+               AVATAR graphics is less common than both ANSI or ASCII, but is
+               becoming more popular as time goes by. Compare ASCII and ANSI.
+
+
+BAUD           "baud" or "baud rate" are generally used as a synonym for "BPS".
+
+
+BPS            BPS is an acronym for "Bits Per Second", and refers to the rate
+               at which data is being sent over a communications medium. There
+               are two important BPS rates which are relevant to OpenDoors. The
+               serial port BPS rate (also called the DCE rate) is the speed at
+               which the computer is communicating with the local modem. The
+               connect speed, on the other hand, is the speed at which the
+               local modem is communicating with the remote modem. The serial
+               port speed must be at least as fast as the connection speed.
+               Often the serial port speed will be locked at a fixed speed that
+               is higher than the fastest possible connection speed of the
+               modem. For example, the serial port might be locked at a speed
+               of 38400 BPS, while the modem could be connected at 28,800,
+               14,400 or slower speeds. OpenDoors usually needs to know the
+               serial port BPS rate in order to function correctly (as stored
+               in od_control.baud). Under certain situations, OpenDoors will
+               also be able to report the connection speed to you (as stored in
+               od_control.od_connect_speed), although OpenDoors does never
+               requires this information to operate.
+
+
+BIT-MAPPED     As with Boolean values, described below, bit mapped flags
+FLAGS          are used to indicate whether or not various conditions exist.
+               (For example, whether or not a certain setting is enabled, or
+               whether or not a particular event has occurred.) However, unlike
+               Boolean variables, a single bit-mapped flag represents more than
+               one of these TRUE/FALSE values. In fact, each bit (BInary
+               Digit), which makes of the variable can be used to represent a
+               separate TRUE/FALSE state. (ie, each bit maps to a particular
+               piece of information, and hence the term "Bit Map").
+
+               For an example of using bit-mapped flags, let us take a case of
+               a single "unsigned char" which contains three independent
+               TRUE/FALSE values. We will call this variable user_info, and it
+               will indicate whether or not a user has ANSI graphics, whether
+               or not the user has screen clearing turned on, and whether or
+               not the user has end-of-page "more" prompts enabled. Internally,
+               the bits of the user_info variable will be as follows:
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 257
+
+                     Bit:  7 6 5 4 3 2 1 0
+                                     | | |
+                                     | | +--- ANSI Graphics
+                                     | +----- Screen Clearing
+                                     +------- More prompts
+
+               In this case, we will have three constants which we define in
+               order to simplify access to these bit-mapped flags, as follows:
+
+                     #define ANSI_GRAPHICS        0x01
+                     #define SCREEN_CLEARING      0x02
+                     #define MORE_PROMPTS         0x04
+
+               Note that normally within OpenDoors, these constants will be
+               defined for you, and you will have no need to know what their
+               values are, nor in which bit which piece of information is
+               stored.
+
+               Using bit-mapped flags, you are able to set or clear any of the
+               individual flags, and check whether any of the flags are set,
+               using these simple methods: (Not that a set flag is the
+               equivalent of a Boolean value of "True", and a cleared flag is
+               the equivalent of a Boolean value of "False".)
+
+                    Set Flag:      variable |= FLAG_CONSTANT;
+                    Clear Flag:    variable &=~ FLAG_CONSTANT;
+                    Test Flag:     variable & FLAG_CONSTANT
+
+               Where "variable" is the name of the bit-mapped flag variable,
+               and "FLAG_CONSTANT" is the pre-defined constant for the
+               individual setting. To return to our example, you could turn on
+               the user's ANSI graphics setting by using the line:
+
+                    user_info |= ANSI_GRAPHICS;
+
+               and to turn off screen clearing you would:
+
+                    user_info &=~ ANSI_GRAPHICS;
+
+               To perform an action (such as waiting for the user to press
+               [Return]/[Enter]) only if "More" prompts are enabled, you would:
+
+                    if(user_info & MORE_PROMPTS)
+                    {
+                         ...            /* Whatever you want */
+                    }
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 258
+
+BOOLEAN        Many of the variables used within OpenDoors contain a
+VALUES         "Boolean Value". A Boolean value is a two-state variable, who's
+               states are referred to as "True" and "False'. If the variable
+               contains a value of "True", it indicates that a certain
+               condition is so, and if it contains a value of "False", it
+               indicates that the condition is not so. For example, a Boolean
+               variable "wait" might be used to indicate whether or not
+               OpenDoors should wait for the user to press a key, or continue
+               without waiting. In this case, a value of "True" would indicate
+               that OpenDoors should wait, and a value of "False" would
+               indicate that it should not wait.
+
+               Note that in the C programming language, there is no actual
+               Boolean variable type - usually a char or an int are used to
+               store Boolean values.
+
+               The constants TRUE and FALSE, as defined in the OPENDOOR.H file,
+               are used to represent the two states of a Boolean value. Thus,
+               to set a Boolean variable "wait" to the value of "True", you
+               would use this line:
+
+                         wait=TRUE;
+
+               and to set the variable "wait" to "False", you would:
+
+                         wait=FALSE;
+
+               However, you SHOULD NOT test whether a Boolean variable is
+               "True" or "False" by using the C compare (==) operator, as the
+               value "True" will not always be the same numerical value.
+               (Actually, the TRUE constant represents just one of many
+               possible numerical values for "True"). Instead, to perform an
+               action of the "wait" Boolean variable is "True", you would:
+
+                         if(wait)
+                         {
+                              ...        /* Whatever you want */
+                         }
+
+               and to perform an action if the "wait" Boolean variable is
+               "False', you would:
+
+                         if(!wait)
+                         {
+                              ...       /* Whatever you want */
+                         }
+
+               For interest sake, Boolean values are named after the 19th
+               century English mathematician, who studied formal logic, and
+               created Boolean algebra - an algebra which deals with TRUE and
+               FALSE values.
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 259
+
+
+BPS            BPS is an acronym for "Bits Per Second". For our purposes here,
+               the terms BPS and BAUD refer to the same thing.
+
+
+CARRIER        The term "Carrier" or "Carrier Detect" refers to a signal which
+DETECT         most modems send to the computer, which indicates whether or not
+               the modem is currently connected to (communicating with) another
+               modem. The door driver module of OpenDoors, as with most other
+               BBS software, uses the status of this carrier detect signal in
+               order to know whether the user is still connected to the BBS
+               computer. Thus, if the user hangs up, or if something goes wrong
+               and the connection is lost, OpenDoors is able to detect this
+               state, and exit to the BBS. The BBS will then also detect that
+               the carrier signal has been "lost", and will reset itself, and
+               then again be ready to accept calls.
+
+
+CHAT MODE      The term "chat mode" refers to a means by which the sysop can
+               communicate with a user of the BBS / door. During sysop chat,
+               anything typed by the sysop will appear on the user's screen,
+               and likewise, anything typed by the user will appear on the
+               sysop's screen. Sysop chatting is available on both single and
+               multi-line systems. Sysop chatting is initiated by the sysop,
+               either at any time a user is online, or specifically in response
+               to a sysop page.
+
+
+COMPILE        "Compiling" refers to the process of converting the source code
+               that you write for your program, into an executable file (such
+               as a .EXE file) that an end user can use. The process of
+               building an executable file is generally divided into two
+               stages. In the first stage, called compiling, source files are
+               converted to object files, often named .OBJ. In the second
+               stage, called linking, one or more object files are combined,
+               along with any library files, to produce the final executable
+               file.
+
+
+DLL            DLL is an acronym for "Dynamic Link Library". A dynamic link
+               library is similar to a static library, in that it contains one
+               or more functions that an application program can use. Unlike a
+               static library, the code from a dynamic link library is not
+               added to the program's executable file at link time. Instead,
+               the dynamic link library exists as a separate file which must be
+               loaded when the program is run. The Win32 version of OpenDoors
+               resides in a DLL.
+
+               See also "Library".
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 260
+
+DOOR           A "door" is a program that runs as part of a BBS system, but
+               which is separate from the central BBS software (RemoteAccess,
+               Maximus, QuickBBS, PC-Board, etc.) itself. A door provides
+               additional features not built into the BBS software, such as on-
+               line games, on-line shopping services, voting booths, match
+               making systems, access to special files or messages, and much
+               much more. Since the user also communicates with the door
+               online, as they do with the BBS, it may not necessarily be
+               obvious to the user that the door is even a separate entity from
+               the central BBS software itself.
+
+
+DOOR           Also referred to as a "drop file", "exit file", or "chain
+INFORMATION    file". The door information file is a file passed from the
+FILE           central BBS software to a door program, providing it with
+               information about the user who is online, the BBS the door is
+               running under, and the current modem connection. The door
+               information file may also be used to pass changed information
+               back to the BBS, such as the amount of time that the user has
+               used in the door. OpenDoors takes care of all of the work
+               involved in reading and writing the door information file for
+               you, as described in the "Basics of Door Programming" section,
+               in chapter 4. Examples of door information files supported by
+               OpenDoors include: DOOR.SYS, EXITINFO.BBS, DORINFO?.DAT,
+               SFDOORS.DAT, CALLINFO.BBS and CHAIN.TXT.
+
+DTR            DTR is an acronym for "Data Terminal Ready". This is a signal
+               that the computer sends to the modem, indicating that the
+               computer is ready to send or receive information. Most modems
+               are configured to hangup if the DTR signal is lowered. This is a
+               convenient means of hanging up the modem, but cases problems
+               under Windows 95, where the DTR signal is always lowered when a
+               program closes the serial port.
+
+
+ECHO           See "Local Echo".
+
+
+FOSSIL         The FOSSIL driver, or simply FOSSIL, is a TSR program or
+DRIVER         device driver which OpenDoors can optionally make use of in
+               order to communicate with the modem. The FOSSIL driver is loaded
+               prior to starting up the BBS or your door, usually from the
+               AUTOEXEC.BAT or CONFIG.SYS files. The two most commonly used
+               FOSSIL drivers are X00 and BNU. (FOSSIL is an acronym for
+               "Fido/Opus/SEAdog Standard Interface Layer", although it has now
+               become the standard for nearly all BBS software.) FOSSIL drivers
+               are also available for other specialized serial port hardware,
+               such as the popular "DigiBoard" multi-port serial card.
+
+
+IMPORT LIBRARY See "Library".
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 261
+
+
+LIBRARY        A "library" or "library file" is a collection of precompiled
+               functions and variables that can be used by other programs. All
+               of the features, capabilities and functions of OpenDoors that
+               you can make use of are contained within the OpenDoors library
+               files. (Likewise, the C runtime library, consisting of the
+               familiar functions such as fopen(), printf() and atoi(), is also
+               contained within a library file.) For more information on the
+               different OpenDoors library files, see the section that begins
+               on page 22.
+
+               There are several different kinds of library files. A static
+               library file is actually a collection of individual object
+               files. When you compile a program that makes use of a static
+               library file, only those portions of the library file that your
+               program actually uses are linked into your program's executable
+               (.EXE) file. Static library files can be identified by a .LIB
+               extension. The DOS version of OpenDoors resides in a static
+               library.
+
+               A dynamic link library, on the other hand, is not combined with
+               the program's executable file. Instead dynamic link libraries
+               exist in separate .DLL files that must also be present when the
+               program is executed. The Win32 version of OpenDoors resides in a
+               dynamic link library.
+
+               An import library is a small file that describes a dynamic link
+               library. The most common way for a program to call functions in
+               a dynamic link library requires that an import library be used a
+               program link time.
+
+               See also "DLL".
+
+
+LINK           "Linking" generally refers to the process of combining several
+               object files into a final executable file, during which
+               references to symbol names (such as od_printf()) are resolved to
+               the address of the corresponding object. See also "Compiling".
+
+
+LOCAL MODE     The term "local mode" refers to a mode in which a BBS system or
+               door program may operate. In local mode, the BBS/door behave as
+               they would if a user were connected via modem to the BBS, except
+               that all display and input is done simply on the BBS software,
+               but not through the modem. Local mode allows the sysop or
+               another person with direct access to the BBS computer to use the
+               BBS/door software, either for their own user, or for testing
+               that the software is running correctly. When programming door
+               software, local mode can be very useful in testing and debugging
+               the door, without requiring the door to be connected to a remote
+               system. All doors written with OpenDoors automatically support
+               local mode operation. Compare "Remote".
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 262
+
+
+
+LOCAL ECHO     The term "Local Echo" refers to a door displaying the same
+               characters which are sent to the modem on the local screen
+               ("Output Window"). This allows the sysop to view the same
+               information that is sent to the user's system, in the same
+               manner that it will appear on the user's screen.
+
+
+LOCKED         (eg. "Locked Baud Rate", "Locked BPS Rate", "Locked Commport
+               Speed", etc.) Usually, the communication port to which a modem
+               is connected is set to transfer data at the same BPS rate as the
+               rate at which the modem is communicating. However, many high
+               speed modems allow very high speed data transfer by using built-
+               in data compression methods. In this case, the actual rate of
+               data transfer can easily exceed the true BPS rate of the
+               connection. As a result, the BPS rate of the port is kept a
+               single speed, faster than any of the true modem connections, in
+               order to increase modem speed performance. This is referred to
+               as locking the commport BPS rate. OpenDoors has full support for
+               the use of locked BPS rates.
+
+
+LOG FILE       A log file is a normal text file in which BBS software records
+               all major activities that have taken place. As such, the log
+               file permits the sysop, to review what activities have taken
+               place on the BBS during the time which they have been away from
+               the computer. A log file can be helpful in identifying system
+               errors or crashes that have occurred, in alerting the sysop in
+               the case that any users have been causing problems on the BBS,
+               or in simply letting the sysop know who has called recently, and
+               what when they did when they called.
+
+
+MEMORY MODEL   C and C++ programs can be compiled under a variety of different
+               memory models. Each memory model describes how data and program
+               code are addressed in memory. When writing MS-DOS programs, you
+               generally have the choice of six different memory models (named
+               tiny, small, compact, medium, large and huge), each of which
+               provides a different combination of the maximum sizes of data
+               and code that your program may have. When writing Win32
+               programs, there is a single, flat 32-bit memory model that all
+               programs use. This memory model allows a program to address (in
+               theory) up to 4 gigabytes of data and code.
+
+
+MODEM          A device connected to a computer which permits it to communicate
+               with other computers, usually over standard telephone lines.
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 263
+
+OBJECT FILE    An object file contains the compiled version of a source code
+               file of a program. The source code file may be a .C file, .CPP
+               file, .ASM file, .PAS file, .BAS file, or any number of other
+               extensions associated with other programming languages. When any
+               of these language's source code files are compiled, a .OBJ file
+               is created containing information such as the executable code,
+               and names of symbols (variables and functions) that are to be
+               shared with other .OBJ files. In order to produce a .EXE file
+               that may be executed, a process known as linking must be
+               performed. During the link process, one or more object files
+               composing your program are combined, along with the necessary
+               code from any library files being used, in order to produce the
+               final .EXE file.
+
+
+ONLINE         In the case of BBS software and BBS door programs, the term
+               online refers to the state of a user using the BBS. Usually, the
+               user will be connected to the BBS from a remote location, using
+               a modem. However, it is also possible that the user will be
+               using the actual BBS computer, with the software operating in
+               "local mode".
+
+
+OUTPUT WINDOW  The local screen of the BBS on which BBS software is running is
+               usually divided into two sections. At the bottom of the screen,
+               there is often a one or two line status line, which displays
+               information to the sysop about the BBS and the user who is
+               currently online. The rest of the screen is usually an "output
+               window", in which the information which is being displayed to
+               the user, is also displayed on the local screen. In some cases,
+               there will be no status line, in which case the entire screen
+               will be the output window. Usually, the upper 23 lines of the
+               screen in an OpenDoors door will be the output window, with the
+               bottom two lines being the status line. However, it is possible
+               to disable the OpenDoors status line, in which case the entire
+               screen will be the output window. See also "Status Line"
+
+
+PAGE           See "SYSOP PAGE"
+
+
+PARAMETER      In the C programming language, many tasks are accomplished by
+               calling functions. When a function is called, one or more pieces
+               of information may be passed to a function, in the form of
+               parameters. For example, a function used to set the foreground
+               and background color of displayed text might accept two
+               parameters, one for each of the two color settings. In this
+               example, a function such as od_set_color(), would be called as
+               follows:
+
+                              od_set_color(D_GREEN,D_RED);
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 264
+
+               In this case, D_GREEN, the foreground color, is the first
+               parameter, and D_RED, the background color, is the second
+               parameter.
+
+               In C, parameters are enclosed in parentheses, ( and ), which are
+               located after the name of the function to be called. Each
+               parameter is then separated by a comma character. If a function
+               does not accept any parameters, the parentheses will have
+               nothing between them. (ie. od_clr_scr() ).
+
+
+REGISTRATION   This is a demonstration version of OpenDoors, which may only be
+               used under limited circumstances, for a limited period of time.
+               If you wish to continue using OpenDoors after this "evaluation
+               period", you must "register" it. For more information on
+               registering OpenDoors, please see chapter 2 of this manual.
+
+
+REMOTE         When used in reference to BBS software or door programs, the
+               term remote is used to refer to a user or computer that is
+               communicating with the BBS, for a distant location, by use of a
+               modem. Compare "Local Mode"
+
+
+RIP            "RIP", "RIPScrip" or "Remote Imaging Protocol" is a popular
+               graphical terminal standard that is used with BBS systems.
+               Unlike other terminal emulation standards, such as the ANSI and
+               AVATAR modes supported by OpenDoors, RIP operates in bit mapped
+               graphics mode, allowing features such as lines, circles and
+               icons to be drawn on the remote screen. OpenDoors provides
+               support for RIP graphics, although OpenDoors operates in text
+               mode itself.
+
+
+STATUS LINE    Usually, the bottom two lines of the screen, as displayed by an
+               OpenDoors door, is devoted to a status line (although this
+               status line may be turned off). This status line will display
+               information about the user who is online, along with information
+               about the current state of the BBS system, and a reference to
+               the sysop function keys. See also "Local Window".
+
+
+SYSOP          The term sysop is a short-form for "SYStem OPerator", and refers
+               to the individual who is responsible for running and maintaining
+               the BBS system. The sysop is usually the only person who has
+               direct access to the local keyboard and computer on which the
+               BBS, BBS utilities and BBS doors are running.
+
+
+SYSOP CHAT     See "CHAT MODE".
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 265
+
+SOURCE CODE    The term "source code" refers to the original file or files that
+               where used to produce a library or executable program. The
+               source code files contain the language statements or commands
+               that are directly written by the programmer. These source code
+               files are then compiled to produce an executable file that may
+               be "run".
+
+
+SYSOP PAGE     Sysop paging refers to the process whereby a user of the BBS
+               system may call or page for the sysop's attention, when they
+               wish to "chat" with the sysop, and can be thought of as being
+               similar to the ringing of a telephone. When a user pages the
+               sysop, the BBS system will produce some sort of sound, which the
+               sysop may elect to respond to if they are within hearing range
+               of the computer. The most common reasons for a user to page a
+               sysop include the case that they are having difficulty with some
+               aspect of the BBS, that they have a question, or if they are
+               simply interested in having a friendly conversation with the
+               sysop. Obviously, since the sysop may not wish to be disturbed
+               by users paging at certain times (such as when they are in bed),
+               most BBS software provides controls to allow you to control
+               paging. These features might include the ability to set hours
+               for each day of the week during which paging will be permitted,
+               and the ability to temporarily override the ability of some or
+               all callers to page the sysop.
+
+
+USER           When applied to computers in general, the term user simply
+               refers to any person using the computer hardware and software.
+               However, when applied particularly to BBSes, the term user
+               refers specifically to a person who calls the BBS, to carry out
+               activities such as communicating via messages or chatting,
+               uploading and downloading files, or using doors. Often, the term
+               user is used in contrast with the term sysop. In this case,
+               users are all of the people who call and user the BBS, other
+               than the sysop themselves.
+
+
+WIN32          Win32 is the name of the API that programs written to run under
+               Microsoft Windows 95 and Microsoft Windows NT use to access
+               operating system services. Win32 programs use a flat, 32-bit
+               memory model and have access to advanced operating system
+               services such as multithreading. Win32 programs cannot run under
+               DOS nor OS/2. While some Win32 programs can run under Windows
+               3.x using the Win32s system, OpenDoors cannot since it requires
+               multithreading services that are not provided by Win32s.
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 266
+
+ IIIIII
+   II
+   II
+   II
+   II
+   II
+ IIIIII
+-------------------------------------------------------------------------------
+INDEX
+
+
+
+A                                          D
+
+About This Manual ..................21     Debugging                     21, 241
+Access Level ......................178     Demo Version........................9
+Alias .............................170     Display Functions..............42, 63
+ANSI Graphics                              Displaying Text....30, 41, 42, 60, 62
+Archive Contents ..................248     Door Driver Functions..............40
+ASCII Chart ........................86     Door Driver Module..............6, 40
+ASCII Mode ........................255     Door Functions.....................45
+AVATAR Graphics ....118, 134, 167, 256     Door Information File30, 33, 150, 158
+                                           Door Settings.....................182
+B                                          DORINFOx.DEF File..................33
+                                           DOS Shell.........................192
+Baud Rate .........................154     Download Limit....................169
+BBS Information ...................158
+BBS Name ..........................164     E
+BBS Systems ........................30
+Before Exit Function ..............191     Error Free Connection.............170
+Box Characters ...............185, 191     Example Program - Changing Only
+BPS Rate ..........................154      Foreground/Background Colour ....132
+Built-In Function Keys ............212     Example Program - Choosing Text
+                                            Colour ..........................129
+C                                          Example Program - Clearing A Line..56
+                                           Example Program - Dialog Box.......66
+Caller Information ................158     Example Program - Door And Utility In
+Carrier Detect .................51, 97      One Program ......................92
+Chat ..........................38, 104     Example Program - Drawing A Window118
+Chat Mode ....................104, 259     Example Program - Exiting A Door...79
+Colour Attribute Codes ............128     Example Program - First Door.......29
+Colour Constants ..................132     Example Program - Hanging Up In CBV
+Colour Customization215, 229, 232, 236      Door .............................51
+Colour Functions ...................42     Example Program - Hotkeyed Menu....91
+Colours .................110, 128, 131     Example Program - Input Key.......115
+Common Problems ...................243     Example Program - Setting Door Info
+Compiler Errors ...................243      File Location ...................150
+Compiling With OpenDoors ...........22     Example Program - Shelling To DOS.141
+Custom Function Keys ..............213     Example Program - Terminal Emu.....62
+                                           Example Program - Testing Available
+                                            Door Information ................158
+
+===============================================================================
+OpenDoors 6.00 Manual                                          End of Page 267
+
+
+
+Example Program - Testing Screen           M
+  Clearing Capabilities             57
+Example Program - Transferring A File      Memory Models..................23, 24
+  Using DSZ........................142     Memory Swapping...................209
+Example Program - User Statistics          Modem Port........................157
+  Door.............................113     Modem Settings....................153
+Example Program - Vote .............33
+Example Program - Waiting For CR ...54     N
+Exiting A Door Program .............79
+                                           New Version.......................249
+F                                          Node Number.......................152
+
+Features ............................6     O
+Feedback Form ......................19
+File Display Functions .............44     Object File.......................263
+FILES.BBS File .....................98     od_autodetect() Function...........48
+Fossil Driver .....................260     od_carrier() Function..............51
+FOSSIL port .......................157     od_chat() Function.................50
+Function Keys .................97, 211     od_clear_keybuffer() Function......53
+Future Versions ...................253     od_clr_line() Function.............55
+                                           od_clr_scr() Function.........57, 243
+G                                          od_colour_config() Function........59
+                                           od_control Structure..........31, 148
+Getting In Touch With Us ..........246     od_disable Variable...............198
+Graphics Mode ...........165, 167, 255     od_disp() Function.................60
+                                           od_disp_emu() Function.............62
+H                                          od_disp_str() Function.............63
+                                           od_draw_box() Function.............65
+History ...........................249     od_edit_str() Function.............68
+Hotkeys ............................90     od_exit() Function...31, 79, 191, 195
+                                           od_get_answer() Function...........81
+I                                          od_get_input() Function............82
+                                           od_get_key() Function......30, 53, 85
+IBM Colour Attribute Codes ........128     od_gettext() Function..............89
+IEMSI Session Information .........161     od_hotkey_menu() Function..........90
+Inactivity Timeout ......199, 200, 202     od_init() Function.............31, 92
+Input Functions ............44, 81, 85     od_input_str() Function........53, 95
+                                           od_kernal() Function...............31
+K                                          od_kernel() Function...............97
+                                           od_list_files() Function...........98
+Keyboard Buffer ...........53, 97, 115     od_log_write() Function...........100
+Keys ...............................97     od_multiline_edit() Function......101
+                                           od_page() Function...........104, 207
+L                                          od_parse_cmd_line() Function......105
+                                           od_popup_menu() Function..........107
+Language Customization ............216     od_printf() Function.....29, 110, 195
+Learning OpenDoors .................29     od_putch() Function...............115
+Library ...........................261     od_puttext() Function.............116
+LIBrary Files ......................24     od_repeat() Function..............118
+Line Number .......................152     od_restore_screen() Function......120
+Linking ............................23     od_save_screen() Function.........121
+Local Mode ...............33, 200, 261     od_scroll() Function..............123
+Locked ............................262     od_send_file() Function...........124
+                                           od_set_attrib() Function..........128
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 268
+
+
+
+od_set_color() Function ...........131     Solutions To Problems.............243
+od_set_cursor() Function ..........134     Source Code................10, 17, 20
+od_set_dtr() Function .............135     Special Thanks....................254
+od_set_personality() Function .....136     Status Line...104, 137, 209, 210, 264
+od_set_statusline() Function ......137     Stop Key..........................203
+od_sleep() Function ...............139     Support...........................244
+od_spawn Function .................208     Support BBS.............244, 245, 246
+od_spawn() Function ...............141     Swapping..........................209
+od_spawnvpe() Function ............143     Sysop Chat...............38, 104, 192
+od_window_create() Function .......145     Sysop Function Keys...............211
+od_window_remove() Function ..147, 148     Sysop Keys.........................97
+OPENDOOR.H File ............22, 29, 34     Sysop Name........................164
+OpenDoors BBS ................244, 245     Sysop Next Setting................184
+OpenDoors Customization ...........187     Sysop Page........................207
+OPENDOORS Echo ....................245     Sysop Paging.................104, 265
+OpenDoors History .................249     System Event......................162
+Our Address .......................246     System Name.......................164
+Output Functions ...................42
+Output Window .................34, 263     T
+
+P                                          Terminal Emulator........62, 124, 125
+                                           Terminal Emulator Control Codes...126
+Paging Hours .................182, 183     Text Customization................216
+Paging The Sysop ..................104     Thank-yous........................254
+Pause Key .........................203     Time Left.........................179
+Phone Number ......................171     Timeout............................97
+Printing ..30, 41, 60-63, 90, 110, 115     Troubleshooting....................21
+Printing Manual ....................22
+Problems ...........................21     U
+Product Support ...................244
+Project Files ......................23     User Handle (Alias)...............170
+                                           User Information..................158
+R                                          User Keyboard Off Key..............53
+                                           User Keyboard Setting.............184
+Registration ..9, 10, 12, 18, 246, 264     User Name.........................174
+Registration Form ..............15, 18     User Password.....................176
+RIP ...............................264     User Timeout.......................97
+RIPScrip ..........................264
+                                           V
+S
+                                           Version History...................249
+Screen Functions ...................42
+Screen Length .....................177     W
+Screen Width ......................178
+Security Level ....................178     Want-Chat Setting.................180
+Setting Colours .........110, 128, 131
+
+
+
+
+
+
+
+
+
+===============================================================================
+OpenDoors 6.00 Manual                                           End of Page 269
+

+ 99 - 0
odoors/OpenDoor.def

@@ -0,0 +1,99 @@
+;  OpenDoors Online Software Programming Toolkit
+;  (C) Copyright 1991 - 1999 by Brian Pirie.
+;
+; Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+;
+;  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: OpenDoors.def
+;
+;  Description: Module definition file for the OpenDoors Win32 DLL.
+;
+;    Revisions: Date          Ver   Who  Change
+;               ---------------------------------------------------------------
+;               Dec 12, 1995  6.00  BP   Created.
+;               Jan 11, 1996  6.00  BP   Added exports for undecorated names.
+;               Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+;               Mar 03, 1996  6.10  BP   Begin version 6.10.
+;               Mar 03, 1996  6.10  BP   Added od_get_cursor.
+;               Mar 21, 1996  6.10  BP   Added od_control_get().
+;               Oct 19, 2001  6.20  RS   Added door32.sys and socket support.
+LIBRARY ODOORS62
+DESCRIPTION "OpenDoors"
+VERSION 6.2
+EXPORTS
+   ODConfigInit=_ODConfigInit@0
+   ODLogEnable=_ODLogEnable@0
+   ODMPSEnable=_ODMPSEnable@0
+   od_add_personality=_od_add_personality@16
+   od_autodetect=_od_autodetect@4
+   od_carrier=_od_carrier@0
+   od_chat=_od_chat@0
+   od_clear_keybuffer=_od_clear_keybuffer@0
+   od_clr_line=_od_clr_line@0
+   od_clr_scr=_od_clr_scr@0
+   od_color_config=_od_color_config@4
+   od_control_get=_od_control_get@0
+   od_disp=_od_disp@12
+   od_disp_emu=_od_disp_emu@8
+   od_disp_str=_od_disp_str@4
+   od_draw_box=_od_draw_box@16
+   od_edit_str=_od_edit_str@32
+   od_emulate=_od_emulate@4
+   od_exit=_od_exit@8
+   od_get_answer=_od_get_answer@4
+   od_get_cursor=_od_get_cursor@8
+   od_get_input=_od_get_input@12
+   od_get_key=_od_get_key@4
+   od_gettext=_od_gettext@20
+   od_hotkey_menu=_od_hotkey_menu@12
+   od_init=_od_init@0
+   od_input_str=_od_input_str@16
+   od_kernel=_od_kernel@0
+   od_key_pending=_od_key_pending@0
+   od_list_files=_od_list_files@4
+   od_log_open=_od_log_open@0
+   od_log_write=_od_log_write@4
+   od_multiline_edit=_od_multiline_edit@12
+   od_page=_od_page@0
+   od_parse_cmd_line=_od_parse_cmd_line@4
+   od_popup_menu=_od_popup_menu@24
+   _od_printf=od_printf
+   od_printf=od_printf
+   od_putch=_od_putch@4
+   od_puttext=_od_puttext@20
+   od_repeat=_od_repeat@8
+   od_restore_screen=_od_restore_screen@4
+   od_save_screen=_od_save_screen@4
+   od_scroll=_od_scroll@24
+   od_send_file=_od_send_file@4
+   od_set_attrib=_od_set_attrib@4
+   od_set_color=_od_set_color@8
+   od_set_cursor=_od_set_cursor@8
+   od_set_dtr=_od_set_dtr@4
+   od_set_personality=_od_set_personality@4
+   od_set_statusline=_od_set_statusline@4
+   od_sleep=_od_sleep@4
+   od_spawn=_od_spawn@4
+   od_spawnvpe=_od_spawnvpe@16
+   od_window_create=_od_window_create@36
+   od_window_remove=_od_window_remove@4
+   pdef_opendoors=_pdef_opendoors@4
+   pdef_pcboard=_pdef_pcboard@4
+   pdef_ra=_pdef_ra@4
+   pdef_wildcat=_pdef_wildcat@4
+   _od_control=od_control DATA
+   od_control=od_control DATA

+ 1123 - 0
odoors/OpenDoor.h

@@ -0,0 +1,1123 @@
+/* OpenDoors Online Software Programming Toolkit
+ * (C) Copyright 1991 - 1999 by Brian Pirie.
+ *
+ * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net)
+ *
+ * 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: OpenDoor.h
+ *
+ * Description: C/C++ definition of the OpenDoors API. Any program source file
+ *              that uses OpenDoors must #include this file.
+ *
+ *   Revisions: Date          Ver   Who  Change
+ *              ---------------------------------------------------------------
+ *              Dec 02, 1995  6.00  BP   New file header format.
+ *              Dec 09, 1995  6.00  BP   Added od_multiline_edit() prototype.
+ *              Dec 12, 1995  6.00  BP   Cleaned up, added DLL definitions.
+ *              Dec 12, 1995  6.00  BP   Moved ODPLAT_??? to OpenDoor.h.
+ *              Dec 21, 1995  6.00  BP   Add ability to use already open port.
+ *              Dec 22, 1995  6.00  BP   Added od_connect_speed.
+ *              Dec 23, 1995  6.00  BP   Added EDIT_FLAG_SHOW_SIZE.
+ *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
+ *              Jan 01, 1996  6.00  BP   BCC32 compatibility changes.
+ *              Jan 01, 1996  6.00  BP   Added new mulitline editor options.
+ *              Jan 01, 1996  6.00  BP   Added od_disable_dtr, DIS_DTR_DISABLE.
+ *              Jan 03, 1996  6.00  BP   Further BCC32 compatiblity changes.
+ *              Jan 04, 1996  6.00  BP   Added od_get_input() and related defs.
+ *              Jan 07, 1996  6.00  BP   Added OD_GLOBAL_CONV.
+ *              Jan 19, 1996  6.00  BP   Removed some unused stuff.
+ *              Jan 19, 1996  6.00  BP   Added od_internal_debug.
+ *              Jan 23, 1996  6.00  BP   Added od_exiting and ERR_UNSUPPORTED.
+ *              Jan 30, 1996  6.00  BP   New extern "C" decl for od_control.
+ *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
+ *              Jan 31, 1996  6.00  BP   Added DIS_NAME_PROMPT.
+ *              Jan 31, 1996  6.00  BP   Added tODMilliSec, OD_NO_TIMEOUT.
+ *              Jan 31, 1996  6.00  BP   Added timeout for od_get_input().
+ *              Feb 02, 1996  6.00  BP   Add RA 2.50-related od_control vars.
+ *              Feb 03, 1996  6.00  BP   Added more editor options.
+ *              Feb 06, 1996  6.00  BP   Added od_silent_mode.
+ *              Feb 08, 1996  6.00  BP   Added editor buffer grow option.
+ *              Feb 13, 1996  6.00  BP   Added od_get_input() flags parameter.
+ *              Feb 14, 1996  6.00  BP   Recognize Borland's __WIN32__ define.
+ *              Feb 17, 1996  6.00  BP   Added OD_KEY_F1 thru OD_KEY_F10.
+ *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
+ *              Feb 27, 1996  6.00  BP   Added od_max_key_latency.
+ *              Mar 03, 1996  6.10  BP   Begin version 6.10.
+ *              Mar 03, 1996  6.10  BP   Fixed OD_COMPONENT for medium mem mod.
+ *              Mar 06, 1996  6.10  BP   Added TRIBBS.SYS support.
+ *              Mar 06, 1996  6.10  BP   Added COM_DOOR32.
+ *              Mar 11, 1996  6.10  BP   Added OD_VERSION.
+ *              Mar 13, 1996  6.10  BP   Added od_get_cursor().
+ *              Mar 13, 1996  6.10  BP   Added od_local_win_col.
+ *              Mar 14, 1996  6.10  BP   Added od_config_callback.
+ *              Mar 21, 1996  6.10  BP   Added od_control_get().
+ *              Apr 08, 1996  6.10  BP   Added command-line parsing callbacks.
+ *              Oct 19, 2001  6.20  RS   Added door32.sys and socket support.
+ *              Oct 19, 2001  6.21  RS   Fixed socket disconnect bug.
+ */
+
+/* Only parse OpenDoor.h once. */
+#ifndef _INC_OPENDOOR
+#define _INC_OPENDOOR
+
+
+/* ========================================================================= */
+/* Version and platform definitions.                                         */
+/* ========================================================================= */
+
+/* OpenDoors API version number. */
+#define OD_VERSION 0x624
+
+#define DIRSEP		'\\'
+#define DIRSEP_STR	"\\"
+
+/* OpenDoors target platform. */
+#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32)
+#define ODPLAT_WIN32
+#undef ODPLAT_DOS
+#ifdef OD_WIN32_STATIC
+#pragma message("Compiling for Win32 static version of OpenDoors")
+#else /* !OD_WIN32_STATIC */
+#pragma message("Compiling for Win32 DLL version of OpenDoors")
+#define OD_DLL
+#endif /* !OD_WIN32_STATIC */
+#else /* !WIN32 */
+#if defined(__unix__) || defined(__NetBSD__) || defined(__APPLE__)
+#define ODPLAT_NIX
+#undef ODPLAT_DOS
+#undef DIRSEP
+#define DIRSEP '/'
+#undef DIRSEP_STR
+#define DIRSEP_STR "/"
+#else
+#define ODPLAT_DOS
+#undef ODPLAT_WIN32
+#pragma message("Compiling for DOS version of OpenDoors")
+#endif /* !NIX */
+#endif /* !WIN32 */
+
+
+/* Include any other headers required by OpenDoor.h. */
+#ifdef ODPLAT_WIN32
+#include "windows.h"
+#endif /* ODPLAT_WIN32 */
+
+/* For DLL versions, definitions of function or data that is exported from */
+/* a module or imported into a module.                                     */
+#ifdef OD_DLL
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+#define OD_EXPORT __declspec(dllexport)
+#else /* !_MSC_VER || __BORLANDC__ */
+#define OD_EXPORT _export
+#endif /* !_MSC_VER */
+#define OD_IMPORT DECLSPEC_IMPORT
+#else /* !OD_DLL */
+#define OD_EXPORT
+#define OD_IMPORT
+#endif /* !OD_DLL */
+
+/* Definition of function naming convention used by OpenDoors. */
+#ifdef __cplusplus
+#define OD_NAMING_CONVENTION extern "C"
+#else /* !__cplusplus */
+#define OD_NAMING_CONVENTION
+#endif /* !__cplusplus */
+
+/* Definition of function calling convention used by OpenDoors. */
+#ifdef ODPLAT_WIN32
+#define ODCALL WINAPI
+#define ODVCALL WINAPIV
+#define OD_GLOBAL_CONV WINAPI
+#else /* !ODPLAT_WIN32 */
+#define ODCALL
+#define ODVCALL
+#define OD_GLOBAL_CONV
+#endif /* !ODPLAT_WIN32 */
+
+/* OpenDoors API function declaration type. */
+#ifdef BUILDING_OPENDOORS
+#define ODAPIDEF OD_NAMING_CONVENTION OD_EXPORT
+#else /* !BUILDING_OPENDOORS */
+#define ODAPIDEF OD_NAMING_CONVENTION OD_IMPORT
+#endif /* !BUILDING_OPENDOORS */
+
+/* OpenDoors API global variable definition and declaration types. */
+#define OD_API_VAR_DEFN OD_NAMING_CONVENTION OD_EXPORT
+
+#ifdef BUILDING_OPENDOORS
+#define OD_API_VAR_DECL extern OD_EXPORT
+#else /* !BUILDING_OPENDOORS */
+#define OD_API_VAR_DECL extern OD_IMPORT
+#endif /* !BUILDING_OPENDOORS */
+
+/* Explicitly far pointers. */
+#ifdef ODPLAT_DOS
+#define ODFAR far
+#else /* !ODPLAT_DOS */
+#define ODFAR
+#endif /* !ODPLAT_DOS */
+
+
+/* ========================================================================= */
+/* Primitive data types.                                                     */
+/* ========================================================================= */
+
+/* Portable types that are the same size across all platforms */
+#ifndef ODPLAT_WIN32
+#ifndef BYTE
+typedef unsigned char      BYTE;                        /* Unsigned, 8 bits. */
+#endif
+#ifndef WORD
+typedef unsigned short     WORD;                       /* Unsigned, 16 bits. */
+#endif
+#ifndef DWORD
+typedef unsigned long      DWORD;                      /* Unsigned, 32 bits. */
+#endif
+#ifndef CHAR
+typedef char               CHAR;         /* Native character representation. */
+#endif
+#define DWORD_DEFINED
+#define WORD_DEFINED
+#endif /* !ODPLAT_WIN32 */
+
+typedef signed char        INT8;                          /* Signed, 8 bits. */
+typedef signed short int   INT16;                        /* Signed, 16 bits. */
+#ifndef ODPLAT_WIN32	/* avoid type redefinition from basetsd.h */
+typedef signed long int    INT32;                        /* Signed, 32 bits. */
+#endif
+
+
+/* Types that vary in size depending on platform. These are guranteed to be */
+/* at least the given size, but may be larger if this platform can          */
+/* represent a larger value more efficiently (or as efficiently).           */
+#ifndef ODPLAT_WIN32
+typedef int                INT;                /* Integer, at least 16 bits. */
+typedef unsigned int       UINT;      /* Unsigned integer, at least 16 bits. */
+#ifndef BOOL
+typedef char               BOOL;           /* Boolean value, at least 1 bit. */
+#endif /* !BOOL */
+#endif /* !ODPLAT_WIN32 */
+
+/* TRUE and FALSE manifest constants, for use with BOOL data type. */
+#ifndef FALSE
+#define FALSE 0
+#endif /* !FALSE */
+#ifndef TRUE
+#define TRUE 1
+#endif /* !TRUE */
+
+
+/* ========================================================================= */
+/* OpenDoors complex data types and defines.                                 */
+/* ========================================================================= */
+
+/* Millisecond time type. */
+typedef DWORD tODMilliSec;
+
+/* Special value tODMilliSec value for no timeouts. */
+#ifdef ODPLAT_WIN32
+#define OD_NO_TIMEOUT INFINITE
+#else /* !ODPLAT_WIN32 */
+#define OD_NO_TIMEOUT 0xffffffffL
+#endif /* !ODPLAT_WIN32 */
+
+
+/* Multi-line editor defintions. */
+
+/* Editor text formats. */
+typedef enum
+{
+    FORMAT_PARAGRAPH_BREAKS
+   ,FORMAT_LINE_BREAKS
+   ,FORMAT_FTSC_MESSAGE
+   ,FORMAT_NO_WORDWRAP
+} tODEditTextFormat;
+
+/* Menu callback function return values. */
+typedef enum
+{
+    EDIT_MENU_DO_NOTHING
+   ,EDIT_MENU_EXIT_EDITOR
+} tODEditMenuResult;
+
+/* Editor flags. */
+#define EFLAG_NORMAL       0x00000000
+
+/* Optional multi-line editor settings. */
+typedef struct
+{
+   INT nAreaLeft;
+   INT nAreaTop;
+   INT nAreaRight;
+   INT nAreaBottom;
+   tODEditTextFormat TextFormat;
+   tODEditMenuResult (*pfMenuCallback)(void *pUnused);
+   void * (*pfBufferRealloc)(void *pOriginalBuffer, UINT unNewSize);
+   DWORD dwEditFlags;
+   char *pszFinalBuffer;
+   UINT unFinalBufferSize;
+} tODEditOptions;
+
+/* Editor return values. */
+#define OD_MULTIEDIT_ERROR          0
+#define OD_MULTIEDIT_SUCCESS        1
+
+
+/* Input event information. */
+
+/* Input event types. */
+typedef enum
+{
+    EVENT_CHARACTER
+   ,EVENT_EXTENDED_KEY
+} tODInputEventType;
+
+/* Extended key codes. */
+#define OD_KEY_F1          0x3b
+#define OD_KEY_F2          0x3c
+#define OD_KEY_F3          0x3d
+#define OD_KEY_F4          0x3e
+#define OD_KEY_F5          0x3f
+#define OD_KEY_F6          0x40
+#define OD_KEY_F7          0x41
+#define OD_KEY_F8          0x42
+#define OD_KEY_F9          0x43
+#define OD_KEY_F10         0x44
+#define OD_KEY_UP          0x48
+#define OD_KEY_DOWN        0x50
+#define OD_KEY_LEFT        0x4b
+#define OD_KEY_RIGHT       0x4d
+#define OD_KEY_INSERT      0x52
+#define OD_KEY_DELETE      0x53
+#define OD_KEY_HOME        0x47
+#define OD_KEY_END         0x4f
+#define OD_KEY_PGUP        0x49
+#define OD_KEY_PGDN        0x51
+#define OD_KEY_F11         0x85
+#define OD_KEY_F12         0x86
+#define OD_KEY_SHIFTTAB    0x0f
+
+/* Input event structure. */
+typedef struct
+{
+   tODInputEventType EventType;
+   BOOL bFromRemote;
+   char chKeyPress;
+} tODInputEvent;
+
+
+/* Third option (in addition to TRUE and FALSE) for tri-state options. */
+#define MAYBE 2
+
+/* od_spawnvpe() flags. */
+#define P_WAIT                  0
+#define P_NOWAIT                1
+#define CURRENT                 0
+#define IRET                    1
+
+/* od_edit_str() flags. */
+#define EDIT_FLAG_NORMAL        0x0000
+#define EDIT_FLAG_NO_REDRAW     0x0001
+#define EDIT_FLAG_FIELD_MODE    0x0002
+#define EDIT_FLAG_EDIT_STRING   0x0004
+#define EDIT_FLAG_STRICT_INPUT  0x0008
+#define EDIT_FLAG_PASSWORD_MODE 0x0010
+#define EDIT_FLAG_ALLOW_CANCEL  0x0020
+#define EDIT_FLAG_FILL_STRING   0x0040
+#define EDIT_FLAG_AUTO_ENTER    0x0080
+#define EDIT_FLAG_AUTO_DELETE   0x0100
+#define EDIT_FLAG_KEEP_BLANK    0x0200
+#define EDIT_FLAG_PERMALITERAL  0x0400
+#define EDIT_FLAG_LEAVE_BLANK   0x0800
+#define EDIT_FLAG_SHOW_SIZE     0x1000
+
+/* od_edit_str() return values. */
+#define EDIT_RETURN_ERROR       0
+#define EDIT_RETURN_CANCEL      1
+#define EDIT_RETURN_ACCEPT      2
+#define EDIT_RETURN_PREVIOUS    3
+#define EDIT_RETURN_NEXT        4
+
+/* od_popup_menu() flag values. */
+#define MENU_NORMAL             0x0000
+#define MENU_ALLOW_CANCEL       0x0001
+#define MENU_PULLDOWN           0x0002
+#define MENU_KEEP               0x0004
+#define MENU_DESTROY            0x0008
+
+/* od_autodetect() flag values. */
+#define DETECT_NORMAL           0x0000
+
+/* od_scroll() flags. */
+#define SCROLL_NORMAL           0x0000
+#define SCROLL_NO_CLEAR         0x0001
+
+/* OpenDoors status line settings */
+#define STATUS_NORMAL           0
+#define STATUS_NONE             8
+#define STATUS_ALTERNATE_1      1
+#define STATUS_ALTERNATE_2      2
+#define STATUS_ALTERNATE_3      3
+#define STATUS_ALTERNATE_4      4
+#define STATUS_ALTERNATE_5      5
+#define STATUS_ALTERNATE_6      6
+#define STATUS_ALTERNATE_7      7
+
+/* OpenDoors color definitions. */
+#define D_BLACK                 0
+#define D_BLUE                  1
+#define D_GREEN                 2
+#define D_CYAN                  3
+#define D_RED                   4
+#define D_MAGENTA               5
+#define D_BROWN                 6
+#define D_GREY                  7
+#define L_BLACK                 8
+#define L_BLUE                  9
+#define L_GREEN                 10
+#define L_CYAN                  11
+#define L_RED                   12
+#define L_MAGENTA               13
+#define L_YELLOW                14
+#define L_WHITE                 15
+#define B_BLACK                 L_BLACK
+#define B_BLUE                  L_BLUE
+#define B_GREEN                 L_GREEN
+#define B_CYAN                  L_CYAN
+#define B_RED                   L_RED
+#define B_MAGENTA               L_MAGENTA
+#define B_BROWN                 L_YELLOW
+#define B_GREY                  L_WHITE
+
+/* Door information file formats (od_control.od_info_type). */
+#define DORINFO1              0                              /* DORINFO?.DEF */
+#define EXITINFO              1     /* QBBS 2.6? EXITINFO.BBS & DORINFO?.DEF */
+#define RA1EXITINFO           2       /* RA 1.?? EXITINFO.BBS & DORINFO?.DEF */
+#define CHAINTXT              3                                 /* CHAIN.TXT */
+#define SFDOORSDAT            4                               /* SFDOORS.DAT */
+#define CALLINFO              5                              /* CALLINFO.BBS */
+#define DOORSYS_GAP           6                     /* GAP/PC-Board DOOR.SYS */
+#define DOORSYS_DRWY          7                          /* DoorWay DOOR.SYS */
+#define QBBS275EXITINFO       8               /* QuickBBS 2.75+ EXITINFO.BBS */
+#define CUSTOM                9                /* User-defined custom format */
+#define DOORSYS_WILDCAT       10                        /* WildCat! DOOR.SYS */
+#define RA2EXITINFO           11                    /* RA 2.00+ EXITINFO.BBS */
+#define TRIBBSSYS             12                               /* TRIBBS.SYS */
+#define DOOR32SYS             13                               /* DOOR32.SYS */
+#define NO_DOOR_FILE          100      /* No door information file was found */
+
+/* Error type (od_control.od_error). */
+#define ERR_NONE              0                              /* No error yet */
+#define ERR_MEMORY            1          /* Unable to allocate enough memory */
+#define ERR_NOGRAPHICS        2    /* Function requires ANSI/AVATAR/RIP mode */
+#define ERR_PARAMETER         3    /* Invalid value was passed to a function */
+#define ERR_FILEOPEN          4                       /* Unable to open file */
+#define ERR_LIMIT             5       /* An internal limit has been exceeded */
+#define ERR_FILEREAD          6                  /* Unable to read from file */
+#define ERR_NOREMOTE          7  /* Function may not be called in local mode */
+#define ERR_GENERALFAILURE    8       /* Percise cause of failure is unknown */
+#define ERR_NOTHINGWAITING    9    /* A request for data when none was ready */
+#define ERR_NOMATCH           10                       /* No match was found */
+#define ERR_UNSUPPORTED       11            /* Not supported in this version */
+
+/* od_control.od_errorlevel indicies. */
+#define ERRORLEVEL_ENABLE     0
+#define ERRORLEVEL_CRITICAL   1
+#define ERRORLEVEL_NOCARRIER  2
+#define ERRORLEVEL_HANGUP     3
+#define ERRORLEVEL_TIMEOUT    4
+#define ERRORLEVEL_INACTIVITY 5
+#define ERRORLEVEL_DROPTOBBS  6
+#define ERRORLEVEL_NORMAL     7
+
+/* Special od_popup_menu() return values. */
+#define POPUP_ERROR           -1
+#define POPUP_ESCAPE          0
+#define POPUP_LEFT            -2
+#define POPUP_RIGHT           -3
+
+/* od_get_input() flags. */
+#define GETIN_NORMAL          0x0000
+#define GETIN_RAW             0x0001
+#define GETIN_RAWCTRL         0x0002
+
+/* od_control.od_box_chars array indicies. */
+#define BOX_UPPERLEFT         0
+#define BOX_TOP               1
+#define BOX_UPPERRIGHT        2
+#define BOX_LEFT              3
+#define BOX_LOWERLEFT         4
+#define BOX_LOWERRIGHT        5
+#define BOX_BOTTOM            6
+#define BOX_RIGHT             7
+
+/* od_control.od_okaytopage settings. */
+#define PAGE_DISABLE          0
+#define PAGE_ENABLE           1
+#define PAGE_USE_HOURS        2
+
+/*  Method used for serial I/O (od_control.od_com_method). */
+#define COM_FOSSIL            1
+#define COM_INTERNAL          2
+#define COM_WIN32             3
+#define COM_DOOR32            4
+#define COM_SOCKET				5
+#define COM_STDIO			  6
+
+/* Flow control method (od_control.od_com_flow_control). */
+#define COM_DEFAULT_FLOW      0
+#define COM_RTSCTS_FLOW       1
+#define COM_NO_FLOW           2
+
+/* Optional component initialization functions. */
+ODAPIDEF void ODCALL ODConfigInit(void);
+ODAPIDEF void ODCALL ODLogEnable(void);
+ODAPIDEF void ODCALL ODMPSEnable(void);
+
+/* Optional OpenDoors component settings. */
+typedef void(ODFAR OD_COMPONENT)(void);
+#define INCLUDE_CONFIG_FILE   (OD_COMPONENT *)ODConfigInit
+#define NO_CONFIG_FILE        NULL
+#define INCLUDE_LOGFILE       (OD_COMPONENT *)ODLogEnable
+#define NO_LOGFILE            NULL
+#define INCLUDE_MPS           (OD_COMPONENT *)ODMPSEnable
+#define NO_MPS                NULL
+
+/* Built-in personality defintion functions. */
+ODAPIDEF void ODCALL pdef_opendoors(BYTE btOperation);
+ODAPIDEF void ODCALL pdef_pcboard(BYTE btOperation);
+ODAPIDEF void ODCALL pdef_ra(BYTE btOperation);
+ODAPIDEF void ODCALL pdef_wildcat(BYTE btOperation);
+
+/* Personality proc type. */
+typedef void(ODFAR OD_PERSONALITY_PROC)(BYTE);
+
+/* Personality identifiers. */
+#define PER_OPENDOORS         (void *)pdef_opendoors
+#define PER_PCBOARD           (void *)pdef_pcboard
+#define PER_RA                (void *)pdef_ra
+#define PER_WILDCAT           (void *)pdef_wildcat
+
+/* od_control.od_disable flags. */
+#define DIS_INFOFILE          0x0001
+#define DIS_CARRIERDETECT     0x0002
+#define DIS_TIMEOUT           0x0004
+#define DIS_LOCAL_OVERRIDE    0x0008
+#define DIS_BPS_SETTING       0x0010
+#define DIS_LOCAL_INPUT       0x0020
+#define DIS_SYSOP_KEYS        0x0040
+#define DIS_DTR_DISABLE       0x0080
+#define DIS_NAME_PROMPT       0x0100
+
+/* Event status settings. */
+#define ES_DELETED            0
+#define ES_ENABLED            1
+#define ES_DISABLED           2
+
+/* Personality proceedure operations. */
+#define PEROP_DISPLAY1        0
+#define PEROP_DISPLAY2        1
+#define PEROP_DISPLAY3        2
+#define PEROP_DISPLAY4        3
+#define PEROP_DISPLAY5        4
+#define PEROP_DISPLAY6        5
+#define PEROP_DISPLAY7        6
+#define PEROP_DISPLAY8        7
+#define PEROP_UPDATE1         10
+#define PEROP_UPDATE2         11
+#define PEROP_UPDATE3         12
+#define PEROP_UPDATE4         13
+#define PEROP_UPDATE5         14
+#define PEROP_UPDATE6         15
+#define PEROP_UPDATE7         16
+#define PEROP_UPDATE8         17
+#define PEROP_INITIALIZE      20
+#define PEROP_CUSTOMKEY       21
+#define PEROP_DEINITIALIZE    22
+
+
+/* ========================================================================= */
+/* The OpenDoors control structure (od_control)                              */
+/* ========================================================================= */
+
+/* Force byte alignment, if possible */
+#ifdef __TURBOC__
+#if(__TURBOC__ >= 0x295)
+#pragma option -a-
+#endif /* __TURBOC__ >= 0x295 */
+#endif /* __TURBOC__ */
+#if defined(_MSC_VER) || defined(WIN32)
+#pragma pack(1)
+#endif /* _MSC_VER */
+
+typedef struct
+{
+   /* Location or name of door information file (if one is to be used). */
+   char          info_path[60];
+
+   /* Serial port settings. */
+   DWORD         baud;
+   DWORD         od_connect_speed;
+   INT16         od_com_address;
+   BYTE          od_com_irq;
+   BYTE          od_com_method;
+   BYTE          od_com_flow_control;
+   WORD          od_com_rx_buf;
+   WORD          od_com_tx_buf;
+   BYTE          od_com_fifo_trigger;
+   BOOL          od_com_no_fifo;
+   BOOL          od_no_fossil;
+   BOOL          od_use_socket;
+   INT16         port;
+   DWORD         od_open_handle;
+
+   /* Caller and system information. */
+   char          system_name[40];
+   char          sysop_name[40];
+   INT32         system_calls;
+   char          system_last_caller[36];
+   char          timelog_start_date[9];
+   INT16         timelog_busyperhour[24];
+   INT16         timelog_busyperday[7];
+
+   char          user_name[36];
+   char          user_location[26];
+   char          user_password[16];
+   char          user_dataphone[16];
+   char          user_homephone[16];
+   char          user_lasttime[6];
+   char          user_lastdate[9];
+   BYTE          user_attribute;
+   BYTE          user_flags[4];
+   DWORD         user_net_credit;
+   DWORD         user_pending;
+   WORD          user_messages;
+   DWORD         user_lastread;
+   WORD          user_security;
+   DWORD         user_numcalls;
+   DWORD         user_uploads;
+   DWORD         user_downloads;
+   DWORD         user_upk;
+   DWORD         user_downk;
+   DWORD         user_todayk;
+   WORD          user_time_used;
+   WORD          user_screen_length;
+   BYTE          user_last_pwdchange;
+   BYTE          user_attrib2;
+   WORD          user_group;
+
+   BYTE          event_status;
+   char          event_starttime[6];
+   BYTE          event_errorlevel;
+   BYTE          event_days;
+   BYTE          event_force;
+   char          event_last_run[9];
+
+   BYTE          user_netmailentered;
+   BYTE          user_echomailentered;
+   char          user_logintime[6];
+   char          user_logindate[9];
+   INT16         user_timelimit;
+   INT32         user_loginsec;
+   INT32         user_credit;
+   WORD          user_num;
+   INT16         user_readthru;
+   INT16         user_numpages;
+   INT16         user_downlimit;
+   char          user_timeofcreation[6];
+   char          user_logonpassword[16];
+   BYTE          user_wantchat;
+   BYTE          user_ansi;
+   INT16         user_deducted_time;
+   char          user_menustack[50][9];
+   BYTE          user_menustackpointer;
+   char          user_handle[36];
+   char          user_comment[81];
+   char          user_firstcall[9];
+   BYTE          user_combinedrecord[200];
+   char          user_birthday[9];
+   char          user_subdate[9];
+   BYTE          user_screenwidth;
+   BYTE          user_language;
+   BYTE          user_date_format;
+   char          user_forward_to[36];
+   BYTE          user_error_free;
+   BYTE          sysop_next;
+   BYTE          user_emsi_session;
+   char          user_emsi_crtdef[41];
+   char          user_emsi_protocols[41];
+   char          user_emsi_capabilities[41];
+   char          user_emsi_requests[41];
+   char          user_emsi_software[41];
+   BYTE          user_hold_attr1;
+   BYTE          user_hold_attr2;
+   BYTE          user_hold_len;
+   char          user_reasonforchat[78];
+   char          user_callsign[12];
+   WORD          user_msg_area;
+   WORD          user_file_area;
+   char          user_protocol;
+   WORD          user_file_group;
+   BYTE          user_last_birthday_check;
+   char          user_sex;
+   DWORD         user_xi_record;
+   WORD          user_msg_group;
+   BYTE          user_avatar;
+   char          user_org[51];
+   char          user_address[3][51];
+   INT32         user_pwd_crc;
+   INT32         user_logon_pwd_crc;
+   char          user_last_cost_menu[9];
+   WORD          user_menu_cost;
+   BYTE          user_rip;
+   BYTE          user_rip_ver;
+   BYTE          user_attrib3;
+   BOOL          user_expert;
+   char          system_last_handle[36];
+
+   /* Door information file statistics. */
+   BYTE          od_info_type;
+   BYTE          od_extended_info;
+   WORD          od_node;
+   BYTE          od_ra_info;
+
+   /* Current program settings. */
+   BOOL          od_always_clear;
+   BOOL          od_force_local;
+   BOOL          od_chat_active;
+   BOOL          od_current_statusline;
+   INT16         od_error;
+   BYTE          od_last_input;
+   BOOL          od_logfile_disable;
+   char          od_logfile_name[80];
+   WORD          od_maxtime;
+   INT16         od_maxtime_deduction;
+   BOOL          od_okaytopage;
+   INT16         od_pagestartmin;
+   INT16         od_pageendmin;
+   BOOL          od_page_pausing;
+   INT16         od_page_statusline;
+   BOOL          od_user_keyboard_on;
+   BOOL          od_update_status_now;
+   INT16         od_cur_attrib;
+
+   /* OpenDoors customization settings. */
+   char          od_box_chars[8];
+   char          od_cfg_text[48][33];
+   char          od_cfg_lines[25][33];
+   OD_COMPONENT  *od_config_file;
+   const char *  od_config_filename;
+   void          (*od_config_function)(char *keyword, char *options);
+   char          od_color_char;
+   char          od_color_delimiter;
+   char          od_color_names[12][33];
+   BOOL          od_clear_on_exit;
+   void          (*od_cmd_line_handler)(char *pszKeyword, char *pszOptions);
+   void          (*od_cmd_line_help_func)(void);
+   void          (*od_default_personality)(BYTE operation);
+   BOOL          od_default_rip_win;
+   WORD          od_disable;
+   char          od_disable_dtr[40];
+   BOOL          od_disable_inactivity;
+   BOOL          od_emu_simulate_modem;
+   BYTE          od_errorlevel[8];
+   BOOL          od_full_color;
+   BOOL          od_full_put;
+   WORD          od_in_buf_size;
+   INT16         od_inactivity;
+   INT16         od_inactive_warning;
+   BOOL          od_internal_debug;
+   tODMilliSec   od_max_key_latency;
+   char          od_list_pause;
+   char          od_list_stop;
+   OD_COMPONENT  *od_logfile;
+   char          *od_logfile_messages[14];
+   OD_COMPONENT  *od_mps;
+   BOOL          od_nocopyright;
+   BOOL          od_noexit;
+   BOOL          od_no_ra_codes;
+   BYTE          od_page_len;
+   char          od_prog_copyright[40];
+   char          od_prog_name[40];
+   char          od_prog_version[40];
+   DWORD         od_reg_key;
+   char          od_reg_name[36];
+   BOOL          od_silent_mode;
+   BOOL          od_status_on;
+   BOOL          od_spawn_freeze_time;
+   BOOL          od_swapping_disable;
+   BOOL          od_swapping_noems;
+   char          od_swapping_path[80];
+
+   /* Custom function hooks. */
+   void          (*od_no_file_func)(void);
+   void          (*od_before_exit)(void);
+   void          (*od_cbefore_chat)(void);
+   void          (*od_cafter_chat)(void);
+   void          (*od_cbefore_shell)(void);
+   void          (*od_cafter_shell)(void);
+   void          (*od_config_callback)(void);
+   void          (*od_help_callback)(void);
+   void          (*od_ker_exec)(void);
+   void          (*od_local_input)(INT16 key);
+   void          (*od_time_msg_func)(char *string);
+
+   /* OpenDoors function key customizations. */
+   WORD          key_chat;
+   WORD          key_dosshell;
+   WORD          key_drop2bbs;
+   WORD          key_hangup;
+   WORD          key_keyboardoff;
+   WORD          key_lesstime;
+   WORD          key_lockout;
+   WORD          key_moretime;
+   WORD          key_status[9];
+   WORD          key_sysopnext;
+
+   /* Additional function keys. */
+   BYTE          od_num_keys;
+   INT16         od_hot_key[16];
+   INT16         od_last_hot;
+   void          (*od_hot_function[16])(void);
+
+   /* OpenDoors prompt customizations. */
+   char *        od_after_chat;
+   char *        od_after_shell;
+   char *        od_before_chat;
+   char *        od_before_shell;
+   char *        od_chat_reason;
+   char *        od_continue;
+   char          od_continue_yes;
+   char          od_continue_no;
+   char          od_continue_nonstop;
+   char *        od_day[7];
+   char *        od_hanging_up;
+   char *        od_exiting;
+   char *        od_help_text;
+   char *        od_help_text2;
+   char *        od_inactivity_timeout;
+   char *        od_inactivity_warning;
+   char *        od_month[12];
+   char *        od_no_keyboard;
+   char *        od_no_sysop;
+   char *        od_no_response;
+   char *        od_no_time;
+   char *        od_offline;
+   char *        od_paging;
+   char *        od_press_key;
+   char *        od_sending_rip;
+   char *        od_status_line[3];
+   char *        od_sysop_next;
+   char *        od_time_left;
+   char *        od_time_warning;
+   char *        od_want_chat;
+   char *        od_cmd_line_help;
+
+   /* OpenDoors color customizations. */
+   BYTE          od_chat_color1;
+   BYTE          od_chat_color2;
+   BYTE          od_list_comment_col;
+   BYTE          od_list_name_col;
+   BYTE          od_list_offline_col;
+   BYTE          od_list_size_col;
+   BYTE          od_list_title_col;
+   BYTE          od_local_win_col;
+   BYTE          od_continue_col;
+   BYTE          od_menu_title_col;
+   BYTE          od_menu_border_col;
+   BYTE          od_menu_text_col;
+   BYTE          od_menu_key_col;
+   BYTE          od_menu_highlight_col;
+   BYTE          od_menu_highkey_col;
+
+   /* Platform-specific settings. */
+#ifdef ODPLAT_WIN32
+   HICON         od_app_icon;
+   int           od_cmd_show;
+#endif /* ODPLAT_WIN32 */
+} tODControl;
+
+/* Restore original structure alignment, if possible. */
+#if defined(_MSC_VER) || defined(WIN32)
+#pragma pack()
+#endif /* _MSC_VER */
+
+
+/* The od_control external variable. */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+OD_API_VAR_DECL tODControl
+#ifndef _WIN32	/* warning C4229: anachronism used : modifiers on data are ignored */
+OD_GLOBAL_CONV
+#endif
+od_control;
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+/* ========================================================================= */
+/* OpenDoors API function prototypes.                                        */
+/* ========================================================================= */
+
+/* Programs interface with OpenDoors by calling any of the OpenDoors API
+ * functions. A summary of these functions appears below, followed by the
+ * function definition prototypes. Full information on these functions is
+ * provided by the OpenDoors manual. Functions denoted (ANS/AVT) require ANSI
+ * or AVATAR display modes to be active.
+ *
+ * OUTPUT FUNCTIONS - TEXT DISPLAY
+ *    od_printf()             - Performs formatted output, with color settings
+ *    od_disp_str()           - Displays a normal, nul-terminated string.
+ *    od_disp()               - Sends chars to modem, with/without local echo
+ *    od_disp_emu()           - Displays a string, translating ANSI/AVT codes
+ *    od_repeat()             - Efficiently displays a character repeatedly
+ *    od_putch()              - Displays a single character.
+ *
+ * OUTPUT FUNCTIONS - COLOUR AND CURSOR CONTROL
+ *    od_set_color()          - Sets color according to fore/background values
+ *    od_set_attrib()         - Sets color to specified IBM-PC attribute
+ *    od_set_cursor()         - Positions cursor on screen            (ANS/AVT)
+ *    od_get_cursor()         - Estimates the current cursor position on screen
+ *
+ * OUTPUT FUNCTIONS - SCREEN MANIPULATION
+ *    od_clr_scr()            - Clears the screen
+ *    od_save_screen()        - Saves the contents of entire screen
+ *    od_restore_screen()     - Restores the contents of entire screen
+ *
+ * OUTPUT FUNCTIONS - BLOCK MANIPULATION
+ *    od_clr_line()           - Clears the remainder of the current line
+ *    od_gettext()            - Gets the contents a block of screen   (ANS/AVT)
+ *    od_puttext()            - Displays block stored by gettext()    (ANS/AVT)
+ *    od_scroll()             - Scrolls a portion of the screen       (ANS/AVT)
+ *
+ * OUTPUT FUNCTIONS - WINDOWS & MENUS
+ *    od_draw_box()           - Draws a box on the screen             (ANS/AVT)
+ *    od_window_create()      - Creates a window, storing underlying  (ANS/AVT)
+ *    od_window_remove()      - Removes window, restoring underlying  (ANS/AVT)
+ *    od_popup_menu()         - Displays popup menu with "light" bar  (ANS/AVT)
+ *
+ * OUTPUT FUNCTIONS - FILE DISPLAY
+ *    od_send_file()          - Displays an ASCII/ANSI/AVATAR/RIP file
+ *    od_hotkey_menu()        - Displays ASC/ANS/AVATAR/RIP menu, with hotkeys
+ *    od_list_files()         - Lists files avail for download using FILES.BBS
+ *
+ * INPUT FUNCTIONS
+ *    od_get_answer()         - Inputs a key, allowing only specified responses
+ *    od_get_key()            - Inputs a key, optionally waiting for next key
+ *    od_get_input()          - Obtains next input, with translation
+ *    od_input_str()          - Inputs string of specified length from keyboard
+ *    od_edit_str()           - Fancy formatted string input function (ANS/AVT)
+ *    od_clear_keybuffer()    - Removes any waiting keys in keyboard buffer
+ *    od_multiline_edit()     - Edits text that spans multiple lines  (ANS/AVT)
+ *    od_key_pending()        - Returns TRUE if a key is waiting to be processed
+ *
+ * COMMON DOOR ACTIVITY FUNCTIONS
+ *    od_page()               - Allows user to page sysop
+ *    od_spawn()              - Suspends OpenDoors & starts another program
+ *    od_spawnvpe()           - Like od_spawn, but with more options
+ *    od_log_write()          - Writes a logfile entry
+ *    od_parse_cmd_line()     - Handles standard command-line parameters
+ *
+ * SPECIAL CONTROL FUNCTIONS
+ *    od_init()               - Forces OpenDoors initialization
+ *    od_color_config()       - Translates color description to color value
+ *    od_add_personality()    - Adds another local interface personality
+ *    od_set_statusline()     - Sets the current status line setting
+ *    od_autodetect()         - Determines the remote system terminal type
+ *    od_kernel()             - Call when not calling other functions
+ *    od_exit()               - Ends OpenDoors program
+ *    od_carrier()            - Indicates whether remote connection is present
+ *    od_set_dtr()            - Raises / lowers the DTR signal to the modem
+ *    od_chat()               - Manually starts chat mode
+ *    od_sleep()              - Yield to other processes
+ *    od_control_get()        - Returns a pointer to the od_control structure.
+ */
+ODAPIDEF BOOL ODCALL   od_add_personality(const char *pszName, BYTE btOutputTop,
+                          BYTE btOutputBottom,
+                          OD_PERSONALITY_PROC *pfPerFunc);
+ODAPIDEF void ODCALL   od_autodetect(INT nFlags);
+ODAPIDEF BOOL ODCALL   od_carrier(void);
+ODAPIDEF void ODCALL   od_chat(void);
+ODAPIDEF void ODCALL   od_clear_keybuffer(void);
+ODAPIDEF void ODCALL   od_clr_line(void);
+ODAPIDEF void ODCALL   od_clr_scr(void);
+ODAPIDEF BYTE ODCALL   od_color_config(char *pszColorDesc);
+ODAPIDEF tODControl *  ODCALL od_control_get(void);
+ODAPIDEF void ODCALL   od_disp(const char *pachBuffer, INT nSize, BOOL bLocalEcho);
+ODAPIDEF void ODCALL   od_disp_emu(const char *pszToDisplay, BOOL bRemoteEcho);
+ODAPIDEF void ODCALL   od_disp_str(const char *pszToDisplay);
+ODAPIDEF BOOL ODCALL   od_draw_box(BYTE btLeft, BYTE btTop, BYTE btRight,
+                          BYTE btBottom);
+ODAPIDEF WORD ODCALL   od_edit_str(char *pszInput, char *pszFormat, INT nRow,
+                          INT nColumn, BYTE btNormalColour,
+                          BYTE btHighlightColour, char chBlank,
+                          WORD nFlags);
+ODAPIDEF void ODCALL   od_exit(INT nErrorLevel, BOOL bTermCall);
+ODAPIDEF char ODCALL   od_get_answer(const char *pszOptions);
+ODAPIDEF void ODCALL   od_get_cursor(INT *pnRow, INT *pnColumn);
+ODAPIDEF BOOL ODCALL   od_get_input(tODInputEvent *pInputEvent,
+                          tODMilliSec TimeToWait, WORD wFlags);
+ODAPIDEF BOOL ODCALL   od_key_pending(void);
+ODAPIDEF char ODCALL   od_get_key(BOOL bWait);
+ODAPIDEF BOOL ODCALL   od_gettext(INT nLeft, INT nTop, INT nRight,
+                          INT nBottom, void *pBlock);
+ODAPIDEF char ODCALL   od_hotkey_menu(char *pszFileName, char *pszHotKeys,
+                          BOOL bWait);
+ODAPIDEF void ODCALL   od_init(void);
+ODAPIDEF void ODCALL   od_input_str(char *pszInput, INT nMaxLength,
+                          unsigned char chMin, unsigned char chMax);
+ODAPIDEF void ODCALL   od_kernel(void);
+ODAPIDEF BOOL ODCALL   od_list_files(char *pszFileSpec);
+ODAPIDEF BOOL ODCALL   od_log_write(char *pszMessage);
+ODAPIDEF INT ODCALL    od_multiline_edit(char *pszBufferToEdit,
+                          UINT unBufferSize, tODEditOptions *pEditOptions);
+ODAPIDEF void ODCALL   od_page(void);
+#ifdef ODPLAT_WIN32
+ODAPIDEF void ODCALL   od_parse_cmd_line(LPSTR pszCmdLine);
+#else /* !ODPLAT_WIN32 */
+ODAPIDEF void ODCALL   od_parse_cmd_line(INT nArgCount,
+                          char *papszArguments[]);
+#endif /* !ODPLAT_WIN32 */
+ODAPIDEF INT ODCALL    od_popup_menu(char *pszTitle, char *pszText,
+                          INT nLeft, INT nTop, INT nLevel, WORD uFlags);
+ODAPIDEF void ODVCALL  od_printf(const char *pszFormat, ...);
+ODAPIDEF void ODCALL   od_putch(char chToDisplay);
+ODAPIDEF BOOL ODCALL   od_puttext(INT nLeft, INT nTop, INT nRight,
+                          INT nBottom, void *pBlock);
+ODAPIDEF void ODCALL   od_repeat(char chValue, BYTE btTimes);
+ODAPIDEF BOOL ODCALL   od_restore_screen(void *pBuffer);
+ODAPIDEF BOOL ODCALL   od_save_screen(void *pBuffer);
+ODAPIDEF BOOL ODCALL   od_scroll(INT nLeft, INT nTop, INT nRight,
+                          INT nBottom, INT nDistance, WORD nFlags);
+ODAPIDEF BOOL ODCALL   od_send_file(const char *pszFileName);
+ODAPIDEF BOOL ODCALL   od_send_file_section(char *pszFileName, char *pszSectionName);
+ODAPIDEF void ODCALL   od_set_attrib(INT nColour);
+ODAPIDEF void ODCALL   od_set_color(INT nForeground, INT nBackground);
+ODAPIDEF void ODCALL   od_set_cursor(INT nRow, INT nColumn);
+ODAPIDEF void ODCALL   od_set_dtr(BOOL bHigh);
+ODAPIDEF BOOL ODCALL   od_set_personality(const char *pszName);
+ODAPIDEF void ODCALL   od_set_statusline(INT nSetting);
+ODAPIDEF void ODCALL   od_sleep(tODMilliSec Milliseconds);
+ODAPIDEF BOOL ODCALL   od_spawn(const char *pszCommandLine);
+ODAPIDEF INT16 ODCALL  od_spawnvpe(INT16 nModeFlag, char *pszPath,
+                          char *papszArg[], char *papszEnv[]);
+ODAPIDEF void * ODCALL od_window_create(INT nLeft, INT nTop, INT nRight,
+                          INT nBottom, char *pszTitle, BYTE btBorderCol,
+                          BYTE btTitleCol, BYTE btInsideCol, INT nReserved);
+ODAPIDEF BOOL ODCALL   od_window_remove(void *pWinInfo);
+
+
+/* ========================================================================= */
+/* Definitions for compatibility with previous versions.                     */
+/* ========================================================================= */
+
+/* Alternative spelling for the word color (colour). */
+#define od_chat_colour1                od_chat_color1
+#define od_chat_colour2                od_chat_color2
+#define od_colour_char                 od_color_char
+#define od_colour_delimiter            od_color_delimiter
+#define od_colour_names                od_color_names
+#define od_full_colour                 od_full_color
+#define od_colour_config               od_color_config
+#define od_set_colour                  od_set_color
+
+/* Definitions for renamed od_control members and manifest constants. */
+#define key_help                       key_status[6]
+#define key_nohelp                     key_status[0]
+#define user_credit                    user_net_credit
+#define caller_netmailentered          user_netmailentered
+#define caller_echomailentered         user_echomailentered
+#define caller_logintime               user_logintime
+#define caller_logindate               user_logindate
+#define caller_timelimit               user_timelimit
+#define caller_loginsec                user_loginsec
+#define caller_credit                  user_credit
+#define caller_userrecord              user_num
+#define caller_readthru                user_readthru
+#define caller_numpages                user_numpages
+#define caller_downlimit               user_downlimit
+#define caller_timeofcreation          user_timeofcreation
+#define caller_logonpassword           user_logonpassword
+#define caller_wantchat                user_wantchat
+#define caller_ansi                    user_ansi
+#define ra_deducted_time               user_deducted_time
+#define ra_menustack                   user_menustack
+#define ra_menustackpointer            user_menustackpointer
+#define ra_userhandle                  user_handle
+#define ra_comment                     user_comment
+#define ra_firstcall                   user_firstcall
+#define ra_combinedrecord              user_combinedrecord
+#define ra_birthday                    user_birthday
+#define ra_subdate                     user_subdate
+#define ra_screenwidth                 user_screenwidth
+#define ra_msg_area                    user_msg_area
+#define ra_file_area                   user_file_area
+#define ra_language                    user_language
+#define ra_date_format                 user_date_format
+#define ra_forward_to                  user_forward_to
+#define ra_error_free                  user_error_free
+#define ra_sysop_next                  sysop_next
+#define ra_emsi_session                user_emsi_session
+#define ra_emsi_crtdef                 user_emsi_crtdef
+#define ra_emsi_protocols              user_emsi_protocols
+#define ra_emsi_capabilities           user_emsi_capabilities
+#define ra_emsi_requests               user_emsi_requests
+#define ra_emsi_software               user_emsi_software
+#define ra_hold_attr1                  user_hold_attr1
+#define ra_hold_attr2                  user_hold_attr2
+#define ra_hold_len                    user_hold_len
+#define caller_usernum                 user_num
+#define caller_callsign                user_callsign
+#define caller_sex                     user_sex
+#define od_avatar                      user_avatar
+#define B_YELLOW                       L_YELLOW
+#define B_WHITE                        L_WHITE
+#define od_rbbs_node                   od_node
+#define STATUS_USER1                   STATUS_ALTERNATE_1
+#define STATUS_USER2                   STATUS_ALTERNATE_2
+#define STATUS_USER3                   STATUS_ALTERNATE_3
+#define STATUS_USER4                   STATUS_ALTERNATE_4
+#define STATUS_SYSTEM                  STATUS_ALTERNATE_5
+#define STATUS_HELP                    STATUS_ALTERNATE_7
+#define od_registered_to               od_control.od_reg_name
+#define od_registration_key            od_control.od_reg_key
+#define od_program_name                od_control.od_prog_name
+#define od_log_messages                od_control.od_logfile_messages
+#define od_config_text                 od_control.od_cfg_text
+#define od_config_lines                od_control.od_cfg_lines
+#define od_config_colours              od_control.od_colour_names
+#define od_config_colors               od_control.od_colour_names
+#define config_file                    od_config_file
+#define config_filename                od_config_filename
+#define config_function                od_config_function
+#define default_personality            od_default_personality
+#define logfile                        od_logfile
+#define mps                            od_mps
+#define od_kernal                      od_kernel
+
+/* Obsolete functions. */
+#define od_init_with_config(filename,function)\
+                                  od_control.config_file=INCLUDE_CONFIG_FILE;\
+                                  od_control.config_filename=filename;\
+                                  od_control.config_function=function;\
+                                  od_init()
+ODAPIDEF BOOL ODCALL                   od_log_open(void);
+ODAPIDEF void ODCALL                   od_emulate(register char in_char);
+
+#endif /* _INC_OPENDOOR */

+ 0 - 0
odoors/README.NIX


+ 36 - 0
odoors/TODO-v7.txt

@@ -0,0 +1,36 @@
+Features/Changes which will break the API and so must go into v7
+----------------------------------------------------------------
+- Do not pack the od_control structure in memory.  This is plain silly.
+- Do not use the BOOL type in od_control or in any of the public functions.
+  Using it causes problems with stuff that defines a BOOL as a different
+  size.  For example, xpdev uses int as a BOOL whereas OpenDoors uses a char.
+- Support telnet IAC escaping, add an item to the od_control struct to
+  keep track of the current status of IAC escapes.
+- Fix up the local display for Win32.  It's horribly slow currently.
+  Possibly just dust of the DOS stuff and use ciolib?
+- Make output functions take an optional position and attribute to cut down
+  on small send()s.
+- Audit for small send()s in general.
+- Possibly improve the output thread and make *nix multi-threaded for block
+  buffering ala Synchronet.
+- Change to 24-line default.  This means fixing the personalities, and the
+  screen stuff.
+- Autodetect if passed handle is a socket (Like I did for MyCroft doors)
+- We need some non-socket way of dealing with I/O for Win32.  Something that
+  operates along the lines of stdio on *nix.  When BBSs commonly start doing
+  SSH, is the door supposed to do SSH also?  A local socket would work, but it
+  seems like the Wrong Thing.  The main reason stdio is out is that there's no
+  standard way of passing hangup info etc.
+- Check the timestamp on the dropfile... do not use an old one!  Add and
+  od_control variable to prevent this behaviour.
+- Add a bunch of status-line specific functions to allow new personalities:
+	od_status_set_cursor()
+	od_status_set_attrib()
+	od_status_puttext()
+	od_status_gettext()
+	od_status_printf()
+	od_status_disp_str()
+	od_status_putch()
+	od_status_addkey()
+	od_status_delkey()
+- OD_DISP_EMU() has problems with ANSI music (of course)

BIN
odoors/Toolbar.bmp


+ 7 - 0
odoors/buildall.bat

@@ -0,0 +1,7 @@
+make -fDOS.mak -DTARGET=s > out.txt
+make -fDOS.mak -DTARGET=c >> out.txt
+make -fDOS.mak -DTARGET=m >> out.txt
+make -fDOS.mak -DTARGET=l >> out.txt
+make -fDOS.mak -DTARGET=h >> out.txt
+set INCLUDE=c:\msdev\include;c:\msdev\mfc\include
+nmake -fWin32.mak >> out.txt

+ 5 - 0
odoors/builddos.bat

@@ -0,0 +1,5 @@
+make -fDOS.mak -DTARGET=s > out.txt
+make -fDOS.mak -DTARGET=c >> out.txt
+make -fDOS.mak -DTARGET=m >> out.txt
+make -fDOS.mak -DTARGET=l >> out.txt
+make -fDOS.mak -DTARGET=h >> out.txt

+ 346 - 0
odoors/ex_chat.c

@@ -0,0 +1,346 @@
+/* EX_CHAT.C - Example of a multi-window full-screen chat program written    */
+/*             with OpenDoors. See manual for instructions on how to compile */
+/*             a program using OpenDoors.                                    */
+/*                                                                           */
+/*             This program shows how to do the following:                   */
+/*                                                                           */
+/*                - Replace the standard OpenDoors chat mode with your own   */
+/*                  chat mode implementation. See instructions below.        */
+/*                - How to scroll a portion of the screen, leaving the rest  */
+/*                  of the screen unaltered.                                 */
+/*                - How to determine whether input came from the local or    */
+/*                  remote keyboard.                                         */
+/*                - How to display a popup window with a message in it when  */
+/*                  the sysop shells to DOS. (DOS version only.)             */
+/*                - How to save and restore the entire contents of the       */
+/*                  screen.                                                  */
+/*                                                                           */
+/*             Conditional compilation directives allow this program to      */
+/*             be compiled as a stand-alone chat door, or as a               */
+/*             replacement chat mode to be integrated into any OpenDoors     */
+/*             program. If STANDALONE is #defined, ex_chat.c will be         */
+/*             compiled as a split-screen chat door that can be run like     */
+/*             any door program. If STANDALONE is not #defined, the chat     */
+/*             mode function will be compiled as a replacement chat mode     */
+/*             for the chat mode built into OpenDoors. In this case,  the    */
+/*             demo mainline function simply displays a prompt, and will     */
+/*             exit the door as soon as you press the [ENTER] key. While     */
+/*             the program is running, if you invoke chat mode (press        */
+/*             [ALT]-[C]), the current screen will be saved and the          */
+/*             split-screen chat mode will be activated. When you press      */
+/*             [ESC] the split-screen chat mode will end, and the            */
+/*             original screen will be restored. To integrate this chat      */
+/*             mode into your own program, you should simply set             */
+/*             od_control.od_cbefore_chat to point to the                    */
+/*             fullscreen_chat function, as shown, and remove the mainline   */
+/*             (main()/WinMain()) function from this file. The compile this  */
+/*             file into your program after removing the #define STANDALONE  */
+/*             line.                                                         */
+
+/* Include required header files. */
+#include "OpenDoor.h"
+#include <string.h>
+
+
+/* The following #define forces this code to compile as a stand-alone door */
+/* program. If you wish to use this code to replace the standard OpenDoors */
+/* chat mode in your own program, remove this #define.                     */
+#define STANDALONE
+
+
+/* Full-screen chat function prototypes. */
+void fullscreen_chat(void);
+void chat_new_line(void);
+void display_shell_window(void);
+void remove_shell_window(void);
+
+
+
+/* The main() or WinMain() function: program execution begins here. */
+#ifdef ODPLAT_WIN32
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+   LPSTR lpszCmdLine, int nCmdShow)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+   /* Handle standard command-line options and perform other needed setup. */
+#ifdef ODPLAT_WIN32
+   od_control.od_cmd_show = nCmdShow;
+   od_parse_cmd_line(lpszCmdLine);
+#else
+   od_parse_cmd_line(argc, argv);
+#endif
+
+#ifdef STANDALONE               /* If compiled as a stand-alone chat program */
+   od_init();                                        /* Initialize OpenDoors */
+
+   fullscreen_chat();                    /* Invoke full-screen chat function */
+
+#else                  /* If compiled as replacement for OpenDoors chat mode */
+                      /* Setup OpenDoors to use our custom chat mode instead */
+   od_control.od_cbefore_chat=fullscreen_chat;
+
+   od_printf("Press [Enter] to exit door, or invoke chat mode.\n\r");
+   od_get_answer("\n\r");
+#endif
+
+   od_exit(0, FALSE);                                       /* Exit program. */
+   return(0);                                                   
+}
+
+
+
+
+
+                                   /* FULL-SCREEN CHAT CUSTOMIZATION OPTIONS */
+
+char window_colour[2]={0x0b,0x0c};       /* Text colour used for each person */
+char bar_colour=0x70;                     /* Colour of window seperation bar */
+char top_line[2]={13,1};      /* Specifies location of each window on screen */
+char bottom_line[2]={23,11};         /* Line number of bottom of each window */
+char bar_line=12;                    /* Line number of window seperation bar */
+char scroll_distance=2;           /* Distance to scroll window when required */
+char shell_window_title=0x1a;  /* Colour of title of DOS shell notice window */
+char shell_window_boarder=0x1f;        /* Colour of DOS shell window boarder */
+char shell_window_text=0x1b;           /* Colour of text in DOS shell window */
+
+
+
+int cursor_window;                   /* FULL-SCREEN CHAT INTERNAL VARIABLES */
+char current_word[2][81];
+int word_length[2];
+int cursor_col[2];
+int cursor_line[2];
+unsigned char key;
+int old_chat_key;
+void *shell_window;
+char *before_shell_text;
+char *after_shell_text;
+#ifndef STANDALONE     /* If compiled as replacement for OpenDoors chat mode */
+char screen_buffer[4004];
+#endif
+
+                                                /* FULL-SCREEN CHAT FUNCTION */
+void fullscreen_chat(void)
+{
+   cursor_window=0;                               /* Reset working variables */
+   word_length[0]=word_length[1]=0;
+   cursor_col[0]=cursor_col[1]=1;
+   cursor_line[0]=top_line[0];
+   cursor_line[1]=top_line[1];
+
+
+                         /* If ANSI or AVATAR graphics mode is not available */
+   if(!od_control.user_ansi && !od_control.user_avatar)
+   {                           /* Then use OpenDoor's line chat mode instead */
+#ifdef STANDALONE               /* If compiled as a stand-alone chat program */
+      od_chat();
+#endif
+      return;
+   }
+
+   od_control.od_cbefore_shell=display_shell_window;   /* Set shell settings */
+   od_control.od_cafter_shell=remove_shell_window;
+   before_shell_text=od_control.od_before_shell;
+   after_shell_text=od_control.od_after_shell;
+   od_control.od_before_shell=NULL;
+   od_control.od_after_shell=NULL;
+   od_control.od_chat_active=TRUE;
+
+#ifdef STANDALONE               /* If compiled as a stand-alone chat program */
+   old_chat_key=od_control.key_chat;      /* Prevent internal chat mode from */
+   od_control.key_chat=0;                                   /* being invoked */
+
+#else                  /* If compiled as replacement for OpenDoors chat mode */
+   od_save_screen(screen_buffer);           /* Save current screen contents. */
+#endif
+
+                                                     /* DRAW THE CHAT SCREEN */
+   od_set_attrib(window_colour[0]);
+   od_clr_scr();                                         /* Clear the screen */
+
+   od_set_cursor(bar_line,1);                  /* Draw window separation bar */
+   od_set_attrib(bar_colour);
+   od_clr_line();
+   od_set_cursor(bar_line,67);
+   od_printf("Ctrl-A: Clear");
+   od_set_cursor(bar_line,1);
+   od_printf(" Top : %-.28s    Bottom : %-.28s    ",
+      od_control.sysop_name, od_control.user_name);
+
+   od_set_cursor(top_line[0],1);    /* Locate cursor where typing will begin */
+   od_set_attrib(window_colour[0]);           /* Set appropriate text colour */
+
+                                                           /* MAIN CHAT LOOP */
+   for(;;)                             /* (Repeats for each character typed) */
+   {
+      do
+      {
+         key=(char)od_get_key(FALSE);    /* Get next keystroke from keyboard */
+
+                                                    /* CHECK FOR SYSOP ABORT */
+         if((key==27 && od_control.od_last_input==1) /* If sysop pressed ESC */
+            || !od_control.od_chat_active)
+         {
+            od_set_attrib(0x07);                     /* Reset display colour */
+            od_clr_scr();                                /* Clear the screen */
+
+            od_control.od_cbefore_shell=NULL;  /* Restore DOS shell settings */
+            od_control.od_cafter_shell=NULL;
+            od_control.od_before_shell=before_shell_text;
+            od_control.od_after_shell=after_shell_text;
+#ifdef STANDALONE               /* If compiled as a stand-alone chat program */
+            od_control.key_chat=old_chat_key;/* Re-enable internal chat mode */
+
+#else                  /* If compiled as replacement for OpenDoors chat mode */
+            od_control.od_chat_active=FALSE;           /* Turn off chat mode */
+            od_restore_screen(screen_buffer);      /* Restore orignal screen */
+#endif
+            return;                                 /* Exit full-screen chat */
+
+         }
+      } while(key==0);
+
+                                                     /* CHECK FOR NEW TYPIST */
+      if(od_control.od_last_input!=cursor_window)/* If new person typing now */
+      {                               /* Switch cursor to appropriate window */
+         cursor_window=od_control.od_last_input;       /* Set current typist */
+
+                                                /* Move cursor to new window */
+         od_set_cursor(cursor_line[cursor_window],cursor_col[cursor_window]);
+
+         od_set_attrib(window_colour[cursor_window]);  /* Change text colour */
+      }
+
+
+      if(key==13 || key==10)           /* IF USER PRESSED [ENTER] / [RETURN] */
+      {
+         word_length[cursor_window]=0;      /* Enter constitutes end of word */
+
+         chat_new_line();                               /* Move to next line */
+      }
+
+
+      else if(key==8)                           /* IF USER PRESS [BACKSPACE] */
+      {
+         if(cursor_col[cursor_window] > 1)       /* If not at left of screen */
+         {
+            --cursor_col[cursor_window];    /* Move cursor back on character */
+            if(word_length[cursor_window] > 0) --word_length[cursor_window];
+            od_printf("\b \b");          /* Erase last character from screen */
+         }
+      }
+
+
+      else if(key==32)                            /* IF USER PRESSED [SPACE] */
+      {
+         word_length[cursor_window]=0;    /* [Space] constitutes end of word */
+
+         if(cursor_col[cursor_window]==79)              /* If at end of line */
+            chat_new_line();                     /* Move cursor to next line */
+         else                                       /* If not at end of line */
+         {
+            ++cursor_col[cursor_window];        /* Increment cursor position */
+            od_putch(32);                                 /* Display a space */
+         }
+      }
+
+
+      else if(key==1)                    /* IF USER PRESSED CLEAR WINDOW KEY */
+      {                                               /* Clear user's window */
+         od_scroll(1,top_line[cursor_window],79,bottom_line[cursor_window],
+            bottom_line[cursor_window]-top_line[cursor_window]+1,0);
+
+         word_length[cursor_window]=0;  /* We are no longer composing a word */
+
+         cursor_col[cursor_window]=1;               /* Reset cursor position */
+         cursor_line[cursor_window]=top_line[cursor_window];
+         od_set_cursor(cursor_line[cursor_window],cursor_col[cursor_window]);
+      }
+
+
+      else if(key>32)                 /* IF USER TYPED A PRINTABLE CHARACTER */
+      {                                    /* PERFORM WORD WRAP IF NECESSARY */
+         if(cursor_col[cursor_window]==79)    /* If cursor is at end of line */
+         {
+                                               /* If there is a word to wrap */
+            if(word_length[cursor_window]>0 && word_length[cursor_window]<78)
+            {
+                                         /* Move cursor to beginning of word */
+               od_set_cursor(cursor_line[cursor_window],
+                          cursor_col[cursor_window]-word_length[cursor_window]);
+
+               od_clr_line();                /* Erase word from current line */
+
+               chat_new_line();                  /* Move cursor to next line */
+
+                                                           /* Redisplay word */
+               od_disp(current_word[cursor_window],word_length[cursor_window],
+                                                                          TRUE);
+               cursor_col[cursor_window]+=word_length[cursor_window];
+            }
+
+            else                            /* If there is no word to "wrap" */
+            {
+               chat_new_line();                  /* Move cursor to next line */
+               word_length[cursor_window]=0;             /* Start a new word */
+            }
+         }
+
+                                            /* ADD CHARACTER TO CURRENT WORD */
+                              /* If there is room for more character in word */
+         if(strlen(current_word[cursor_window])<79)     /* Add new character */
+            current_word[cursor_window][word_length[cursor_window]++]=key;
+
+                                            /* DISPLAY NEWLY TYPED CHARACTER */
+         ++cursor_col[cursor_window];
+         od_putch(key);
+      }
+   }
+}
+
+
+
+              /* FUNCTION USED BY FULL-SCREEN CHAT TO START A NEW INPUT LINE */
+void chat_new_line(void)
+{                                        /* If cursor is at bottom of window */
+   if(cursor_line[cursor_window]==bottom_line[cursor_window])
+   {                                  /* Scroll window up one line on screen */
+      od_scroll(1,top_line[cursor_window],79, bottom_line[cursor_window],
+                scroll_distance, 0);
+      cursor_line[cursor_window]-=(scroll_distance - 1);
+   }
+
+   else                              /* If cursor is not at bottom of window */
+   {
+      ++cursor_line[cursor_window];             /* Move cursor down one line */
+   }
+
+                                         /* Move cursor's position on screen */
+   od_set_cursor(cursor_line[cursor_window],cursor_col[cursor_window]=1);
+
+   od_set_attrib(window_colour[cursor_window]);        /* Change text colour */
+}
+
+
+void display_shell_window(void)
+{
+   if((shell_window=od_window_create(17,9,63,15,"DOS Shell",
+                                     shell_window_boarder, shell_window_title, 
+                                     shell_window_text, 0))==NULL) return;
+
+   od_set_attrib(shell_window_text);
+   od_set_cursor(11,26);
+   od_printf("The Sysop has shelled to DOS");
+   od_set_cursor(13,21);
+   od_printf("He/She will return in a few moments...");
+}
+
+
+void remove_shell_window(void)
+{
+   od_window_remove(shell_window);
+   od_set_cursor(cursor_line[cursor_window],cursor_col[cursor_window]);
+   od_set_attrib(window_colour[cursor_window]);
+}

+ 578 - 0
odoors/ex_diag.c

@@ -0,0 +1,578 @@
+/* ex_diag.c - Diagnostic door program, written to test environment in which
+ *             an OpenDooors door will run. Reads configuration settings from
+ *             command line and configuration file, and displays diagnostic
+ *             information on the local (and when possible, remote) screens.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "OpenDoor.h"
+
+/******************/
+/* Wrapper macros */
+/******************/
+#if defined(__unix__)
+	#if !defined(stricmp)
+		#define stricmp(x,y)            strcasecmp(x,y)
+		#define strnicmp(x,y,z)         strncasecmp(x,y,z)
+        #endif
+#endif
+
+typedef enum
+{
+   kParamLocal,
+   kParamBPS,
+   kParamPort,
+   kParamNode,
+   kParamHelp,
+   kParamPersonality,
+   kParamMaxTime,
+   kParamAddress,
+   kParamIRQ,
+   kParamNoFOSSIL,
+   kParamNoFIFO,
+   kParamDropFile,
+   kParamUserName,
+   kParamTimeLeft,
+   kParamSecurity,
+   kParamLocation,
+   kParamUnknown
+} tCommandLineParameter;
+
+char *BoolAsStr(int bValue);
+void ParseStandardCommandLine(int nArgCount, char *papszArguments[]);
+static void AdvanceToNextArg(int *pnCurrentArg, int nArgCount,
+			     char *pszOption);
+static void GetNextArgName(int *pnCurrentArg, int nArgCount,
+			   char *papszArguments[], char *pszString,
+			   int nStringSize);
+static tCommandLineParameter GetCommandLineParameter(char *pszArgument);
+
+
+int main(int argc, char *argv[])
+{
+   char sz[80];
+   int n;
+
+   /* Parse command-line. */
+   ParseStandardCommandLine(argc, argv);
+
+   /* Initialize OpenDoors. */
+   od_init();
+
+   od_clr_scr();
+
+   od_printf("OpenDoors has been initialized.\n\r");
+   for(;;)
+   {
+      od_printf("\n\rPOST-INITIALIZATION DIAGNOSTIC INFORMATION:\n\r");
+      od_printf("    Running in REMOTE mode : %s\n\r",
+         BoolAsStr(od_control.baud));
+      od_printf("   Port <-> Modem BPS Rate : %lu\n\r", od_control.baud);
+      od_printf("        Serial Port Number : %d (COM%d:)\n\r", od_control.port,
+         od_control.port + 1);
+      od_printf("         Serial I/O Method : ");
+      switch(od_control.od_com_method)
+      {
+         case COM_FOSSIL:
+            od_printf("FOSSIL Driver\n\r");
+            break;
+         case COM_INTERNAL:
+            od_printf("OpenDoors Internal I/O Module\n\r");
+            break;
+         case COM_SOCKET:
+            od_printf("TCP Socket/Telnet\n\r");
+            break;
+         default:
+            od_printf("Unknown\n\r");
+            break;
+      }
+      od_printf("            Drop File Type : ");
+      switch(od_control.od_info_type)
+      {
+         case DORINFO1:
+            od_printf("DORINFO?.DEF\n\r");
+            break;
+         case EXITINFO:
+            od_printf("Basic EXITINFO.BBS & DORINFO1.DEF\n\r");
+            break;
+         case RA1EXITINFO:
+            od_printf("RA 1.x EXITINFO.BBS & DORINFO1.DEF\n\r");
+            break;
+         case CHAINTXT:
+            od_printf("CHAIN.TXT\n\r");
+            break;
+         case SFDOORSDAT:
+            od_printf("SFDOORS.DAT\n\r");
+            break;
+         case CALLINFO:
+            od_printf("CALLINFO.BBS\n\r");
+            break;
+         case DOORSYS_GAP:
+            od_printf("GAP style DOOR.SYS\n\r");
+            break;
+         case DOORSYS_DRWY:
+            od_printf("DoorWay DOOR.SYS\n\r");
+            break;
+         case QBBS275EXITINFO:
+            od_printf("QuickBBS 2.75+ EXITINFO.BBS\n\r");
+            break;
+         case CUSTOM:
+            od_printf("User-Defined Custom Format\n\r");
+            break;
+         case DOORSYS_WILDCAT:
+            od_printf("WildCat! DOOR.SYS\n\r");
+            break;
+         case RA2EXITINFO:
+            od_printf("RA 2.x+ EXITINFO.BBS & DORINFO1.DEF\n\r");
+            break;
+         case NO_DOOR_FILE:
+            od_printf("No Drop File in Use\n\r");
+            break;
+         case DOOR32SYS:
+            od_printf("Door32.sys\n\r");
+            break;
+         default:
+            od_printf("Unknown Type\n\r");
+            break;
+      }
+      od_printf("       ANSI Mode Available : %s\n\r",
+         BoolAsStr(od_control.user_ansi));
+      od_printf("     AVATAR Mode Available : %s\n\r",
+         BoolAsStr(od_control.user_avatar));
+      od_printf("    RIP Graphics Available : %s\n\r",
+         BoolAsStr(od_control.user_rip));
+      od_printf("         User's Time Limit : %d\n\r", od_control.user_timelimit);
+      od_printf("          User's Full Name : %s\n\r", od_control.user_name);
+
+      od_printf("\n\rChoose Option: [E]xit, [T]yping Test,");
+      if(od_control.od_com_method == COM_INTERNAL)
+      {
+         od_printf(" [I]nternal I/O Diags,");
+      }
+      od_printf("\n\r");
+      od_printf("               [A]utodetect ANSI/RIP, [R]e-Display, [D]isplay Tests\n\r");
+		n=od_get_answer("eitard");
+      switch(n)
+      {
+         case 'e':
+            od_clr_scr();
+            od_printf("\n\rExit - Are You Sure (Y/N)? ");
+            if(od_get_answer("yn") == 'y')
+            {
+               return(0);
+            }
+            break;
+
+         case 'i':
+            od_clr_scr();
+            od_printf("INTERNAL SERIAL I/O DIAGNOSTIC INFORMATION:\n\r");
+            od_printf("  Serial Port Base Address : %x\n\r",
+               od_control.od_com_address);
+            od_printf("           IRQ Line Number : %d\n\r",
+               od_control.od_com_irq);
+            od_printf("       Receive Buffer Size : %d\n\r",
+               od_control.od_com_rx_buf);
+            od_printf("      Transmit Buffer Size : %d\n\r",
+               od_control.od_com_tx_buf);
+            od_printf(" Use FIFO Buffer, if avail : %s\n\r",
+               BoolAsStr(!od_control.od_com_no_fifo));
+            od_printf("         FIFO Trigger Size : %d\n\r",
+               od_control.od_com_fifo_trigger);
+
+            od_printf("\n\rPress [ENTER] to return.\n\r");
+            od_get_answer("\n\r");
+            break;
+
+         case 't':
+            od_clr_scr();
+            od_printf("\n\rTyping Test - Type any text below:\n\r");
+            od_printf("[------------------------------------------------------"
+                      "-----------------------]\n\r");
+            od_input_str(sz, 79, 0, 255);
+            od_printf("\n\rEntered Text:\n\r%s\n\r", sz);
+            od_printf("\n\rPress [ENTER] to return.\n\r");
+            od_get_answer("\n\r");
+            break;
+
+         case 'a':
+            od_clr_scr();
+            od_printf("\n\rAutodetecting ANSI/RIP mode ...\n\r");
+            od_printf("(Detected modes will be turned on.)\n\r");
+            od_autodetect(0);
+            od_printf("\n\rDone, press [ENTER] to return.\n\r");
+            od_get_answer("\n\r");
+            break;
+
+         case 'd':
+            od_clr_scr();
+            od_printf("CLEAR SCREEN TEST\n\r");
+            od_printf("About to test clear screen. The screen should\n\r");
+            od_printf("be cleared before the next test if screen\n\r");
+            od_printf("clearing is enabled.\n\r");
+            od_printf("\n\rPress [ENTER] to perform test.\n\r");
+            od_get_answer("\n\r");
+            od_clr_scr();
+
+            od_printf("CARRIAGE RETURN TEST:\n\r");
+            od_printf("This should not be visible\r");
+            od_printf("This should cover it up...\n\r\n\r");
+            od_printf("The text \"This should not be visible\" will\n\r");
+            od_printf("appear above if this test failed.\n\r");
+            od_printf("\n\rPress [ENTER] to perform next test.\n\r");
+            od_get_answer("\n\r");
+
+            od_clr_scr();
+            od_printf("COLOR TEST:\n\r\n\r");
+            for(n = 0; n < 256; ++n)
+            {
+               od_set_attrib(n);
+               od_printf("%x", n % 16);
+               if(n % 32 == 31)
+               {
+                  od_set_attrib(0x07);
+                  od_printf("\n\r");
+               }
+            }
+            od_printf("\n\rIf ANSI or AVATAR modes are available, the\n\r");
+            od_printf("above test pattern should print in color.\n\r");
+            od_printf("\n\rPress [ENTER] to perform next test.\n\r");
+            od_get_answer("\n\r");
+
+            od_clr_scr();
+            od_printf("CURSOR POSITIONING TEST:\n\r");
+            for(n = 15; n > 2; --n)
+            {
+               od_set_cursor(n, n);
+               od_printf("\\");
+            }
+            for(n = 15; n > 2; --n)
+            {
+               od_set_cursor(n, 17 - n);
+               od_printf("/");
+            }
+
+            od_set_cursor(17, 1);
+            od_printf("If ANSI or AVATAR modes are available, a large X\n\r");
+            od_printf("should appear on lines 3 to 15.\n\r");
+            od_printf("\n\rPress [ENTER] to return.\n\r");
+            od_get_answer("\n\r");
+            break;
+      }
+
+      od_clr_scr();
+   }
+
+   /* Return with success. */
+   return(0);
+}
+
+
+char *BoolAsStr(int bValue)
+{
+   return(bValue ? "Yes (TRUE)" : "No (FALSE)");
+}
+
+
+void ParseStandardCommandLine(int nArgCount, char *papszArguments[])
+{
+   char *pszCurrentArg;
+   int nCurrentArg;
+
+   for(nCurrentArg = 1; nCurrentArg < nArgCount; ++nCurrentArg)
+   {
+      pszCurrentArg = papszArguments[nCurrentArg];
+
+      switch(GetCommandLineParameter(pszCurrentArg))
+      {
+	 case kParamLocal:
+	    od_control.od_force_local = TRUE;
+	    break;
+
+	 case kParamBPS:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.baud = atol(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamPort:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.port = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamHelp:
+	    printf("AVALIABLE COMMAND LINE PARAMETERS:\n");
+            printf(" -L or -LOCAL     - Causes door to operate in local mode, without requiring a\n");
+            printf("                    door information (drop) file.\n");
+            printf(" -DROPFILE x      - Door information file directory or directory+filename.\n");
+            printf(" -N x or -NODE x  - Sets the node number to use.\n");
+            printf(" -B x or -BPS x   - Sets the serial port <---> modem bps (baud) rate to use.\n");
+            printf(" -P x or -PORT x  - Sets the serial port to use, were 0=COM1, 1=COM2, etc.\n");
+            printf(" -ADDRESS x       - Sets serial port address in decimal NOT hexidecimal\n");
+            printf("                    (only has effect if FOSSIL driver is not being used).\n");
+            printf(" -IRQ x           - Sets the serial port IRQ line (only has effect if FOSSIL\n");
+            printf("                    driver is not being used).\n");
+            printf(" -NOFOSSIL        - Disables use of FOSSIL driver, even if available.\n");
+            printf(" -NOFIFO          - Disables use of 16550 FIFO buffers (only if FOSSIL driver\n");
+            printf("                    is not being used).\n");
+            printf(" -PERSONALITY x   - Sets the sysop status line / function key personality to\n");
+            printf("                    use - one of Standard, PCBoard, RemoteAccess or Wildcat.\n");
+            printf(" -MAXTIME x       - Sets the maximum number of minutes that any user will be\n");
+            printf("                    permitted to access the door.\n");
+            printf(" -USERNAME x      - Name of user who is currently online.\n");
+            printf(" -TIMELEFT x      - User's time remaining online.\n");
+            printf(" -SECURITY x      - User's security level.\n");
+            printf(" -LOCATION x      - Location from which user is calling.\n");
+            printf(" -?, -H or -HELP  - Displays command-line help and exits.\n");
+	    exit(1);
+	    break;
+
+	 case kParamNode:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_node = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamPersonality:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    if(stricmp(papszArguments[nCurrentArg], "Standard") == 0)
+	    {
+	       od_control.od_default_personality = PER_OPENDOORS;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "PCBoard") == 0)
+	    {
+	       od_control.od_default_personality = PER_PCBOARD;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "RemoteAccess") == 0)
+	    {
+	       od_control.od_default_personality = PER_RA;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "Wildcat") == 0)
+	    {
+	       od_control.od_default_personality = PER_WILDCAT;
+	    }
+	    else
+	    {
+	       printf("Unknown personality: %s\n", papszArguments[nCurrentArg]);
+	       exit(1);
+	    }
+	    break;
+
+	 case kParamMaxTime:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_maxtime = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamAddress:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_com_address = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamIRQ:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_com_irq = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamNoFOSSIL:
+	    od_control.od_no_fossil = TRUE;
+	    break;
+
+	 case kParamNoFIFO:
+	    od_control.od_com_no_fifo = TRUE;
+	    break;
+
+	 case kParamDropFile:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    strncpy(od_control.info_path, papszArguments[nCurrentArg],
+	       sizeof(od_control.info_path) - 1);
+	    od_control.info_path[sizeof(od_control.info_path) - 1] = '\0';
+	    break;
+
+	 case kParamUserName:
+	    GetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+	       od_control.user_name, sizeof(od_control.user_name));
+	    break;
+
+	 case kParamTimeLeft:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.user_timelimit = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamSecurity:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.user_security = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamLocation:
+	    GetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+	       od_control.user_location, sizeof(od_control.user_location));
+	    break;
+
+	 default:
+	    printf("Unrecognized command line option: %s\n", pszCurrentArg);
+	    exit(1);
+	    break;
+      }
+   }
+}
+
+
+static void AdvanceToNextArg(int *pnCurrentArg, int nArgCount, char *pszOption)
+{
+   if(++*pnCurrentArg >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n", pszOption);
+      exit(1);
+   }
+}
+
+
+static void GetNextArgName(int *pnCurrentArg, int nArgCount,
+			   char *papszArguments[], char *pszString,
+			   int nStringSize)
+{
+   int bFirst = TRUE;
+
+   if((*pnCurrentArg) + 1 >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n",
+         papszArguments[(*pnCurrentArg) - 1]);
+      exit(1);
+   }
+
+   pszString[0] = '\0';
+
+   while(++*pnCurrentArg < nArgCount)
+   {
+      if(GetCommandLineParameter(papszArguments[*pnCurrentArg])
+	 != kParamUnknown)
+      {
+         --*pnCurrentArg;
+	 break;
+      }
+
+      if(strlen(pszString) >= nStringSize - 1)
+      {
+	 break;
+      }
+
+      if(!bFirst)
+      {
+	 strcat(pszString, " ");
+      }
+
+      strncat(pszString, papszArguments[*pnCurrentArg],
+         strlen(pszString) - nStringSize - 1);
+      pszString[nStringSize - 1] = '\0';
+
+      bFirst = FALSE;
+   }
+
+}
+
+
+static tCommandLineParameter GetCommandLineParameter(char *pszArgument)
+{
+   if(*pszArgument == '-' || *pszArgument == '/')
+   {
+      ++pszArgument;
+   }
+
+   if(stricmp(pszArgument, "L") == 0
+      || stricmp(pszArgument, "LOCAL") == 0)
+   {
+      return(kParamLocal);
+   }
+   else if(stricmp(pszArgument, "B") == 0
+      || stricmp(pszArgument, "BPS") == 0
+      || stricmp(pszArgument, "BAUD") == 0)
+   {
+      return(kParamBPS);
+   }
+   else if(stricmp(pszArgument, "P") == 0
+      || stricmp(pszArgument, "PORT") == 0)
+   {
+      return(kParamPort);
+   }
+   else if(stricmp(pszArgument, "N") == 0
+      || stricmp(pszArgument, "NODE") == 0)
+   {
+      return(kParamNode);
+   }
+   else if(stricmp(pszArgument, "?") == 0
+      || stricmp(pszArgument, "H") == 0
+      || stricmp(pszArgument, "HELP") == 0)
+   {
+      return(kParamHelp);
+   }
+   else if(stricmp(pszArgument, "PERSONALITY") == 0)
+   {
+      return(kParamPersonality);
+   }
+   else if(stricmp(pszArgument, "MAXTIME") == 0)
+   {
+      return(kParamMaxTime);
+   }
+   else if(stricmp(pszArgument, "ADDRESS") == 0)
+   {
+      return(kParamAddress);
+   }
+   else if(stricmp(pszArgument, "IRQ") == 0)
+   {
+      return(kParamIRQ);
+   }
+   else if(stricmp(pszArgument, "NOFOSSIL") == 0)
+   {
+      return(kParamNoFOSSIL);
+   }
+   else if(stricmp(pszArgument, "NOFIFO") == 0)
+   {
+      return(kParamNoFIFO);
+   }
+   else if(stricmp(pszArgument, "DROPFILE") == 0)
+   {
+      return(kParamDropFile);
+   }
+   else if(stricmp(pszArgument, "USERNAME") == 0)
+   {
+      return(kParamUserName);
+   }
+   else if(stricmp(pszArgument, "TIMELEFT") == 0)
+   {
+      return(kParamTimeLeft);
+   }
+   else if(stricmp(pszArgument, "SECURITY") == 0)
+   {
+      return(kParamSecurity);
+   }
+   else if(stricmp(pszArgument, "LOCATION") == 0)
+   {
+      return(kParamLocation);
+   }
+   else
+   {
+      return(kParamUnknown);
+   }
+}
+
+
+void NoDoorFileHandler(void)
+{
+   /* Alter OpenDoors behaviour, so that we proceed with defaults if  */
+   /* no door information file is available, rather than exiting with */
+   /* an error. Set od_no_file_func to point to this function.        */
+   if(strlen(od_control.user_name) == 0)
+   {
+      strcpy(od_control.user_name, "Unknown User");
+   }
+   if(strlen(od_control.user_location) == 0)
+   {
+      strcpy(od_control.user_location, "Unknown Location");
+   }
+   if(od_control.user_timelimit == 0)
+   {
+      od_control.user_timelimit = 30;
+   }
+
+   od_control.od_info_type = CUSTOM;
+}

+ 42 - 0
odoors/ex_hello.c

@@ -0,0 +1,42 @@
+/* EX_HELLO.C - Example of a trivial OpenDoors program. Demonstrates         */
+/*              just how simple a fully functional door program can be. Also */
+/*              shows all the basic elements required by any program using   */
+/*              OpenDoors. See manual for instructions on how to compile     */
+/*              this program.                                                */
+/*                                                                           */
+/*              This program shows how to do the following:                  */
+/*                                                                           */
+/*                 - #include the OpenDoors header file, opendoor.h.         */
+/*                 - Create a mainline function that can be compiled under   */
+/*                   both DOS and Windows versions of OpenDoors.             */
+/*                 - How to display text on multiple lines.                  */
+/*                 - How to wait for a single key to be pressed.             */
+/*                 - How to properly exit a program that uses OpenDoors.     */
+
+
+/* The opendoor.h file must be included by any program using OpenDoors. */
+#include "OpenDoor.h"
+
+
+/* The main() or WinMain() function: program execution begins here. */
+#ifdef ODPLAT_WIN32
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+   LPSTR lpszCmdLine, int nCmdShow)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+
+   /* Display a message. */
+   od_printf("Hello world! This is a very simple OpenDoors program.\n\r");
+   od_printf("Press any key to return to the BBS!\n\r");
+
+
+   /* Wait for user to press a key. */
+   od_get_key(TRUE);
+
+
+   /* Exit door program, returning to the BBS. */
+   od_exit(0, FALSE);
+   return(0);
+}

+ 153 - 0
odoors/ex_music.c

@@ -0,0 +1,153 @@
+/* EX_MUSIC.C - Example program plays "Happy Birthday" to the remote user,   */
+/*              if possible. See the manual for instructions on how to       */
+/*              compile this program.                                        */
+/*                                                                           */
+/*              This program shows how to do the following:                  */
+/*                                                                           */
+/*                 - Demonstrates how to play sounds effects or music on a   */
+/*                   remote terminal program that supports the so-called     */
+/*                   "ANSI music" standard.                                  */
+/*                 - Shows how to send text to the remote system without it  */
+/*                   being displayed on the local screen.                    */
+
+
+/* The opendoor.h file must be included by any program using OpenDoors. */
+#include "OpenDoor.h"
+
+#include <string.h>
+
+/* Functions for playing "ANSI music" and testing "ANSI music" capabilities. */
+void PlayANSISound(char *pszSounds);
+char TestSound(void);
+
+/* Variable indicates whether or not sound is on */
+char bSoundEnabled = TRUE;
+
+
+/* The main() or WinMain() function: program execution begins here. */
+#ifdef ODPLAT_WIN32
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+   LPSTR lpszCmdLine, int nCmdShow)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+   /* Handle standard command-line options and perform other needed setup. */
+#ifdef ODPLAT_WIN32
+   od_control.od_cmd_show = nCmdShow;
+   od_parse_cmd_line(lpszCmdLine);
+#else
+   od_parse_cmd_line(argc, argv);
+#endif
+
+   /* Display introductory message. */
+   od_printf("This is a simple door program that will play the song Happy Birthday\n\r");
+   od_printf("tune on the remote system, if the user's terminal program supports ANSI\n\r");
+   od_printf("music. Music is not played on the local speaker, as BBS system operators\n\r");
+   od_printf("do not wish to have the BBS computer making sounds at any time of the day\n\r");
+   od_printf("or night. However, the program can easily be modified to also echo sound to\n\r");
+   od_printf("the local speaker.\n\r\n\r");
+
+
+   /* Test whether user's terminal supports "ANSI music". */
+   TestSound();
+
+
+   /* Send birthday greetings to the remote user. */
+
+   /* Clear the screen. */
+   od_clr_scr();
+
+   /* Display a message. */
+   od_printf("\n\rHappy Birthday!\n\r");
+
+   /* If "ANSI music" is available, play "Happy Birthday". */
+   PlayANSISound("MBT120L4MFMNO4C8C8DCFE2C8C8DCGF2C8C8O5CO4AFED2T90B-8B-8AFGF2");
+
+   /* Reset sound after finished playing. */
+   PlayANSISound("00m");
+
+
+   /* Wait for user to press a key before returning to the BBS. */
+   od_printf("\n\rPress any key to return to BBS...\n\r");
+   od_get_key(TRUE);
+   od_exit(0, FALSE);
+   return(0);
+}
+
+
+/* Function to test whether the user's terminal program supports ANSI music. */
+/* You can either do this every time the user uses your program, or only the */
+/* first time they use the program, saving the result in a data file.        */
+char TestSound(void)
+{
+   /* Variable to store user's response to question. */
+   char chResponse;
+
+   /* Display description of test to user. */
+   od_printf("We need to know whether or not your terminal program supports ANSI music.\n\r");
+   od_printf("In order to test this, we will send a short ANSI music sequence. We will then\n\r");
+   od_printf("ask whether or not you heard any sound.\n\r");
+   od_printf("Press any key to begin this test... ");
+
+   /* Wait for user to press a key to begin. */
+   od_get_key(TRUE);
+   od_printf("\n\r\n\r");
+
+   /* Temporarily enable sound. */
+   bSoundEnabled = TRUE;
+
+   /* Send sound test sequence. */
+   PlayANSISound("MBT120L4MFMNO4C8C8DC");
+
+   /* Reset sound after finished test. */
+   PlayANSISound("00m");
+
+   /* Clear screen and ask whether user heard the sound. */
+   od_clr_scr();
+   od_printf("Did you just hear sound from your speaker? (Y/n)");
+   chResponse = od_get_answer("YN");
+
+   /* Set ANSI music on/off according to user's response. */
+   bSoundEnabled = (chResponse == 'Y');
+   
+   return(bSoundEnabled);
+}
+
+
+/* Function to play "ANSI" music or sound effects. The play_sound() function
+ * can be called with a string of 0 to 250 characters. The caracters of the
+ * string define what sounds should be played on the remote speaker, as
+ * follows:
+ *
+ *      A - G       Musical Notes
+ *      # or +      Following A-G note means sharp
+ *      -           Following A-G note means flat
+ *      <           Move down one octave
+ *      >           Move up one octave
+ *      .           Period acts as dotted note (extend note duration by 3/2)
+ *      MF          Music Foreground (pause until finished playing music)
+ *      MB          Music Background (continue while music plays)
+ *      MN          Music note duration Normal (7/8 of interval between notes)
+ *      MS          Music note duration Staccato
+ *      ML          Music note duration Legato
+ *      Ln          Length of note (n=1-64, 1=whole note, 4=quarter note, etc)
+ *      Pn          Pause length (same n values as Ln above)
+ *      Tn          Tempo, n=notes/minute (n=32-255, default n=120)
+ *      On          Octave number (n=0-6, default n=4)
+ */
+
+void PlayANSISound(char *pszSounds)
+{
+   /* Beginning of sound sequence. */
+   char szStartSound[255] = {27, '[', '\0'};
+
+   /* Abort if sound is not enabled. */
+   if(!bSoundEnabled) return;
+
+   /* Send sequence to start playing sound to remote system only. */
+   od_disp(szStartSound, strlen(szStartSound), FALSE);
+
+   /* Send the sounds codes to the remote system only. */
+   od_disp(pszSounds, strlen(pszSounds), FALSE);
+}

+ 602 - 0
odoors/ex_ski.c

@@ -0,0 +1,602 @@
+/* EX_SKI.C - EX_SKI is a simple but addictive door game that is written     */
+/*            using OpenDoors. In this action game, the player must control  */
+/*            a skier through a downhill slalom course. The user may turn    */
+/*            the skier left or right, and the game ends as soon as the      */
+/*            player skis outside the marked course. The game begins at      */
+/*            an easy level, but quickly becomes more and more difficult     */
+/*            as the course to be navigated becomes more and more narrow.    */
+/*            The game maintains a list of players with high scores, and     */
+/*            this list may be viewed from the main menu.                    */
+/*                                                                           */
+/*            This program shows how to do the following:                    */
+/*                                                                           */
+/*               - Maintain a high-score file in a game, in a multi-node     */
+/*                 compatible manner.                                        */
+/*               - How to use your own terminal control sequences.           */
+/*               - How to perform reasonably percise timing under both DOS   */
+/*                 and Windows.                                              */
+
+
+/* Header file for the OpenDoors API */
+#include "OpenDoor.h"
+
+/* Other required C header files */
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "genwrap.h"
+
+
+/* Hard-coded configurable constants - change these values to alter game */
+#define HIGH_SCORES           15       /* Number of high scores in list */
+#define INITIAL_COURSE_WIDTH  30       /* Initial width of ski course */
+#define MINIMUM_COURSE_WIDTH  4        /* Minimum width of course */
+#define DECREASE_WIDTH_AFTER  100      /* # of ticks before course narrows */
+#define CHANGE_DIRECTION      10       /* % of ticks course changes direction */
+#define MAX_NAME_SIZE         35       /* Maximum characters in player name */
+#define WAIT_FOR_FILE         10       /* Time to wait for access to file */
+#define SCORE_FILENAME   "skigame.dat" /* Name of high score file */
+
+
+/* High-score file format structure */
+typedef struct
+{
+   char szPlayerName[MAX_NAME_SIZE + 1];
+   DWORD lnHighScore;
+   time_t lnPlayDate;
+} tHighScoreRecord;
+
+typedef struct
+{
+   tHighScoreRecord aRecord[HIGH_SCORES];
+} tHighScoreFile;
+
+
+/* Prototypes for functions defined and used in this file */
+FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents);
+void CloseHighScores(FILE *pfHighScoreFile);
+void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents);
+FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait);
+int FileExists(char *pszFileName);
+void ShowHighScores(void);
+void PlayGame(void);
+void SpaceRight(int nColumns);
+void MoveLeft(int nColumns);
+int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord);
+
+
+/* The main() or WinMain() function: program execution begins here. */
+#ifdef ODPLAT_WIN32
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+   LPSTR lpszCmdLine, int nCmdShow)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+   char chMenuChoice;
+
+#ifdef ODPLAT_WIN32
+   /* In Windows, pass in nCmdShow value to OpenDoors. */
+   od_control.od_cmd_show = nCmdShow;
+#endif
+   
+   /* Set program's name for use by OpenDoors. */
+   strcpy(od_control.od_prog_name, "Grand Slalom");
+   strcpy(od_control.od_prog_version, "Version 6.00");
+   strcpy(od_control.od_prog_copyright, "Copyright 1991-1996 by Brian Pirie");
+
+   /* Call the standard command-line parsing function. You will probably     */
+   /* want to do this in most programs that you write using OpenDoors, as it */
+   /* automatically provides support for many standard command-line options  */
+   /* that will make the use and setup of your program easer. For details,   */
+   /* run the vote program with the /help command line option.               */
+#ifdef ODPLAT_WIN32
+   od_parse_cmd_line(lpszCmdLine);
+#else
+   od_parse_cmd_line(argc, argv);
+#endif
+
+   /* Loop until the user chooses to exit the door */
+   do
+   {
+      /* Clear the screen */
+      od_clr_scr();
+
+      /* Display program title */
+      od_printf("`bright white`                Ûßß ÛßÜ ÛßÛ ÛÜ Û ÛßÜ   Ûßß Û   ÛßÛ Û   ÛßÛ ÛßÛßÛ\n\r");
+      od_printf("`bright red`ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ`bright white`ÛßÛ`bright red`Ä`bright white`ÛßÜ`bright red`Ä`bright white`ÛßÛ`bright red`Ä`bright white`Û`bright red`Ä`bright white`ßÛ`bright red`Ä`bright white`Û");
+      od_printf("`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`ßßÛ`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`ÛßÛ`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`Û");
+      od_printf("`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r");
+      od_printf("`bright white`                ßßß ß ß ß ß ß  ß ßß    ßßß ßßß ß ß ßßß ßßß ß ß ß\n\r\n\r");
+
+      /* Display instructions */
+      od_printf("`dark green`Prepare yourself for the challenge of Grand Slalom downhill skiing!\n\r\n\r");
+      od_printf("When `flashing dark green`playing`dark green` the game, press:\n\r");
+      od_printf("`dark green`          [`bright green`Q`dark green`] key to ski left\n\r");
+      od_printf("          [`bright green`W`dark green`] key to ski right\n\r\n\r");
+      od_printf("All that you have to do is ski within the slalom course.\n\r");
+      od_printf("It may sound easy - but be warned - it gets harder as you go!\n\r");
+      od_printf("(Each time you hear the beep, the course becomes a bit narrower.)\n\r\n\r");
+
+      /* Get menu choice from user. */
+      od_printf("`bright white`Now, press [ENTER] to begin game, [H] to view High Scores, [E] to Exit: ");
+      chMenuChoice = od_get_answer("HE\n\r");
+
+      /* Perform appropriate action based on user's choice */
+      switch(chMenuChoice)
+      {
+         case '\n':
+         case '\r':
+            /* If user chooses to play the game */
+            PlayGame();
+            break;
+
+         case 'H':
+            /* If user chose to view high scores */
+            ShowHighScores();
+            break;
+
+         case 'E':
+            /* If user chose to return to BBS */
+            od_printf("\n\rGoodbye from SKIGAME!\n\r");
+            break;
+      }
+   } while(chMenuChoice != 'E');
+
+   /* Exit door at errorlevel 10, and do not hang up */
+   od_exit(10, FALSE);
+   return(1);
+}
+
+
+/* OpenAndReadHighScores() - Opens high score file and reads contents. If */
+/*                           file does not exist, it is created. File is  */
+/*                           locked to serialize access by other nodes on */
+/*                           this system.                                 */
+FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents)
+{
+   FILE *pfFile;
+   int iHighScore;
+
+   /* If high score file does not exist */
+   if(!FileExists(SCORE_FILENAME))
+   {
+      /* Open and create it */
+      pfFile = OpenExclusiveFile(SCORE_FILENAME, "wb", WAIT_FOR_FILE);
+
+      /* If open was successful */
+      if(pfFile != NULL)
+      {
+         /* Initialize new high score list */
+         for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
+         {
+            pFileContents->aRecord[iHighScore].lnHighScore = 0L;
+         }
+
+         /* Write high score list to the file */
+         WriteHighScores(pfFile, pFileContents);
+      }
+   }
+
+   /* If high score file does exit */
+   else
+   {
+      /* Open the existing file */
+      pfFile = OpenExclusiveFile(SCORE_FILENAME, "r+b",
+                                WAIT_FOR_FILE);
+
+      /* Read the contents of the file */
+      if(fread(pFileContents, sizeof(tHighScoreFile), 1, pfFile) != 1)
+      {
+         /* If unable to read file, then return with an error */
+         fclose(pfFile);
+         pfFile = NULL;
+      }
+   }
+
+   /* Return pointer to high score file, if avilable */
+   return(pfFile);
+}
+
+
+/* FileExists() - Returns TRUE if file exists, otherwise returns FALSE */
+int FileExists(char *pszFileName)
+{
+   /* Attempt to open the specified file for reading. */
+   FILE *pfFile = OpenExclusiveFile(pszFileName, "rb", WAIT_FOR_FILE);
+
+   if(pfFile != NULL)
+   {
+      /* If we are able to open the file, then close it and return */
+      /* indicating that it exists.                                */
+      fclose(pfFile);
+      return(TRUE);
+   }
+   else
+   {
+      /* If we are unable to open the file, we proceed as if the file        */
+      /* doesn't exist (note that this may not always be a valid assumption) */
+      return(FALSE);
+   }
+}
+
+
+/* OpenExclusiveFile() - Opens a file for exclusive access, waiting if the */
+/*                       file is not currently available.                  */
+FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait)
+{
+   FILE *pfFile;
+   time_t StartTime = time(NULL);
+
+   for(;;)
+   {
+      /* Attempt to open file */
+      pfFile = fopen(pszFileName, pszAccess);
+
+      /* If file was opened successfuly, then exit */
+      if(pfFile != NULL) break;
+
+      /* If open failed, but not due to access failure, then exit */
+      if(errno != EACCES) break;
+
+      /* If maximum time has elapsed, then exit */
+      if(StartTime + Wait < time(NULL)) break;
+
+      /* Give the OpenDoors kernel a chance to execute before trying again */
+      od_kernel();
+   }
+
+   /* Return pointer to file, if opened */
+   return(pfFile);
+}
+
+
+/* CloseHighScores() - Closes the high score file, allowing other nodes on */
+/*                     system to access it.                                */
+void CloseHighScores(FILE *pfHighScoreFile)
+{
+   if(pfHighScoreFile != NULL)
+   {
+      fclose(pfHighScoreFile);
+   }
+}
+
+
+/* WriteHighScores() - Writes the information from pFileContents to the */
+/*                     high score file.                                 */
+void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents)
+{
+   if(pfHighScoreFile != NULL)
+   {
+      fseek(pfHighScoreFile, 0L, SEEK_SET);
+      fwrite(pFileContents, sizeof(tHighScoreFile), 1, pfHighScoreFile);
+   }
+}
+
+
+/* ShowHighScores() - Called From DoDoor() to display list of high scores */
+void ShowHighScores(void)
+{
+   FILE *pfFile;
+   tHighScoreFile HighScores;
+   int iHighScore;
+   struct tm *pTimeBlock;
+   char szTimeString[34];
+
+   /* Clear the screen */
+   od_clr_scr();
+
+   /* Attempt to read high scores from file */
+   pfFile = OpenAndReadHighScores(&HighScores);
+   CloseHighScores(pfFile);
+
+   if(pfFile == NULL)
+   {
+      /* If unable to open high score file, display an error message */
+      od_printf("`bright red`Unable to access high score file!\n\r");
+   }
+   else
+   {
+      /* Display header line */
+      od_printf("`bright green`Player                            Score     "
+                "Record Date`dark green`\n\r");
+      od_printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r");
+
+      /* Display high scores */
+      for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
+      {
+         /* Exit loop when we have reached the end of the high scores */
+         if(HighScores.aRecord[iHighScore].lnHighScore == 0L) break;
+
+         /* Get local time when player set the high score */
+         pTimeBlock = localtime(&HighScores.aRecord[iHighScore].lnPlayDate);
+         strftime(szTimeString, sizeof(szTimeString),
+            "%B %d, %Y at %I:%M%p", pTimeBlock);
+
+         /* Display next high score */
+         od_printf("%-32.32s  %-8ld  %s\n\r",
+                   HighScores.aRecord[iHighScore].szPlayerName,
+                   HighScores.aRecord[iHighScore].lnHighScore,
+                   szTimeString);
+      }
+   }
+
+   /* Display footer line */
+   od_printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r\n\r");
+
+   /* Wait for user to press a key */
+   od_printf("`bright white`Press [ENTER]/[RETURN] to continue: ");
+   od_get_answer("\n\r");
+}
+
+
+/* PlayGame() - Called from DoDoor() when user chooses to play a game. */
+void PlayGame(void)
+{
+   int nLeftEdge = 1;
+   int nRightEdge = nLeftEdge + 1 + INITIAL_COURSE_WIDTH;
+   int nPlayerPos = nLeftEdge + 1 + (INITIAL_COURSE_WIDTH / 2);
+   long lnScore = 0;
+   int nDistanceSinceShrink = 0;
+   int bMovingRight = TRUE;
+   char cKeyPress;
+   tHighScoreRecord ScoreRecord;
+   FILE *pfFile;
+   tHighScoreFile HighScores;
+   int nBackup=0;
+   clock_t StartClock;
+
+   /* Clear the Screen */
+   od_set_color(L_WHITE, B_BLACK);
+   od_clr_scr();
+
+   /* Set current display colour to white */
+   od_set_attrib(L_WHITE);
+
+   /* Re-seed random number generator */
+   srand((unsigned int)time(NULL));
+
+   /* Loop until game is over */
+   for(;;)
+   {
+      StartClock = msclock();
+
+      /* Display current line */
+      if(od_control.user_ansi || od_control.user_avatar)
+      {
+         SpaceRight(nLeftEdge - 1);
+         od_set_color(L_WHITE, D_RED);
+         od_putch((char)223);
+         od_repeat((unsigned char)219, 
+            (unsigned char)(nPlayerPos - nLeftEdge - 1));
+         od_putch((char)254);
+         od_repeat((unsigned char)219,
+            (unsigned char)(nRightEdge - nPlayerPos - 1));
+         od_putch((char)223);
+         nBackup = nRightEdge - nPlayerPos + 1;
+      }
+      else
+      {
+         /* If neither ANSI nor AVATAR modes are active, then display */
+         /* course using plain-ASCII.                                 */
+         SpaceRight(nLeftEdge - 1);
+         od_putch((char)(bMovingRight ? '\\' : '/'));
+         SpaceRight(nPlayerPos - nLeftEdge - 1);
+         od_putch('o');
+         SpaceRight(nRightEdge - nPlayerPos - 1);
+         od_putch((char)(bMovingRight ? '\\' : '/'));
+      }
+
+      /* Loop for each key pressed by user */
+      while((cKeyPress = (char)od_get_key(FALSE)) != '\0')
+      {
+         if(cKeyPress == 'q' || cKeyPress == 'Q')
+         {
+            /* Move left */
+            --nPlayerPos;
+         }
+         else if(cKeyPress == 'w' || cKeyPress == 'W')
+         {
+            /* Move right */
+            ++nPlayerPos;
+         }
+      }
+
+      /* Check whether course should turn */
+      if((rand() % 100) < CHANGE_DIRECTION)
+      {
+         bMovingRight = !bMovingRight;
+      }
+      else
+      {
+         /* If no change in direction, then position moves */
+         /* Adjust course position appropriately */
+         if(bMovingRight)
+         {
+            ++nLeftEdge;
+            ++nRightEdge;
+         }
+         else
+         {
+            --nLeftEdge;
+            --nRightEdge;
+         }
+      }
+
+      /* Check whether course size should shink */
+      if(++nDistanceSinceShrink >= DECREASE_WIDTH_AFTER)
+      {
+         /* Reset distance */
+         nDistanceSinceShrink = 0;
+
+         /* Randomly choose a side to shrink */
+         if((rand() % 100) < 50)
+         {
+            ++nLeftEdge;
+         }
+         else
+         {
+            --nRightEdge;
+         }
+
+         /* Beep when we shrink the size. */
+         od_printf("\a");
+      }
+
+      /* Change course direction if it collides with edge of screen */
+      if(nLeftEdge < 1)
+      {
+         bMovingRight = TRUE;
+         ++nLeftEdge;
+         ++nRightEdge;
+      }
+      else if(nRightEdge > 79)
+      {
+         bMovingRight = FALSE;
+         --nLeftEdge;
+         --nRightEdge;
+      }
+
+      /* Check that player is still within the course */
+      if(nPlayerPos <= nLeftEdge || nPlayerPos >= nRightEdge)
+      {
+         /* Player has left course - game over! */
+         od_set_color(D_GREY, D_BLACK);
+         od_clr_scr();
+         od_printf("`bright red`       !!! Game Over !!!\n\r\n\r");
+         od_printf("`dark green`You have veered off the course!\n\r\n\r");
+         od_printf("Your Score is: %ld\n\r", lnScore);
+
+         /* Create a score record */
+         ScoreRecord.lnHighScore = lnScore;
+         strncpy(ScoreRecord.szPlayerName, od_control.user_name, MAX_NAME_SIZE);
+         ScoreRecord.szPlayerName[MAX_NAME_SIZE] = '\0';
+         ScoreRecord.lnPlayDate = time(NULL);
+
+         /* Attempt to read high scores from file */
+         pfFile = OpenAndReadHighScores(&HighScores);
+
+         if(pfFile == NULL)
+         {
+            /* If unable to open high score file, display an error message */
+            od_printf("`bright red`Unable to access high score file!\n\r");
+         }
+         else
+         {
+            /* Check whether user made it to high score list */
+            if(AddHighScore(&HighScores, &ScoreRecord))
+            {
+               od_printf("Congratulations! You have made it to the high score list!\n\r");
+               /* If so, write the new high score list */
+               WriteHighScores(pfFile, &HighScores);
+            }
+
+            /* Close and unlock file */
+            CloseHighScores(pfFile);
+         }
+
+         /* Wait for user to press enter */
+         od_printf("`bright white`\n\rPress [ENTER]/[RETURN] to return to menu: ");
+         od_get_answer("\n\r");
+
+         return;
+      }
+
+      /* Delay for about 1/10th of a second, to add a constant delay after */
+      /* each line is displayed that does not depend on the connect speed. */
+      while(msclock() < StartClock + (((clock_t)MSCLOCKS_PER_SEC) / 10))
+         od_sleep(0);
+
+      /* Increase score */
+      ++lnScore;
+
+      /* Replace skiier character with track character */
+      if(od_control.user_ansi)
+      {
+         MoveLeft(nBackup);
+         od_set_color(L_WHITE, D_GREY);
+         od_putch((char)178);
+         od_set_color(L_WHITE, B_BLACK);
+      }
+
+      /* Move to next line */
+      od_printf("\r\n");
+   }
+}
+
+
+/* SpaceRight() - Moves right the specified number of columns. In ANSI mode, */
+/*                uses the move cursor right control sequence. Otherwise,    */
+/*                uses od_repeat(), which is optimized for ASCII and AVATAR  */
+/*                modes.                                                     */
+void SpaceRight(int nColumns)
+{
+   char szSequence[6];
+
+   /* If we don't have a positive column count, then return immediately */
+   if(nColumns <= 0) return;
+
+   /* If operating in ANSI mode */
+   if(od_control.user_ansi)
+   {
+      /* Move cursor right using ESC[nC control sequence */
+      sprintf(szSequence, "\x1b[%02dC", nColumns);
+      od_disp_emu(szSequence, TRUE);
+   }
+
+   /* If not operating in ANSI mode */
+   else
+   {
+      od_repeat(' ', (unsigned char)nColumns);
+   }
+}
+
+
+/* MoveLeft() - Moves the cursor right the specified number of columns. */
+/*              Intended for use in ANSI mode only.                     */
+void MoveLeft(int nColumns)
+{
+   /* Move cursor left using ESC[nD control sequence */
+   char szSequence[6];
+   sprintf(szSequence, "\x1b[%02dD", nColumns);
+   od_disp_emu(szSequence, TRUE);
+}
+
+
+/* AddHighScore() - Adds a new score to the high score list, if it is high   */
+/*                  enough. Returns TRUE if score is added, FALSE otherwise. */
+int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord)
+{
+   int iHighScore;
+   int iExistingScore;
+
+   /* Loop through each existing high score */
+   for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
+   {
+      /* If new score is greater than or equal to this one, then its */
+      /* position has been found.                                    */
+      if(pHighScores->aRecord[iHighScore].lnHighScore <=
+         pScoreRecord->lnHighScore)
+      {
+         /* Move remaining scores down one in list */
+         for(iExistingScore = HIGH_SCORES - 1; iExistingScore >= iHighScore + 1;
+          --iExistingScore)
+         {
+            pHighScores->aRecord[iExistingScore] =
+               pHighScores->aRecord[iExistingScore - 1];
+         }
+
+         /* Add new score to list */
+         pHighScores->aRecord[iHighScore] = *pScoreRecord;
+
+         /* Return with success */
+         return(TRUE);
+      }
+   }
+
+   /* Score did not make it to list */
+   return(FALSE);
+}

+ 1294 - 0
odoors/ex_vote.c

@@ -0,0 +1,1294 @@
+/* EX_VOTE.C - This program demonstrates an online voting program that is    */
+/*             written using OpenDoors. The Vote program allows users to     */
+/*             create questions or surveys for other users to respond to.    */
+/*             Users are also able to view the results of voting on each     */
+/*             topic. The program supports up to 200 questions, and can be   */
+/*             configured to either allow or disallow viewing of results     */
+/*             prior to voting.                                              */
+/*                                                                           */
+/*             This program shows how to do the following:                   */
+/*                                                                           */
+/*                - How to display text using od_printf(), using imbedded    */
+/*                  strings to change the display color.                     */
+/*                - Add support for standard command-line options.           */
+/*                - Use the OpenDoors configuration file system, including   */
+/*                  adding your own configuration file options.              */
+/*                - Activate the OpenDoors log file system and write         */
+/*                  information to the log file.                             */
+/*                - Display a menu from an external ASCI/ANSI/Avatar/RIP     */
+/*                  file.                                                    */
+/*                - How to setup a user file and general data file, and how  */
+/*                  to access it in a multi-node compatible way (if          */
+/*                  MULTINODE_AWARE is defined).                             */
+/*                                                                           */
+/*             To recompile this program, follow the instructions in the     */
+/*             OpenDoors manual. For a DOS compiler, be sure to set your     */
+/*             compiler to use the large memory model, and add the           */
+/*             ODOORL.LIB file to your project/makefile.                     */
+
+/* Uncomment the following line for multi-node compatible file access. */
+/* #define MULTINODE_AWARE */
+
+/* Include standard C header files required by Vote. */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <errno.h>
+#include <ctype.h>
+
+/* Include the OpenDoors header file. This line must be done in any program */
+/* using OpenDoors.                                                         */
+#include "OpenDoor.h"
+
+#ifdef MULTINODE_AWARE
+#include <filewrap.h>
+#endif
+#include "genwrap.h"
+
+/* Manifest constants used by Vote */
+#define NO_QUESTION              -1
+#define NEW_ANSWER               -1
+
+#define QUESTIONS_VOTED_ON        0x0001
+#define QUESTIONS_NOT_VOTED_ON    0x0002
+
+#define MAX_QUESTIONS             200
+#define MAX_USERS                 30000
+#define MAX_ANSWERS               15
+#define QUESTION_STR_SIZE         71
+#define ANSWER_STR_SIZE           31
+
+#define USER_FILENAME             "vote.usr"
+#define QUESTION_FILENAME         "vote.qst"
+
+#define FILE_ACCESS_MAX_WAIT      20
+
+#define QUESTION_PAGE_SIZE        17
+
+
+/* Structure of records stored in the VOTE.USR file */
+typedef struct
+{
+   char szUserName[36];
+   BYTE bVotedOnQuestion[MAX_QUESTIONS];
+} tUserRecord;
+              
+tUserRecord CurrentUserRecord;
+int nCurrentUserNumber;
+
+
+/* Structure of records stored in the VOTE.QST file */
+typedef struct
+{
+   char szQuestion[72];
+   char aszAnswer[MAX_ANSWERS][32];
+   INT32 nTotalAnswers;
+   DWORD auVotesForAnswer[MAX_ANSWERS];
+   DWORD uTotalVotes;
+   DWORD bCanAddAnswers;
+   char szCreatorName[36];
+   time_t lCreationTime;
+} tQuestionRecord;
+
+
+/* Global variables. */
+int nViewResultsFrom = QUESTIONS_VOTED_ON;
+int nQuestionsVotedOn = 0;
+
+
+/* Prototypes for functions that form EX_VOTE */
+void CustomConfigFunction(char *pszKeyword, char *pszOptions);
+void BeforeExitFunction(void);
+void VoteOnQuestion(void);
+void ViewResults(void);
+int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord);
+void AddQuestion(void);
+int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation);
+void DisplayQuestionResult(tQuestionRecord *pQuestionRecord);
+int ReadOrAddCurrentUser(void);
+void WriteCurrentUser(void);
+FILE *ExclusiveFileOpen(char *pszFileName, char *pszMode, int *phHandle);
+void ExclusiveFileClose(FILE *pfFile, int hHandle);
+void WaitForEnter(void);
+
+
+/* main() or WinMain() function - Program execution begins here. */
+#ifdef ODPLAT_WIN32
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+   LPSTR lpszCmdLine, int nCmdShow)
+#else
+int main(int argc, char *argv[])
+#endif
+{
+   /* Variable to store user's choice from the menu */
+   char chMenuChoice = '\0';
+   char chYesOrNo;
+
+#ifdef ODPLAT_WIN32
+   /* In Windows, pass in nCmdShow value to OpenDoors. */
+   od_control.od_cmd_show = nCmdShow;
+
+   /* Ignore unused parameters. */
+   (void)hInstance;
+   (void)hPrevInstance;
+#endif
+   
+   /* Set program's name for use by OpenDoors. */
+   strcpy(od_control.od_prog_name, "Vote");
+   strcpy(od_control.od_prog_version, "Version 6.00");
+   strcpy(od_control.od_prog_copyright, "Copyright 1991-1996 by Brian Pirie");
+
+   /* Call the standard command-line parsing function. You will probably     */
+   /* want to do this in most programs that you write using OpenDoors, as it */
+   /* automatically provides support for many standard command-line options  */
+   /* that will make the use and setup of your program easer. For details,   */
+   /* run the vote program with the /help command line option.               */
+#ifdef ODPLAT_WIN32
+   od_parse_cmd_line(lpszCmdLine);
+#else
+   od_parse_cmd_line(argc, argv);
+#endif
+
+   /* Enable use of OpenDoors configuration file system. */
+   od_control.od_config_file = INCLUDE_CONFIG_FILE;
+
+   /* Set function to process custom configuration file lines. */
+   od_control.od_config_function = CustomConfigFunction;
+
+   /* Include the OpenDoors multiple personality system, which allows    */
+   /* the system operator to set the sysop statusline / function key set */
+   /* to mimic the BBS software of their choice.                         */
+   od_control.od_mps = INCLUDE_MPS;
+   
+   /* Include the OpenDoors log file system, which will record when the */
+   /* door runs, and major activites that the user performs.            */ 
+   od_control.od_logfile = INCLUDE_LOGFILE;
+
+   /* Set filename for log file. If not set, DOOR.LOG will be used by */
+   /* default.                                                        */
+   strcpy(od_control.od_logfile_name, "vote.log");
+
+   /* Set function to be called before program exits. */
+   od_control.od_before_exit = BeforeExitFunction;
+
+   /* Initialize OpenDoors. This function call is optional, and can be used */
+   /* to force OpenDoors to read the door informtion file and begin door    */
+   /* operations. If a call to od_init() is not included in your program,   */
+   /* OpenDoors initialization will be performed at the time of your first  */
+   /* call to any OpenDoors function. */
+   od_init();
+
+   /* Call the Vote function ReadOrAddCurrentUser() to read the current   */
+   /* user's record from the Vote user file, or to add the user to the    */
+   /* file if this is the first time that they have used Vote.            */
+   if(!ReadOrAddCurrentUser())
+   {
+      /* If unable to obtain a user record for the current user, then exit */
+      /* the door after displaying an error message.                       */
+      od_printf("Unable to access user file. File may be locked or full.\n\r");
+      WaitForEnter();
+      od_exit(1, FALSE);
+   }   
+
+   /* Loop until the user choses to exit the door. For each iteration of  */
+   /* this loop, we display the main menu, get the user's choice from the */
+   /* menu, and perform the appropriate action for their choice.          */
+
+   while(chMenuChoice != 'E' && chMenuChoice != 'H')
+   {
+      /* Clear the screen */
+      od_clr_scr();
+
+      /* Display main menu. */
+      
+      /* First, attempt to display menu from an VOTE.ASC/ANS/AVT/RIP file. */
+      if((chMenuChoice = od_hotkey_menu("VOTE", "VRADPEH", TRUE)) == 0)
+      {
+         /* If the VOTE file could not be displayed, display our own menu. */
+         od_printf("`bright red`                     Vote - OpenDoors 6.00 example program\n\r");\
+         od_printf("`dark red`");
+         if(od_control.user_ansi || od_control.user_avatar)
+         {
+            od_repeat((unsigned char)196, 79);
+         }
+         else
+         {
+            od_repeat('-', 79);
+         }
+         od_printf("\n\r\n\r\n\r`dark green`");
+         od_printf("                        [`bright green`V`dark green`] Vote on a question\n\r\n\r");
+         od_printf("                        [`bright green`R`dark green`] View the results of question\n\r\n\r");
+         od_printf("                        [`bright green`A`dark green`] Add a new question\n\r\n\r");
+         od_printf("                        [`bright green`P`dark green`] Page system operator for chat\n\r\n\r");
+         od_printf("                        [`bright green`E`dark green`] Exit door and return to the BBS\n\r\n\r");
+         od_printf("                        [`bright green`H`dark green`] End call (hangup)\n\r\n\r\n\r");
+         od_printf("`bright white`Press the key corresponding to the option of your choice. (%d mins)\n\r`dark green`",
+            od_control.user_timelimit);
+                                                                                                       \
+         /* Get the user's choice from the main menu. This choice may only be */
+         /* V, R, A, D, P, E or H.                                            */
+         chMenuChoice = od_get_answer("VRADPEH");
+      }
+
+      /* Perform the appropriate action based on the user's choice */
+      switch(chMenuChoice)
+      {
+         case 'V':
+            /* Call Vote's function to vote on question */
+            VoteOnQuestion();
+            break;
+            
+         case 'R':
+            /* Call Vote's function to view the results of voting */
+            ViewResults();
+            break;
+            
+         case 'A':
+            /* Call Vote's function to add a new question. */
+            AddQuestion();
+            break;
+            
+         case 'P':
+            /* If the user pressed P, allow them page the system operator. */
+            od_page();
+            break;
+
+         case 'H':
+            /* If the user pressed H, ask whether they wish to hangup. */
+            od_printf("\n\rAre you sure you wish to hangup? (Y/N) ");
+
+            /* Get user's response */
+            chYesOrNo = od_get_answer("YN");
+
+            if(chYesOrNo == 'N')
+            {
+               /* If user answered no, then reset menu choice, so that */
+               /* program will not exit.                               */
+               chMenuChoice = '\0';
+            }
+            break;
+      }
+   }
+
+   if(chMenuChoice == 'H')
+   {
+      /* If the user chooses to hangup, then hangup when exiting. */
+      od_exit(0, TRUE);
+   }
+   else
+   {
+      /* Otherwise, exit normally (without hanging up). */
+      od_printf("Returning to BBS, please wait...\n\r");
+      od_exit(0, FALSE);
+   }
+
+   return(0);
+}
+
+
+/* CustomConfigFunction() is called by OpenDoors to process custom */
+/* configuration file keywords that Vote uses.                     */
+void CustomConfigFunction(char *pszKeyword, char *pszOptions)
+{
+   if(stricmp(pszKeyword, "ViewUnanswered") == 0)
+   {
+      /* If keyword is ViewUnanswered, set local variable based on contents */
+      /* of options string.                                                 */
+      if(stricmp(pszOptions, "Yes") == 0)
+      {
+         nViewResultsFrom = QUESTIONS_VOTED_ON | QUESTIONS_NOT_VOTED_ON;
+      }
+      else if(stricmp(pszOptions, "No") == 0)
+      {
+         nViewResultsFrom = QUESTIONS_VOTED_ON;
+      }
+   }
+}
+
+
+/* Vote configures OpenDoors to call the BeforeExitFunction() before      */
+/* the door exists for any reason. You can use this function to close any */
+/* files or perform any other operations that you wish to have peformed   */
+/* before OpenDoors exists for any reason. The od_control.od_before_exit  */
+/* variable sets the function to be called before program exit.           */
+void BeforeExitFunction(void)
+{
+   char szLogMessage[80];
+   
+   /* Write number of messages voted on to log file. */
+   sprintf(szLogMessage, "User has voted on %d question(s)",
+      nQuestionsVotedOn);
+   od_log_write(szLogMessage);
+}
+
+
+/* Vote calls the VoteOnQuestion() function when the user chooses the     */
+/* vote command from the main menu. This function displays a list of      */
+/* available topics, asks for the user's answer to the topic they select, */
+/* and display's the results of voting on that topic.                     */
+void VoteOnQuestion(void)
+{
+   int nQuestion;
+   int nAnswer;
+   tQuestionRecord QuestionRecord;
+   char szNewAnswer[ANSWER_STR_SIZE];
+   char szUserInput[3];
+   FILE *fpFile;
+   int hFile;
+   int nPageLocation = 0;
+
+   /* Loop until the user chooses to return to the main menu, or until */
+   /* there are no more questions to vote on.                          */
+   for(;;)   
+   {
+      /* Allow the user to choose a question from the list of questions */
+      /* that they have not voted on.                                   */
+      nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON,
+         "                              Vote On A Question\n\r",
+         &nPageLocation);
+   
+
+      /* If the user did not choose a question, return to main menu. */   
+      if(nQuestion == NO_QUESTION)
+      {
+         return;
+      }
+
+      /* Read the question chosen by the user. */
+      if(!GetQuestion(nQuestion, &QuestionRecord))
+      {
+         /* If unable to access file, return to main menu. */
+         return;
+      }
+   
+      /* Don't allow addition of new answers if maximum number of answers */
+      /* have already been added.                                         */
+      if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
+      {
+         QuestionRecord.bCanAddAnswers = FALSE;
+      }
+   
+      /* Loop until user makes a valid respose. */
+      for(;;)
+      {
+         /* Display question to user. */
+
+         /* Clear the screen. */
+         od_clr_scr();
+
+         /* Display question itself. */
+         od_printf("`bright red`%s\n\r\n\r", QuestionRecord.szQuestion);
+
+         /* Loop for each answer to the question. */   
+         for(nAnswer = 0; nAnswer < QuestionRecord.nTotalAnswers; ++nAnswer)
+         {
+            /* Display answer number and answer. */
+            od_printf("`bright green`%d. `dark green`%s\n\r",
+               nAnswer + 1,
+               QuestionRecord.aszAnswer[nAnswer]);
+         }
+
+         /* Display prompt to user. */
+         od_printf("\n\r`bright white`Enter answer number, ");
+         if(QuestionRecord.bCanAddAnswers)
+         {
+            od_printf("[A] to add your own response, ");
+         }
+         od_printf("[Q] to quit: `dark green`");
+   
+         /* Get response from user. */
+         od_input_str(szUserInput, 2, ' ', 255);
+         /* Add a blank line. */      
+         od_printf("\n\r");
+   
+         /* If user entered Q, return to main menu. */
+         if(stricmp(szUserInput, "Q") == 0)
+         {
+            return;
+         }
+
+         /* If user enetered A, and adding answers is premitted ... */
+         else if (stricmp(szUserInput, "A") == 0
+            && QuestionRecord.bCanAddAnswers)
+         {
+            /* ... Prompt for answer from user. */
+            od_printf("`bright green`Please enter your new answer:\n\r");
+            od_printf("`dark green`[------------------------------]\n\r ");
+         
+            /* Get string from user. */
+            od_input_str(szNewAnswer, ANSWER_STR_SIZE - 1, ' ', 255);
+         
+            /* Record that user entered a new answer answer. */
+            nAnswer = NEW_ANSWER;
+
+            /* If user entered a valid answer, then exit loop. */
+            if(strlen(szNewAnswer) > 0)
+            {
+               break;
+            }         
+         }
+
+         /* Otherwise, attempt to get answer number from user. */      
+         nAnswer = atoi(szUserInput) - 1;
+
+         /* If user input is not a valid answer. */      
+         if(nAnswer < 0 || nAnswer >= QuestionRecord.nTotalAnswers)
+         {
+            /* Display message. */
+            od_printf("That is not a valid response.\n\r");
+            WaitForEnter();
+         }
+         else
+         {
+            /* Otherwise, exit loop. */
+            break;
+         }
+      }
+
+      /* Add user's vote to question. */
+   
+      /* Open question file for exclusive access by this node. */
+      fpFile = ExclusiveFileOpen(QUESTION_FILENAME, "r+b", &hFile);
+      if(fpFile == NULL)
+      {
+         /* If unable to access file, display error and return. */
+         od_printf("Unable to access the question file.\n\r");
+         WaitForEnter();
+         return;
+      }
+   
+      /* Read the answer record from disk, because it may have been changed. */
+      /* by another node. */
+      fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
+      if(fread(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
+      {
+         /* If unable to access file, display error and return. */
+         ExclusiveFileClose(fpFile, hFile);
+         od_printf("Unable to read from question file.\n\r");
+         WaitForEnter();
+         return;
+      }
+   
+      /* If user entered their own answer, try to add it to the question. */
+      if(nAnswer == NEW_ANSWER)
+      {
+         /* Check that there is still room for another answer. */
+         if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
+         {
+            ExclusiveFileClose(fpFile, hFile);
+            od_printf("Sorry, this question already has the maximum number of answers.\n\r");
+            WaitForEnter();
+            return;
+         }
+      
+         /* Set answer number to number of new answer. */
+         nAnswer = QuestionRecord.nTotalAnswers;
+      
+         /* Add 1 to total number of answers. */
+         ++QuestionRecord.nTotalAnswers;
+      
+         /* Initialize new answer string and count. */
+         strcpy(QuestionRecord.aszAnswer[nAnswer], szNewAnswer);
+         QuestionRecord.auVotesForAnswer[nAnswer] = 0;
+      }
+   
+      /* Add user's vote to question. */
+      ++QuestionRecord.auVotesForAnswer[nAnswer];
+      ++QuestionRecord.uTotalVotes;
+   
+      /* Write the question record back to the file. */
+      fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
+      if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
+      {
+         /* If unable to access file, display error and return. */
+         ExclusiveFileClose(fpFile, hFile);
+         od_printf("Unable to write question to file.\n\r");
+         WaitForEnter();
+         return;
+      }
+   
+      /* Close the question file to allow access by other nodes. */
+      ExclusiveFileClose(fpFile, hFile);
+   
+      /* Record that user has voted on this question. */
+      CurrentUserRecord.bVotedOnQuestion[nQuestion] = TRUE;
+   
+      /* Open user file for exclusive access by this node. */
+      fpFile = ExclusiveFileOpen(USER_FILENAME, "r+b", &hFile);
+      if(fpFile == NULL)
+      {
+         /* If unable to access file, display error and return. */
+         od_printf("Unable to access the user file.\n\r");
+         WaitForEnter();
+         return;
+      }
+
+      /* Update the user's record in the user file. */
+      fseek(fpFile, nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
+      if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpFile) != 1)
+      {
+         /* If unable to access file, display error and return. */
+         ExclusiveFileClose(fpFile, hFile);
+         od_printf("Unable to write to user file.\n\r");
+         WaitForEnter();
+         return;
+      }
+   
+      /* Close the user file to allow access by other nodes. */
+      ExclusiveFileClose(fpFile, hFile);
+   
+      /* Display the result of voting on this question to the user. */
+      DisplayQuestionResult(&QuestionRecord);
+
+      /* Add 1 to count of questions that the user has voted on. */
+      nQuestionsVotedOn++;
+   }
+}
+
+
+/* The ViewResults function is called when the user chooses the "view    */
+/* results" command from the main menu. This function alows the user to  */
+/* choose a question from the list of questions, and then displays the   */
+/* results of voting on that question.                                   */
+void ViewResults(void)
+{
+   int nChoice;
+   tQuestionRecord QuestionRecord;
+   int nPageLocation = 0;
+
+   /* Loop until user chooses to return to main menu. */
+   for(;;)
+   {   
+      /* Allow the user to choose a question from the list of questions that */
+      /* they have already voted on.                                         */
+      nChoice = ChooseQuestion(nViewResultsFrom,
+         "                                 View Results\n\r", &nPageLocation);
+
+      /* If the user did not choose a question, return to main menu. */   
+      if(nChoice == NO_QUESTION)
+      {
+         return;
+      }
+   
+      /* Read the specified question number from the question file. */
+      if(!GetQuestion(nChoice, &QuestionRecord))
+      {
+         return;
+      }
+   
+      /* Display the results for the selected question. */
+      DisplayQuestionResult(&QuestionRecord);
+   }
+}
+
+
+/* The GetQuestion function read the record for the specified question */
+/* number from the question file.                                      */
+int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord)
+{
+   FILE *fpQuestionFile;
+   int hQuestionFile;
+
+   /* Open the question file for exculsive access by this node. */
+   fpQuestionFile = ExclusiveFileOpen(QUESTION_FILENAME, "r+b",
+      &hQuestionFile);
+   if(fpQuestionFile == NULL)
+   {
+      /* If unable to access file, display error and return. */
+      od_printf("Unable to access the question file.\n\r");
+      WaitForEnter();
+      return(FALSE);
+   }
+   
+   /* Move to location of question in file. */
+   fseek(fpQuestionFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
+   
+   /* Read the question from the file. */
+   if(fread(pQuestionRecord, sizeof(tQuestionRecord), 1, fpQuestionFile) != 1)
+   {
+      /* If unable to access file, display error and return. */
+      ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+      od_printf("Unable to read from question file.\n\r");
+      WaitForEnter();
+      return(FALSE);;
+   }
+   
+   /* Close the question file to allow access by other nodes. */
+   ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+
+   /* Return with success. */
+   return(TRUE);
+}
+ 
+
+/* The AddQuestion() function is called when the user chooses the "add    */
+/* question" option from the main menu. This function allows the user     */
+/* to enter a new question, possible responses, and save the question for */
+/* other users to vote on.                                                */
+void AddQuestion(void)
+{
+   tQuestionRecord QuestionRecord;
+   FILE *fpQuestionFile;
+   int hQuestionFile;
+   char szLogMessage[100];
+
+   /* Clear the screen. */
+   od_clr_scr();
+   
+   /* Display screen header. */
+   od_printf("`bright red`                                Add A Question\n\r");
+   od_printf("`dark red`");
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      od_repeat((unsigned char)196, 79);
+   }
+   else
+   {
+      od_repeat('-', 79);
+   }
+   od_printf("\n\r\n\r");
+   
+   /* Obtain quesiton text from the user. */
+   od_printf("`bright green`Enter Your Question (blank line cancels)\n\r");
+   od_printf("`dark green`[----------------------------------------------------------------------]\n\r ");
+   od_input_str(QuestionRecord.szQuestion, QUESTION_STR_SIZE - 1, ' ', 255);
+   
+   /* If question was empty, then return to main menu. */
+   if(strlen(QuestionRecord.szQuestion) == 0)
+   {
+      return;
+   }
+   
+   /* Display prompt for answers. */
+   od_printf("\n\r`bright green`Enter Possible Answers (blank line when done)\n\r");
+   od_printf("`dark green`   [------------------------------]\n\r");
+   
+   /* Loop, getting answers from user. */
+   for(QuestionRecord.nTotalAnswers = 0;
+       QuestionRecord.nTotalAnswers < MAX_ANSWERS;
+       QuestionRecord.nTotalAnswers++)
+   {
+      /* Display prompt with answer number. */
+      od_printf("`bright green`%2d: `dark green`", QuestionRecord.nTotalAnswers + 1);
+      
+      /* Get string from user. */
+      od_input_str(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers],
+         ANSWER_STR_SIZE - 1, ' ', 255);
+         
+      /* If string was empty, then exit loop. */
+      if(strlen(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers]) == 0)
+      {
+         break;
+      }
+      
+      /* Reset count of votes for this answer to zero. */
+      QuestionRecord.auVotesForAnswer[QuestionRecord.nTotalAnswers] = 0;
+   }
+   
+   /* If no answers were supplied, then cancel, returning to main menu. */
+   if(QuestionRecord.nTotalAnswers == 0)
+   {
+      return;
+   }
+
+   /* Ask whether users should be able to add their own answers. */
+   od_printf("\n\r`bright green`Should voters be able to add their own options? (Y/N) `dark green`");
+   
+   /* Get answer from user. */
+   if(od_get_answer("YN") == 'Y')
+   {
+      /* If user pressed the 'Y' key. */
+      od_printf("Yes\n\r\n\r");
+      
+      /* Record user's response. */
+      QuestionRecord.bCanAddAnswers = TRUE;
+   }
+   else
+   {
+      /* If user pressed the 'N' key. */
+      od_printf("No\n\r\n\r");
+
+      /* Record user's response. */
+      QuestionRecord.bCanAddAnswers = FALSE;
+   }
+   
+   /* Confirm save of new question. */
+   od_printf("`bright green`Do you wish to save this new question? (Y/N) `dark green`");
+   
+   /* If user does not want to save the question, return to main menu now. */
+   if(od_get_answer("YN") == 'N')
+   {
+      return;
+   }
+
+   /* Set total number of votes for this question to 0. */   
+   QuestionRecord.uTotalVotes = 0;
+   
+   /* Set creator name and creation time for this question. */
+   strcpy(QuestionRecord.szCreatorName, od_control.user_name);
+   QuestionRecord.lCreationTime = time(NULL);
+   
+   /* Open question file for exclusive access by this node. */
+   fpQuestionFile = ExclusiveFileOpen(QUESTION_FILENAME, "a+b",
+      &hQuestionFile);
+   if(fpQuestionFile == NULL)
+   {
+      od_printf("Unable to access the question file.\n\r");
+      WaitForEnter();
+      return;
+   }
+   
+   /* Determine number of records in question file. */
+   fseek(fpQuestionFile, 0, SEEK_END);
+   
+   /* If question file is full, display message and return to main menu */
+   /* after closing file.                                               */
+   if(ftell(fpQuestionFile) / sizeof(tQuestionRecord) >= MAX_QUESTIONS)
+   {
+      ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+      od_printf("Cannot add another question, Vote is limited to %d questions.\n\r", MAX_QUESTIONS);
+      WaitForEnter();
+      return;
+   }
+   
+   /* Add new question to file. */
+   if(fwrite(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) != 1)
+   {
+      ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+      od_printf("Unable to write to question file.\n\r");
+      WaitForEnter();
+      return;
+   }
+   
+   /* Close question file, allowing other nodes to access file. */
+   ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+
+   /* Record in the logfile that user has added a new question. */
+   sprintf(szLogMessage, "User adding questions: %s",
+      QuestionRecord.szQuestion);
+   od_log_write(szLogMessage);
+}
+
+
+/* The ChooseQuestion() function provides a list of questions and allows   */
+/* the user to choose a particular question, cancel back to the main menu, */ 
+/* and page up and down in the list of questions. Depending upon the value */
+/* of the nFromWhichQuestions parameter, this function will present a list */
+/* of questions that the user has voted on, a list of questions that the   */
+/* user has not voted on, or a list of all questions.                      */
+int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation)
+{
+   int nCurrent;
+   int nFileQuestion = 0;
+   int nPagedToQuestion = *nLocation;
+   int nDisplayedQuestion = 0;
+   char bVotedOnQuestion;
+   char chCurrent;
+   tQuestionRecord QuestionRecord;
+   FILE *fpQuestionFile;
+   int hQuestionFile;
+   static char szQuestionName[MAX_QUESTIONS][QUESTION_STR_SIZE];
+   static int nQuestionNumber[MAX_QUESTIONS];
+   
+   /* Attempt to open question file. */
+   fpQuestionFile = ExclusiveFileOpen(QUESTION_FILENAME, "r+b",
+      &hQuestionFile);
+
+   /* If unable to open question file, assume that no questions have been */
+   /* created.                                                            */
+   if(fpQuestionFile == NULL)
+   {
+      /* Display "no questions yet" message. */
+      od_printf("\n\rNo questions have been created so far.\n\r");
+      
+      /* Wait for user to press enter. */
+      WaitForEnter();
+      
+      /* Indicate that no question has been chosen. */
+      return(NO_QUESTION);
+   }
+   
+   /* Loop for every question record in the file. */
+   while(fread(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) == 1)
+   {
+      /* Determine whether or not the user has voted on this question. */
+      bVotedOnQuestion = CurrentUserRecord.bVotedOnQuestion[nFileQuestion];
+      
+      /* If this is the kind of question that the user is choosing from */
+      /* right now.                                                     */
+      if((bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_VOTED_ON)) ||
+         (!bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON)))
+      {
+         /* Add this question to list to be displayed. */
+         strcpy(szQuestionName[nDisplayedQuestion],
+            QuestionRecord.szQuestion);
+         nQuestionNumber[nDisplayedQuestion] = nFileQuestion;
+         
+         /* Add one to number of questions to be displayed in list. */
+         nDisplayedQuestion++;
+      }
+      
+      /* Move to next question in file. */
+      ++nFileQuestion;
+   }   
+   
+   /* Close question file to allow other nodes to access the file. */
+   ExclusiveFileClose(fpQuestionFile, hQuestionFile);
+
+   /* If there are no questions for the user to choose, display an */
+   /* appropriate message and return. */
+   if(nDisplayedQuestion == 0)
+   {
+      /* If we were to list all questions. */
+      if((nFromWhichQuestions & QUESTIONS_VOTED_ON)
+         && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON))
+      {
+         od_printf("\n\rThere are no questions.\n\r");
+      }
+      /* If we were to list questions that the user has voted on. */
+      else if(nFromWhichQuestions & QUESTIONS_VOTED_ON)
+      {
+         od_printf("\n\rThere are no questions that you have voted on.\n\r");
+      }
+      /* Otherwise, we were to list questions that use has not voted on. */
+      else
+      {
+         od_printf("\n\rYou have voted on all the questions.\n\r");
+      }
+      
+      /* Wait for user to press enter key. */
+      WaitForEnter();
+      
+      /* Return, indicating that no question was chosen. */
+      return(NO_QUESTION);
+   }
+
+   /* Ensure that initial paged to location is within range. */
+   while(nPagedToQuestion >= nDisplayedQuestion)
+   {
+      nPagedToQuestion -= QUESTION_PAGE_SIZE;
+   }
+
+   /* Loop, displaying current page of questions, until the user makes a */
+   /* choice.                                                            */
+   for(;;)
+   {
+      /* Clear the screen. */
+      od_clr_scr();
+
+      /* Display header. */
+      od_printf("`bright red`");
+      od_printf(pszTitle);
+      od_printf("`dark red`");
+      if(od_control.user_ansi || od_control.user_avatar)
+      {
+         od_repeat((unsigned char)196, 79);
+      }
+      else
+      {
+         od_repeat('-', 79);
+      }
+      od_printf("\n\r");
+   
+      /* Display list of questions on this page. */
+      for(nCurrent = 0;
+         nCurrent < QUESTION_PAGE_SIZE
+         && nCurrent < (nDisplayedQuestion - nPagedToQuestion);
+         ++nCurrent)
+      {
+         /* Determine character to display for current line. */
+         if(nCurrent < 9)
+         {
+            chCurrent = (char)('1' + nCurrent);
+         }
+         else
+         {
+            chCurrent = (char)('A' + (nCurrent - 9));
+         }
+      
+         /* Display this question's title. */
+         od_printf("`bright green`%c.`dark green`", chCurrent);
+         od_printf(" %s\n\r", szQuestionName[nCurrent + nPagedToQuestion]);
+      }
+
+      /* Display prompt for input. */
+      od_printf("\n\r`bright white`[Page %d]  Choose a question or",
+         (nPagedToQuestion / QUESTION_PAGE_SIZE) + 1);
+      if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
+      {
+         od_printf(" [N]ext page,");
+      }
+      if(nPagedToQuestion > 0)
+      {
+         od_printf(" [P]revious page,");
+      }
+      od_printf(" [Q]uit.\n\r");
+      
+      /* Loop until the user makes a valid choice. */
+      for(;;)
+      {      
+         /* Get input from user */
+         chCurrent = (char)od_get_key(TRUE);
+         chCurrent = (char)toupper(chCurrent);
+      
+         /* Respond to user's input. */
+      
+         /* If user pressed Q key. */
+         if(chCurrent == 'Q')
+         {
+            /* Return without a choosing a question. */
+            return(NO_QUESTION);
+         }
+      
+         /* If user pressed P key. */
+         else if(chCurrent == 'P')
+         {
+            /* If we are not at the first page. */
+            if(nPagedToQuestion > 0)
+            {
+               /* Move paged to location up one page. */
+               nPagedToQuestion -= QUESTION_PAGE_SIZE;
+               
+               /* Exit user input loop to display next page. */
+               break;
+            }
+         }
+      
+         /* If user pressed N key. */
+         else if(chCurrent == 'N')
+         {
+            /* If there is more questions after this page. */
+            if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
+            {
+               /* Move paged.to location down one page. */
+               nPagedToQuestion += QUESTION_PAGE_SIZE;
+
+               /* Exit user input loop to display next page. */
+               break;
+            }
+         }
+      
+         /* Otherwise, check whether the user chose a valid question. */
+         else if ((chCurrent >= '1' && chCurrent <= '9')
+            || (chCurrent >= 'A' && chCurrent <= 'H'))
+         {
+            /* Get question number from key pressed. */
+            if(chCurrent >= '1' && chCurrent <= '9')
+            {
+               nCurrent = chCurrent - '1';
+            }
+            else
+            {
+               nCurrent = (chCurrent - 'A') + 9;
+            }
+         
+            /* Add current paged to position to user's choice. */
+            nCurrent += nPagedToQuestion;
+
+            /* If this is valid question number. */            
+            if(nCurrent < nDisplayedQuestion)
+            {
+               /* Set caller's current question number. */
+               *nLocation = nPagedToQuestion;
+            
+               /* Return actual question number in file. */
+               return(nQuestionNumber[nCurrent]);
+            }
+         }
+      }
+   }
+}
+
+
+/* The DisplayQuestionResult() function is called to display the results */
+/* of voting on a paricular question, and is passed the question record  */
+/* of the question. This function is called when the user selects a      */
+/* question using the "view results" option, and is also called after    */
+/* the user has voted on a question, to display the results of voting on */
+/* that question.                                                        */
+void DisplayQuestionResult(tQuestionRecord *pQuestionRecord)
+{
+   int nAnswer;
+   int uPercent;
+
+   /* Clear the screen. */
+   od_clr_scr();
+
+   /* Check that there have been votes on this question. */
+   if(pQuestionRecord->uTotalVotes == 0)
+   {
+      /* If there have been no votes for this question, display a message */
+      /* and return.                                                      */
+      od_printf("Nobody has voted on this question yet.\n\r");
+      WaitForEnter();
+      return;
+   }
+
+   /* Display question itself. */
+   od_printf("`bright red`%s\n\r", pQuestionRecord->szQuestion);
+
+   /* Display author's name. */
+   od_printf("`dark red`Question created by %s on %s\n\r",
+      pQuestionRecord->szCreatorName,
+      ctime(&pQuestionRecord->lCreationTime));
+   
+   /* Display heading for responses. */
+   od_printf("`bright green`Response                        Votes  Percent  Graph\n\r`dark green`");
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      od_repeat((unsigned char)196, 79);
+   }
+   else
+   {
+      od_repeat('-', 79);
+   }
+   od_printf("\n\r");
+
+   /* Loop for each answer to the question. */   
+   for(nAnswer = 0; nAnswer < pQuestionRecord->nTotalAnswers; ++nAnswer)
+   {
+      /* Determine percent of users who voted for this answer. */
+      uPercent = (pQuestionRecord->auVotesForAnswer[nAnswer] * 100)
+         / pQuestionRecord->uTotalVotes;
+      
+      /* Display answer, total votes and percentage of votes. */
+      od_printf("`dark green`%-30.30s  %-5u  %3u%%     `bright white`",
+         pQuestionRecord->aszAnswer[nAnswer],
+         pQuestionRecord->auVotesForAnswer[nAnswer],
+         uPercent);
+
+      /* Display a bar graph corresponding to percent of users who voted */
+      /* for this answer.                                                */
+      if(od_control.user_ansi || od_control.user_avatar)
+      {
+         od_repeat((unsigned char)220, (unsigned char)((uPercent * 31) / 100));
+      }
+      else
+      {
+         od_repeat('=', (unsigned char)((uPercent * 31) / 100));
+      }
+
+      /* Move to next line. */
+      od_printf("\n\r");
+   }
+   
+   /* Display footer. */
+   od_printf("`dark green`");
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      od_repeat((unsigned char)196, 79);
+   }
+   else
+   {
+      od_repeat('-', 79);
+   }
+   od_printf("\n\r");
+   od_printf("`dark green`                         TOTAL: %u\n\r\n\r",
+      pQuestionRecord->uTotalVotes);
+   
+   /* Wait for user to press enter. */
+   WaitForEnter();
+}
+
+
+/* The ReadOrAddCurrentUser() function is used by Vote to search the      */
+/* Vote user file for the record containing information on the user who   */
+/* is currently using the door. If this is the first time that the user   */
+/* has used this door, then their record will not exist in the user file. */
+/* In this case, this function will add a new record for the current      */
+/* user. This function returns TRUE on success, or FALSE on failure.      */
+int ReadOrAddCurrentUser(void)
+{
+   FILE *fpUserFile;
+   int hUserFile;
+   int bGotUser = FALSE;
+   int nQuestion;
+
+   /* Attempt to open the user file for exclusize access by this node.     */
+   /* This function will wait up to the pre-set amount of time (as defined */   
+   /* near the beginning of this file) for access to the user file.        */
+   fpUserFile = ExclusiveFileOpen(USER_FILENAME, "a+b", &hUserFile);
+
+   /* If unable to open user file, return with failure. */   
+   if(fpUserFile == NULL)
+   {
+      return(FALSE);
+   }
+
+   /* Begin with the current user record number set to 0. */
+   nCurrentUserNumber = 0;
+
+   /* Loop for each record in the file */
+   while(fread(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
+   {
+      /* If name in record matches the current user name ... */
+      if(strcmp(CurrentUserRecord.szUserName, od_control.user_name) == 0)
+      {
+         /* ... then record that we have found the user's record, */
+         bGotUser = TRUE;
+         
+         /* and exit the loop. */
+         break;
+      }
+
+      /* Move user record number to next user record. */      
+      nCurrentUserNumber++;
+   }
+
+   /* If the user was not found in the file, attempt to add them as a */
+   /* new user if the user file is not already full.                  */
+   if(!bGotUser && nCurrentUserNumber < MAX_USERS)
+   {
+      /* Place the user's name in the current user record. */
+      strcpy(CurrentUserRecord.szUserName, od_control.user_name);
+      
+      /* Record that user hasn't voted on any of the questions. */
+      for(nQuestion = 0; nQuestion < MAX_QUESTIONS; ++nQuestion)
+      {
+         CurrentUserRecord.bVotedOnQuestion[nQuestion] = FALSE;
+      }
+      
+      /* Write the new record to the file. */
+      if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
+      {
+         /* If write succeeded, record that we now have a valid user record. */
+         bGotUser = TRUE;
+      }
+   }
+
+   /* Close the user file to allow other nodes to access it. */
+   ExclusiveFileClose(fpUserFile, hUserFile);
+
+   /* Return, indciating whether or not a valid user record now exists for */
+   /* the user that is currently online.                                   */   
+   return(bGotUser);
+}
+
+
+/* The WriteCurrentUser() function is called to save the information on the */
+/* user who is currently using the door, to the VOTE.USR file.              */
+void WriteCurrentUser(void)
+{
+   FILE *fpUserFile;
+   int hUserFile;
+
+   /* Attempt to open the user file for exclusize access by this node.     */
+   /* This function will wait up to the pre-set amount of time (as defined */   
+   /* near the beginning of this file) for access to the user file.        */
+   fpUserFile = ExclusiveFileOpen(USER_FILENAME, "r+b", &hUserFile);
+
+   /* If unable to access the user file, display an error message and */
+   /* return.                                                         */
+   if(fpUserFile == NULL)
+   {
+      od_printf("Unable to access the user file.\n\r");
+      WaitForEnter();
+      return;
+   }
+   
+   /* Move to appropriate location in user file for the current user's */
+   /* record. */
+   fseek(fpUserFile, (long)nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
+
+   /* Write the new record to the file. */
+   if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
+   {
+      /* If unable to write the record, display an error message. */
+      ExclusiveFileClose(fpUserFile, hUserFile);
+      od_printf("Unable to update your user record file.\n\r");
+      WaitForEnter();
+      return;
+   }
+   
+   /* Close the user file to allow other nodes to access it again. */
+   ExclusiveFileClose(fpUserFile, hUserFile);
+}
+
+
+/* This function is used by Vote to open a file. If Vote has been compiled */
+/* with #define MULTINODE_AWARE uncommented (see the beginning of this     */
+/* file), file access is performed in a multinode-aware way. This implies  */
+/* that the file is opened of exclusive access, using share-aware open     */
+/* functions that may not be available using all compilers.                */
+FILE *ExclusiveFileOpen(char *pszFileName, char *pszMode, int *phHandle)
+{
+#ifdef MULTINODE_AWARE
+   /* If Vote is being compiled for multinode-aware file access, then   */
+   /* attempt to use compiler-specific share-aware file open functions. */
+   FILE *fpFile = NULL;
+   time_t StartTime = time(NULL);
+   int hFile;
+
+   /* Attempt to open the file while there is still time remaining. */    
+   while((hFile = sopen(pszFileName, O_BINARY | O_RDWR, SH_DENYRW,
+      S_IREAD | S_IWRITE)) == -1)
+   {
+      /* If we have been unable to open the file for more than the */
+      /* maximum wait time, or if open failed for a reason other   */
+      /* than file access, then attempt to create a new file and   */
+      /* exit the loop.                                            */
+      if(errno != EACCES ||
+         difftime(time(NULL), StartTime) >= FILE_ACCESS_MAX_WAIT)
+      {
+         hFile = sopen(pszFileName, O_BINARY | O_CREAT, SH_DENYRW,
+            S_IREAD | S_IWRITE);
+         break;
+      }
+
+      /* If we were unable to open the file, call od_kernel, so that    */
+      /* OpenDoors can continue to respond to sysop function keys, loss */
+      /* of connection, etc.                                            */
+      od_kernel();
+   }
+
+   /* Attempt to obtain a FILE * corresponding to the handle. */
+   if(hFile != -1)
+   {
+      fpFile = fdopen(hFile, pszMode);
+      if(fpFile == NULL)
+      {
+         close(hFile);
+      }
+   }
+
+   /* Pass file handle back to the caller. */
+   *phHandle = hFile;
+
+   /* Return FILE pointer for opened file, if any. */   
+   return(fpFile);
+#else
+   /* Ignore unused parameters. */
+   (void)phHandle;
+
+   /* If Vote is not being compiled for multinode-aware mode, then just */
+   /* use fopen to access the file.                                     */
+   return(fopen(pszFileName, pszMode));
+#endif
+}
+
+
+/* The ExclusiveFileClose() function closes a file that was opened using */
+/* ExclusiveFileOpen().                                                  */
+void ExclusiveFileClose(FILE *pfFile, int hHandle)
+{
+   fclose(pfFile);
+#ifdef MULTINODE_AWARE
+   close(hHandle);
+#else
+   /* Ignore unused parameters. */
+   (void)hHandle;
+#endif
+}
+
+
+/* The WaitForEnter() function is used by Vote to create its custom   */
+/* "Press [ENTER] to continue." prompt.                               */
+void WaitForEnter(void)
+{
+   /* Display prompt. */
+   od_printf("`bright white`Press [ENTER] to continue.\n\r");
+   
+   /* Wait for a Carriage Return or Line Feed character from the user. */
+   od_get_answer("\n\r");
+}

+ 1 - 0
odoors/hbuild.bat

@@ -0,0 +1 @@
+make -fDOS.mak -DTARGET=h > out.txt

+ 809 - 0
odoors/historic/ODHIST.TXT

@@ -0,0 +1,809 @@
+                     OpenDoors Door Programming Toolkit History
+                     ------------------------------------------
+
+This document describes the development history of the OpenDoors door
+programming toolkit. This document is divided into two sections. The
+first section provides a brief timeline of the OpenDoors releases since
+version 1.00. The second section provides detailed information on the
+changes and enhancements that were made for each version.
+
+
+OPENDOORS TIME LINE
+-------------------
+
+VERSION   RELEASE DATE      HIGHLIGHTS
+-------------------------------------------------------------------------------
+1.00      Fall, 1990        Initial beta version
+1.10      Winter, 1991      First public release
+1.20      Spring, 1991      Minor enhancments, including RA 1.00 support
+1.30      Spring, 1991      A Few bug fixes
+1.40      Spring, 1991      Message customizability
+2.00      Summer, 1991      AVATAR support, improved ANSI support
+2.10      Summer, 1991      Added od_printf() and a new registration key system
+2.20      Summer, 1991      Further customizability, DesqView support
+2.30      Summer, 1991      Minor bug fix
+3.00      Fall, 1991        Beta release, with RA system file support
+3.10      Fall, 1991        Public release with bug fixes from 3.00
+3.20      Winter, 1992      Support for enhanced FILES.BBS format
+3.30      Winter, 1992      Further bug fiexes
+3.40      May, 1992         Full locked-BPS rate support
+4.00      July, 1992        New manual, inline colour setting with od_printf()
+4.10      February, 1993    Configuration file and log file systems
+5.00      September, 1994   Built-in serial I/O, multiple compiler support
+
+
+
+DETAILED HISTORY OF OPENDOORS EVOLUTION
+---------------------------------------
+
+VERSION 1.00   Initial beta test version of the OpenDoors doordriver. Proved to
+               be very bug-free.
+
+
+VERSION 1.10   First public release.
+
+
+VERSION 1.20   Made several changes:
+
+                    - Support for the new RemoteAccess 1.00 enhanced
+                      exitinfo.bbs file, with many extra pieces of information.
+
+                    - Added a Alt-K function key to allow the sysop to
+                      temporarily disable the user's keyboard
+
+                    - Added full support for turning on and off status line.
+                      Status line has been changed slightly in format, and [F9]
+                      help function key added.
+
+                    - Improved sysop chat mode (added multi-colour and wordwrap)
+
+                    - Fixed up shell-to-DOS to automatically shell to the
+                      command processor specified in COMSPEC instead of always
+                      using COMMAND.COM. OpenDoors now also returns to system to
+                      the drive and directory it was in before DOS shell was
+                      issued.
+
+                    - Added support for the new RemoteAccess "sysop next" key.
+
+
+VERSION 1.30   A few quick changes to perfect all the features of this version
+               before beginning major development work on OpenDoors 2.00. Fixed
+               two problems:
+
+                    - The status line can no longer be turned back on by the
+                      sysop using F1 - F9 keys when a door program has disable
+                      the status line itself.
+
+                    - A rather major problem was fixed for use of OpenDoors in
+                      conjunction with RA 1.00. We accidentally forgot to save
+                      some of the data that is unused in previous versions, but
+                      is now used in the new version. This bug caused some
+                      unexpected problems, including damage to the USERSXI.BBS
+                      file.
+
+
+VERSION 1.40   Another maintenance release. This version should now function
+               perfectly when used in conjunction with older versions of Turbo
+               C. Other changes in this version include:
+
+                    - Better error recovery in the case that the door
+                      information file has been damaged.
+
+                    - OpenDoors was made more customizable, including allowing
+                      the programmer to alter the various OpenDoors messages,
+                      and provisions for user defined function keys for the
+                      sysop. (ie, it is now possible for the programmer to make
+                      Alt-Y another hotkey for the sysop)
+
+
+VERSION 2.00   Another release, adding a number of new features, such as:
+
+                    - Added support for AVATAR graphics. OpenDoors will
+                      automatically detect the presence of AVATAR graphics mode
+                      when running under Remote Access, and will allow your door
+                      to toggle it when running under other BBS systems.
+
+                    - Improved ANSI routines. Added some new functions, and
+                      changed existing functions to send more efficient ANSI
+                      codes in some circumstances.
+
+                    - The "Sysop Next" key should now work correctly with RA
+                      1.00 and later.
+
+
+VERSION 2.10   Changes in this version include:
+
+                    - Implementation of a registration key-code to allow
+                      registered users to more easily upgrade to new versions.
+
+                    - Added an od_printf() function for ease of formatted output
+                      from within OpenDoors.
+
+
+VERSION 2.20   More improvements, including:
+
+                    - Fixing of some minor bugs, such as incorrect handling of
+                      the path to DORINFO1.DEF/EXITINFO.BBS files.
+
+                    - Added support for more customization, such as hooks for
+                      functions that will be called before and after Shell to
+                      DOS and sysop chat.
+
+                    - OpenDoors is now DesqView aware. OpenDoors will
+                      automatically detect the presence of DesqView, and uses
+                      the DesqView `virtual screen buffer' for screen display if
+                      present.
+
+                    - A QuickBBS 2.75 compatibility problem has also been fixed.
+
+
+VERSION 2.30   Fixed a small bug in the registration system.
+
+
+VERSION 3.00   A major upgrade, released as a beta-test version, including the
+               following additions/changes:
+
+                    - Eliminated many bugs.
+
+                    - Added support for door information files from: WWIV, PC-
+                      Board, Spitfire, WildCat, GAP, TriTel and others.
+
+                    - Added .ASC/.ANS/.AVT file display support with automatic
+                      interpretation of QBBS/SuperBBS/RA control characters.
+
+                    - Added ALT-D key to drop the user back to the BBS without
+                      hanging up.
+
+                    - Added direct access to RA style configuration, file area,
+                      message area, external protocols, event configuration,
+                      caller history, users online, menu files, user base and
+                      other system files.
+
+                    - Added complete set of message base manipulation routines,
+                      with full support for the RA 1.01 message base locking
+                      scheme.
+
+                    - The user manual has also been re-written in order to make
+                      it easier to work with.
+
+
+VERSION 3.10   The following bug fixes and changes have been made since the
+               release of the beta version, 3.00:
+
+                    - Time fields in messages are now correctly formatted
+
+                    - Corrected a bug in the od_set_attrib function where the
+                      intensity setting would not correctly be transmitted to
+                      the remote when using ANSI graphics.
+
+                    - Fixed a bug in the re-writing of the DORINFO1.DEF which
+                      cause sysop and user's last names to be corrupted.
+
+                    - Registered users may now disable the display of copyright
+                      and registration information when the door starts up.
+
+
+VERSION 3.20   A few more changes and bug fixes were made since version 3.10,
+               including:
+
+                    - Fixed the FILES.BBS lister to correctly support FILES.BBS
+                      files located in directories other than the default dir,
+                      and added page pausing to the FILES.BBS lister.
+
+
+VERSION 3.30   The following changes and bug fixes were made since version 3.20:
+
+                    - OpenDoors no longer re-writes the DORINFO1.DEF upon
+                      exiting. No BBS's are known to actually make use of the
+                      information changed in DORINFO1.DEF, and re-writing this
+                      file was causing more troubles than it was worth.
+
+                    - The od_msg_read_hdr() function's NEXT_MESSAGE command now
+                      works correctly.
+
+                    - Added an od_errno variable to assist in debugging of
+                      programs written with the BBS file engine portion of
+                      OpenDoors.
+
+
+VERSION 3.40   A minor upgrade version, with the following changes:
+
+                    - Fixed a compatibility problem with some locked baud rates.
+                      Now, if OpenDoors receives a baud rate the door
+                      information file that is not supported in the FOSSIL
+                      definitions, it will continue without setting the baud
+                      rate. (Whereas before, OpenDoors would report an error and
+                      exit.)
+
+                    - Made some changes to the manual, and included a utility to
+                      remove the extended-ASCII characters from the manual to
+                      ease printing on some printers.
+
+
+VERSION 4.00   This version is a major overhaul of the entire OpenDoors package,
+               including a great many enhancements and additions. As of version
+               4.00, OpenDoors is available as two separate packages - the door
+               programming toolkit (this package), and the BBS interface package
+               (which is available separately) Among the major changes to
+               version 4.00 of the OpenDoors door programming toolkit are:
+
+                    - A complete re-organization of the manual, including the
+                      re-writing of a large portion of the manual. In order to
+                      ease printing on some printers, the manual has been re-
+                      formatted in order that it no longer contains extended
+                      ASCII characters. More thorough documentation on the
+                      OpenDoors functions and structures was written, along with
+                      the addition of many more examples. Also added to the
+                      manual are an index, glossary and other features intended
+                      to make the reference manual an even more powerful and
+                      flexible tool.
+
+                    - Full support for the changes to RemoteAccess 1.10/1.11 has
+                      been added for version 4.00. These include the addition of
+                      some new fields stored in the EXITINFO.BBS door
+                      information file, and proper adjusting of the user's time
+                      remaining online. Version 4.00 also now has full support
+                      for the new QuickBBS-specific EXITINFO.BBS file.
+
+                    - All of the text displayed by OpenDoors is now fully
+                      customizable using od_control structure variables. This
+                      permits both greater door customization, and adds the
+                      ability to write 100% non-English doors and programs.
+
+                    - The OpenDoors status lines have been changed. OpenDoors
+                      now provides additional user information through multiple
+                      RemoteAccess-style status lines, accessible through the
+                      F2, F3, etc. keys. Also, the status line may now be turned
+                      off by using the F10 key, allowing the sysop to view all
+                      25-lines of the information displayed by a door program. A
+                      new function od_set_statusline(), permits program
+                      selection of the current status line setting.
+
+                    - OpenDoors now allows colour codes to be embedded in
+                      od_printf() functions, to eliminate the need for long
+                      chains of alternating od_disp_str(), od_set_colour() /
+                      od_set_attrib() function calls.
+
+                    - A new formatted input function, od_edit_str() has been
+                      added for use in door programs running in ANSI or AVATAR
+                      graphics mode. The od_edit_str() function features
+                      advanced line editing capabilities which are normally
+                      found only in non-door programs, such as inserting or
+                      deleting text from the middle of a string, moving the
+                      cursor with the arrow keys, and so on. The od_edit_str()
+                      function also provides input formatting, allowing you to
+                      force the user's input into any format you wish, from
+                      phone number formats to date formats to username formats.
+                      The od_edit_str() also provides special modes for
+                      implementing features such as password input, field input
+                      (where the user may move from one field to another using
+                      arrow/tab keys), input field highlighting, editing
+                      existing strings, auto-delete, and much more. The old
+                      od_input_str() function still provides a subset of these
+                      features which do not require ANSI or AVATAR graphics.
+
+                    - New functions have been added to the door driver module of
+                      OpenDoors. Among these, are an od_putch() function for
+                      displaying one character at a time, and an od_spawn()
+                      function, for easily executing other programs from within
+                      OpenDoors. The od_spawn() function automatically saves the
+                      contents of the current door screen, system drive and
+                      directory, and provides a separate screen on which the
+                      spawned-to program can execute. The od_draw_box() function
+                      allows you to easily display windows in door programs,
+                      using ANSI or AVATAR graphics codes. Also added is are
+                      od_carrier(), od_set_statusline() and od_edit_str()
+                      functions, mentioned elsewhere.
+
+                    - More changes have been made in order to permit greater
+                      customization and flexibility of OpenDoors. An
+                      od_carrier() function has been added to detect the state
+                      of the carrier detect signal in programs that disable
+                      OpenDoor's internal carrier detection. Also, it is now
+                      possible to shut down OpenDoors without exiting via the
+                      od_exit() function.
+
+                    - OpenDoors now yeilds the processor to other executing
+                      tasks in multitasking environments (ie. DesqView), when
+                      the door is inactive or waiting for input.
+
+                    - The door driver function od_clr_scr() now only checks the
+                      user's screen clearing setting if that information is
+                      available from the door information file. If the
+                      information is not available, the od_clr_scr() function
+                      will always clear the screen.
+
+                    - Many other small changes were also made for version 4.00.
+                      Among these, you now have access to the user's reason for
+                      chat and you can switch the pause and stop keys on and off
+                      during listing of available files or displaying a text
+                      file. Also, previous versions of OpenDoors would read the
+                      user's information from the first door information file
+                      found. Instead, version 4.00 now reads the most recently
+                      created door information file. A bug in the od_clr_line()
+                      function has also been fixed.
+
+
+VERSION 4.10   A great deal of work has been done between version 4.00 and 4.10
+               of OpenDoors. This work falls into three major categories: bug
+               fixes, improved performance, and new features. In fact, enough
+               changes and improvements have been made that this version really
+               ought to be numbered 5.00. Below is a summary of the changes that
+               have occurred since version 4.00:
+                    - Much of the door information file interfacing code has
+                      been revamped, in order that OpenDoors now works correctly
+                      with the newest versions of the BBS packages it supports.
+                      OpenDoors now differentiates between three different
+                      DOOR.SYS formats - the DoorWay format, the PC-Board / GAP
+                      format, and the Wildcat format. Also, the SFDOORS.DAT code
+                      has been fixed to correctly work with the newest version
+                      of Spitfire.
+
+                    - OpenDoors will now attempt to swap itself and your entire
+                      door program to expanded memory or disk when the sysop
+                      shells to DOS, or when you call one of the od_spawn...()
+                      functions. Memory swapping may be configured in a number
+                      of ways, or even disabled. The OpenDoors swapping code
+                      adds only 2K to the door's .EXE file size.
+
+                    - OpenDoors now includes a new od_spawnvpe() function. In
+                      addition to the features of the "quick-spawn" od_spawn()
+                      function, od_spawnvpe() also returns the errorlevel the
+                      called program returned, allows you to alter the
+                      environment passed to the child process, and uses the same
+                      parameter format as the C spawnvpe() function. (see page
+                      117)
+
+                    - The od_page() function now checks the sysop paging hours,
+                      set in the OpenDoors control structure. If the user
+                      attempts to page the sysop outside of the defined paging
+                      hours, he or she will be notified that the sysop is not
+                      available.
+
+                    - OpenDoors now includes a configuration file sub-system
+                      that you may choose to include in your OpenDoors programs.
+                      This sub-system automatically parses the configuration
+                      file you specify, responding to any of the built-in
+                      configuration commands, and passing configuration options
+                      specific to your program back to you. With only a single
+                      line of code on your part, this sub-system will allow
+                      people running your program to configure many options such
+                      as sysop paging hours, system directories, maximum time
+                      within the door, etc. It also allows the sysop to provide
+                      information that may not be supplied by their particular
+                      BBS software, such as modem settings, the system's name
+                      and so on. In addition to all these built in commands, you
+                      can add your own configuration options, such as display
+                      colours, registration key numbers and other information
+                      needed by your program - without the need to write your
+                      own configuration file parsing routines. (See page 76)
+
+                    - OpenDoors now supports custom, sysop-defined door
+                      information file (drop file) formats. By defining a custom
+                      door information file format in the cofiguration file,
+                      OpenDoors door programs can now be made to run directly
+                      under BBS packages that use proprietary file formats that
+                      are not directly supported by OpenDoors. (see page 78)
+
+                    - In order to make doors written with OpenDoors even more
+                      foolproof for the sysop to setup, an intelligent door
+                      information file (drop file) locator has been added.
+                      OpenDoors will automatically search for a door information
+                      file in the directory specified by the configuration file,
+                      the directory specified by your door's source code, the
+                      current directory, and the directory pointed to by the
+                      environment variables used by any of a number of BBS
+                      packages.
+
+                    - OpenDoors now includes a log file sub-system that you may
+                      choose to include in your programs. The log file system
+                      handles all access and formatting of the logfile, allowing
+                      the programmer to make log file entries by simple function
+                      calls such as od_write_log("User downloading file");.
+                      Also, since the log file system is closely integrated with
+                      the rest of OpenDoors, choosing to include the logfile
+                      system in a program causes OpenDoors to automatically
+                      output the most common logfile entries for events such as
+                      the user paging sysop, the user hanging up, sysop chatting
+                      with the user, user inactivity timeouts, and so on. (see
+                      page 89)
+
+                    - OpenDoors 4.00 would not always correctly turn on and off
+                      high intensity or flashing colour attributes in ANSI mode.
+                      The ANSI colour handling code has been reworked for
+                      version 4.10, to eliminate these problems.
+
+                    - An od_get_answer() function has been added, which can be
+                      used to easily permit only certain keys to be pressed in
+                      response to a prompt. For instance, to get a Yes/No
+                      response from the user, use od_get_answer("YN"); (see page
+                      66)
+
+                    - A popular addition to OpenDoors 4.00 was the ability to
+                      change the current display colour within od_printf()
+                      format strings, using imbedded control characters.
+                      However, the programmer was forced to use a rather cryptic
+                      two-byte control sequence, where the second character of
+                      the sequence contained an 8-bit colour attribute value. It
+                      is now possible to change the display colour within
+                      od_printf() by specifying the names of the desired
+                      foreground and background colours, delimited by a set of
+                      BACK-QUOTE (`) characters. For example:
+
+          od_printf("`Red` THIS TEXT IS RED `Blue` THIS TEXT IS BLUE");
+          od_printf("`Flashing bright green on dark green` AND THIS IS GREEN");
+
+                      (see page 93)
+
+                    - Version 4.10 would not correctly "freeze" the user's time
+                      during DOS shell and sysop page operations, when the door
+                      was operating under RemoteAccess 1.11. This has been
+                      fixed.
+
+                    - A new variable, od_spawn_freeze_time, has been added to
+                      the OpenDoors control structure. When set to FALSE, the
+                      user's time remaining continues to be deducted during the
+                      execution of any of the od_spawn... functions. When set to
+                      TRUE, the user's time remaining is frozen during the
+                      execution of an od_spawn... function.
+
+                    - The current directory is now correctly restored to its
+                      original setting after the sysop returns from a DOS shell,
+                      and after calls to the od_spawn... functions.
+
+                    - A number of people were experiencing difficulty using the
+                      od_edit_str() function in version 4.00. A number of
+                      improvements to this function's logic have been made in an
+                      attempt to make od_edit_str() more foolproof to use. Also,
+                      a new flag setting, EDIT_FLAG_LEAVE_BLANK has been added.
+                      However, there were a few reports of problems which we
+                      were not able to reproduce. If you are still having
+                      difficulty with this function, please carefully re-read
+                      the section of the manual pertaining to it's use. In
+                      particular, be sure that your difficulty is not resulting
+                      from the flag settings you are using. If you still suspect
+                      a bug in this function, please include with your bug
+                      report the source code that is causing the problem.
+
+                    - Page pausing within the od_send_file() and od_list_files()
+                      (FILES.BBS listing routine) functions can now be disabled
+                      and re-enabled by the programmer.
+
+                    - The "Continue? [Y/n/=]" end of screen prompt and response
+                      keys are now fully customizable.
+
+                    - The od_list_files() FILES.BBS listing function now works
+                      correctly in all memory models. The function has also been
+                      fixed to correctly handle cases where the trailing
+                      backslash is not supplied in the path parameter.
+
+                    - The actual BBS line (node) number is now displayed on the
+                      default status line, provided that this information is
+                      supplied by the BBS software.
+
+                    - It is now possible to detect whether keystrokes originated
+                      from the remote or local keyboard. This is a feature that
+                      is useful in some special applications, such as split-
+                      screen sysop chat programs.
+
+                    - Version 4.00 would not always correctly display the status
+                      lines, if there was information missing from the
+                      EXITINFO.BBS file. This has been fixed. In addition, the
+                      "next event" information is now correctly displayed on the
+                      status lines. Also, if the user's birthday is available,
+                      their age will also be calculated and displayed on the
+                      status line.
+
+                    - If you temporarily disable inactivity timeouts, OpenDoors
+                      will no longer automatically trigger and inactivity
+                      timeout as soon as you re-enable this feature.
+
+                    - A new function, od_hotkey_menu(), has been added to
+                      facilitate displaying a menu with "hot keys". Like the
+                      od_send_file() function, od_hotkey_menu() will display an
+                      ASCII, ANSI or AVATAR file. However, od_hotkey_menu() also
+                      allows you to pass a string listing possible hot keys. If
+                      the user presses any of these keys while the menu is being
+                      displayed, menu display will immediately cease, and the
+                      function will return the key pressed by the user. (See
+                      page 71)
+
+                    - The od_send_file() (the ASCII/ANSI/AVATAR file display
+                      routine) no longer sends the EOF character if it happens
+                      to exist at the end of a file.
+
+                    - In addition to the EZVote OpenDoors tutorial door, an
+                      number of other example doors are now included in the
+                      OpenDoors package.
+
+                    - A few errors have been corrected in the documentation, and
+                      additional information has been added about the new
+                      features in this version.
+
+
+
+VERSION 5.00   Version 5.00 represents several major steps forward for
+               OpenDoors. In addition to numerous bug fixes and minor
+               improvements, a number of major new features have been added to
+               this version. These include an optional multiple personality
+               system which allow the sysop to choose the status line and
+               function key style they prefer. This version also adds text-mode
+               support for RIP (Remote Imaging Protocol) graphics, and adds a
+               group of advanced ANSI/AVATAR/RIP functions for scrolling areas
+               of the screen, saving and restoring portions of the screen and
+               creating pop-up windows and menus. Also new in this version is
+               support for compilers other than Borland/Turbo C(++), such as
+               compilers from Microsoft. Version 5.00 also adds built-in
+               communications support, making the use of a FOSSIL driver
+               optional. Furthermore, direct support for additional BBS systems
+               has been added. The list below provides more detail of the
+               changes and new features in version 5.00:
+
+                    - The nonstop key ([=]) now works correctly during
+                      FILES.BBS listing.
+
+                    - New door information file formats now supported include:
+                      RA 2.00 EXITINFO.BBS.
+
+                    - If the TASK environment variable is set, OpenDoors will
+                      now use its value to determine the current node number.
+
+                    - The od_control.od_spawn_freeze_time variable now works
+                      correctly. Previously, the user's time would always be
+                      frozen during od_spawn...() execution, regardless of the
+                      value of this variable.
+
+                    - A new feature known as the "Multiple Personality System"
+                      has been added to this version. If you choose to include
+                      the Multiple Personality System in a door, the sysop will
+                      be able to specify which of a number of "personalities"
+                      should be used. Each personality defines the statusline
+                      appearance and function keys seen by the sysop, and has
+                      no effect on the door's operation from the user's
+                      standpoint. OpenDoors 5.00 includes personality
+                      definitions for WildCat, RemoteAccess, PC-Board, and it's
+                      own simplified RA style status lines. You can also define
+                      your own personalities by writing a personality
+                      definition function. If your choose not to include the
+                      Multiple Personality System in a door, you will still be
+                      able to define which single personality you wish
+                      OpenDoors to use.
+
+                    - This version of OpenDoors can be used with a larger
+                      variety of compilers than where supported by the previous
+                      version. OpenDoors 5.00 is known to work with all
+                      versions of Turbo C, Turbo C++, Borland C++, Microsoft C,
+                      Microsoft C++, Quick C and Visual C++. It should also
+                      work with any other MS-DOS based ANSI C compiler that
+                      supports the Microsoft/DOS .OBJect and .LIBrary file
+                      formats.
+
+                    - A new diagnostics feature has been added to OpenDoors,
+                      which allows you to determine the reason for the most
+                      recent OpenDoors function failure. When any OpenDoors
+                      function returns a failure condition, it also sets the
+                      new od_control.od_error variable to indicate the reason
+                      for the failure.
+
+                    - Added additional definitions to OPENDOOR.H, to map names
+                      of OpenDoors functions and variables with the word
+                      "colour" from the U.S. spelling "color". In other words,
+                      both od_set_colour() and od_set_color() are now
+                      recognized by OpenDoors.
+
+                    - The od_list_files() now supports more intelligent path
+                      specifications. If the parameter to od_list_files() is
+                      NULL or empty, it will search for a FILES.BBS file in the
+                      current directory. If a directory path is specified, it
+                      will look for a FILES.BBS in that directory. If a full
+                      directory and filename are specified, the specified
+                      filename will be used in place of FILES.BBS.
+
+                    - To save space, the compact memory model library is no
+                      longer included in the normal OpenDoors package. The
+                      compact memory model library is now available seperately.
+
+                    - A new function, od_set_dtr(), has been added to allow the
+                      DTR line to the modem to be manually controlled. This can
+                      be useful in writing programs where you wish to force the
+                      modem to hangup, such as a call-back verification door.
+
+                    - Added additional support for various DOS multitasking
+                      environments. OpenDoors is now specifically Microsoft
+                      Windows aware. OpenDoors also now gives up time to other
+                      waiting tasks when it is idle during chat mode.
+
+                    - The od_edit_str() "M" mode now capitializes a character
+                      following a dash '-' character.
+
+                    - When transmitting more than one character at a time,
+                      OpenDoors now uses the FOSSIL trasfer block function,
+                      instead of multiple calls to the transfer character
+                      function. This should help to improve performance over
+                      high speed connections when running on slow PCs or under
+                      multitasking environments.
+
+                    - OpenDoors 4.10 would not correctly change the display
+                      colour from high-intensity back to low-intensity. This
+                      problem has been fixed.
+
+                    - OpenDoors now recognizes DORINFO?.DEF filenames with
+                      alphabetical identifiers (ie, DORINFOA.DEF thru
+                      DORINFOZ.DEF) for nodes 10 thru 35.
+
+                    - Improvements have been made to the logfile system. An
+                      exit at errorlevel zero no longer causes garbage to be
+                      written to the logfile. The logfile functions have been
+                      made more reliable when operating under low stack
+                      availability conditions. In the past, if a large number
+                      of local variables where allocated on the stack, the
+                      logfile functions would fail, often writing garbage to
+                      the logfile. When the user pages the sysop for chat, the
+                      user's reason for wishing a chat is also written to the
+                      logfile.
+
+                    - Support for text-mode RIP (Remote Imaging Protocol)
+                      graphics has been added. Because this version of
+                      OpenDoors always operates in DOS text-mode, none of the
+                      graphics mode RIP features (such as drawing lines,
+                      circles and displaying icons) will appear on the local
+                      screen. Plans for a version of OpenDoors that will
+                      operate in graphics mode and optionally display graphics
+                      locally are currently under consideration. In this
+                      version, RIP support includes a number of new features.
+                      OpenDoors will now recognize the RIP setting passed in an
+                      RA 2.00 EXITINFO.BBS file and WildCat DOOR.SYS file, and
+                      also allows the RIP setting to be specified in a custom
+                      door information file. The od_send_file() and
+                      od_hotkey_menu() functions will now search for files with
+                      .RIP, .AVT, .ANS and .ASC extensions. When displaying RIP
+                      graphics to the remote user, a pop-up window appears on
+                      the local screen, indicating to the sysop which file is
+                      being displayed.
+
+                    - A set of new functions have been added to permit advanced
+                      screen manipulations. These functions include
+                      od_gettext() and od_puttext() to save and restore
+                      portions of the screen, and od_save_screen() and
+                      od_restore_screen() to save and restore the entire
+                      screen. od_scroll() can be used to scroll any portion of
+                      the screen upwards or downwards. od_save_screen() and
+                      od_restore_screen() will operate in any mode, but the
+                      other functions require ANSI/AVATAR/RIP mode to be
+                      available.
+
+                    - Three additional functions, od_window_create(),
+                      od_window_remove() and od_popup_menu(), have been added
+                      to facilitate the creation of popup windows and menus.
+                      When such a window or menu is removed from the screen,
+                      the are of the screen "under" the window is returned to
+                      it's original state. This allows you to create multiple
+                      overlapping windows within a door program. The
+                      od_popup_menu() function creates a popup window with a
+                      menu from a simple menu definition string. The user can
+                      select an option from this menu by pressing the key
+                      associated with an option, or by moving a menu selection
+                      bar using their arrow keys. These three functions require
+                      an ANSI/AVATAR/RIP mode to be available.
+
+                    - A new function, od_chat() has been added, to allow you to
+                      explicitly invoke the OpenDoors chat mode from within
+                      your program.
+
+                    - A new setting variable, od_control.od_always_clear has
+                      been added. When set to TRUE, od_clr_scr() will always
+                      clear the screen, regardless of the user's screen
+                      clearing setting. When set to FALE, od_clr_scr() will
+                      only clear the screen if the user has screen clearing
+                      enabled.
+
+                    - It is now possible to configure the errorlevels OpenDoors
+                      exits with under various circumstances, such as when the
+                      user runs out of time remaining online. See the
+                      od_control.od_errorlevel variable
+
+                    - A new setting variable, od_control.od_force_local, can be
+                      used to easily force OpenDoors to operate in local mode.
+                      Using this variable you can easily add a command line
+                      parameter such as "-local" to allow the sysop to force
+                      your door to operate in local mode. When OpenDoors is
+                      forced into local mode using this variable, it does not
+                      look for a door information file, and uses default
+                      settings for the user's name, etc.
+
+                    - OPENDOOR.H now sets structure packing to single byte
+                      alignment for the od_control structure when Borland and
+                      Microsoft compilers are being used. In the past,
+                      programmers using OpenDoors have experienced difficulties
+                      the od_control structure when the compiler has been set
+                      to use word packing.
+
+                    - OpenDoors now closes the FOSSIL driver prior to
+                      performing a spawn or sysop DOS shell. This allows doors
+                      or other communications programs which use the FOSSIL
+                      driver to be executed while the door's execution is
+                      suspended.
+
+                    - When used with a FOSSIL driver, OpenDoors normally
+                      changes the BPS rate to that passed from the BBS (if the
+                      BBS passes a valid FOSSIL BPS rate). This BPS rate
+                      setting may now be disabled by setting the
+                      DIS_BPS_SETTING bit of the od_control.od_disable
+                      variable.
+
+                    - A function hook has been added to allow you to install a
+                      function to be called whenever od_kernel() executes
+                      (od_control.od_ker_exec). Another function hook,
+                      od_control.od_time_msg_func, can be installed to override
+                      OpenDoor's time limit warning messages.
+
+                    - A new array, od_control.od_hot_function, allows the you
+                      to define functions to be called when any of the
+                      programmer-defined sysop hotkeys have been pressed.
+
+                    - A function hook, od_control.od_no_file_func, has been
+                      added. This function will be called whenever OpenDoors is
+                      unable to find or read a door information file. This
+                      allows you to add your own door information file reader,
+                      or to provide a local login prompt when no door
+                      information file is present.
+
+                    - Previously, OpenDoors would stop correctly updating the
+                      user's remaining time at midnight when running under
+                      certain BIOSes. This problem has been fixed.
+
+                    - The current display colour attribute can now be accessed
+                      through an control structure member,
+                      od_control.od_cur_attrib.
+
+                    - od_send_file() and od_hotkey_menu() no longer pause with
+                      a "Continue?" prompt prematurely in files that have line
+                      lengths greater than 254 characters.
+
+                    - The local keyboard may now be disabled by setting the
+                      DIS_LOCAL_INPUT bit of od_control.od_disable. This only
+                      affects the sysop's input in circumstances that input is
+                      also accepted from the remote user; this setting has no
+                      effect on the sysop function keys.
+
+                      A new function hook:                      -
+
+                         void (*od_control.od_local_input)(int);
+
+                      has been added. If set, this function will be called
+                      whenever the sysop presses a non-sysop-function key on
+                      the local keyboard.
+
+                    - od_control.od_clear_on_exit now controls whether the
+                      screen is cleared before shelling or executing
+                      od_spawn...(), in addition to before OpenDoors shuts
+                      down.
+
+                    - od_page() now restores the original display colour before
+                      returning.
+
+                    - It is now possible to display an entire string of
+                      characters with terminal emulation, using the new
+                      function od_disp_emu().
+
+                    - OpenDoors will now display a small popup window when
+                      disconnecting the current connection.
+
+                    - A new variable, od_control.od_in_buf_size, can now be set
+                      prior to calling any OpenDoors function to set the size
+                      of OpenDoors combined local/remote keyboard input buffer.
+                      By default, this buffer is 256 bytes in size.
+
+                    - Previously, there were a number of OpenDoors API
+                      functions that would not correctly initialize OpenDoors
+                      if they were the first function called in the program.
+                      This has been fixed.
+
+                    - To facilitate setting of bps rates up to 115,200,
+                      od_control.baud is now an unsigned long.
+
+                    - A new setting, od_control.od_no_ra_codes, has been added
+                      to disable the use of RemoteAccess/QuickBBS control codes
+                      by od_send_file()/od_hotkey_menu()/od_disp_emu(). The
+                      RemoteAccess/QuickBBS ASCII 1 "pause for key" is also now
+                      recognized.

+ 70 - 0
odoors/historic/ODN.FRM

@@ -0,0 +1,70 @@
+Essentially, the OpenDoors Distribution Network is a list of "official"
+OpenDoors distribution sites that will be distributed with future
+versions of OpenDoors, and anyone is welcome to participate. The idea
+behind the "OpenDoors distribution network" is simply to make it easier
+for you, the OpenDoors programmer, to obtain OpenDoors updates. While
+the newest version of OpenDoors is always available from the OpenDoors
+Support BBS, and often from any system carrying the "Programmer's
+Distribution Network", most OpenDoors programmers would find it useful
+to know of a system closer to them where the newest version of OpenDoors
+is always available. Although I would like to be able to send the newest
+version of OpenDoors to any support site, the cost of doing so
+unfortunately makes this impossible. However, it is likely that you
+would pick up the newest version of OpenDoors when it is available
+anyhow, so this shouldn't really make any difference. So, if you are
+interested in becoming an official OpenDoors distribution site, simply
+fill in the form below, and send it to me, either electronically or by
+conventional mail at one of the addresses listed at the end of this
+file.
+
+Brian Pirie
+
+OPENDOORS OFFICIAL DISTRIBUTION SITE APPLICATION FORM
+-----------------------------------------------------
+
+YOUR NAME :         ________________________________________
+                    (eg. Brian Pirie)
+
+LOCATION :          ________________________________________
+                    (eg. Ottawa, Ontario, Canada)
+
+DATA NUMBER(S) :    ________________________________________
+                    (eg. (613) 526-4466)
+
+NETWORK ADDRESSES:  ________________________________________
+                    (eg. 1:243/8 in FidoNet)
+
+MODEM TYPE:         ________________________________________
+                    (eg. 9600, V32bis, v42bis, HST)
+
+OTHER INFORMATION:  ________________________________________
+
+                    ________________________________________
+                    (eg. Hours of BBS operation, file request hours,
+                    guest login and password, etc.)
+
+
+I CAN BE INFORMED OF NEW RELEASES BY:
+         ____
+        |    |  - ***ROUTED*** FIDONET NETMAIL
+        |____|
+         ____
+        |    | - OPENDOORS SUPPORT CONFERENCE
+        |____|
+         ____
+        |    | - INTERNET EMAIL
+        |____|
+         ____
+        |    | - OTHER: ________________________________
+        |____|
+
+
+  FidoNet NetMail: 1:243/8
+   InterNet EMail: [email protected]
+    OpenDoors BBS: +1 613 526 4466
+Conventional mail: 1416 - 2201 Riverside Drive
+                   Ottawa, Ontario
+                   Canada
+                   K1H 8K9
+
+

+ 170 - 0
odoors/historic/ODN.NFO

@@ -0,0 +1,170 @@
+Below is a list of sites from which the newest version of OpenDoors is
+available, as of July 11, 1993, sorted by country. If you would like
+to join the list of official OpenDoors distribution systems, please see
+the following message.
+
+In addition to the sites listed below, the newest verion of OpenDoors
+will likely be available from any system that carries "Programmer's
+Distribution Network" files. Also, if you send a self-addressed
+envelope, along with either a 3-1/2" or 5-1/4" (360K) diskette, and
+$2.00 to cover postage, I would be happy to mail the newest version of
+OpenDoors to you. My address is included in the list, below.
+
+Also, the newest version of this file is always available for download
+or file request from my system, with the filename OD_SITES.ZIP.
+
+AUSTRALIA
+---------
+Sydney, Australia - Rosalyn Anderson
+     Data Number : +61 2 552 3255
+     Modem       : 9600, v.32/PEP
+     Fidonet     : 3:712/618
+     Intlnet     : 58:2100/146
+     Comments    : 24 hours - log on as "opendoors user" password "doors"
+
+Sydney, Australia - Chris Patten
+     Data Number : +61 2 977 6820
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 3:714/906
+     Comments    : 24 hours, file request for nodelisted sysops
+
+
+CANADA
+------
+Lancaster Park, Alberta, Canada - Thomas King
+     Data Number : +1 403 973 3856
+     Modem       : 16800, v.32bis/HST/v.42bis
+     Fidonet     : 1:342/49
+     IMEX        : 89:701/513
+     Worldnet    : 62:3200/50
+     Comments    : Freq by Magic name ODOORS 23hrs/day
+                   Guest Name "Visiting Sysop" PW is "PhoneBill"
+
+Saint John, New Brunswick, Canada - George Hannah
+     Data Number : +1 506 652 7292
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 1:255/7
+     Comments    : Freq ODOORS, except during ZMH
+                   Login as OPENDOORS password GUEST
+
+Ottawa, Ontario, Canada - Brian Pirie
+     Data Number : +1 613 526 4466
+     Modem       : 9600, v.32/v.42bis
+     Fidonet     : 1:243/8
+     Internet    : [email protected]
+     Postal addr : Brian Pirie
+                   1416 - 2201 Riverside Drive
+                   Ottawa, Ontario
+                   Canada
+                   K1H 8K9
+     Comments    : Freq and BBS available 24 hours / day to everyone
+
+Mascouche, Quebec, Canada - Robert La Ferte
+     Data Number : +1 514 968 1709
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 1:167/235
+     Comments    : BBS opened 24 hours a day, 7 days/week,
+                   file request OPENDOORS for the latest version
+
+
+ITALY
+-----
+Trieste, Italy - Pietro Budicin
+     Data Number : +39 40 3783111
+     Modem       : 14400, v.32bis/HST/v.42bis
+     Fidonet     : 2:333/603
+     Comments    : Freq ODOORS and BBS 24hrs/day
+
+
+NEW ZEALAND
+-----------
+Paraparaumu, New Zealand - Phill Mckenna
+     Data Number : +64 4 298 4194
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 3:771/180
+     Comments    : 24 hour system, magic name ODOORS for file reuquest
+                   Guest User account (no password required)
+
+
+UNITED KINGDOM
+--------------
+Cambridge, United Kingdom - Marcel Cook
+     Data Number : +44 223 301487
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 2:440/34
+     Comments    : 24 hours for callers and F'REQs, instant registration.
+                   Magic name OPENDOORS gets latest version.
+
+Ipswich, Suffolk, United Kingdom - Mark Clark
+     Data Number : +44 473 692882
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 2:440/107
+     Comments    : 24 Hours/Freqs Instant Registration
+
+Ipswich, Suffolk, United Kingdom - Mike Tatum
+     Data Number : +44 473 87450
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 2:440/112
+     Comments    : 23 hours a day,
+                   Magic name of OPENDOORS to get latest version online.
+
+Mildenhall, Suffolk, United Kingdom - Dale Elrod
+     Data Number : +44 638 718623
+     Modem       : 16800, v.32bis/HST/v.42bis
+     Fidonet     : 2:440/37
+     Comments    : 23 hours a day,
+                   magic name of OPENDOORS to get latest version online.
+
+
+UNITED STATES
+-------------
+San Jose, California, USA - Darryl Perry
+     Data Number : +1 408 265 4660
+     Modem       : 9600, v.32/v.42bis
+     Fidonet     : 1:143/324
+     Comments    : Freq the full filename only.
+
+San Ramon, California, USA - Brent Johnson
+     Data Number : +1 510 830 4616
+     Modem       : 14400, v.32bis/HST
+     Fidonet     : 1:161/610
+     Comments    : 23 hours, FREQ almost anytime if listed in nodelist.
+
+Fort Myers, Florida, USA - Jeff Cochran
+     Data Number : +1 813 939 3009
+     Modem       : 16800, v.32bis/HST/v.42bis
+     Fidonet     : 1:371/26
+     Comments    : Downloads available first call
+
+Columbus, Georgia, USA - Scott Burkett
+     Data Number : +1 706 596 8126
+     Modem       : 9600, v.32
+     Fidonet     : 1:3613/12
+     Comments    : 24 Hour Operation and FREQ's
+
+Chicago, Illinois, USA - John Kristoff
+     Data Number : +1 312 587 8756
+     Modem       : 16800, v.32bis/HST
+     Fidonet     : 1:115/743
+     Comments    : Freq avaiable, 24 hrs., GUEST account available
+
+Baltimore, Maryland, USA - Mike Gurski
+     Data Number : +1 410 256 1979
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 1:261/1062
+     Echonet     : 50:5410/1062
+     Comments    : 24 hour FREQs, unlisted systems welcome
+
+Minneapolis, Minnesota, USA - Bing Wu
+     Data Number : +1 612 378 7783
+     Modem       : 19200, v.32bis/ZYX/v.42bis
+     Fidonet     : 1:282/1016
+     Comments    : 24 hours a day, F'req anytime except ZMH
+
+Muskogee, Oklahoma, USA - Vince Jacobs
+     Data Number : +1 918 687 1612
+     Modem       : 14400, v.32bis/v.42bis
+     Fidonet     : 1:3813/309
+     DoorNet     : 75:7918/200
+     Comments    : 24 Hours, FREQ hours anytime but ZMH,
+                   Guest Log In as The Inspector, password Gadget

+ 447 - 0
odoors/historic/ODTJ9304.TXT

@@ -0,0 +1,447 @@
+The
+OPENDOORS  TECH  JOURNAL
+Volume 93, Number 4                                          June 21st, 1993
+
+"The Greatest Thing to happen to Journalism Since Real People"
+                                                                                                                                      	
+This Issue:     A Word from the Editor - Scott Burkett
+                The Open Door - Brian Pirie
+                The OPENDOORS Echo
+                Where's the Source? - John Kristoff
+                Opendoors Tech Corner - Dropfile Location Logic
+                Review: BFE v1.30.2à
+                OpendDoors Snippets!
+                OpenDoors Roll Call
+                OpenDoors Distribution Network Sites
+                OpenDoors Tech Journal Information
+                                                                                                                                      	
+----------------------------------------------------------------------------
+                        A Word from the Editor:
+----------------------------------------------------------------------------
+
+Another day, another fifty cents.  Welcome once again to that info-filled,
+free-as-can-be periodical dedicated to the proposition that all C-based door 
+libraries are not equal!
+
+ODTJ is getting read worldwide.  Yup.  You know what that means?  Aside from
+the fact that it provides a wonderful forum for OD coders to share ideas and
+information, it provides:
+
+                         FREE ADVERTISING!
+
+Man!  With the distribution we are getting, ODTJ is the perfect place to
+advertise that new door!  I'll leave the rest up to you guys....
+
+All in all this is a decent issue.  It will probably be the last issue before
+the 4.20 release of OpenDoors (it should be a doozy).  On a side note, our
+BBS (Under the Nile) is not running on a USR 14.4 Sportster.  Too cool.
+
+That's it.  No more words of wisdom this month.  No more ranting and raving
+on and on about how RIP is inevitably gonna die.... :-)
+
+Alles Klaar und spater!
+
+----------------------------------------------------------------------------
+                     THE OPEN DOOR - By Brian Pirie
+----------------------------------------------------------------------------
+
+** Editor's Note **
+
+Has anyone seen this guy?  Seriously, rumor has it that Brian is up to some-
+thing *BIG*.  Of course, once the rumors have been resolved, our readers will
+be the first to know!  Why?  Because we have inquiring minds.  Hrmpf!  Look
+for Brian next month....or the next month....or the.....ad infinitum.
+
+----------------------------------------------------------------------------
+                             OPENDOORS Echo!
+----------------------------------------------------------------------------
+                                                                                                                                      		
+OPENDOORS is an internationally distributed echo designed for discussion of
+BBS door/utility programming, and in particular, Brian Pirie's OpenDoors C
+Library!
+
+The OPENDOORS Echo was created by a group of dedicated BBS door and utility
+programmers and designers, in order to promote discussions on BBS utility
+programming techniques, tasks, standards, and trends. This echo is not just
+for BBS door authors!  Discussion of practically any aspect of BBS programming
+is encouraged.  Brian Pirie, the author of OpenDoors is available for tech
+support, as are his beta-testers.
+
+The echo is not currently on the FidoNet backbone, but a feed is more than
+likely available at your favorite ODN Support Site.  Efforts are under way
+to put OPENDOORS on the fido backbone...stay tuned!
+
+----------------------------------------------------------------------------
+                        Where's the Source?
+----------------------------------------------------------------------------
+          By: John Kristoff, The Crossroads BBS (1:115/743)
+
+There seems to be a problem with source code for BBS related utilities and
+door programs being released.  Why is this?  I know we have BinkleyTerm,
+Maximus and a handful of others, but how many door games do you see that come
+with actual source code?  I don't think I've seen any.  In fact, the only
+door program that I've seen the entire source for is Brian's RAVote.
+
+Not that I expect the source code for TradeWars (even if I wanted it), but I
+find it surprising that so many door programmers are so greedy.  I started
+teaching myself C because I'm sick of relying on other authors.  I wouldn't
+be surprised if many other small time programmers have done the same.  I've
+always thought that the BBS community, and computer networks in general, are
+one of the most free, open and chaotic cultures our planet has ever seen.
+So I can't understand why people request $10, $25 or more for a files list
+generator, a simple voting booth door or those other 'dime a dozen' programs.
+
+Don't get me wrong, I pay for quality software and I have at least a dozen
+shareware programs registered.  However, if I was to register every program
+I've wanted to use for more than 30 days, I would be in the poor house.
+There are just simply too many trivial programs in which I feel their price
+isn't justified for lack of programmer committment, exhorborant price, or
+just simply the value of the product.  I'm not looking for a free ride, but
+I am looking for quite a bit more respect from my fellow programmers.  For
+users, the hobby is relatively cheap, but for sysops, it's a different story.
+Well, at least if you're honest. Us sysops have enough to worry about in
+terms of phone bills, trouble users, hardware, updates and upgrades... and so
+on.
+
+I'm including a text, simliar to this message with all programs I write to
+encourage the programmers for sysops to write free or cheap software.  I plan
+on releasing all my BBS related utilities and door programs as freeware or
+public domain (even if that is all they're worth). It's my way of giving
+something back to the community that has given me so much.  I encourage you
+to do the same, or at the very least, with some of your less popular programs.
+
+John Kristoff
+The Crossroads BBS
+(312) 587-8756
+Chicago, IL
+FidoNet: 1:115/743
+Internet: [email protected]
+
+
+** Editor's response:
+
+While I am somewhat in agreeance with John on this subject, I must also play
+devil's advocate, and come to the aid of door writers who produce solid
+products, often without the availability of source.  Source code availability
+for shareware products is simply not available, for obvious reasons.  The
+author(s) of the product cannot make source available, as potential customers
+could simply recompile the source, and effectively use the software without
+properly registering it with the author(s).  In my own case, I choose not
+to distribute source code for this reason.  For smaller, freeware titles, I
+suppose I never considered the fact that other programmers would want to
+peek at my code.  As far as price goes, I have a set $10 registration fee for
+all of my door packages, a price which I consider to be extremely low for the
+quality of my products.
+
+Very interesting point, John...anyone out there have any feelings on this?
+
+----------------------------------------------------------------------------
+              OpenDoors Tech Corner: Dropfile Location Logic
+----------------------------------------------------------------------------
+
+At this time, OpenDoors searches for the door information file (aka dropfile)
+in the following order:
+
+   1.) First, if there was a custom door information file format
+       defined in the OpenDoors configuration file, OpenDoors will
+       begin by looking for this file. OpenDoors searches for the
+       custom information file in the following order:
+             A.) The path defined in the info_path variable
+                 (which can be set by your door code and over-
+                 ridden by the configuration file BBSDir setting)
+             B.) The directory which was the current default dir
+                 at door startup time
+             C.) If any of the following environment variables
+                 exist, OpenDoors will then search for the file
+                 in the directories pointed to by these variables,
+                 in the following order:
+                              RA
+                              QUICK
+                              PCB
+                              BBS
+
+   2.) If no custom door information file was found / defined,
+       OpenDoors will then search for door information files
+       corresponding to one of the built in formats. It will search
+       for these files in the same directories, and same order, as
+       it does for the custom door information file (A - C). Within
+       each directory, it will search for files with the following
+       filenames:
+
+                         CHAIN.TXT
+                         SFDOORS.DAT
+                         DOOR.SYS
+                         CALLINFO.BBS
+                         DORINFO1.DEF
+                         DORINFOx.DEF, where x is the node number
+                                       set by your program, if
+                                       x != 1.
+
+       As soon as it finds a directory containing one of these
+       filenames, OpenDoors will stop it's door information file
+       search phase. It then begins to decide what to do with what
+       it has found. If more than one of the above filenames was
+       found in the directory in question, OpenDoors will read the
+       file with the most recent date and time stamp. This is intended
+       to overcome abiguities that can arise when a door information
+       file conversion program is being used, and a number of
+       different door information files may still exist in the
+       directory. In such a case, it is assumed that the most recently
+       created file is the one that should be used. If more than one
+       file exist with an identical date and time, OpenDoors will use
+       the file that is closer to the beginning of the above list. (ie
+       they are listed in their order of priority)
+
+       Once OpenDoors has decided which file it is going to use, it
+       may have still more decision-making to do. In the case of
+       door information file names that correspond to more than one
+       format (such as DOOR.SYS), OpenDoors will examine the file
+       to determine which format it actually is. If a dorinfo?.def
+       file is found, OpenDoors will then also search for an
+       EXITINFO.BBS file. (An EXITINFO.BBS file is always acompanyed
+       by a DORINFO?.DEF file, as it does not include all the
+       information needed by even the most basic of doors). Again,
+       if an EXITINFO.BBS file is found, OpenDoors must determine
+       which of the many EXITINFO.BBS formats it is actually dealing
+       with.
+
+       This may all sound rather complicated, but it is a well thought-
+       out strategy that is intended to asure that the correct door
+       information file will be located and used in the vast majority
+       of cases. (and to think - it does all this in the blink of an
+       eye!)
+
+----------------------------------------------------------------------------
+                          REVIEW: BFE v1.30.2à
+----------------------------------------------------------------------------
+
+BFE v1.30.2à, the flexible BBS front end system from Cairo Research Labs is
+now available!  This is an alpha release of the upcoming 1.30 version of
+BFE.
+
+BFE is a BBS front-end system that was designed to provide sysops with a
+method of running more than one BBS from the same line.  In addition, it has
+a wealth of other options available to put in on the front-end of your BBS,
+such as allowing file transfers, ANSI/ASCII file viewing, shelling to DOS
+from remote, running external programs and batch files, and much more!
+
+BFE was designed to be called from a front-end mailer, such as Frontdoor or
+Binkleyterm.  Instead of spawning directly to the BBS system, it presents
+a menu to the user, which details his immediate options.  These options can
+range from several different BBS systems, remote jobs, literally anything
+you can think of!  The BFE system can also be configured to be called
+straight from your BBS software itself, in essence, running as a normal
+door, using one of several popular BBS dropfile formats.  Read onward....
+
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ   Features  ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßß
+
+        * Complete support for ANSI/ASCII/AVATAR users (Auto ANSI detect!)
+        * *TRUE* Multinode/Multiuser Compatibility!
+        * DESQview aware!
+        * Configurable for security concerns!
+        * Custom multi-level menus with item-level password protection!
+        * Complete carrier monitoring and timeout checking
+        * Use any of 11 standard dropfiles, or define custom ones!
+        * Run as a normal door or as a frontend!  Dropfile not required!
+        * Complete BBS carousel - run multiple BBS systems
+        * ASCII/ANSI/AVATAR file support
+        * File transfer system with support for external protocols
+        * Run remote jobs, such as batch files, programs, other doors, etc.
+        * Remote OS shells!
+        * Built in chat mode
+        * Much more!
+        
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ   What's New in This Release?  ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ     o New  * Change  ! Fix
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+        o *Major* code cleanup and internal re-documenting and optimizing.
+          This will be done every periodically in order for the product to
+          continue to grow.
+        
+        o New beta naming convention:   MAJOR.MINOR.REV (Staging Level)
+          (i.e. this is 1.30.2à, v1.30, rev. 2, in alpha staging)
+
+        o Custom user input using the new PROMPT keyword!  Now, you can 
+          utilize custom input as the value for SECONDARY data fields for 
+          *any* menu type in BFE!
+        
+        o New keywords: NOPASSPARMS and PROCESS.  These are used to directly
+          manipulate the way that BFE performs calls to external processes.
+          When used with the PROMPT keyword above, just about anything can 
+          be called, in any order, with any arguments!
+          
+        o The COLOVERRIDE option has been added, to allow each individual
+          menu option to use its own unique color.  This overrides the global
+          DESCRIPCOL keyword in each .CTL file.  (Thanks to Chris Koziol)
+
+        o Upload capability now in place!  This involved changes to the
+          PROTOCOL.BFE file, and adding a new type "U" option.
+          
+        ! If BFE cannot locate ASCII/ANSI/AVATAR screens at display time,
+          it will log an error entry into the logfile, and will no longer
+          wait for a remote keystroke to continue. (Thanks to R. Guevarra) 
+
+        o Generic File Transfer System now in place!  The new system allows
+          the use of configurable external protocols (no more hardcoded DSZ!)
+
+        o WELCOMESCREEN option added, to provide a global intro screen to be
+          displayed upon entering the BFE system (shown once only).  As with
+          all of the file display capabilities of BFE, the file can be in
+          ASCII, ANSI, or in AVATAR formats.  BFE will display the one which
+          best fits the user's terminal settings.
+
+        o The "time to next event" option has been put back into the system,
+          and is now passed via a new "-t" switch. (i.e. -t60, -t%3, etc).
+          This value is passed to external procedures (Type "R").
+
+        * The "O" type (Remote OS Shell) now utilizes the COMSPEC environment
+          variable to locate the command processor.  The command processor
+          was formerly specified in the SECONDARY field.  Previously, if
+          this value was keyed in wrong, it resulted in BFE locking up
+          the system.  Using COMSPEC should make this a bit cleaner.
+
+        o Still more documentation changes!
+
+FREQ:  BFE from:  Under the Nile! 14.4/v.32   1:3613/12 (706) 596-8126
+       93K                      
+                
+Scott Burkett,
+Cairo Research Labs
+
+----------------------------------------------------------------------------
+                       OPENDOORS SNIPPPPPPPPPETS!!!!!!
+----------------------------------------------------------------------------
+
+By: Mark Williamson, Software Solutions (1:214/54)
+
+Here's yet another way to center your favorite text string:
+
+void str_center(int line,char *string,char *color)
+{
+    int col = 40 - (strlen(string) / 2);
+
+    /* This method uses the length of the string to center it.  So,
+       you would get strange results if you embedded control codes
+       in your string.
+    */
+
+    if(od_control.user_ansi) {         // This way allows you to specify
+       od_set_cursor(line,col);        // the line to put the text on.
+       od_printf(color);               // Use the `white on blue` code types
+       od_disp_str(string);
+    }
+    else {
+       od_repeat(' ',col);             // This method uses the CURRENT
+       od_disp_str(string);            // line the cursor happens to be on.
+    }
+    return;
+}
+
+Mark Williamson
+Software Solutions BBS
+1:214/54
+Lemoore CA
+(209)997-0224
+v32/v42bis
+Open access to first time callers.
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+By: Mark Williamson, Software Solutions (1:214/54)
+
+/******************************************************
+* fill_box() by Mark Williamson, Software Solutions BBS
+*               Fidonet 1:214/54, (209)997-0224
+*
+* This code will paint a box in the
+* specified color using the specified
+* character using ansi/avatar graphics.
+* Note that this does not make a border!
+* Only 'fills' a block on the screen.
+* Can be used to clear just parts of a screen.
+*
+* Call the function with the following parameters:
+*
+*   int srow,scol:  Starting row,col of the box
+*   int erow,ecol:  Ending row,col of the box
+*   int attrib:     od_set_attrib() style color code
+*   char fill_char: The character to use to paint the
+*                   block.  Use ' ' to clear a block.
+*
+*   This code is placed in the public domain.
+******************************************************/
+
+#include <stdio.h>
+#include "opendoor.h"
+
+void fill_box(int srow, int scol, int erow, int ecol,
+              int attrib, char fill_char);
+
+void main(void)
+{
+    fill_box(3,10,13,40,0x1f,'°');
+}
+
+void fill_box(int srow, int scol, int erow, int ecol,
+              int attrib,char fill_char)
+{
+   int line_len,x;
+   if(srow<1) srow=1;
+   if(erow>24) erow=24;
+   if(scol<1) scol=1;
+   if(ecol>80) ecol=80;
+   line_len=ecol-scol;
+   od_clr_scr();
+   od_set_attrib(attrib);
+   for(x=srow;x<erow+1;x++) {
+      od_set_cursor(x,scol);
+      od_repeat(fill_char,line_len);
+      }
+}
+
+
+----------------------------------------------------------------------------
+                 OpenDoors Tech Journal Information
+----------------------------------------------------------------------------
+
+Editors: Scott Burkett
+         Under the Nile BBS
+         1:3613/[email protected]
+         56:101/[email protected]
+         (706) 596-8126 14.4     
+         1113 29th Street
+         Columbus, GA 31902
+
+         Brian Pirie
+         BP ECOMM Systems
+         1:243/[email protected]
+         [email protected] (Internet EMail)
+         (613) 526-4466 14.4
+         1416 - 2201 Riverside Drive
+         Ottawa, Ontario
+         Canada
+         K1H 8K9
+
+Published by and for programmers and users of the OpenDoors C Communications
+Library and BBS Interface Kit by Pirie Enterprises.  It is a compilation of
+tips, reviews, and tidbits pertaining to BBS programming and general usage.
+The opinions expressed in this publication do not necessarily represent those
+of its editors, the OpenDoors author, or other contributors.
+
+OBTAINING COPIES:  The latest copy of the OpenDoors Tech Journal will always
+be available under the magic name of TECHD (for the DOS version), and TECHW
+(for the Windows .WRI version) at both of the systems listed above.
+
+SUBMISSIONS: You are encouraged to submit articles for publication in the
+journal.  Please send all items to one of the afore-mentioned systems via BBS
+upload or mailer file/attach.
+
+----------------------------------------------------------------------------
+   ->< End of The OpenDoors Tech Journal - Volume 93 Issue Number 4 ><-
+----------------------------------------------------------------------------
+ 	

+ 724 - 0
odoors/historic/ODTJ9305.TXT

@@ -0,0 +1,724 @@
+The
+OPENDOORS  TECH  JOURNAL
+Volume 93, Number 5                                          July 20th, 1993
+
+"The Greatest Thing to happen to Journalism Since Real People"
+                                                                                                                                      	
+This Issue:     A Word from the Editor - Scott Burkett
+                The Open Door - Brian Pirie (not!)
+                The Fidonet OPENDOORS Echo - Latest FAQ
+                Opendoors Tech Corner - ColorToString();       
+                In Search Of - The Art of Debugging
+                Review: VID v2.01 - The Virus Information Door
+                OpenDoors Snippets!
+                OpenDoors Tech Journal Information
+                                                                                                                                      	
+----------------------------------------------------------------------------
+                        A Word from the Editor:
+----------------------------------------------------------------------------
+
+Finally!  After months of long-distance mail polling, the OPENDOORS echo is
+now residing on the North American fidonet backbone!  Yep.  Sorry, Ma Bell.
+More on this later, and now .... the rest of the story.  Sorry Paul Harvey.
+
+Listen up children, today's editorial survey question is one which is sure to 
+draw quite a bit of flak from programmers around the globe.  Which is the 
+better stimulant, Jolt Cola or Maxwell House? :-)
+
+DoorNet!  That's right.  Strange things are amiss at the Circle-K!  Look for
+an OPENDOORS conference (both mail and file based) to appear soon on the
+DoorNet backbone listing.  Thanks go out to Vince Jacobs for his work on
+getting this implemented.  This should make ODTJ and OD distribution a bit
+easier on all of us.  There has been mention of gating the current fidonet
+OPENDOORS echo through to Doornet.  I applaud this idea and welcome it with
+much grandiose.  Ahem.
+
+On another wavelength entirely, several hundred netmail messages were lost
+from Under the Nile's mail machine a few weeks ago.  Unfortunately, there
+were a few requests for product announcements contained therein.  If the
+authors would be so kind as to send them back in, we will gladly publish
+them in the next edition.  Danke!
+                                 
+On a serious note, my gerbil died.  Peace, and a bottle of hair grease....
+
+(Scott, it's time for your medication....yes, mother)
+
+----------------------------------------------------------------------------
+                     THE OPEN DOOR - By Brian Pirie
+----------------------------------------------------------------------------
+
+Unfortunately (I know, I know), Brian was unavailable for this issue.  I
+finally tracked him down in Tibet, busily polling the local populace for
+new ideas for the 4.20 release.  At any rate, look for him in ODTJ #6, along
+with (hopefully) OpenDoors 4.20... :-)
+
+----------------------------------------------------------------------------
+               OPENDOORS Echo FAQ - Frequently Asked Questions
+----------------------------------------------------------------------------
+By: Brian Pirie                                                                                                                                 
+July 17th, 1993
+
+ABOUT THIS FAQ
+--------------
+
+This document is a collection of frequently asked questions and answers.
+If you have any questions about OpenDoors or the OPENDOORS echo, please
+first refer to this FAQ. Not only will this help to reduce unnecessary
+trafic in the echo, but it will also provide you with the answer to many
+questions much more quickly than you would have otherwise.
+
+If you have any suggestions for additions or changes to this FAQ, please
+direct them to the moderator, Brian Pirie (FidoNet: 1:243/8, Internet:
[email protected]).
+
+Since the OPENDOORS echo and this FAQ are currently under a state of
+change, this FAQ will be posted on a very regular basis for the time
+being. In the future, the FAQ will probably be automatically posted on a
+bi-weekly basis.
+
+CONTENTS
+--------
+
+ 1.) What are the rules of the OPENDOORS echo?
+ 2.) What IS OpenDoors?
+ 3.) Where can I get a copy of OpenDoors?
+ 4.) What is the newest version of OpenDoors?
+ 5.) How much does OpenDoors cost?
+ 6.) Is the OpenDoors source code available?
+ 7.) Are there beta test versions of OpenDoors?
+ 8.) How can I contact the author of OpenDoors?
+ 9.) What IS the OpenDoors echo?
+10.) What about the echo archives?
+11.) How can I get help with OpenDoors or BBS door/utility programming?
+12.) What guidelines are there for posting source code?
+
+1.) WHAT ARE THE RULES OF THE OPENDOORS ECHO?
+    ----------------------------------------
+
+The rules of the OPENDOORS echo are few and far between. The most
+important rules are those that are standard to all EchoMail conferences
+that are distributed as a part of the FidoNet backbone distribution
+system. The echo may not be used for illegal purposes, nor is profane
+language accepted. Beyond this, your are trusted to use your own
+judgement. While it is important to have as high a "signal to noise
+ratio" as possible, it is also important to recognize the diverse group
+of people you are communicating with though the OPENDOORS echo. There is
+a wide range of technical knowledge, knowledge of network etiquette, and
+personal background. If you can try to be as understanding and helpful
+as possible, your doing so will help to keep friction and flaming to a
+minimum.
+
+Since the participants in the OPENDOORS echo are generally all
+programmers, it seems natural that they will want to tell the world
+about the programs they have written. For this reason, announcements of
+new programs - either written with OpenDoors or of interest to the
+participants of this echo - is encourage. However, advertising of new
+programs is not the primary purpose of the echo. For this reason, we
+would ask that you refrain from posting the same advertisment message
+more than once within a reasonable length of time (perhaps a month).
+
+If you are having any difficulty with the OPENDOORS echo - technical,
+social, or otherwise - please feel more than free to contact the
+moderator, Brian Pirie (1:243/8).
+
+2.) WHAT IS OPENDOORS?
+    -----------------
+
+OpenDoors is a Turbo C(++) / Borland C++ door programming toolkit used
+by well over 100 door programmers around the world. OpenDoors handles
+all of the details of door programming, such as communicating with the
+modem, interfacing with virtually any BBS package, maintaining status
+lines, monitoring carrier detect and user timeouts, handling sysop
+function keys such as DOS shell or chat, providing advanced ANSI/AVATAR
+graphics support, and much more.
+
+OpenDoors is designed to allow you to write door programs just as you
+would write any other C program, without requiring any additional effort
+or knowledge. You can easily create fully functional door programs with
+just a few lines of code. OpenDoors is so easy to use that many
+OpenDoors programmers have begun using it with no programming
+experience.
+
+OpenDoors directly interfaces with all of the most popular BBS packages
+including RemoteAccess, QuickBBS, Telegard, Wildcat, PC-Board, Maximus,
+Renegade, EzyCom, RBBS-PC, Spitfire, SuperBBS, RoboBoard, WWIV and many
+others. In addition, OpenDoors allows the sysop to specify custom door
+information file formats to permit your doors to run on virtually any
+BBS system.
+
+Included with OpenDoors are a number of example doors that you can
+compile, alter, or use as a basis for your own doors. Among these
+example doors are a voting booth type door and an ANSI music
+demonstration door, and dozens of sample doors within the door
+programming tutorial manual.
+
+OpenDoors also provides a number of special features that you can
+optionally include in your doors, such as transparent configuation file
+support, logfile support, FILES.BBS listing, and many advanced
+ANSI/AVATAR graphics routines.
+ 
+3.) WHERE CAN I GET A COPY OF OPENDOORS?
+    -----------------------------------
+
+Below is a short table listing sites from which the newest version of
+OpenDoors is available, as of April 19th, 1993. In addition to the sites
+listed below, the newest verion of OpenDoors will likely be available
+from any system that carries "Programmer's Distribution Network" files.
+Also, if you send a self-addressed envelope, along with either a 3-1/2"
+or 5-1/4" (360K) diskette, and $2.00 to cover postage, I would be happy
+to mail the newest version of OpenDoors to you. My address is listed in
+section 8.
+
+Also, the most recent list of OpenDoors distribution sites is always
+available for download or file request from my system, in the file
+OD_SITES.ZIP. If you are interested in becoming an official OpenDoors
+distribution site, please see the file included in the OD_SITES.ZIP
+archive.
+
+-------------------------------------------------------------------------------
+                                    FIDONET
+LOCATION                            ADDRESS       DATA NUMBER     MODEM
+-------------------------------------------------------------------------------
+Sydney, Australia                   3:712/618     +61 2 552 3255  (v.32/PEP)
+Sydney, Australia                   3:714/906     +61 2 977 6820  (v.32bis/HST)
+Lancaster Park, Alberta, Canada     1:342/49      +1 403 973 3856 (v.32bis/HST)
+Saint John, New Brunswick, Canada   1:255/7       +1 506 652 7292 (v.32bis)
+Ottawa, Ontario, Canada             1:243/8       +1 613 526 4466 (v.32)
+Mascouche, Quebec, Canada           1:167/235     +1 514 968 1709 (v.32bis)
+Trieste, Italy                      2:333/603     +39 40 3783111  (v.32bis/HST)
+Paraparaumu, New Zealand            3:771/180     +64 4 298 4194  (v.32bis)
+Cambridge, United Kingdom           2:440/34      +44 223 301487  (v.32bis)
+Ipswich, Suffolk, United Kingdom    2:440/107     +44 473 692882  (v.32bis)
+Ipswich, Suffolk, United Kingdom    2:440/112     +44 473 87450   (v.32bis)
+Mildenhall, Suffolk, United Kingdom 2:440/37      +44 638 718623  (v.32bis/HST)
+San Jose, California, USA           1:143/324     +1 408 265 4660 (v.32)
+San Ramon, California, USA          1:161/610     +1 510 830 4616 (v.32bis/HST)
+Fort Myers, Florida, USA            1:371/26      +1 813 939 3009 (v.32bis/HST)
+Columbus, Georgia, USA              1:3613/12     +1 706 596 8126 (v.32)
+Chicago, Illinois, USA              1:115/743     +1 312 587 8756 (v.32bis/HST)
+Baltimore, Maryland, USA            1:261/1062    +1 410 256 1979 (v.32bis)
+Minneapolis, Minnesota, USA         1:282/1016    +1 612 378 7783 (v.32bis)
+Muskogee, Oklahoma, USA             1:3813/309    +1 918 687 1612 (v.32bis)
+-------------------------------------------------------------------------------
+ 
+4.) WHAT IS THE NEWEST VERSION OF OPENDOORS?
+    ---------------------------------------
+
+Version 4.10 is the most recently released version of OpenDoors. There
+is a more recent beta-test version of OpenDoors. For information on this
+beta-test version, see section 7.
+ 
+5.) HOW MUCH DOES OPENDOORS COST?
+    ----------------------------
+
+OpenDoors is distributed on a try-before-you-buy basis. You can pickup a
+copy of OpenDoors from any of the OpenDoors distribution sites, listed
+above, to have a closer look at the package. However, if you wish to
+continue using OpenDoors after the three week trial period, you must
+purchase an OpenDoors registration. Full details on registering
+OpenDoors is included in the OpenDoors manual. However, a brief table
+listing the prices within a number of countries is listed below:
+
+        -----------------------------------------------
+                                 REGISTRATION
+        REGISTRATION ONLY        AND SOURCE CODE
+        -----------------------------------------------
+        34 Canadian Dollars      68 Canadian Dollars
+        28 US Dollars            56 US Dollars
+        18 British Pounds        36 British Pounds
+        150 French Francs        300 French Francs
+        44 German Marks          88 German Marks
+        50 Netherland Gilders    100 Netherland Gilders
+        39 Australian Dollars    78 Australian Dollars
+        -----------------------------------------------
+ 
+6.) IS THE OPENDOORS SOURCE CODE AVAILABLE?
+    --------------------------------------
+
+Yes, the OpenDoors source code is available, at a cost equal to the
+registration, to registered users. Both the regisration and source code
+may also be purchased at the same time, for a cost equal to twice the
+normal registration fee. When you purchase the OpenDoors source code,
+the most recent version of the source code is sent to directly. Also,
+you will be entitled to free upgrades to all future versions of the
+source code. Whenever you wish to pick up a new version of the source
+code, you may download it from the OpenDoors support BBS, arrange to
+pick it up via a FidoNet-compatible mailer (simply send me a message
+asking me to place the source code package "on hold" for you to poll and
+pick up), or by sending a diskette and self-addressed diskette mailer.
+ 
+7.) ARE THERE BETA TEST VERSIONS OF OPENDOORS?
+    -----------------------------------------
+
+Yes. The beta test versions of OpenDoors are available to registered
+OpenDoors users. However, keep in mind that the beta test version has
+some new features that are still under development, and has not yet been
+thoroughly tested. To save space, the documentation is not included with
+the beta test version. As such, it is assumed that you also have the
+most recent non-beta version of OpenDoors. The most recent beta version
+may be file-requested from 1:243/8 as OD_BETA
+  
+8.) HOW CAN I CONTACT THE AUTHOR OF OPENDOORS?
+    -----------------------------------------
+
+If you wish to contact the author of OpenDoors, Brian Pirie, please feel
+free to do so. I may be reached by any of the following means:
+
+        FidoNet NetMail   : 1:243/8
+
+        Internet EMail    : [email protected]
+
+        Modem (BBS)       : +1 613 526 4466
+
+        Conventional Mail : Brian Pirie
+                            Apt. #1416 - 2201 Riverside Drive
+                            Ottawa, Ontario
+                            K1H 8K9
+                            Canada
+ 
+9.) WHAT IS THE OPENDOORS ECHO?
+    --------------------------
+
+The OPENDOORS echomail conference is devoted to OpenDoors and BBS door /
+utility programming in general. The OPENDOORS echo serves as a place
+where people working with OpenDoors can share ideas, source code
+examples, and other tricks and techniques. Through the OPENDOORS echo
+you can receive help with OpenDoors and programming in general. Also
+available through the OPENDOORS echo is information on future versions
+of OpenDoors and recent developments of concern to BBS door and utility
+programmers. The OPENDOORS echo is also place for suggestions for future
+versions of OpenDoors, OpenDoors bug reports, a place to announce the
+availablility of your programs, and much more information of interest to
+OpenDoors programmers. There are participants in the OpenDoors echo from
+throughout Canada and the U.S., as well as people in Europe and
+Australia.
+
+10.) WHAT ABOUT THE ECHO ARCHIVES?
+     ----------------------------
+
+Although we attempt to answer the most commonly asked questions in this
+FAQ, there is so much discussed in the OPENDOORS echo that it is
+impossible to address every possible question. As such, you may be
+interested in referring to the OPENDOORS echo archives, in order to
+learn more about OpenDoors and BBS door/utility programming in general.
+
+The OPENDOORS echo archives are prepared on a monthly basis, and
+are available from the moderators system. Currently, the following
+archives are available for request from 1:243/8 or download from the BBS
+at +1 613 526 4466:
+
+ODJUL92.ZIP    42776  Discussion from the OpenDoors echo, July '92
+ODAUG92.ZIP    35432  Discussion from the OpenDoors echo, August '92
+ODSEP92.ZIP    36308  Discussion from the OpenDoors echo, September '92
+ODOCT92.ZIP    30922  Discussion from the OpenDoors echo, October '92
+ODNOV92.ZIP    34844  Discussion from the OpenDoors echo, November '92
+ODDEC92.ZIP    53647  Discussion from the OpenDoors echo, December '92
+ODJAN93.ZIP    24683  Discussion from the OpenDoors echo, January '93
+ODFEB93.ZIP    41562  Discussion from the OpenDoors echo, February '93
+ODMAR93.ZIP    24080  Discussion from the OpenDoors echo, March '93
+ODAPR93.ZIP    30027  Discussion from the OpenDoors echo, April '93
+ODMAY93.ZIP    39440  Discussion from the OpenDoors echo, May '93
+ODJUN93.ZIP    56615  Discussion from the OpenDoors echo, June '93
+
+11.) HOW CAN I GET HELP WITH OPENDOORS OR BBS DOOR/UTILITY PROGRAMMING?
+     -----------------------------------------------------------------
+
+If you have any problems with a program, please feel more than free to
+post a message here, asking for help. Afterall, that is one of the
+central purposes of this echo. However, try to keep the following points
+in mind when asking a question. Doing so will help others to better
+understand your problem, and as such will help them help you.
+
+          A.) If you are having a problem with a program, try to
+              describe as much about the problem as possible. If you
+              can, precisely how to reproduce the problem. If you wish
+              to do so, posting source code can also be of great
+              advantage. For more information on posting source code in
+              the echo, please see section 12.
+
+          B.) Explain what steps you have already taken in trying to
+              solve your problem. This will allow others to pick up from
+              where you have become "stuck", and not suggest solutions
+              that you have already tried yourself.
+
+
+
+12.) WHAT GUIDELINES ARE THERE FOR POSTING SOURCE CODE?
+     -------------------------------------------------
+
+You are more than welcome to post source code in this echo. If you do
+so, please keep the following guidelines in mind.
+
+          A.) Unless you explicitly say otherwise, any source code
+              posted in this echo will be considered to be released to
+              the public domain. If you have some source code that you
+              do not wish others to copy, don't post it here!
+
+          B.) For your source code to be useful to others, it has to be
+              understandable. Adding comments can be of great benifit to
+              someoone trying to understand your code.
+
+          C.) If you are posting a program written with OpenDoors,
+              please be sure that you do NOT include your registration
+              key in the source code!
+
+----------------------------------------------------------------------------
+               OpenDoors Tech Corner: ColorToString()        
+----------------------------------------------------------------------------
+             
+             Authored by: Brian Pirie, OpenDoors author
+
+/* Function to convert an integer color attribute to an OpenDoors */
+/* style color description string, in the format:                 */
+/*                                                                */
+/*         [Flashing] [Bright] [Foreground] on [Background]       */
+/*                                                                */
+/* The function takes two parameters, an unsigned integer         */
+/* representing the input color, and a pointer to a string where  */
+/* the colour description should be output. Be sure that this     */
+/* string is large enough to hold the largest possible color      */
+/* description string. (If the code is not altered, the largest   */
+/* possible string will be 35 characters, including the null      */
+/* terminator                                                     */
+
+void ColorToString(unsigned int color, char *outString)
+{
+   /* Array containing names of various colors */
+   static char colorString[8][33]={"Black",
+                                   "Blue",
+                                   "Green",
+                                   "Cyan",
+                                   "Red",
+                                   "Magenta",
+                                   "Yellow",
+                                   "White"};
+
+   /* Initialize string */
+   outString[0]='\0';
+
+   /* If flashing bit is set, output "Flashing" string + space */
+   if(color & 0x80) {
+      strcat(outString, "Flashing ");
+   }
+
+   /* If bright bit is set, output "Bright" string + space */
+   if(color & 0x08) {
+      strcat(outString, "Bright ");
+   }
+
+   /* Output foreground color */
+   strcat(outString, colorString[color & 0x07]);
+
+   /* Output the word "on" with a space before & after */
+   strcat(outString, " on ");
+
+   /* Output background color */
+   strcat(outString, colorString[(color & 0x70) >> 4]);
+}
+
+Editor's Note:  Brian was kind enough to put this function together for us
+upon request for a function to perform the reverse of the od_color_config()
+function.  I had to add a bracket or two that was left out (he did mention
+that it was untested code!), but it works like a champ.
+
+----------------------------------------------------------------------------
+                 In Search Of - The Art of Debugging
+                        By Mark Williamson
+----------------------------------------------------------------------------
+
+It happens.  The inevitable.  You have spent so many hours trying to
+write the most optimized, cleanest code possible.  But your best efforts
+are laid to rest when some verocious little creature pops up, seemingly
+at random, and wreaks havoc on all your efforts.
+
+What causes these bugs to appear?  Can it be programming style?  Or a
+forgotten temporary variable?  Where, or where, is that bug!
+
+If this has happened to you, don't feel bad.  50 percent of your time
+programming will be devoted to the debugging cycle.  Many books have
+been written that are devoted to the art of programming style, debugging
+and the development cycle.  I will only touch on a couple of pointers to
+get you started in the right direction in locating that invisible target
+called so affectionately, the "bug."
+
+Case scenario:  I was working on a program that would shell to DOS using
+one of the OD_SPAWN... functions.  OPENDOOR.DOC discusses in
+good detail how to use either of these functions.  But, being the eager
+programmer that I am, I happily went about my way after I read what I
+thought I needed to know.  The program in question uses a temporary
+directory to store files extracted from .ZIP/.ARJ/etc.. archive files.
+As the program was running, it worked just great.  Unpacked the files
+using PKZIP.  Repacked them using ARJ.
+
+Then I tried it again.  That's when it all fell apart for me.
+Unwillingly, I just started a four-day straight debugging session,
+lasting until the wee hours of the night.
+
+Point one:  Always read the docs...thoroughly!
+
+During this four day debugging frenzy I removed 20 or more variables I
+didn't need, rewrote five functions to be more independent and portable
+(ie modular programming) and greatly optimized the program overall.
+
+But I still couldn't find the bug.
+
+Here's a sample of the code:
+
+    od_set_cursor(15,20);
+	od_printf("`bright cyan`Unpacking");
+    error=run_it(progname,unpackcommand);     // see the run_it function
+                                              // below.
+    if(error==0){
+	    od_set_cursor(15,33);
+        od_printf("`bright green`ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ `bright cyan` Ok    ");
+	}
+
+The above code did not do as it appears it should.  In fact, as soon as
+the trace feature of Turbo C++ 3.0 hit the line that prints out the bar
+and the word "Ok", what actually was printed out was something like
+this:
+
+
+           Unpacking :\BBSFILES\BBSUPLOADS\CHKZIP.ZIP
+
+but not:
+
+           Unpacking ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ  Ok
+
+Now remember, I hadn't read the section on the od_swapping_path variable
+yet.
+
+The OD_SPAWN.. functions will swap to either EMS memory or DISK.  The
+variable od_control.od_swapping_path is empty by default, which equates
+to the CURRENT DIRECTORY!.  Thus, when my utility unpacked/repacked the
+files, it packed up the swap file also.  Then, when I ran it the second
+and future times on the same archive file, I was overwriting the swap
+file!  Effectively overwriting the program's data and code segments.
+
+Lesson learned:  Read the docs..first.  Don't use the old tried and
+untrue "If all else fails, read the docs."  Read them first.  Read them
+several times.
+
+How to debug:
+
+If your compiler has a trace feature, use it.
+
+Put all the variables in the watch window that could possibly have any
+bearing on the function your are testing.  Look for suspicious changes
+in values when the variable has not been 'touched'.
+
+Minimize global variable useage.  This makes portability difficult.  If
+you write functions that do not rely on any outside information except
+that which is passed as an parameter, then it will be much easier to use
+the same functions over and over, throughout many of your projects.
+
+Use descriptive variables.  C gives you much luxury in the length of
+your variable names.  Use this to the max.  How many times have you
+scratched your head and wondered "what does this one do?"
+
+Use descriptive function names.  See above
+
+Read other programmer's source for ideas.  Many times you will probably
+be reinventing the wheel.  Read the C snippets for ideas.  Check out
+some other source code to see how it's done elsewhere.  This may give
+you much needed insight into the art of programming style.
+
+And last but not least.. Use comment lines!  Don't be vague.  Commenting
+your source code will most likely help only one person.  You.  But, you
+are the only one that matters anyway right?
+
+Good luck and press on!
+
+Mark Williamson
+Software Solutions
+Fido 1:214/54
+
+Home of Labtest 2.00 - The Definitive Upload Processor
+Written entirely in Turbo C++ 3.0 using Brian Pirie's Open Doors 4.1
+
+
+----------------------------------------------------------------------------
+                          REVIEW: VID v2.01
+----------------------------------------------------------------------------
+
+VID v2.01, the BBS Virus Information Door from Cairo Research Labs, is now
+available!
+
+The original VID was designed to provide a quick online virus reference for 
+BBS users, but has quickly evolved into quite a bit more!  Thanks to a 
+tremendous response from Sysops around the world, VID has provided BBS users 
+with quick, accurate viral assessments while online on their favorite BBS 
+system.
+
+What's New in This Release?
+
+  o Over 100 new viruses added, bringing the total up to 1,557 entries.
+
+  o Cleaned up display in several of the search and list options,
+    including multi-column virus listings (Thanks to Chris Koziol).
+
+  o Provided an option for online documentation for end-users.
+
+  * Due to an internal problem in the original 2.00 release, you must
+    use v2.01+ to use any new enhancement modules.  The database
+    structures have changed a bit.
+
+  * The periodic VID integrity check has been enhanced.  VID now
+    stores its integrity information in a file called SANITY.CHK.
+    Ensure that this file resides in the VID directory!
+          
+  * In the 2.00 release, the VIDDEF.TBL file had to reside in the
+    path.  This was causing a message subsystem initialization error
+    if VID could not locate this file in the path!  VID now looks in
+    the current directory first. (Thanks to Steve Pepin)
+
+  ! In the Behavior search, although the default answer is "Ignore",
+    the prompt showed the default as "No".  Fixed! (Steve Pepin).
+
+  ! If VID was typed alone with no arguments, the old switches from
+    the 1.10 release were displayed! Fixed! (Steve Pepin, again!).
+                  
+  ! VID now handles extraneous spaces in registration keys.  This 
+    occurred when clipping a key from a netmail message.  Squashed! 
+    (Thanks to Bart Holiman).
+
+  ! In the 2.0 datafiles, the "Stealth" flag on several viruses was
+    not set properly.  Squashed!
+
+FREQ:     VID - This will get you the VID engine, documentation, Lite-level
+                database, and any release notes.  165K in size (VID201.ZIP).
+
+      VIDPLUS - This will get you the VID+ modules.  You must have the VID
+                engine (above) for this to be functional!  This expands
+                to around 1.5MB or so.  323K in size (VP0793.ZIP).
+
+         FROM - Under the Nile BBS, 1:3613/12, 14.4 USR
+                (706) 596-8126
+
+---------------------------------------------------------------------------
+                       OPENDOORS SNIPPPPPPPPPETS!!!!!!
+----------------------------------------------------------------------------
+By : Mark Williamson			 
+
+The previous post of this code had a little error.  In the fill_box()
+function, remove the od_clr_scr().  I had put that in for test purposes.  Sorry
+bout that!
+
+/********************************************************************/
+
+I would like to submit this code for the next issue of ODTJ.  Please
+post my name and bbs info in the ODTJ with this code:
+
+/******************************************************
+* fill_box() by Mark Williamson, Software Solutions BBS
+*               Fidonet 1:214/54, (209)997-0224
+*
+* This code will paint a box in the
+* specified color using the specified
+* character using ansi/avatar graphics.
+* Note that this does not make a border!
+* Only 'fills' a block on the screen.
+* Can be used to clear just parts of a screen.
+*
+* Call the function with the following parameters:
+*
+*   int srow,scol:  Starting row,col of the box
+*   int erow,ecol:  Ending row,col of the box
+*   int attrib:     od_set_attrib() style color code
+*   char fill_char: The character to use to paint the
+*                   block.  Use ' ' to clear a block.
+*
+*   This code is placed in the public domain.
+******************************************************/
+
+#include <stdio.h>
+#include "opendoor.h"
+
+void fill_box(int srow, int scol, int erow, int ecol,
+              int attrib, char fill_char);
+
+void main(void)
+{
+    fill_box(3,10,13,40,0x1f,'°');
+}
+
+void fill_box(int srow, int scol, int erow, int ecol,
+              int attrib,char fill_char)
+{
+   int line_len,x;
+   if(srow<1) srow=1;
+   if(erow>24) erow=24;
+   if(scol<1) scol=1;
+   if(ecol>80) ecol=80;
+   line_len=ecol-scol;
+/*   od_clr_scr();                     OOPS!  TAKE THIS LINE OUT */
+   od_set_attrib(attrib);
+   for(x=srow;x<erow+1;x++) {
+      od_set_cursor(x,scol);
+      od_repeat(fill_char,line_len);
+      }
+}
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+// The following function provided by Brian Pirie
+// during a telephone conversation with Brian and myself
+//
+// Arguments:  Prog - the path/filename of the program to run
+//             command_line - path/filename of the program and any
+//             command line arguments the program needs.
+//
+// Example:    prog = "PKUNZIP"
+//             command_line = "PKUNZIP TESTFILE.ZIP"
+//
+// Return value:  errorlevel of child process
+int run_it(char *prog,char *command_line)
+{
+    char *arg[3];
+    arg[0]=prog;
+    arg[1]=command_line;
+    arg[2]=NULL;
+    strcpy(arg[1],strshl(arg[1],strlen(prog)));
+    return(od_spawnvpe(P_WAIT,prog,arg,NULL));
+}
+
+----------------------------------------------------------------------------
+                 OpenDoors Tech Journal Information
+----------------------------------------------------------------------------
+
+Editors: Scott Burkett
+         Under the Nile BBS
+         1:3613/[email protected]
+	 [email protected] (Internet EMail)
+	 (706) 596-8126 14.4 USR 
+         1113 29th Street
+         Columbus, GA 31902
+
+         Brian Pirie
+         BP ECOMM Systems
+         1:243/[email protected]
+         [email protected] (Internet EMail)
+         (613) 526-4466 14.4
+         1416 - 2201 Riverside Drive
+         Ottawa, Ontario
+         Canada
+         K1H 8K9
+
+Published by and for programmers and users of the OpenDoors C Communications
+Library and BBS Interface Kit by Pirie Enterprises.  It is a compilation of
+tips, reviews, and tidbits pertaining to BBS programming and general usage.
+The opinions expressed in this publication do not necessarily represent those
+of its editors, the OpenDoors author, or other contributors.
+
+OBTAINING COPIES:  The latest copy of the OpenDoors Tech Journal will always
+be available under the magic name of TECHD (for the DOS version), and TECHW
+(for the Windows .WRI version) at both of the systems listed above.
+
+SUBMISSIONS: You are encouraged to submit articles for publication in the
+journal.  Please send all items to one of the afore-mentioned systems via BBS
+upload or mailer file/attach.
+
+----------------------------------------------------------------------------
+   ->< End of The OpenDoors Tech Journal - Volume 93 Issue Number 5 ><-
+----------------------------------------------------------------------------
+ 	

+ 2244 - 0
odoors/historic/ODTJ9402.TXT

@@ -0,0 +1,2244 @@
+ÚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¿
+³ Ü  Ü                             Ü                  ³ Volume 94  Issue 2  ³
+³ÜÛÜ Û   Ûßß     ÜÜÜ ÛßÛ Ûßß ÛÛÜ ÜÜÛ ÜÜÜ ÜÜÜ ÜÜÜ Ûßß  ³ September 23, 1994  ³
+³ Û  ÛßÛ Ûß      Û Û ÛÜÛ Ûß  Û Þ Û Û Û Û Û Û Û   ßßÛ  ³                     ³
+³ ÛÛ Û Û ÛÛÛ     ÛÛÛ Û   ÛÛÛ Û Þ ÛÛÛ ÛÛÛ ÛÛÛ Û   ÛÛÛ  ³ "The greatest thing ³
+³ Ü          Ü                               Ü        ³  to happen to       ³
+³ÜÛÜ Ûßß ÜÜÜ Û         Û ÜÜÜ Û Û ÜÜÜ ÛÛÜ ßßÛ Û        ³  journalism since   ³
+³ Û  Ûß  Û   ÛßÛ     Ý Û Û Û Û Û Û   Û Þ ÛßÛ Û        ³  Real People!"      ³
+³ ÛÛ ÛÛÛ ÛÛÛ Û Û     ÛÛÛ ÛÛÛ ÛÛÛ Û   Û Þ ÛÛÛ ÛÛÛ      ³                     ³
+³                                                     ³ Ed: Scott Burkett   ³
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+
+In this action-packed issue:
+
+        - A Word from the Editor
+        - OpenDoors 5.0 Released!
+        - OpenDoors 5.0 Changes
+        - Bits 'n Bytes 'n Data types
+        - Adding InterBBS Capabilities to OpenDoors programs
+        - Opendoors Tech Corner - Inside OD 5.0's Internal Async Routines
+        - Opendoors Tech Corner - Adding Local Mode Functionality
+        - Opendoors Tech Corner - Finding physical cursor position in OD 5.0
+        - Opendoors Tech Corner - Using UP/DOWN Arrow Keys in OD 5.0
+        - Opendoors Tech Corner - Drawing bar graphs with OD!
+        - Opendoors Tech Corner - Obtaining Names/Passwords in ASCII Mode
+        - Opendoors Tech Corner - Files Transfers under OpenDoors
+        - Opendoors Tech Corner - Comparing file stamps
+        - Opendoors Tech Corner - Generating Fidonet *.MSG Messages
+        - Code Snippets!
+        - Opendoors Release Notice: BCHECKERS v1.2
+        - Opendoors Release Notice: BFE 4.00.2r
+        - Opendoors Release Notice: GIF View Preview Door v1.0
+        - Opendoors Release Notice: Operation: Office v.05
+        - OpenDoors Tech Journal Information
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ A Word from the Editor ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßß
+
+By: Scott Burkett
+
+After many months of toiling, sweating, complaining, and otherwise
+groveling - it is now official - OpenDoors v5.0 has arrived!  Brian has
+once again displayed his ability to provide his customers with simply
+the best C-based door kit in existence.
+
+I've moved!  Actually, I'm still moving.  My wife and I just moved to
+the Tampa Bay, Florida area and so far, so good!  Actually, the author
+of RemoteAccess, Andrew Milner lives in Clearwater now - supposedly owns
+his own bar.  I would go looking him up, but I'm afraid what might
+happen if Andrew and I spent too much time together with his alcohol
+supply.  Such is life.
+
+If you haven't noticed by now, this issue is big - no, huge is more like
+it.  I've been saving bits and pieces here and there.  Actually, I was
+waiting on OD 5.0 to be released, and the pile just kept growing.  I
+am convinced that the only reason Brian finally released 5.0, was to
+diminish my growing stack of code snippets.... :-)
+
+Just a reminder, but please be sure to send me any release notices for
+your latest OpenDoors creations!  Without being notified, chances are,
+it won't appear in ODTJ.  Free advertising is nice, isn't it.... :-)
+
+Alles Klaar und spater! (Until the next issue...)
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors 5.0 Released! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+By: Brian Pirie
+
+This edition of the ODTJ marks the release of OpenDoors 5.00.  Version 5.00
+embodies some major steps forward for OpenDoors, including built-in
+asynchronous serial I/O capabilities, support for non-Borland C compilers
+such as Microsoft C, and a set of new advanced screen, window and popup menu
+functions. Many small enhancements have also been made, along with many
+efficiency improvements. As the long "new features and enhancements" list for
+version 5.00 will testify, this new release is one of the largest upgrades
+ever made to the capabilities of OpenDoors. Version 5.00 has also been more
+extensively tested than any previous version of OpenDoors, making it one of
+the most stable and efficient foundations on which you could choose to build
+your on-line software.
+
+Yet, even with all of the improvements that have been made for OpenDoors
+5.00, work is already going ahead for version 5.10. The aim of version 5.10
+won't be so much to add new features, as to improve on already existing
+features. More efficiency improvements and greater flexibility is the goal of
+OpenDoors 5.10.
+
+What will come after OpenDoors 5.10? More than anything else, the future
+directions of OpenDoors is guided by the programmers who work with it. Your
+suggestions, ideas and even bug reports will determine what enhancements will
+continue to be made to OpenDoors. Please don't hesitate to pass along any
+changes that you would like to see in future versions of OpenDoors! I am also
+interested to find out how much interest there would be in the following:
+
+-   An OS/2-specific version of OpenDoors
+-   A Windows-specific version of OpenDoors
+-   Graphics-mode RIP support
+-   A programmer-configurable door installation/configuration utility
+    that would work in conjunction with the OpenDoors configuration file
+    system.
+-   A multi-node file system that would facilitate easy implementation
+    of multi-node doors with the ability to send messages between nodes
+    and control concurrent access to configuration and user files.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors 5.0 Released! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+By: Brian Pirie
+
+Version 5.00 represents several major steps forward for OpenDoors. In
+addition to numerous bug fixes and minor improvements, a number of major
+new features have been added to this version. These include an optional
+multiple personality system which allow the sysop to choose the status
+line and function key style they prefer. This version also adds
+text-mode support for RIP (Remote Imaging Protocol) graphics, and adds a
+group of advanced ANSI/AVATAR/RIP functions for scrolling areas of the
+screen, saving and restoring portions of the screen and creating pop-up
+windows and menus. Also new in this version is support for compilers
+other than Borland/Turbo C(++), such as compilers from Microsoft.
+Version 5.00 also adds built-in communications support, making the use
+of a FOSSIL driver optional. Furthermore, direct support for additional
+BBS systems has been added.
+
+The list below provides more detail of the changes and new features in
+version 5.00. For a summary of changes made for previous versions of
+OpenDoors, refer to the OpenDoors History file that is available from my
+system.
+
+     - The nonstop key ([=]) now works correctly during FILES.BBS
+       listing.
+
+     - New door information file formats now supported include: RA 2.00
+       EXITINFO.BBS.
+
+     - If the TASK environment variable is set, OpenDoors will now use
+       its value to determine the current node number.
+
+     - The od_control.od_spawn_freeze_time variable now works correctly.
+       Previously, the user's time would always be frozen during
+       od_spawn...() execution, regardless of the value of this
+       variable.
+
+     - A new feature known as the "Multiple Personality System" has been
+       added to this version. If you choose to include the Multiple
+       Personality System in a door, the sysop will be able to specify
+       which of a number of "personalities" should be used. Each
+       personality defines the statusline appearance and function keys
+       seen by the sysop, and has no effect on the door's operation from
+       the user's standpoint. OpenDoors 5.00 includes personality
+       definitions for WildCat, RemoteAccess, PC-Board, and it's own
+       simplified RA style status lines. You can also define your own
+       personalities by writing a personality definition function. If
+       your choose not to include the Multiple Personality System in a
+       door, you will still be able to define which single personality
+       you wish OpenDoors to use.
+
+     - This version of OpenDoors can be used with a larger variety of
+       compilers than where supported by the previous version. OpenDoors
+       5.00 is known to work with all versions of Turbo C, Turbo C++,
+       Borland C++, Microsoft C, Microsoft C++, Quick C and Visual C++.
+       It should also work with any other MS-DOS based ANSI C compiler
+       that supports the Microsoft/DOS .OBJect and .LIBrary file
+       formats.
+
+     - A new diagnostics feature has been added to OpenDoors, which
+       allows you to determine the reason for the most recent OpenDoors
+       function failure. When any OpenDoors function returns a failure
+       condition, it also sets the new od_control.od_error variable to
+       indicate the reason for the failure.
+
+     - Added additional definitions to OPENDOOR.H, to map names of
+       OpenDoors functions and variables with the word "colour" from the
+       U.S. spelling "color". In other words, both od_set_colour() and
+       od_set_color() are now recognized by OpenDoors.
+
+     - The od_list_files() now supports more intelligent path
+       specifications. If the parameter to od_list_files() is NULL or
+       empty, it will search for a FILES.BBS file in the current
+       directory. If a directory path is specified, it will look for a
+       FILES.BBS in that directory. If a full directory and filename are
+       specified, the specified filename will be used in place of
+       FILES.BBS.
+
+     - To save space, the compact memory model library is no longer
+       included in the normal OpenDoors package. The compact memory
+       model library is now available seperately.
+
+     - A new function, od_set_dtr(), has been added to allow the DTR
+       line to the modem to be manually controlled. This can be useful
+       in writing programs where you wish to force the modem to hangup,
+       such as a call-back verification door.
+
+     - Added additional support for various DOS multitasking
+       environments. OpenDoors is now specifically Microsoft Windows
+       aware. OpenDoors also now gives up time to other waiting tasks
+       when it is idle during chat mode.
+
+     - The od_edit_str() "M" mode now capitializes a character following
+       a dash '-' character.
+
+     - When transmitting more than one character at a time, OpenDoors
+       now uses the FOSSIL trasfer block function, instead of multiple
+       calls to the transfer character function. This should help to
+       improve performance over high speed connections when running on
+       slow PCs or under multitasking environments.
+
+     - OpenDoors 4.10 would not correctly change the display colour from
+       high-intensity back to low-intensity. This problem has been
+       fixed.
+
+     - OpenDoors now recognizes DORINFO?.DEF filenames with alphabetical
+       identifiers (ie, DORINFOA.DEF thru DORINFOZ.DEF) for nodes 10
+       thru 35.
+
+     - Improvements have been made to the logfile system. An exit at
+       errorlevel zero no longer causes garbage to be written to the
+       logfile. The logfile functions have been made more reliable when
+       operating under low stack availability conditions. In the past,
+       if a large number of local variables where allocated on the
+       stack, the logfile functions would fail, often writing garbage to
+       the logfile. When the user pages the sysop for chat, the user's
+       reason for wishing a chat is also written to the logfile.
+
+     - Support for text-mode RIP (Remote Imaging Protocol) graphics has
+       been added. Because this version of OpenDoors always operates in
+       DOS text-mode, none of the graphics mode RIP features (such as
+       drawing lines, circles and displaying icons) will appear on the
+       local screen. Plans for a version of OpenDoors that will operate
+       in graphics mode and optionally display graphics locally are
+       currently under consideration. In this version, RIP support
+       includes a number of new features. OpenDoors will now recognize
+       the RIP setting passed in an RA 2.00 EXITINFO.BBS file and
+       WildCat DOOR.SYS file, and also allows the RIP setting to be
+       specified in a custom door information file. The od_send_file()
+       and od_hotkey_menu() functions will now search for files with
+       .RIP, .AVT, .ANS and .ASC extensions. When displaying RIP
+       graphics to the remote user, a pop-up window appears on the local
+       screen, indicating to the sysop which file is being displayed.
+
+     - A set of new functions have been added to permit advanced screen
+       manipulations. These functions include od_gettext() and
+       od_puttext() to save and restore portions of the screen, and
+       od_save_screen() and od_restore_screen() to save and restore the
+       entire screen. od_scroll() can be used to scroll any portion of
+       the screen upwards or downwards. od_save_screen() and
+       od_restore_screen() will operate in any mode, but the other
+       functions require ANSI/AVATAR/RIP mode to be available.
+
+     - Three additional functions, od_window_create(),
+       od_window_remove() and od_popup_menu(), have been added to
+       facilitate the creation of popup windows and menus. When such a
+       window or menu is removed from the screen, the are of the screen
+       "under" the window is returned to it's original state. This
+       allows you to create multiple overlapping windows within a door
+       program. The od_popup_menu() function creates a popup window with
+       a menu from a simple menu definition string. The user can select
+       an option from this menu by pressing the key associated with an
+       option, or by moving a menu selection bar using their arrow keys.
+       These three functions require an ANSI/AVATAR/RIP mode to be
+       available.
+
+     - A new function, od_chat() has been added, to allow you to
+       explicitly invoke the OpenDoors chat mode from within your
+       program.
+
+     - A new setting variable, od_control.od_always_clear has been
+       added. When set to TRUE, od_clr_scr() will always clear the
+       screen, regardless of the user's screen clearing setting. When
+       set to FALE, od_clr_scr() will only clear the screen if the user
+       has screen clearing enabled.
+
+     - It is now possible to configure the errorlevels OpenDoors exits
+       with under various circumstances, such as when the user runs out
+       of time remaining online. See the od_control.od_errorlevel
+       variable
+
+     - A new setting variable, od_control.od_force_local, can be used to
+       easily force OpenDoors to operate in local mode. Using this
+       variable you can easily add a command line parameter such as
+       "-local" to allow the sysop to force your door to operate in
+       local mode. When OpenDoors is forced into local mode using this
+       variable, it does not look for a door information file, and uses
+       default settings for the user's name, etc.
+
+     - OPENDOOR.H now sets structure packing to single byte alignment
+       for the od_control structure when Borland and Microsoft compilers
+       are being used. In the past, programmers using OpenDoors have
+       experienced difficulties the od_control structure when the
+       compiler has been set to use word packing.
+
+     - OpenDoors now closes the FOSSIL driver prior to performing a
+       spawn or sysop DOS shell. This allows doors or other
+       communications programs which use the FOSSIL driver to be
+       executed while the door's execution is suspended.
+
+     - When used with a FOSSIL driver, OpenDoors normally changes the
+       BPS rate to that passed from the BBS (if the BBS passes a valid
+       FOSSIL BPS rate). This BPS rate setting may now be disabled by
+       setting the DIS_BPS_SETTING bit of the od_control.od_disable
+       variable.
+
+     - A function hook has been added to allow you to install a function
+       to be called whenever od_kernel() executes
+       (od_control.od_ker_exec). Another function hook,
+       od_control.od_time_msg_func, can be installed to override
+       OpenDoor's time limit warning messages.
+
+     - A new array, od_control.od_hot_function, allows the you to define
+       functions to be called when any of the programmer-defined sysop
+       hotkeys have been pressed.
+
+     - A function hook, od_control.od_no_file_func, has been added. This
+       function will be called whenever OpenDoors is unable to find or
+       read a door information file. This allows you to add your own
+       door information file reader, or to provide a local login prompt
+       when no door information file is present.
+
+     - Previously, OpenDoors would stop correctly updating the user's
+       remaining time at midnight when running under certain BIOSes.
+       This problem has been fixed.
+
+     - The current display colour attribute can now be accessed through
+       an control structure member, od_control.od_cur_attrib.
+
+     - od_send_file() and od_hotkey_menu() no longer pause with a
+       "Continue?" prompt prematurely in files that have line lengths
+       greater than 254 characters.
+
+     - The local keyboard may now be disabled by setting the
+       DIS_LOCAL_INPUT bit of od_control.od_disable. This only affects
+       the sysop's input in circumstances that input is also accepted
+       from the remote user; this setting has no effect on the sysop
+       function keys.
+
+       A new function hook:
+
+          void (*od_control.od_local_input)(int);
+
+       has been added. If set, this function will be called whenever the
+       sysop presses a non-sysop-function key on the local keyboard.
+
+     - od_control.od_clear_on_exit now controls whether the screen is
+       cleared before shelling or executing od_spawn...(), in addition
+       to before OpenDoors shuts down.
+
+     - od_page() now restores the original display colour before
+       returning.
+
+     - It is now possible to display an entire string of characters with
+       terminal emulation, using the new function od_disp_emu().
+
+     - OpenDoors will now display a small popup window when
+       disconnecting the current connection.
+
+     - A new variable, od_control.od_in_buf_size, can now be set prior
+       to calling any OpenDoors function to set the size of OpenDoors
+       combined local/remote keyboard input buffer. By default, this
+       buffer is 256 bytes in size.
+
+     - Previously, there were a number of OpenDoors API functions that
+       would not correctly initialize OpenDoors if they were the first
+       function called in the program. This has been fixed.
+
+     - To facilitate setting of bps rates up to 115,200, od_control.baud
+       is now an unsigned long.
+
+     - A new setting, od_control.od_no_ra_codes, has been added to
+       disable the use of RemoteAccess/QuickBBS control codes by
+       od_send_file()/od_hotkey_menu()/od_disp_emu(). The
+       RemoteAccess/QuickBBS ASCII 1 "pause for key" is also now
+       recognized.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Bits and Bytes and Data Types ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ InterBBS Capabilities are Here! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+*** Editor's Note: The following is the press release for Brian's
+InterBBS add-on library for OpenDoors.  It can be obtained by FREQ'ing
+IBBS from either his system or mine.  Enjoy!
+
+BBS "door" programs have always allowed operators of online computer
+services, such as electronic bulletin board systems, to easily expand the
+services provided by their systems. Doors have greatly increased the
+flexibility and usability of BBS systems. A recent trend in door development
+has been to allow doors running on one BBS system to be linked to doors
+running on other systems, often through existing mail networks. While these
+networks have traditionally provided private and conference (echo) mail
+services and file sharing capabilities, they can also be useful in providing
+links between special applications that wish to share information with one
+another.
+
+By taking advantage of existing mail network technology, BBS door developers
+have been able to add a new dimension to the software they create. Among the
+new ideas that have proven successful have been distributed BBS listing
+services, distributed file listing services and games that share top-score
+lists or even allow players to compete with people playing the game hundreds
+or thousands of miles away. The possibilities are really limited only by the
+imagination of the door software developer.
+
+This package is designed to assist in writing Inter-BBS door programs that
+can communicate across FTSC-compatible mail networks, such as FidoNet. Using
+this package, you can:
+
+       - Send information messages through any FTSC-compatible mailer
+         that supports *.MSG format netmail, including FrontDoor,
+         InterMail, BinkleyTerm, D'Bridge, and others.
+
+       - Send messages containing either textual or binary data. This
+         information is stored in the message body, to allow the message
+         to be sent either directly (CrashMail) or by routed netmail.
+
+       - Easily send information to just one other system, or all
+         systems running the door. This allows you to implement both
+         centralized and de-centralized information distribution
+         schemes.
+
+This package includes the source code for an example skiing game door that
+maintains a high score list across multiple BBSes. This game requires the
+OpenDoors door programming toolkit to be compiled. However, the Inter-BBS
+Toolkit itself can also be use in doors that are not written with OpenDoors.
+
+The Inter-BBS Toolkit is available free of charge, and the package includes
+the complete C source code. This source code has been written for Borland C
+and C++ compilers, but may easily be modified for use with other C compilers.
+With the source code, you are able to customize or expand the capabilities of
+the Inter-BBS Toolkit as you wish.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Tech Corner ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßß
+
+-----------------------------------------------------------------------------
+                Inside OD 5.0's Internal Async Routines
+                     By: Brian Pirie/Scott Burkett
+-----------------------------------------------------------------------------
+
+ > Just a quick thought, but it would be nice if we could have
+ > access to the low level communication functions (both fossil and
+ > internal).  The standard od...() functions work great for doors,
+ > but for critical timing matters such as protocols, etc, it
+ > doesn't fly.  I am using my own fossil system for the timing
+ > essential portions of BFE, for example.
+
+I will consider adding these functions to the documented OpenDoors interface. In
+the meantime, they are as follows:
+
+int _com_carrier(void);           /* TRUE if DCD is high */
+void _com_clear_inbound(void);    /* Clears outbound buffer */
+void _com_clear_outbound(void);   /* Clears inbound buffer */
+void _com_close(void);            /* Closes serial port */
+void _com_dtr(char high);         /* TRUE raises DTR, FALSE lowers DTR */
+char _com_getchar(void);          /* Returns next char from modem */
+void _com_ini(void);              /* Opens serial port */
+char _com_inbound(void);          /* FALSE if inbound buffer is empty */
+char _com_outbound(void);         /* FALSE if outbound buffer is empty */
+void _com_sendchar(char ch);      /* Sends character to modem */
+                                  /* Sends multiple chars to modem */
+void _com_send_buf(char *buffer,int size);
+
+These functions assume that the door is not operating in local mode. Do not
+call com_getchar() unless the _com_inbound() returns TRUE.
+
+-----------------------------------------------------------------------------
+                    Adding Local Mode Functionality
+                            By: Brian Pirie
+-----------------------------------------------------------------------------
+
+OpenDoors 5.00 now permits you to easily force your door to operate in local
+mode by setting the od_force_local variable. Normally OpenDoors will use a
+default user name, but you can also use this feature to easily provide a
+local login prompt for your door. The following example shows how you might
+do this:
+
+(This code might look long, but keep in mind that most of it is comments!)
+
+#include "opendoor.h"
+
+int main(int argc, char *argv[])
+   {
+   int counter;
+   char valid_login = TRUE;
+
+   /* Check for /LOCAL command-line parameter */
+   for(counter = 1; counter < argc; ++counter)
+      {
+      strupr(argv[counter]); /* Do this with care! */
+      if(strcmp(argv[counter], "/LOCAL")==0)
+         {
+         /* We are operating in local mode */
+         od_control.od_force_local = TRUE;
+
+         /* Let user login */
+         do {
+            /* Prompt for user name */
+            printf("User Name : ");
+
+            /* Get name from user. Note that it would be better  */
+            /* to use a console input routine that limits the    */
+            /* number of characters that can be inputted by the  */
+            /* user, to prevent od_user_name from being overrun. */
+            /* I use gets() here for simplicity.                 */
+            gets(od_control.od_user_name);
+
+            /* At this point, if you have a user file for your door */
+            /* you might want to check whether the user's name is   */
+            /* valid. If it is not valid, you can set valid_login   */
+            /* to false, to cause door to prompt for a new name.    */
+            /* (Keep in mind that you should provide some way for a */
+            /* new user to log in locally!)                         */
+         } while(!valid_login);
+
+         /* Perform main door operations */
+         doormain();
+
+         /* Exit program */
+         return(0);
+         }
+      }
+
+   /* If we get to this point, then there is no /LOCAL parameter. So, */
+   /* we should operate normally */
+
+   /* Perform main door operations */
+   doormain();
+
+   /* Exit program */
+   return(0);
+   }
+
+
+void doormain(void)
+   {
+   /* Initialize OpenDoors */
+   od_init();
+
+   /* Door's main processing, common to remote and local modes, */
+   /* here */
+   }
+
+-----------------------------------------------------------------------------
+               Finding physical cursor position in OD 5.0
+-----------------------------------------------------------------------------
+                             (Brian Pirie)
+
+ > Quick question:  Is there a method to determine the current
+ > location of the cursor, without keeping track of it manually?  I
+ > vaguely recall a thread about this subject, but can't find it
+ > for the life of me...
+
+In 5.00, the following two variables:
+
+   extern unsigned char phys_curx;
+   extern unsigned char phys_cury;
+
+store the current location of the cursor on the local screen.
+
+-----------------------------------------------------------------------------
+                 Using UP/DOWN Arrow Keys in OpenDoors 5.0
+-----------------------------------------------------------------------------
+                   (Taken from the OPENDOORS echo)
+
+ > Any news on when the next beta will be out, and also do you plan
+ > on allowing support for use of the UP/DOWN arrow keys?
+
+This has been fixed in the 5.00/beta-8 package. The following program
+demonstrates a door which displays a simple message when the up or down arrow
+key is pressed. Since OpenDoors defaults to using the up and down arrow keys
+for adjusting the user's time limit, such a program has to use different keys
+for this purpose. This program reassigns the [Page Up] and [Page Down] keys
+to be used for adjusting the user's remaining time.
+
+/* Simple program to demonstrate using arrow keys within an OpenDoors door */
+/* program. Since the arrow keys are normally used locally to adjust the   */
+/* user's time up or down, you must choose different keys to adjust the    */
+/* time limit. This program simply loops, displaying the word "up" if the  */
+/* up arrow is pressed, "down" if the down arrow is pressed, and exiting   */
+/* if the enter key is pressed.                                            */
+
+#include "opendoor.h"
+
+main()
+   {
+   int choice;
+   char pressed;
+   long timer;
+
+   /* Notice call to od_init() here! */
+   od_init();
+
+   /* Reassign PageUp and PageDown to increase/decrease time */
+   /* Remember to do this after od_init() or your first call to any OpenDoors */
+   /* function. */
+   od_control.key_lesstime = 0x5100;   /* Scan code for Page Down */
+   od_control.key_moretime = 0x4900;   /* Scan code for Page Up */
+
+   od_disp_str("Press up and down arrows keys. Press Enter when done\r\n");
+
+   for(;;)
+      {
+      pressed = od_get_key(TRUE);
+
+      /* Check for ANSI arrow key sequences */
+      if(pressed==27)
+         {
+         timer=(*(long far *)0x46cL)+2L;
+         while(timer>*(long far *)0x46cL && (pressed=od_get_key(FALSE))==0)
+            {
+            od_kernel();
+            }
+
+         if(pressed != '[')
+            {
+            continue;
+            }
+         else
+            {
+            timer=(*(long far *)0x46cL)+9L;
+            while(timer>*(long far *)0x46cL && (pressed=od_get_key(FALSE))==0)
+               {
+               od_kernel();
+               }
+            if(pressed == 0) continue;
+            switch(pressed)
+               {
+               case 'A':
+                  goto up_arrow;
+
+               case 'B':
+                  goto down_arrow;
+               }
+            }
+         }
+
+      /* Check for doorway / local arrow key sequences */
+      else if(pressed==0)
+         {
+         /* Get the next key from the keyboard */
+         timer=(*(long far *)0x46cL)+9L;
+         while(timer>*(long far *)0x46cL && (pressed=od_get_key(FALSE))==0)
+            {
+            od_kernel();
+            }
+         if(pressed == 0) continue;
+
+         /* Respond appropriately */
+         switch(pressed)
+            {
+            case 0x48:
+up_arrow:
+               od_disp_str("Up Arrow.\r\n");
+               break;
+
+            case 0x50:
+down_arrow:
+               od_disp_str("Down Arrow.\r\n");
+               break;
+            }
+         }
+      /* exit program if user presses CR or LF */
+      else if (pressed == '\n' || pressed == '\r')
+         {
+         break;
+         }
+      }
+
+
+   /* Note that od_exit() is now optional in OpenDoors 5.00/beta-8        */
+   /* If you allow the program to return from the main() function without */
+   /* first calling od_exit(), the od_exit() code will automatically be   */
+   /* performed. Note, however, that this does not allow OpenDoors to     */
+   /* determine the errorlevel being used, in order to report the         */
+   /* errorlevel in the logfile.                                          */
+   return(0);
+   }
+
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Drawing Bar Graphs with OD! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+By: Brian Pirie
+
+Many different types of programs can be enhanced by the use of graphical
+information. Often, this graphical information can take the form of
+horizontal bar graphs.
+
+An easy way to draw horizontal bars in door programs written with
+OpenDoors, is to use the od_repeat() function. Not only does od_repeat()
+allow you to easily form a bar by repeating a particular character the
+specified number of times, but it is also a very efficient way to do so.
+od_repeat() will take advantage of terminal emulation optimizations,
+when available. For instance, a character can be repeated any number of
+times with AVATAR by sending a short 3-byte sequence that specifies the
+character and number of times to repeat.
+
+How do you calculate the number of character to use to form a bar in
+your graph? The DrawHorizontalBar() function, which is provided below,
+will do this calculation for you. Simply provide the value to be
+represented by this bar, the minimum and maximum possible values, and
+the maximum number of character to use to draw the bar. For example, if
+you are graphing percentages (which could range from 0% to 100%), and
+wanted the graph to fit in a space of 40 columns, you would use:
+
+    DrawHorizontalBar(nPercent, 0, 100, 40);
+
+Below the listing for the DrawHorizontalBar() function is a complete program
+which demonstrates the DrawHorizontalBar() function as called from another
+function that will create complete horizontal bar graphs. This second function,
+DrawGraphOfPercentages(), takes an array of titles, and array of values
+corresponding to each title, and draws a complete bar graph from this
+information.
+
+
+/* Function to draw a horizontal bar, given a value, the minimum and maximum */
+/* possible values, and the number of characters the horizontal bar should   */
+/* extended for the maximum value.                                           */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars)
+{
+   /* Determine lenght of bar */
+   int nBarChars = ((nValue - nMinValue) * nMaxChars) / nMaxValue;
+
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      /* If ANSI or AVATAR graphics are available, assume that IBM extended */
+      /* ASCII is also available. This function uses character 220 to form  */
+      /* bars that are 1/2 the height of the line. You might also want to   */
+      /* try character 119, which will form bars that are the entire height */
+      /* of the line.                                                       */
+      od_repeat(220, nBarChars);
+   }
+   else
+   {
+      /* In ASCII mode, the bar is formed by the '=' character. */
+      od_repeat('=', nBarChars);
+   }
+}
+
+----- ex_graph.c example program follows ------------------------------
+
+/* Includes */
+#include "opendoor.h"
+
+/* Function prototypes. */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars);
+void DrawGraphOfPercentages(int nItems, int *panPercentages,
+   char **papszTitles, char bTitleColor, char bGraphColor,
+   int nTitleWidth, int nGraphWidth);
+
+
+/* Main function - program execution begins here. */
+main()
+{
+   char *apszTitles[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+                          "Thursday", "Friday", "Saturday"};
+   int anValues[7] = {50, 75, 100, 25, 83, 0, 43};
+
+   od_printf("`bright green`Test graph:\n\r");
+
+   DrawGraphOfPercentages(7, anValues, apszTitles, 0x02, 0x0f, 20, 50);
+
+   od_get_key(TRUE);
+
+   return(0);
+}
+
+/* Function to draw horizontal graph of percentages with titles, to */
+/* demonstrate the use of the DrawHorizontalBar() function.         */
+/* No titles should have more than nTitleWidth characters.          */
+void DrawGraphOfPercentages(int nItems, int *panPercentages,
+   char **papszTitles, char bTitleColor, char bGraphColor,
+   int nTitleWidth, int nGraphWidth)
+{
+   int nCurrentItem;
+
+   /* Loop for each item (line) in the graph. */
+   for(nCurrentItem = 0; nCurrentItem < nItems; ++nCurrentItem)
+   {
+      /* Set display color for title text. */
+      od_set_attrib(bTitleColor);
+
+      /* Add spaces to right-align all titles. */
+      od_repeat(' ', nTitleWidth - strlen(papszTitles[nCurrentItem]));
+
+      /* Display the title. */
+      od_disp_str(papszTitles[nCurrentItem]);
+
+      /* Add space between title and graph. */
+      od_printf(" ");
+
+      /* Set display color for graph. */
+      od_set_attrib(bGraphColor);
+
+      /* Draw bar graph for this line. */
+      DrawHorizontalBar(panPercentages[nCurrentItem], 0, 100, nGraphWidth);
+
+      /* Move to the next line. */
+      od_printf("\n\r");
+   }
+}
+
+
+/* Function to draw a horizontal bar, given a value, the minimum and maximum */
+/* possible values, and the number of characters the horizontal bar should   */
+/* extended for the maximum value.                                           */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars)
+{
+   /* Determine lenght of bar */
+   int nBarChars = ((nValue - nMinValue) * nMaxChars) / nMaxValue;
+
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      /* If ANSI or AVATAR graphics are available, assume that IBM extended */
+      /* ASCII is also available. This function uses character 220 to form  */
+      /* bars that are 1/2 the height of the line. You might also want to   */
+      /* try character 119, which will form bars that are the entire height */
+      /* of the line.                                                       */
+      od_repeat(220, nBarChars);
+   }
+   else
+   {
+      /* In ASCII mode, the bar is formed by the '=' character. */
+      od_repeat('=', nBarChars);
+   }
+}
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Obtaining Names/Passwords in ASCII Mode ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+By: Brett Barnes, THE TOWER BBS 071 636 3957, England
+
+#include <stdio.h>
+#include <string.h>
+#include "opendoor.h"
+
+void Get_Password(char *string, int length);
+void Get_Full_Name(char *string, int length);
+
+void Get_Password(char *string, int length)
+{
+int key;
+int count = 0;
+
+    od_clear_keybuffer();
+    while((key = od_get_key(TRUE)) != 13)
+    {
+        if(key == 0)
+            od_get_key(TRUE);
+
+        if(key >= 32 && key <= 127 && count < length)
+        {
+            string[count++] = key;
+            od_putch('*');
+        }
+        if(key == '\b' && count > 0)
+        {
+            od_putch('\b');
+            od_putch(' ');
+            od_putch('\b');
+            count--;
+            string[count] = '\0';
+        }
+    }
+    strupr(string);/*make string uppercase*/
+}
+
+void Get_Full_Name(char *string, int length)
+{
+int key;
+int count = 0;
+
+    od_clear_keybuffer();
+    while((key = od_get_key(TRUE)) != 13)/* Drops out when Enter key*/
+    {
+        if(key == 0)
+            od_get_key(TRUE);/*cancels prob with DEL key*/
+        /*If key = valid ascii code */
+        if(key >= 32 && key <= 127 && count < length)
+        {
+       /*if uppercase */    if(key >= 65 && key <= 90)
+                key = key+32;/*convert to lowercase*/
+            if(count == 0 && key >= 97 && key <= 122)
+                key = key-32;/*convert to uppercase*/
+            else
+                if(string[count-1] == ' ' && key >= 97 && key <= 122)
+                key = key-32;/*convert to uppercase*/
+        /*Stop spaces being put in first*/
+            if(key != 32)
+            {
+                string[count++] = key;
+                od_putch(key);
+            }
+            else if(count > 0)
+            {
+                string[count++] = key;
+                od_putch(key);
+            }
+        }
+        if(key == '\b' && count > 0)/*If Backspace*/
+        {
+            od_putch('\b');
+            od_putch(' ');
+            od_putch('\b');
+            count--;
+            string[count] = '\0';
+        }
+    }
+}
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ File Transfers Under OpenDoors ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+By: Brian Pirie, taken from the OPENDOORS echomail conference:
+
+Often, it can be useful or necessary to send and receive files between
+a program using OpenDoors, and the remote user's system. To allow file
+uploading and downloadeding, you can either implement the common file
+transfer protocols as part of your program, or you can call an external
+file transfer program, such as DSZ. This tip demonstrates how you can
+call DSZ using OpenDoors.
+
+In order to see this program in action, you should run a terminal
+program on one machine, and connect to second machine that will run this
+example program. (You could also do this in two different windows on one
+machine.) The demonstration program will ask whether you want to upload
+or download files, and will then prompt you for the file transfer protocol
+to use and the filename to transfer. Once this is done, DSZ is invoked
+to perform the file transfer. As such, DSZ will need to be installed on
+the machine that will run the example program.
+
+The tip2.c program provides a function that you can use to perform file
+transfers. This function is defined as follows:
+
+  int TransferFile(char *pszFilename, eProtocol Protocol, char bReceive)
+
+The first parameter should contain the name of the file or files to be
+transfered. The second parameter should specify the file transfer
+protocol to use, and should be one of the following enumerated
+constants:
+
+   XModem
+   XModemCRC
+   XModem1K
+   YModem
+   YModemG
+   ZModem
+
+The third parameter specifies whether the file is being send (FALSE) or
+received (TRUE). From the user's perspective, sending the file means
+downloading, and receiving the file means uploading.
+
+The TransferFile() function returns TRUE if the file transfer was
+completed, and FALSE if it was not.
+
+If the DSZ program is not present in the system's PATH or the current
+directory, then the global variable szDSZFilename must be set to the
+full path and filename of the DSZ program. This could be done by adding
+a custom OpenDoors configuration file line with a keyword such as
+"DSZPath".
+
+The first part of the tip2.c program discussed in the previous message
+follows:
+
+/* tip2.c - Example program to demonstrate how to send or receive files */
+/*          using DSZ, from within an OpenDoors program.                */
+
+/* Include required header files. */
+#include <stdio.h>
+#include <assert.h>
+#include "opendoor.h"
+
+/* File transfer protocol enumeration. */
+typedef enum
+{
+   XModem,
+   XModemCRC,
+   XModem1K,
+   YModem,
+   YModemG,
+   ZModem
+} eProtocol;
+
+/* Function prototypes. */
+int TransferFile(
+   char *pszFilename,
+   eProtocol Protocol,
+   char bReceive);
+eProtocol ChooseProtocol(void);
+void AddParameter(
+   char **papszArguments,
+   int *pnCount,
+   char *pszNewArgument);
+
+/* Manifest constants. */
+#define ARGS_ARRAY_SIZE 10
+
+/* Global variable with DSZ filename. */
+char szDSZFilename[80] = "DSZ";
+
+
+/* Program's execution begins here. */
+main()
+{
+   char chAnswer;
+   char bReceive;
+   eProtocol Protocol;
+   char szFilename[73];
+   int bSuccess;
+
+   od_printf("OpenDoors file transfer demo.\n\r\n\r");
+
+   /* Get file transfer direction from user. */
+   od_printf("Do you wish to [U]pload or [D]ownload? ");
+   chAnswer = od_get_answer("UD");
+   if(chAnswer == 'U')
+   {
+      od_printf("Upload\n\r\n\r");
+      bReceive = TRUE;
+   }
+   else
+   {
+      od_printf("Download\n\r\n\r");
+      bReceive = FALSE;
+   }
+
+   /* Get file transfer protocol from user. */
+   Protocol = ChooseProtocol();
+
+   /* Get filename. */
+   od_printf("\n\rEnter filename(s) : ");
+   od_input_str(szFilename, 72, ' ', 255);
+   od_printf("\n\r");
+
+   /* Perform file transfer. */
+   bSuccess = TransferFile(szFilename, Protocol, bReceive);
+
+   /* Display result of file transfer to user. */
+   od_clr_scr();
+   if(bSuccess)
+   {
+      od_printf("File transfer successful.\n\r");
+   }
+   else
+   {
+      od_printf("File transfer not completed.\n\r");
+   }
+
+   /* Prompt user to exit program. */
+   od_printf("Press [Enter] to return to BBS.\n\r");
+   od_get_answer("\n\r");
+
+   /* Return control to calling BBS software. */
+   od_exit(0, FALSE);
+
+   return(0);
+}
+
+
+/* Function to allow user to choose a file transfer protocol. */
+eProtocol ChooseProtocol(void)
+{
+   eProtocol Protocol;
+   char chAnswer;
+
+   od_printf("Available file transfer protocols:\n\r");
+   od_printf("    [X] XModem\n\r");
+   od_printf("    [C] XModem/CRC\n\r");
+   od_printf("    [1] XModem/1K\n\r");
+   od_printf("    [Y] YModem\n\r");
+   od_printf("    [G] YModem-G\n\r");
+   od_printf("    [Z] ZModem\n\r\n\r");
+   od_printf("Please select a protocol: ");
+
+   chAnswer = od_get_answer("XC1YGZ");
+
+   switch(chAnswer)
+   {
+      case 'X':
+         od_printf("XModem\n\r");
+         Protocol = XModem;
+         break;
+      case 'C':
+         od_printf("XModem/CRC\n\r");
+         Protocol = XModemCRC;
+         break;
+      case '1':
+         od_printf("XModem/1K\n\r");
+         Protocol = XModem1K;
+         break;
+      case 'Y':
+         od_printf("YModem\n\r");
+         Protocol = YModem;
+         break;
+      case 'G':
+         od_printf("YModem-G\n\r");
+         Protocol = YModemG;
+         break;
+      case 'Z':
+      default:
+         od_printf("ZModem\n\r");
+         Protocol = ZModem;
+         break;
+   }
+
+   return(Protocol);
+}
+
+/* Function to send or receive a file to/from remote system. */
+int TransferFile(
+   char *pszFilename,
+   eProtocol Protocol,
+   char bReceive)
+{
+   char szPort[7];
+   char *apszArguments[ARGS_ARRAY_SIZE];
+   int nArgCount = 0;
+
+   /* Filename cannot be NULL. */
+   assert(pszFilename != NULL);
+
+   /* Ensure that we are not operating in local mode. */
+   if(od_control.baud == 0)
+   {
+      od_printf("Operating in local mode;"
+                " file transfer not possible.\n\r");
+      return(FALSE);
+   }
+
+   /* Generate DSZ command line */
+
+   /* Begin with executable filename. */
+   AddParameter(apszArguments, &nArgCount, szDSZFilename);
+
+   /* Add port parameter. */
+   AddParameter(apszArguments, &nArgCount, "port");
+   sprintf(szPort, "%d", od_control.port + 1);
+   AddParameter(apszArguments, &nArgCount, szPort);
+
+   /* Restrict inbound files to current drive and directory. */
+   AddParameter(apszArguments, &nArgCount, "restrict");
+
+   /* Generate DSZ protocol parameters from specified protocol. */
+   if(bReceive)
+   {
+      switch(Protocol)
+      {
+         case XModem:
+         case XModem1K:
+            AddParameter(apszArguments, &nArgCount, "rx");
+            break;
+         case XModemCRC:
+            AddParameter(apszArguments, &nArgCount, "rc");
+            break;
+         case YModem:
+            AddParameter(apszArguments, &nArgCount, "rb");
+            break;
+         case YModemG:
+            AddParameter(apszArguments, &nArgCount, "rb");
+            AddParameter(apszArguments, &nArgCount, "-g");
+            break;
+         case ZModem:
+            AddParameter(apszArguments, &nArgCount, "rz");
+            break;
+         default:
+            assert(FALSE);
+      }
+   }
+   else
+   {
+      switch(Protocol)
+      {
+         case XModem:
+         case XModemCRC:
+            AddParameter(apszArguments, &nArgCount, "sx");
+            break;
+         case XModem1K:
+            AddParameter(apszArguments, &nArgCount, "sx");
+            AddParameter(apszArguments, &nArgCount, "-k");
+            break;
+         case YModem:
+         case YModemG:
+            AddParameter(apszArguments, &nArgCount, "sb");
+            break;
+         case ZModem:
+            AddParameter(apszArguments, &nArgCount, "sz");
+            break;
+         default:
+            assert(FALSE);
+      }
+   }
+
+   /* Add filename parameter to command line. */
+   AddParameter(apszArguments, &nArgCount, pszFilename);
+
+   /* Display prompt to user providing */
+   od_printf("Begin your transfer now,"
+             " or press [Ctrl]-[X] several times to abort.\n\r");
+
+   /* Execute command using the OpenDoors od_spawn() function. */
+   return(od_spawnvpe(P_WAIT, apszArguments[0], apszArguments,
+                      NULL) == 0);
+}
+
+
+/* Function to add next parameter to array of parameters to pass to */
+/* od_spawnvpe().                                                   */
+void AddParameter(
+   char **papszArguments,
+   int *pnCount,
+   char *pszNewArgument)
+{
+   assert(*pnCount < ARGS_ARRAY_SIZE - 1);
+   assert(papszArguments != NULL);
+   assert(pnCount != NULL);
+   assert(pszNewArgument != NULL);
+
+   /* Add next argument to array. */
+   papszArguments[(*pnCount)++] = pszNewArgument;
+
+   /* Ensure that the array is always NULL-terminated. */
+   papszArguments[*pnCount] = NULL;
+}
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Comparing File Stamps ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßß
+
+By: Brian Pirie, taken from the OPENDOORS echomail conference:
+
+ > I am trying to find a way to basically look at a files date and
+ > then determine if it is older than say 10 days old?
+ > I looked thru all of my manuals, and sample programs and could
+ > find no examples, so I thought I would turn to the experts here.
+ > If anyone can give me help I would greatly appreciate it!
+
+For others who were wondering about this problem, the program below will
+display the names of all files in the current directory that are more than
+10 days old.
+
+If you ran the program at 10:35:20 on Friday, August 5th, a file dated
+10:35:20 on Tuesday, July 26th (or later) would not be displayed, but a file
+dated 10:35:18 on Tuesday, July 26th (or earlier) would be displayed.
+(DOS's file time is only accurate to the nearest 2 seconds.)
+
+**** Editor's Note: To further explain Brian's information, here is a
+brief explanation of why DOS file time stamps are only accurate to the
+nearest two seconds.  The file's time field is set when you create the file,
+and updated thereafter whenever you close the file, but *only* if
+information has been written to the file.  This field is not updated if
+the file is merely read, copied, or renamed.  When the time field gets
+updated, though, the new time is retrieved from the system clock.  The
+following is a breakdown of the meaning of each bit in the time field:
+
+        FEDCAB98 76543210
+
+        xxxxx              = Hours
+             xxx xxx       = Minutes
+                    xxxxx  = Two second increments
+
+The hour is contained in five bits (on a 24 hour clock), and the minutes
+in six bits.  Because this only leaves 5 bits in which to store the
+seconds, DOS divides the actual value by two (2).  A little known fact
+about the file time field, is that if all bits in both bytes are set to
+zero (0), the DIR command will not show any time.  Ok, back to Brian's
+snippet (sorry!).
+
+Included in this file is a function named DIRToCTime(), which converts the
+DOS file time format to the C time_t format.
+                       
+/* This example program will display the names of all files that are */
+/* more than ten days old.                                           */
+
+#include <dir.h>
+#include <dos.h>
+#include <time.h>
+#include <stdlib.h>
+
+time_t DIRToCTime(unsigned uDate, unsigned uTime);
+
+#define SECONDS_PER_DAY (60 * 60 * 24)
+
+main()
+{
+   struct ffblk DirEntry;
+   time_t CurrentTime = time(NULL);
+   time_t FileTime;
+   double dElapsedSeconds;
+   double dElapsedDays;
+
+   if(!findfirst("*.*", &DirEntry, FA_ARCH))
+   {
+      do
+      {
+         /* This loop repeats for every found file ... */
+
+         /* Convert the file's time as returned by findfirst() to a time_t. */
+         FileTime = DIRToCTime(DirEntry.ff_fdate, DirEntry.ff_ftime);
+
+         /* Determine the number of seconds that have elapsed between the */
+         /* two times.                                                    */
+         dElapsedSeconds = difftime(CurrentTime, FileTime);
+
+         /* Determine the number of days that have elapsed. */
+         dElapsedDays = dElapsedSeconds / SECONDS_PER_DAY;
+
+         /* If more than ten 24-hour periods have elapsed, display filename. */
+         if(dElapsedDays > 10)
+         {
+            printf("%s is more than ten days old.\n", DirEntry.ff_name);
+         }
+      } while(!findnext(&DirEntry));
+   }
+
+   return(0);
+}
+
+
+time_t DIRToCTime(unsigned uDate, unsigned uTime)
+{
+   struct tm TimeStruct;
+
+   TimeStruct.tm_sec = (uTime & 0x001f) * 2;
+   TimeStruct.tm_min = (uTime & 0x07e0) >> 5;
+   TimeStruct.tm_hour = (uTime & 0xf800) >> 11;
+   TimeStruct.tm_mday = uDate & 0x001f;
+   TimeStruct.tm_mon = ((uDate & 0x01e0) >> 5) - 1;
+   TimeStruct.tm_year = 80 + ((uDate & 0xfe00) >> 9);
+
+   return(mktime(&TimeStruct));
+}
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Generating Fidonet *.MSG Messages ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+By: Mark Williamson, taken from the OPENDOORS echomail conference:
+
+/* NETMAIL.C : This little jewel will write send a netmail message to
+               the names and addresses listed in NAMES.LST. This file
+               has the format of:
+
+               First Lastname
+               Zone Node Net Point  (point is optional)
+
+               example:
+
+               Mark Williamson
+               1 202 750 <0>
+
+               When the program loads, it will load the editor EDIT.EXE.
+               Just change the line system("EDIT.EXE") to load your editor.
+               This program expects to find a file NET.MSG.
+
+               This program is public domain <G>.
+               The attribute used in this program is CRASH KILL SENT.
+
+               Look in the Fidonet Technical Specifications for the full
+               structures and meaning of each field.
+*/
+
+#include <dos.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <alloc.h>
+#include <bp.h>
+#include <string.h>
+
+static char msghdr[50],year[5],msgfile[80],firstname[30];
+char *msgtext,select;
+void writefido(void);
+
+char  *strrep(char *str, char *oldstr, char *newstr)
+{
+          int OldLen, NewLen;
+          char *p, *q;
+
+          if(NULL == (p = strstr(str, oldstr)))
+                return p;
+          OldLen = strlen(oldstr);
+          NewLen = strlen(newstr);
+          memmove(q = p+NewLen, p+OldLen, strlen(p+OldLen)+1);
+          memcpy(p, newstr, NewLen);
+          return q;
+}
+
+/* *.MSG format: */
+struct {
+         char from[36],
+                  to[36],
+                  subject[72],
+                  datetime[20];
+             int  timesread,
+                  destnode,
+                  orignode,
+                  cost,
+                  orignet,
+                  destnet,
+                  destzone,
+                  origzone,
+                  destpoint,
+                  origpoint,
+                  replyto,
+                  attribute,
+                  nextreply;
+        } Msg;
+
+
+char buffer[89];
+void main(void)
+{
+
+   FILE *fp,*fp2;
+   struct tm *time_now;
+   time_t secs_now;
+   char zone[10],node[10],net[10],point[10];
+
+   /* load your editor to create/change the message. */
+
+   system("EDIT C:\\NET.MSG");
+   if(access("C:\\NET.MSG",0)!=0) return;
+
+   /* open the name list */
+
+   fp=fopen("C:\\NAMES.LST","rt");
+   if(fp==NULL) return;
+   time(&secs_now);
+   Msg.timesread=0;
+   Msg.cost=0;
+
+
+   strcpy(Msg.from,"Mark Williamson");
+   strcpy(Msg.subject,"Labtest Beta Message"); /* remember, the subject */
+                                               /* for a file attach message */
+                                               /* is the full path and file */
+                                               /* name of the file(s) to send
+*/
+                                               /* be sure to set the correct
+*/
+                                               /* message attribute to send */
+                                               /* or request files. */
+
+
+   Msg.attribute=0x0183;
+
+   /* change this to your fido address */
+   Msg.orignode=750;
+   Msg.orignet=202;
+   Msg.origzone=1;
+   Msg.origpoint=0;
+   Msg.replyto=0;
+   Msg.nextreply=0;
+   time_now=localtime(&secs_now);
+   strftime(Msg.datetime,20,"%a %d %b %y %X",time_now);
+   while(!feof(fp))
+   {
+           Msg.destnode=Msg.destpoint=Msg.destnet=0;
+           Msg.destzone=1;
+
+           if(fgets(buffer,88,fp)==NULL) break;
+           buffer[strlen(buffer)-1]=0;
+           strcpy(Msg.to,buffer);
+           if(fgets(buffer,88,fp)==NULL) break;
+           buffer[strlen(buffer)-1]=0;
+           zone[0]=point[0]=node[0]=net[0]=0;
+           sscanf(buffer,"%s %s %s %s",zone,net,node,point);
+           Msg.destnode=atoi(node);
+           Msg.destpoint=atoi(point);
+           Msg.destzone=atoi(zone);
+           Msg.destnet=atoi(net);
+
+           ultoa(bp(Msg.to,199309),registerkey,10);
+           fp2=fopen("C:\\NET.MSG","rb");
+           msgtext=(char *)malloc(filelength(fp)+1000);
+           memset(msgtext,0,filelength(fp)+1000);
+           fread(msgtext,filelength(fp),1,fp2);
+           fclose(fp2);
+           sscanf(Msg.to,"%s",firstname);
+
+
+           /* here are some macros to make auto replacement easier. */
+           /* this is great for form letters, kinda personnalizes the */
+           /* message.*/
+
+           strrep(msgtext,"@FIRST@",firstname);
+           printf("\nWriting message to: %s, %s:%s/%s%s%s",
+                     Msg.to,
+                     zone,net,
+                     node,Msg.
+                     destpoint!=0?".":"",
+                     Msg.destpoint!=0?point:"");
+           writefido();
+           free(msgtext);
+   }
+   fclose(fp);
+}
+
+void writefido(void)
+{
+     int count=250;  /* this controls the starting point.  */
+                     /* this function will count down until */
+                     /* finds a *.MSG.  It will then increment */
+                     /* by one and write the message.  There */
+                     /* is probably a better way, but you know. */
+                     /* set this number higher if you have lots */
+                     /* of netmail messages. */
+
+         char firstname[20];
+         FILE *fp;
+         tzset();
+
+          /* Fido *.msgs use a MSGID line which is preceded by */
+          /* a CONTROL-A.  Then your fido address, a carriage  */
+         /* return, CONTROL-A and the name of the program that */
+         /* created the message and version number, then another */
+         /* carriage return. */
+         strcpy(msghdr,"\x01MSGID: 1:202/750\r\x01PID: MM 1.0\r");
+
+         /* this begins our loop to find the last written */
+         /* netmail message in your netmail directory */
+         sprintf(msgfile,"C:\\RA\\MAIL\\%d.MSG",count);
+         while(access(msgfile,0))
+            sprintf(msgfile,"C:\\RA\\MAIL\\%d.MSG",--count);
+
+         /* got this far, we have a number! */
+         /* increment it by one to get the next available slot */
+         sprintf(msgfile,"C:\\RA\\MAIL\\%d.MSG",++count);
+         fp=fopen(msgfile,"wb");
+         if(fp==NULL) puts("\nError opening file...");
+         else
+         {
+           fwrite(&Msg,sizeof(Msg),1,fp);
+           fwrite(&msghdr,strlen(msghdr),1,fp);
+           fwrite(msgtext,strlen(msgtext),1,fp);
+           fwrite("\0x00",1,1,fp);
+           fclose(fp);
+         }
+}
+
+That's it!  This is a simple program I wrote to send form letters to numerous
+people who needed to have the same information.  This could be done in
+Frontdoor with the ALT-L forward command, but then it was much more fun
+creating something and knowing watching it work!
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ Code Snippets! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßß
+
+Sample code to validate credit card numbers
+Posted in the OPENDOORS echo by Robert La Ferte,
+Original by Andrue Carr
+
+/*
+**  Testcard.c 1.0 Copyright (C) 1993 by DruId Software.
+*/
+
+#include <conio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int formula(char card[23],int len);
+
+// actual formula for creditcard!
+// this can be used to build the last CRC digit by changing the len by 1
+// it is mainly used to make sure the card is a correct card by verifying
+// the last CRC digit against the rest of the card.
+// the formula will run all the numbers in the card but the last one
+// which is the CRC digit.  if the number the formula kicks out is the
+// same as the last digit of the card then it is a valid credit card
+// if not then its not correct. this is what a ZON machine uses to
+// determine if it is a good number or not. If you need more help
+// let me know and i can explain this better:
+// Andrue Carr: fido: 1:331/201, SL_net: 250:502/1294, ISG 91:5/201
+//              Acmenet: 400:508/201
+// box 473, West Tisbury, mass 02575 (Over Board BBS (508)693-5344)
+
+int formula(char card[23], int len)
+{
+    int     x,
+            xcard = 0,
+            y;
+
+    for(x = 0; x <= len; x++)
+    {
+        if(len % 2)
+        {
+            if(x % 2)
+                y = (card[x] - 48) * 2;
+            else
+                y = (card[x] - 48);
+        }
+        else
+        {
+            if(x % 2)
+                y = (card[x] - 48);
+            else
+                y = (card[x] - 48) * 2;
+        }
+       if(y >= 10)
+           y = (y - 10) + 1;
+
+       xcard += y;
+    }
+
+    x = (10 - (xcard % 10));
+
+    if(x == 10)
+        x = 0;
+
+    return(x);              // send back the computed check digit!
+}
+
+void main(int argc, char *argv[])
+{
+    int i = 0,
+        size = 0;
+
+    if(argc < 2)
+    {
+            // testcard.exe [cardnumber]  if no card# then error
+        cputs("\007No credit card entered");
+        exit(1);
+    }
+
+    size = strlen(argv[1]);
+
+    i = formula(argv[1], (size - 2));
+
+    // check for all but crc digit
+    // if the number returned from formula() isnt the last digit
+    // of the actual card number then it isnt valid!
+
+    if(i != (argv[1][size - 1] - 48))
+        cputs("\007     -> Invalid Card!");
+    else
+        cputs("     -> Valid Card");
+
+    exit(0);
+}
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Release Notice: BCHECKERS 1.2! ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+BCHECKERS -- Version 1.2 -- is released!
+========================================
+
+** NOW SUPPORTS RA 2.00+! **
+
+As a sysop of a FidoNet BBS, I was disappointed in the lack of a good,
+non-interactive checkers door. Sure, there were some that allowed inter-
+node play and the like, but these were expensive and few offered any
+decent ANSI graphics and the simple ability to have callers make moves
+on alternate logons. I also wanted to try my hand at programming in C,
+having learned a number of other programming languages. BCheckers is the
+result of this effort; and at only $10 is a bargain in shareware.
+
+BCheckers offers the following sysop features (and more I've probably
+overlooked in these docs):
+
+-  As you would expect, BCheckers monitors carrier detect functions, to
+   automatically recover when a user drops carrier.
+
+-  Includes a fully-adjustable inactivity timeout monitor. A warning is
+   sent 5 seconds before the caller is ejected.
+
+-  Share-aware file i/o for use in multi-node BBS systems. You must have
+   DOS's SHARE.EXE loaded for multi-node use.
+
+-  Supports most popular BBS door information files, such as DORINFO1.DEF,
+   EXITINFO.BBS, CHAIN.TXT, DOOR.SYS, etc.
+
+-  Displays and updates a QuickBBS-style status line, with information
+   available to the sysop such as user name, location, baud rate, time left,
+   function keys, ANSI and AVATAR settings, and so on.
+
+-  Keeps track of a user "wants-chat" indicator, just like the one in
+   RemoteAccess, QuickBBS and other BBS systems. Allows for sysop page from
+   the door, and integrated chat mode.
+
+-  Provides the sysop with all the standard function keys for adjusting user
+   time, hanging up on or even locking out the user -- and sysop shell to DOS.
+
+-  Full support for locked baud-rates of up to 38,400 baud, using the FOSSIL
+   driver for maximum compatibility with any system. If a FOSSIL is not
+   available, BCheckers will use its own communications routines. Auto-detect
+   of local operation.
+
+-  BCheckers is also DesqView and Windows aware.
+
+New features:
+
+-  Minor bug fix to the Hall of Fame generation and key recognition code.
+
+-  Minor cosmetic improvements.
+
+-  Altered prompts slightly to alleviate problems with some modems when
+   using XON/XOFF handshaking.
+
+BCHECKERS is coming soon to a DOORNET or DDSDOORS distribution site in your
+area, or you can freq the latest version directly from the author at FidoNet
+node 1:231/710 using the "magic name" of BCHECK. Alternatively, the door can
+be downloaded from the H.O.M.E. BBS at (317)539-6579. The next release will
+have a "play against the computer" mode! Upgrades will be free, but a new
+registration will increase by $5. Register now, while it's still cheap!
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Release Notice: BFE v4.00.2r ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+BFE v4.00.2r, the flexible telecommunications front end system from
+Southeastern DataLINK, is now available!
+
+BFE is a BBS front-end system that was designed to provide sysops with a
+fast, efficient method of "connecting" things at the front end of their
+site.  
+
+BFE features include:
+
+ * Custom multi-level menus, with RIP graphics support
+ * Item and/or menu level password protection with daily scheduling
+ * Hotkey items or SlashCommand(tm) mode
+ * Attractive internal menuing scheme to ease your work load
+ * Support for custom user menus (for aspiring sysops and artists)
+ * Support for popup lightbar menus
+ * Design and employ interactive full-screen data entry forms
+ * Full Multinode/Multiuser Compatibility
+ * Internal DOS-to-UNIX gateway!  Give your users the power of UNIX.
+ * The gateway services can also be used to gate calls to a packet radio
+ * Client/server approach to the UNIX gateway.  Provide interactive access
+ * Provide *REAL* UUCP sessions from your UNIX server to your DOS callers
+ * Intuitive menu-driven setup and customization facility (BFE/Setup)
+ * Feature packed "C" like Script system
+ * Run automated scripts, even offline in a maintenance role.
+ * WFC (Wait-For-Caller) module to handle non-mailer sites (reg. only)
+ * DESQview *and* MS-Windows aware
+ * Configurable for security concerns
+ * Complete carrier monitoring and timeout checking
+ * Use any of 11 standard dropfiles, or define custom ones
+ * Run as a normal door or as a frontend (Dropfile not required!)
+ * Complete support for ANSI/ASCII/AVATAR/RIP users
+ * Configurable automatic ANSI and RIP detection schemes
+ * File transfer system with support for external protocols
+ * Run remote jobs, such as batch files, programs, other doors, etc.
+ * Internal remote shells to the operating system
+ * Built in chat/paging system with hourly/daily time restrictions
+ * Split screen chat mode, along with line chat mode for TTY users
+ * *Total* configurability
+
+What's changed since the last release?
+
+        o New mailing address and contact information for Southeastern
+          DataLINK:
+                      Address: 13500 Feather Sound Circle West
+                               Suite 1313
+                               Clearwater, FL 34622
+                         Data: (813) 572-6817
+                        Voice: (813) 573-5078
+                     Internet: [email protected]  - Scott Burkett
+                               [email protected]   - Sales Q/A
+                               [email protected] - Technical support
+                      Fidonet: 1:3603/500
+
+        o BFE now supports "Before" and "After" scripts for each menu option
+          in your control files.  The BEFORE scripts are called
+          immedicately after the user presses the key corresponding to
+          the menu option, but before the menu option's actions are
+          carried out.  The AFTER scripts are executed upon completion
+          of the menu item selected, and before returning to the BFE
+          menu system.  These scripts should be located in the SCRIPTS
+          directory (configured in BFE/Setup), and should be named as
+          per the following naming convention:
+
+                BEFORE scripts:  <CTLFILENAME>.B<HOTKEY>
+                 AFTER scripts:  <CTLFILENAME>.A<HOTKEY>
+
+          For example, if in your MAIN control file, you have a menu
+          option which uses the "1" key as the hotkey, the BEFORE and
+          AFTER scripts would be named "MAIN.B1" and "MAIN.A1".
+
+        o NEW SCRIPT COMMANDS:
+
+                CompareStr() - Compares a string with the contents of the
+                               internal script buffer.
+
+        o NEW SAMPLE SCRIPTS:
+
+                GETPASS.SCR - Demonstrates the use of the CompareStr()
+                              function call.
+
+        o BFE's menu handler now sports a rudimentary line noise "filter".
+
+        o All of the text used during user system logins is now
+          configurable in the language file.
+
+        * BFE's internal comm routines now supports all IRQ lines from
+          1 to 15.  In addition, full support for 16550A FIFO buffers is
+          in place as well.
+
+        * The RA "personality" now accurately mimics the latest version
+          of the RA status line.
+
+        * Quite a bit of code cleanup in this release.
+
+        * Several additional checks are now in place for the BFE setup
+          sanity check, which verifies the BFE path setup.
+
+        * When opening a .CTL file under BFE/Setup, it now defaults to open
+          an "existing" .CTL file, instead of creating a new one.  (Thanks
+          to George Bynum for this suggestion).
+
+        * Quite a few changes and additions in the BFE user manual (including
+          WFC, BFE/Gateway, and BFE/Setup).
+
+        * The "p" identifier has been removed from the end of the normal
+          archive naming convention, as it was confusing OS/2 users.  It
+          has been replaced by an "r", which means "public (r)elease".
+
+        * The fossil buffers are now flushed before every gateway session
+          is initiated.  This was inserted in order to make BFE's internal
+          serial gateway more "friendly" towards packet radio systems.
+          (Thanks to Mark Stanchin).
+
+        * A popup "dialog" box will appear whenever BFE is dropping
+          carrier, informing you of such.
+
+        * The WFC module for registered users will now place both the
+          COM port number *and* the fossil port number into the
+          DOBBSx.BAT files.
+
+        ! The displayfile() script function should no longer give erroneous
+          password prompts if a password was used to get into the script
+          to begin with. (Thanks to Mark Stanchin).
+
+        o A new dot command has been added to BFE/Formgen.  The ".type"
+          keyword will direct Formgen to output information in either
+          "delimited" format (default), or "linear".  Thanks to Dana
+          Meli-Wischman.
+
+        ! In certain situations, BFE would force the caller to hit a key
+          twice before the keystroke was registered.  Squashed.
+
+        ! BFE/Gateway sessions should now properly monitor the caller's
+          carrier signal.
+
+        ! ANSI detection would fail under certain situations.  Squashed.
+
+        o The BFE registration system has been revamped completely.  If you
+          are a previously registered user of BFE, please contact us for your
+          new key file.
+
+        o Marketing strategy has been updated, and now includes three very
+          distinct levels of BFE registration.
+
+        o New Module:   BFE/Node Monitor for DESQview systems.  BFEMON is a
+          compact monitoring system designed for sites running BFE in a
+          multinode environment.  This module is made available free of charge
+          to registered users of BFE. (+)
+
+        o New Module: BFE/WFC - The Wait For Caller module is designed to
+          provide a flexible method of handling inbound calls on non-mailer
+          nodes.  It's features include:
+
+                - DESQview/Win31/OS2 Aware
+                - Intuitive menu-driven setup and windowed environment
+                - Internal screen blanker
+                - Full support for 50 line VGA mode
+                - Designed with multinode systems in mind
+                - Custom connect strings for FAX, UUCP, etc.
+                - FOSSIL driven, for maximum compatibility
+                - Full internal event system
+
+          The WFC module is made available free of charge to registered users
+          of BFE. (+)
+
+        o New subsystem: BFE/FormGen - a powerful full screen data entry form
+          designer is now included as part of the base BFE package.
+
+        o BFE/Setup now includes an internal user file editor. :-)
+
+        o BFE now supports non-fossil sites by providing an internal serial
+          communications system.  This adds three new command line switches:
+
+                -i = No fossil!  Use internal routines
+                -u = COMx Base Address (i.e. -u03F8)
+                -v = COMx IRQ Line (i.e. -v4)
+
+        o The time adjustment sysop keys (formerly cursor up/down) have been
+          remapped to pageup and pagedown.
+
+        o Multiline descriptions with embedded colors are now available.  In
+          BFE/Setup, the description field is now two lines long.  Also, BFE
+          now supports embedded color tokens in the description field, thus
+          allowing you to highlight certain portions of the description of
+          each menu item.  ** NOTE:  BFE will not automatically align the
+          two lines of text if the first one wraps around!  This will be
+          taken care of in a future release, but for now, you will have to
+          space them out accordingly.
+
+        o When using BFE's internal files system for providing downloads,
+          the file "FILES.BBS" is no longer assumed. :-)  Now, you must
+          provide the full path *AND* filename of the file to be used as the
+          list file.  This allows you to keep your list files in one shared
+          directory.  In addition, the full path is no longer needed in the
+          list files.  If the path is not given, BFE will look for each file
+          in the directory the list file resides in.
+
+        o New Language File Keywords (Thanks to Michael Stumpp)
+
+                PASSWORD   - "Enter password" prompt - Default is "Password:"
+                Y_CONTINUE - Key used to continue in "more" prompting
+                N_CONTINUE - Key used to stop in "more" prompting
+                S_CONTINUE - Key used to go nonstop in "more" prompting
+
+        o BFE's internal user system now has an additional mode of operation.
+          This "Exclusive" mode is identical to the full user system, but
+          will not allow new users at all.  This may come in handy if you
+          wish to allow only "registered" users on certain nodes, or if you
+          run a "member's only" BBS.  (Thanks to Dana Meli-Wischman).
+
+        o NEW SCRIPT COMMANDS:
+
+                FormGen() - Processes a BFE/FormGen Entry Form
+
+                SaveScreen() - Saves contents of current screen
+
+                RestoreScreen() - Restores contents of saved screens
+
+                PopupMenu() - Creates a configurable popup menu
+
+                RemoveFile() - Removes the passed filename (deletes it)
+
+                MakeCustomSem() - Creates a CUSTOM.nnn semaphore in the
+                                  IPC directory.
+
+                RemCustomSem() - Removes CUSTOM.nnn semaphore from IPC dir.
+                
+                NoConsoleLogging() - Temporarily disables console logging
+                                     while in a BFE gateway session. (+)
+
+                ConsoleLogging() - Re-enables console logging in BFE gateway
+                                   sessions. (+)
+
+                DisableServerStr() - Temporarily disables BFE's remote server
+                                     strings. (+)
+
+                EnableServerStr() - Re-enables remote server strings. (+)
+
+                SetAccess() - Adjusts the caller's security level
+
+                UpdateUserRecord() - Update's user's record in user file
+
+        o RIP support updated - when BFE is displaying a RIP file to user,
+          the appropriate ANSI or ASCII version will be displayed locally
+          on the sysop's console.  Note that this doesn't include the
+          default BFE internal menus, although it will in the future.
+
+        o A new utility called COLORUPD.EXE has been provided.  This program
+          will read the default color configuration stored in the GLOBALS.CFG
+          file, and will update the color maps of all .CTL files in the
+          current directory.  This program is distributed in the \UTILS
+          subdirectory in the distribution archive.  The source (COLORUDP.C)
+          is located in the \DEVKIT subdirectory.  Thanks to Keith Ross for
+          this suggestion.
+
+        o If using the BFE User System, BFE will now export the user's name
+          and password to two environment variables in the DOS master
+          environment upon exiting on an errorlevel (USERNAME and PASSWORD
+          are the new environment variables) This should make automatic
+          logins through BFE a bit easier, as you can now pass the name and
+          password on the command line to your BBS packages.
+
+        o Now, a special "log text" field is attached to each menu item.
+          This field serves two purposes:
+
+                - It is used in the log file for the task to describe each
+                  event.
+
+                - It is used in the menu item selector screen in BFE/Setup,
+                  in place of the description field.
+
+          If no log text is supplied, the normal description will be used.
+
+        o The macro system has been completely revamped, and the following
+          new macros are available:
+
+                %U  = User's name
+                %W  = User's password
+                %H  = User's handle
+
+        o BFE will now periodically perform an Integrity check, to ensure
+          that pertinent system files have not been altered by viruses or
+          intruders.
+
+        o Some sample .CTL files are now included under (\SAMPLES\CTL).
+          These will be updated further as more features are added to the
+          product.
+
+        o New networking semaphore files implemented (nnn = node number):
+
+                 SHELL.nnn  - User/sysop in DOS shell
+                  XFER.nnn  - User transferring a file
+                  CHAT.nnn  - User either paging or in a chat session
+               FORMGEN.nnn  - User in a BFE/FormGen Entry Form
+                CUSTOM.nnn  - Custom user-defined semaphores
+
+        o New fields in BFE/Setup:
+
+                Default .CTL file Path (for BFE control files)
+                Default .FRM file Path (for BFE/Formgen files)
+                Default .SCR file Path (for BFE/Script files)
+                Default .ANS file Path (for ANS/ASC/AVT/RIP files)
+
+        o New script hook:  This script will be executed automatically
+          if it exists when a new user has logged into the system.
+
+        * The graphics detection routines have been replaced, and should
+          perform much smoother now.
+
+        * Three new fields have been added to the user file.  They are
+          handle, home phone, and work phone.
+
+        * The documentation has been completely reorganized.
+
+        * BFE will no longer automatically put a divider line in between
+          normal and global commands when using the internal menus.  If you
+          want a divider line, you can just add it to the top of your
+          global menu. :-)
+
+        * When displaying standard ASCII files, BFE will now default to light
+          gray on black.  In previous releases, the color of the text would
+          appear in the color of the user input.
+                                                            
+        * When running external processes (other doors, in particular), BFE
+          will now de-initialize the fossil driver, and re-initialize it upon
+          re-entry.  This was due to the fact that some doors left the fossil
+          driver in quite a shabby state after exiting, so bad, in fact, that
+          BFE would function normally, but not be seen on the remote end!
+
+        * Gateway sessions will now flow much smoother.  So smooth, in fact,
+          that we actually ran a SCO/UNIX UUCP session through it. :-) (+)
+
+        * BFE now tries to avoid the use of high bit ascii characters when
+          running with ASCII callers.  If any of you have seen ANSI emulation
+          under UNIX terminal packages, you know why... :-)
+
+        * Password protection now covers an entire file list when downloading
+          from protected areas.  In previous releases, the user had to enter
+          the password for each file.  Sounds stupid, doesn't it?  It was.
+
+        * Rewrote the import message file routines when using external
+          editors.  You should no longer lose characters under certain
+          word wrapping conditions.
+
+FREQ:  BFE from:  Southeastern DataLINK Zoom 28.8  1:3603/500 (813) 572-6817
+       345K in size
+
+ FTP:  ftp.csn.org:~/crlhq/bfe4002r.zip
+
+Previously registered users of BFE should contact us through the normal
+support channels (email, netmail, dialup) to request your new serialization
+keys.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Release Notice: GIF View Preview Door v1.0 ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+Greetings All, from Lone Wolf Software!
+
+****************************************************************
+*                    GIF View Preview Door                     *
+*         FileName: VUGIF10.ZIP   FileSize: 590k               *
+****************************************************************
+
+FEATURES............
+
+o Tag up to 50 GIFs for download.
+o Users can convert GIF files to JPG files for fast file transfers.
+o Internal file base engine for super quick file handling.
+o RIPterm users can view the JPG files while ON LINE!
+o Converts large GIF files to small (often less than 10k) JPG files
+  that transfer fast. RIPterm users can tag, convert and view GIFs
+  while on line in under 30 seconds at 14,400 baud.
+o Non RIPterm users (ANSI callers) can use many popular graphic
+  file viewers like CShow or The Graphics Work Shop to view JPG
+  preview files to see if they want to download the actual GIF.
+o State of the art sysop management tools, file base editor, and
+  built in GIF graphic file viewer.
+o Others may say "On Line GIF Viewing", but they require you to log
+  off to view the graphic file, that's not on line viewing. This
+  door actually lets RIPterm users view the JPG / GIF while still
+  on line.
+o Extensive use of RIP through out, both to the user and the sysop.
+o Option to disable RIP on either local or remote end.
+
+FREQ the file VUGIF10.ZIP from 1:3813/309 or call the board at
+1-918-687-1612 at speeds up to 19,200.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Release Notice: Operation: Office v0.5 ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+                        Operation: Office v0.5
+                        ----------------------
+                          An OmniWare Product
+                          -------------------
+                           by Mike Aleksiuk
+
+Get your latest copy from...
+
+Name:        C.R.I.S.I.S. HQ
+Number:      686-0449
+Node Number: 1:134/31
+Magic Name:  OPOFF
+File Name:   OPOFFV05.ZIP
+Place:       Calgary, AB, Canada
+
+        In this exciting new game, we have you set in the unexciting
+role of an "office worker".  But last night you overheard your boss
+talking to someone... and for no apparent reason, you decide to kill
+him!  The challenge is about to begin...
+
+        Supports all major dropfiles (DOOR.SYS, DORINFO?.DEF, etc).
+Configure specific game settings.  Usable player editor for registered
+users.  Unregistered users may only delete/look at the player information.
+
+        Features
+        --------
+
+        - chat with other players in a "one-liner" fashion
+        - use the phone
+        - listen to the radio
+        - many different enemies to fight, many different things to buy
+        - work to make money
+
+        Beta Gamma Wanna-Be
+        -------------------
+
+        Version 0.5 is the "Beta Gamma Wanna-Be" version of Operation:
+Office.  In other words, it is a gamma/wide beta, for all to test.  It
+is fully functioning, but still needs to be refined.
+
+        A Few Future Features
+        ---------------------
+
+        - personal player phone numbers!
+        - much more detailed fight method!
+        - more enemies!
+
+        Please send all comments/bug reports via FidoNet netmail to
+1:134/21.10, or conventional mail at the address stated within the
+SysOp documentation.  Unfortunately, I don't have the time/money to
+return all of your messages... Sorry!
+
+Mike Aleksiuk, Author of "O:O"
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+ܳ OpenDoors Tech Journal Information ³
+ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
+
+Editors: Scott Burkett
+         Southeastern DataLINK BBS
+         1:3603/[email protected]
+         [email protected] (Internet EMail)
+         [email protected] (Internet EMail)
+         (813) 572-6817 28.8 Zoom!
+         13500 Feather Sound Circle West
+         Suite #1313
+         Clearwater, FL 34622
+
+         Brian Pirie
+         BP ECOMM Systems
+         1:243/[email protected]
+         [email protected] (Internet EMail)
+         75122,2303 (Compuserve)
+         (613) 526-4466 14.4
+         1416 - 2201 Riverside Drive
+         Ottawa, Ontario
+         Canada
+         K1H 8K9
+
+Published by and for programmers and users of the OpenDoors Door
+Programming Toolkit.  It is a compilation of tips, reviews, and tidbits
+pertaining to BBS programming and general usage. The opinions expressed
+in this publication do not necessarily represent those of its editors,
+the OpenDoors author, or other contributors.
+
+OBTAINING COPIES:  The latest copy of the OpenDoors Tech Journal will always
+be available under the magic name of ODTJ.
+
+SUBMISSIONS: You are encouraged to submit articles for publication in the
+journal.  Please send all items to one of the afore-mentioned systems via BBS
+upload or mailer file/attach.
+
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+   ->< End of The OpenDoors Tech Journal - Volume 94 Issue Number 2 ><-
+ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+

+ 173 - 0
odoors/historic/ROLLCALL.TXT

@@ -0,0 +1,173 @@
+----------------------------------------------------------------------------
+                       The OpenDoors Rollcall!
+----------------------------------------------------------------------------
+
+The OpenDoors Roll Call is a compilation of BBS utilities and doors that were
+authored using Brian Pirie's OpenDoors package.  If you would like to see
+your products listed in this section, send the following information to either
+Brian Pirie or myself (via netmail, echomail, or snailmail):
+
+                         Product Name
+                         Author Name
+                         Description
+                         Price
+                         FREQ Address/BBS Number
+                         FREQ Name
+                         Latest Version
+
+----------------------------------------------------------------------------
+
+Auto-Message
+------------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Message to next caller door.
+Price:
+
+BFE (BBS Front End System)
+--------------------------
+Author(s): Scott Burkett - Cairo Research Labs
+version: 1.30.2à
+Requestable from: 1:3613/12
+Freq name: BFE
+Description: Complete BBS carousel, front end menu builder, remote jobs, etc
+Price: $10
+
+BID (BBS Information Door)
+--------------------------
+Author(s): Brian Pirie - Pirie Enterprises
+version: 2.00
+Requestable from: 1:243/8
+Freq name: BID
+Description: A door to introduce new BBS users to the world of BBSing
+Price: Freeware
+
+CALL-BACK
+---------
+Author(s): Don Laverdure
+version: 4.02
+Requestable from: 1:249/124
+Freq name: CB-402.ARJ
+Description: Callback verifier door for RA/SBBS/QBBS systems
+Price: unknown/Shareware
+
+EZVote
+------
+Author(s): Brian Pirie - Pirie Enterprises
+version: 4.10
+Requestable from: 1:243/8
+Freq name: EZVOTE
+Description: A user voting door for use with most BBS systems
+Price: Freeware
+
+Flame-Thrower
+-------------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Users leave next caller a flaming one-liner.
+Price:
+
+Node-Door
+---------
+Author(s): Don Laverdure
+Version: 2.11
+Requestable from: 1:249/124
+Freq name: ND-211.ARJ
+Description: Nodelist browser door for raw nodelists.
+Price: unknown/Shareware
+
+
+RegPRO
+------
+Author(s): Scott Burkett - Cairo Research Labs
+Version: 2.60
+Requestable from: 3613/12
+Freq name: REGPRO
+Description: Online BBS Fullscreen Entry Form/User Questionnaire System
+Price: $10/Shareware
+
+ROBO TAPE
+---------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version: 2.00
+Requestable from: 1:3813/309, 62:9600/0, 75:400/0  Anytime But ZMH.
+Freq name: ROBOT200.ARJ
+Description: BBS Tape Access Door
+Price: $10/Shareware
+
+TBM (Turbo Bulletin Manager)
+----------------------------
+Author(s): Scott Burkett - Cairo Research Labs
+Version: 2.50
+Requestable from: 3613/12
+Freq name: TBM
+Description: BBS Bulletin Manager/Tons of features/Most BBS Systems
+Price: $10/Shareware
+
+Tic-Tac-Toe
+-----------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Plays just like the real thing.
+Price:
+
+Triple Dare
+-----------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Try and outdraw the dealer! Great graphics.
+Price:
+
+Turbo Poll
+----------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Very graphical voting booth door
+Price:
+
+Turbo Quotes
+------------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Randomly displays quotes to users
+Price:
+
+Users-Info
+----------
+Author(s): Vince Jacobs - Lone Wolf Software
+Version:
+Requestable from:
+Freq name:
+Description: Displays Exteneded Info about users and prints it out.
+Price:
+
+VID (Virus Information Door)
+----------------------------
+Author(s): Scott Burkett - Cairo Research Labs
+Version: 2.00
+Requestable from: 3613/12
+Freq name: VID and VIDPLUS (Enhancement Module)
+Description: Online Virus Reference Database for most BBS types
+Price: $10/Shareware
+
+VKill
+-----
+Author(s): Scott Burkett - Cairo Research Labs
+Version: 3.00a
+Requestable from: 1:3613/12
+Freq name: VKILL
+Description: Upload Integrity Door for Maximus CBCS
+Price: $10/Shareware
+

+ 25 - 0
odoors/historic/odtips3/BPFIND.H

@@ -0,0 +1,25 @@
+/* MSC / BC compatible findfirst()/findnext() definitions. */
+
+#ifdef __TURBOC__
+#include <dir.h>
+#include <dos.h>
+#else
+#include <dos.h>
+struct ffblk
+{
+   char ff_reserved[21];
+   char ff_attrib;
+   unsigned ff_ftime;
+   unsigned ff_fdate;
+   long ff_fsize;
+   char ff_name[13];
+}
+#define findfirst(p, f, a)  _dos_findfirst(p, (struct _find_t *)f, a)
+#define findnext(f)         _dos_findnext((struct _find_t *)f)
+#define FA_RDONLY           _A_RDONLY
+#define FA_HIDDEN           _A_HIDDEN
+#define FA_SYSTEM           _A_SYSTEM
+#define FA_LABEL            _A_VOLID
+#define FA_DIREC            _A_SUBDIR
+#define FA_ARCH             _A_ARCH
+#endif

+ 342 - 0
odoors/historic/odtips3/CMDLINE.C

@@ -0,0 +1,342 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "opendoor.h"
+#include "cmdline.h"
+
+
+#ifndef BOOL
+typedef int BOOL;
+#endif
+
+typedef enum
+{
+   kParamLocal,
+   kParamBPS,
+   kParamPort,
+   kParamNode,
+   kParamHelp,
+   kParamPersonality,
+   kParamMaxTime,
+   kParamAddress,
+   kParamIRQ,
+   kParamNoFOSSIL,
+   kParamNoFIFO,
+   kParamDropFile,
+   kParamUserName,
+   kParamTimeLeft,
+   kParamSecurity,
+   kParamLocation,
+   kParamUnknown
+} tCommandLineParameter;
+
+
+static void AdvanceToNextArg(int *pnCurrentArg, int nArgCount,
+			     char *pszOption);
+static void GetNextArgName(int *pnCurrentArg, int nArgCount,
+			   char *papszArguments[], char *pszString,
+			   int nStringSize);
+static tCommandLineParameter GetCommandLineParameter(char *pszArgument);
+
+
+void ParseStandardCommandLine(int nArgCount, char *papszArguments[])
+{
+   char *pszCurrentArg;
+   int nCurrentArg;
+
+   for(nCurrentArg = 1; nCurrentArg < nArgCount; ++nCurrentArg)
+   {
+      pszCurrentArg = papszArguments[nCurrentArg];
+
+      switch(GetCommandLineParameter(pszCurrentArg))
+      {
+	 case kParamLocal:
+	    od_control.od_force_local = TRUE;
+	    break;
+
+	 case kParamBPS:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.baud = atol(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamPort:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.port = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamHelp:
+	    printf("AVALIABLE COMMAND LINE PARAMETERS:\n");
+            printf(" -L or -LOCAL     - Causes door to operate in local mode, without requiring a\n");
+            printf("                    door information (drop) file.\n");
+            printf(" -DROPFILE x      - Door information file directory or directory+filename.\n");
+            printf(" -N x or -NODE x  - Sets the node number to use.\n");
+            printf(" -B x or -BPS x   - Sets the serial port <---> modem bps (baud) rate to use.\n");
+            printf(" -P x or -PORT x  - Sets the serial port to use, were 0=COM1, 1=COM2, etc.\n");
+            printf(" -ADDRESS x       - Sets serial port address in decimal NOT hexidecimal\n");
+            printf("                    (only has effect if FOSSIL driver is not being used).\n");
+            printf(" -IRQ x           - Sets the serial port IRQ line (only has effect if FOSSIL\n");
+            printf("                    driver is not being used).\n");
+            printf(" -NOFOSSIL        - Disables use of FOSSIL driver, even if available.\n");
+            printf(" -NOFIFO          - Disables use of 16550 FIFO buffers (only if FOSSIL driver\n");
+            printf("                    is not being used).\n");
+            printf(" -PERSONALITY x   - Sets the sysop status line / function key personality to\n");
+            printf("                    use - one of Standard, PCBoard, RemoteAccess or Wildcat.\n");
+            printf(" -MAXTIME x       - Sets the maximum number of minutes that any user will be\n");
+            printf("                    permitted to access the door.\n");
+            printf(" -USERNAME x      - Name of user who is currently online.\n");
+            printf(" -TIMELEFT x      - User's time remaining online.\n");
+            printf(" -SECURITY x      - User's security level.\n");
+            printf(" -LOCATION x      - Location from which user is calling.\n");
+            printf(" -?, -H or -HELP  - Displays command-line help and exits.\n");
+	    exit(1);
+	    break;
+
+	 case kParamNode:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_node = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamPersonality:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    if(stricmp(papszArguments[nCurrentArg], "Standard") == 0)
+	    {
+	       od_control.od_default_personality = PER_OPENDOORS;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "PCBoard") == 0)
+	    {
+	       od_control.od_default_personality = PER_PCBOARD;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "RemoteAccess") == 0)
+	    {
+	       od_control.od_default_personality = PER_RA;
+	    }
+	    else if(stricmp(papszArguments[nCurrentArg], "Wildcat") == 0)
+	    {
+	       od_control.od_default_personality = PER_WILDCAT;
+	    }
+	    else
+	    {
+	       printf("Unknown personality: %s\n", papszArguments[nCurrentArg]);
+	       exit(1);
+	    }
+	    break;
+
+	 case kParamMaxTime:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_maxtime = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamAddress:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_com_address = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamIRQ:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.od_com_irq = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamNoFOSSIL:
+	    od_control.od_no_fossil = TRUE;
+	    break;
+
+	 case kParamNoFIFO:
+	    od_control.od_com_no_fifo = TRUE;
+	    break;
+
+	 case kParamDropFile:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    strncpy(od_control.info_path, papszArguments[nCurrentArg],
+	       sizeof(od_control.info_path) - 1);
+	    od_control.info_path[sizeof(od_control.info_path) - 1] = '\0';
+	    break;
+
+	 case kParamUserName:
+	    GetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+	       od_control.user_name, sizeof(od_control.user_name));
+	    break;
+
+	 case kParamTimeLeft:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.user_timelimit = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamSecurity:
+	    AdvanceToNextArg(&nCurrentArg, nArgCount, pszCurrentArg);
+	    od_control.user_security = atoi(papszArguments[nCurrentArg]);
+	    break;
+
+	 case kParamLocation:
+	    GetNextArgName(&nCurrentArg, nArgCount, papszArguments,
+	       od_control.user_location, sizeof(od_control.user_location));
+	    break;
+
+	 default:
+	    printf("Unrecognized command line option: %s\n", pszCurrentArg);
+	    exit(1);
+	    break;
+      }
+   }
+}
+
+
+static void AdvanceToNextArg(int *pnCurrentArg, int nArgCount, char *pszOption)
+{
+   if(++*pnCurrentArg >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n", pszOption);
+      exit(1);
+   }
+}
+
+
+static void GetNextArgName(int *pnCurrentArg, int nArgCount,
+			   char *papszArguments[], char *pszString,
+			   int nStringSize)
+{
+   BOOL bFirst = TRUE;
+
+   if((*pnCurrentArg) + 1 >= nArgCount)
+   {
+      printf("Missing parameter for option: %s\n",
+         papszArguments[(*pnCurrentArg) - 1]);
+      exit(1);
+   }
+
+   pszString[0] = '\0';
+
+   while(++*pnCurrentArg < nArgCount)
+   {
+      if(GetCommandLineParameter(papszArguments[*pnCurrentArg])
+	 != kParamUnknown)
+      {
+         --*pnCurrentArg;
+	 break;
+      }
+
+      if(strlen(pszString) >= nStringSize - 1)
+      {
+	 break;
+      }
+
+      if(!bFirst)
+      {
+	 strcat(pszString, " ");
+      }
+
+      strncat(pszString, papszArguments[*pnCurrentArg],
+         strlen(pszString) - nStringSize - 1);
+      pszString[nStringSize - 1] = '\0';
+
+      bFirst = FALSE;
+   }
+
+}
+
+
+static tCommandLineParameter GetCommandLineParameter(char *pszArgument)
+{
+   if(*pszArgument == '-' || *pszArgument == '/')
+   {
+      ++pszArgument;
+   }
+
+   if(stricmp(pszArgument, "L") == 0
+      || stricmp(pszArgument, "LOCAL") == 0)
+   {
+      return(kParamLocal);
+   }
+   else if(stricmp(pszArgument, "B") == 0
+      || stricmp(pszArgument, "BPS") == 0
+      || stricmp(pszArgument, "BAUD") == 0)
+   {
+      return(kParamBPS);
+   }
+   else if(stricmp(pszArgument, "P") == 0
+      || stricmp(pszArgument, "PORT") == 0)
+   {
+      return(kParamPort);
+   }
+   else if(stricmp(pszArgument, "N") == 0
+      || stricmp(pszArgument, "NODE") == 0)
+   {
+      return(kParamNode);
+   }
+   else if(stricmp(pszArgument, "?") == 0
+      || stricmp(pszArgument, "H") == 0
+      || stricmp(pszArgument, "HELP") == 0)
+   {
+      return(kParamHelp);
+   }
+   else if(stricmp(pszArgument, "PERSONALITY") == 0)
+   {
+      return(kParamPersonality);
+   }
+   else if(stricmp(pszArgument, "MAXTIME") == 0)
+   {
+      return(kParamMaxTime);
+   }
+   else if(stricmp(pszArgument, "ADDRESS") == 0)
+   {
+      return(kParamAddress);
+   }
+   else if(stricmp(pszArgument, "IRQ") == 0)
+   {
+      return(kParamIRQ);
+   }
+   else if(stricmp(pszArgument, "NOFOSSIL") == 0)
+   {
+      return(kParamNoFOSSIL);
+   }
+   else if(stricmp(pszArgument, "NOFIFO") == 0)
+   {
+      return(kParamNoFIFO);
+   }
+   else if(stricmp(pszArgument, "DROPFILE") == 0)
+   {
+      return(kParamDropFile);
+   }
+   else if(stricmp(pszArgument, "USERNAME") == 0)
+   {
+      return(kParamUserName);
+   }
+   else if(stricmp(pszArgument, "TIMELEFT") == 0)
+   {
+      return(kParamTimeLeft);
+   }
+   else if(stricmp(pszArgument, "SECURITY") == 0)
+   {
+      return(kParamSecurity);
+   }
+   else if(stricmp(pszArgument, "LOCATION") == 0)
+   {
+      return(kParamLocation);
+   }
+   else
+   {
+      return(kParamUnknown);
+   }
+}
+
+
+void NoDoorFileHandler(void)
+{
+   /* Alter OpenDoors behaviour, so that we proceed with defaults if  */
+   /* no door information file is available, rather than exiting with */
+   /* an error. Set od_no_file_func to point to this function.        */
+   if(strlen(od_control.user_name) == 0)
+   {
+      strcpy(od_control.user_name, "Unknown User");
+   }
+   if(strlen(od_control.user_location) == 0)
+   {
+      strcpy(od_control.user_location, "Unknown Location");
+   }
+   if(od_control.user_timelimit == 0)
+   {
+      od_control.user_timelimit = 30;
+   }
+
+   od_control.od_info_type = CUSTOM;
+}

+ 5 - 0
odoors/historic/odtips3/CMDLINE.H

@@ -0,0 +1,5 @@
+#ifndef INC_CMDLINE
+#define INC_CMDLINE
+void ParseStandardCommandLine(int nArgCount, char *papszArguments[]);
+void NoDoorFileHandler(void);
+#endif

+ 352 - 0
odoors/historic/odtips3/FILEVIEW.C

@@ -0,0 +1,352 @@
+/* fileview.c - File viewing door that demonstrates the use of the    */
+/*              PagedViewer() function. This door can be setup to     */
+/*              to display a single text file, or any file from an    */
+/*              entire directory of text files. The program accepts   */
+/*              a single command-line argument, which if present,     */
+/*              specifies the filename or wildcard of the files to    */
+/*              display. If this argument is not present, all files   */
+/*              in the current directory will be available for        */
+/*              viewing. If there is more than one possible file to   */
+/*              be displayed, this program will display a list of     */
+/*              files that the user can choose from to display. If    */
+/*              there is only one possible file, that file is         */
+/*              is displayed. This program uses PagedViewer() for two */
+/*              seperate uses - the list of available files, and for  */
+/*              viewing the file itself.                              */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "bpfind.h"
+#include "opendoor.h"
+#include "pageview.h"
+
+
+/* Configurable constants. */
+#define FILENAME_SIZE        75
+#define PATH_CHARS           (FILENAME_SIZE - 13)
+#define LINE_SIZE            80
+#define ARRAY_GROW_SIZE      20
+
+
+/* Global variables. */
+int nTotalFiles = 0;
+int nFileArraySize = 0;
+char *paszFileArray = NULL;
+
+FILE *pfCurrentFile;
+int nTotalLines = 0;
+int nLineArraySize = 0;
+long *palLineOffset = NULL;
+
+
+/* Function prototypes. */
+void AddFilesMatching(char *pszFileSpec);
+char *GetFilename(int nIndex);
+int AddFilename(char *pszFilename);
+void GetDirOnly(char *pszOutDirName, const char *pszInPathName);
+int DirExists(const char *pszDirName);
+void BuildPath(char *pszOut, char *pszPath, char *pszFilename);
+void FreeFileList(void);
+void DisplayFileName(int nLine, void *pCallbackData);
+void DisplayFile(char *pszFilename);
+void DisplayFileLine(int nLine, void *pCallbackData);
+int AddOffsetToArray(long lOffset);
+void FreeLineArray(void);
+
+
+/* Program execution begins here. */
+int main(int nArgCount, char *papszArgument[])
+{
+   int nArg;
+   int nChoice;
+
+   od_init();
+
+   /* Get file specifiction from command-line, if any. */
+   if(nArgCount >= 2)
+   {
+      for(nArg = 1; nArg < nArgCount; ++nArg)
+      {
+         AddFilesMatching(papszArgument[nArg]);
+      }
+   }
+
+   /* If there are no command-line parameters, use *.* */
+   else
+   {
+      AddFilesMatching("*.*");
+   }
+
+   /* If there are no matching files, display error. */
+   if(nTotalFiles == 0)
+   {
+      od_printf("No files were found.\n\r\n\r");
+      od_printf("Press [Enter] to continue.\n\r");
+      od_get_answer("\n\r");
+      return(0);
+   }
+
+   /* If only one file was found, then display it. */
+   else if(nTotalFiles == 1)
+   {
+      DisplayFile(GetFilename(0));
+   }
+
+   /* If more than one file was found, allow user to choose file */
+   /* to display.                                                */
+   else
+   {
+      /* Loop until user chooses to quit. */
+      nChoice = 0;
+      for(;;)
+      {
+         /* Get user's selection. */
+         nChoice = PagedViewer(nChoice, nTotalFiles, DisplayFileName,
+            NULL, TRUE, "Choose A File To Display", 19);
+
+         /* If user chose to quit, then exit door. */
+         if(nChoice == NO_LINE) break;
+
+         /* Otherwise, display the file that the user chose. */
+         DisplayFile(GetFilename(nChoice));
+      }
+   }
+
+   FreeFileList();
+
+   return(0);
+}
+
+
+void AddFilesMatching(char *pszFileSpec)
+{
+   struct ffblk DirEntry;
+   int bNoMoreFiles;
+   char szDirName[PATH_CHARS + 1];
+   char szFileName[FILENAME_SIZE];
+
+   /* Check that file specification is not too long. */
+   if(strlen(pszFileSpec) > PATH_CHARS)
+   {
+      return;
+   }
+
+   /* Get directory name from path. */
+   GetDirOnly(szDirName, pszFileSpec);
+
+   bNoMoreFiles = findfirst(pszFileSpec, &DirEntry, FA_RDONLY);
+   while(!bNoMoreFiles)
+   {
+      BuildPath(szFileName, szDirName, DirEntry.ff_name);
+
+      AddFilename(szFileName);
+
+      bNoMoreFiles = findnext(&DirEntry);
+   }
+}
+
+
+void GetDirOnly(char *pszOutDirName, const char *pszInPathName)
+{
+   char *pchBackslashChar;
+
+   /* Default dir name is entire path. */
+   strcpy(pszOutDirName, pszInPathName);
+
+   /* If there is a backslash in the string. */
+   pchBackslashChar = strrchr(pszOutDirName, '\\');
+   if(pchBackslashChar != NULL)
+   {
+      /* Remove all character beginning at last backslash from path. */
+      *pchBackslashChar = '\0';
+   }
+   else
+   {
+      /* If there is no backslash in the filename, then the dir name */
+      /* is empty.                                                   */
+      pszOutDirName[0] = '\0';
+   }
+}
+
+
+void BuildPath(char *pszOut, char *pszPath, char *pszFilename)
+{
+   /* Copy path to output filename. */
+   strcpy(pszOut, pszPath);
+
+   /* Ensure there is a trailing backslash. */
+   if(strlen(pszOut) > 0 && pszOut[strlen(pszOut) - 1] != '\\')
+   {
+      strcat(pszOut, "\\");
+   }
+
+   /* Append base filename. */
+   strcat(pszOut, pszFilename);
+}
+
+
+char *GetFilename(int nIndex)
+{
+   return(paszFileArray + (nIndex * FILENAME_SIZE));
+}
+
+
+int AddFilename(char *pszFilename)
+{
+   int nNewArraySize;
+   char *paszNewArray;
+   char *pszNewString;
+
+   /* If array is full, then try to grow it. */
+   if(nTotalFiles == nFileArraySize)
+   {
+      nNewArraySize = nFileArraySize + ARRAY_GROW_SIZE;
+      if((paszNewArray =
+         realloc(paszFileArray, nNewArraySize * FILENAME_SIZE)) == NULL)
+      {
+         return(FALSE);
+      }
+      nFileArraySize = nNewArraySize;
+      paszFileArray = paszNewArray;
+   }
+
+   /* Get address to place new string at, while incrementing total number */
+   /* of filenames.                                                       */
+   pszNewString = GetFilename(nTotalFiles++);
+
+   /* Copy up to the maximum number of filename characters to the string. */
+   strncpy(pszNewString, pszFilename, FILENAME_SIZE - 1);
+   pszNewString[FILENAME_SIZE - 1] = '\0';
+
+   return(TRUE);
+}
+
+
+void FreeFileList(void)
+{
+   if(nFileArraySize > 0)
+   {
+      free(paszFileArray);
+      nFileArraySize = 0;
+      nTotalFiles = 0;
+      paszFileArray = NULL;
+   }
+}
+
+
+void DisplayFileName(int nLine, void *pCallbackData)
+{
+   (void)pCallbackData;
+
+   od_printf(GetFilename(nLine));
+}
+
+
+void DisplayFile(char *pszFilename)
+{
+   char szLine[LINE_SIZE];
+   long lnOffset;
+
+   /* Clear the screen. */
+   od_clr_scr();
+
+   /* Attempt to open the file. */
+   pfCurrentFile = fopen(pszFilename, "r");
+   if(pfCurrentFile == NULL)
+   {
+      od_printf("Unable to open file.\n\r\n\r");
+      od_printf("Press [Enter] to continue.\n\r");
+      od_get_answer("\n\r");
+      return;
+   }
+
+   /* Get file offsets of each line and total line count from file. */
+   for(;;)
+   {
+      lnOffset = fTell(pfCurrentFile);
+
+      if(fgets(szLine, LINE_SIZE, pfCurrentFile) == NULL) break;
+
+      AddOffsetToArray(lnOffset);
+   }
+
+   /* Use PagedViewer() to view the file. */
+   PagedViewer(0, nTotalLines, DisplayFileLine, NULL, FALSE, NULL, 21);
+
+   /* Deallocate array of line offsets. */
+   FreeLineArray();
+
+   /* Close the file. */
+   fclose(pfCurrentFile);
+}
+
+
+void DisplayFileLine(int nLine, void *pCallbackData)
+{
+   char szLine[LINE_SIZE];
+   long lnTargetOffset = palLineOffset[nLine];
+   int nLineLen;
+
+   (void)pCallbackData;
+
+   /* Move to proper offset in file. */
+   if(lnTargetOffset != ftell(pfCurrentFile))
+   {
+      fseek(pfCurrentFile, lnTargetOffset, SEEK_SET);
+   }
+
+   /* Get line from line. */
+   if(fgets(szLine, LINE_SIZE, pfCurrentFile) != NULL)
+   {
+      /* Remote any trailing CR/LF sequence from line. */
+      nLineLen = strlen(szLine);
+      while(nLineLen > 0
+         && (szLine[nLineLen - 1] == '\r' || szLine[nLineLen - 1] == '\n'))
+      {
+         szLine[--nLineLen] = '\0';
+      }
+
+      /* Display the line on the screen. */
+      od_disp_str(szLine);
+   }
+}
+
+
+int AddOffsetToArray(long lOffset)
+{
+   long *palNewArray;
+   int nNewArraySize;
+
+   /* If array is full, then grow it. */
+   if(nTotalLines == nLineArraySize)
+   {
+      nNewArraySize = nLineArraySize + ARRAY_GROW_SIZE;
+
+      if((palNewArray =
+         realloc(palLineOffset, nNewArraySize * sizeof(long))) == NULL)
+      {
+         return(FALSE);
+      }
+
+      nLineArraySize = nNewArraySize;
+      palLineOffset = palNewArray;
+   }
+
+   palLineOffset[nTotalLines++] = lOffset;
+
+   return(TRUE);
+}
+
+
+void FreeLineArray(void)
+{
+   if(nLineArraySize > 0)
+   {
+      nTotalLines = 0;
+      nLineArraySize = 0;
+      free(palLineOffset);
+      palLineOffset = NULL;
+   }
+}
+  

+ 177 - 0
odoors/historic/odtips3/PAGEVIEW.C

@@ -0,0 +1,177 @@
+/* pageview.c - Implementation of the PagedViewer() system. */
+
+#include <string.h>
+
+#include "opendoor.h"
+#include "pageview.h"
+
+char bTitleColor = 0x0c;
+char bTitleLineColor = 0x04;
+char bNumberColor = 0x0a;
+char bTextColor = 0x02;
+char bPromptColor = 0x0f;
+
+int PagedViewer(
+   int nInitialLine,                      /* Zero-based initial line number. */
+   int nTotalLines,                       /* Total line count. */
+   void (*pDisplayCallback)(int nLine, void *pData),
+   void *pCallbackData,                   /* Data to pass to callback func. */
+   BOOL bAllowSelection,                  /* TRUE if selection is permitted. */
+   char *pszTitle,                        /* Title string, or NULL. */
+   int nPageSize)                         /* # of lines to display per page. */
+{
+   int nCurrentPage = 0;
+   int nScreenLine;
+   int nAbsoluteLine;
+   char chPressed;
+   char bCanPageDown;
+   char bCanPageUp;
+
+   /* Determine current page from initial line number, if specified. */
+   if(nInitialLine != NO_LINE)
+   {
+      nCurrentPage = nInitialLine / nPageSize;
+   }
+
+   /* Loop until user makes a selection, or chooses to quit. */
+   for(;;)
+   {
+      /* Display the current page. */
+
+      /* Clear the screen */
+      od_printf("\n\r");
+      od_clr_scr();
+
+      /* If a title has been specified, then display it. */
+      if(pszTitle != NULL)
+      {
+         od_set_attrib(bTitleColor);
+         od_repeat(' ', (80 - strlen(pszTitle)) / 2);
+         od_disp_str(pszTitle);
+         od_printf("\n\r");
+         od_set_attrib(bTitleLineColor);
+         if(od_control.user_ansi || od_control.user_avatar)
+         {
+            od_repeat(196, 79);
+         }
+         else
+         {
+            od_repeat('-', 79);
+         }
+         od_printf("\n\r");
+      }
+
+      /* Display the lines on this page. */
+      nAbsoluteLine = nCurrentPage * nPageSize;
+      nScreenLine = 0;
+      while(nScreenLine < nPageSize && nAbsoluteLine < nTotalLines)
+      {
+         /* If selection is permitted, display an identifier for each line. */
+         if(bAllowSelection)
+         {
+            od_set_attrib(bNumberColor);
+            if(nScreenLine < 9)
+            {
+               od_printf("%d. ", nScreenLine + 1);
+            }
+            else
+            {
+               od_printf("%c. ", 'A' + (nScreenLine - 9));
+            }
+         }
+
+         /* Display the line itself. */
+         od_set_attrib(bTextColor);
+         (*pDisplayCallback)(nAbsoluteLine, pCallbackData);
+         od_printf("\n\r");
+
+         /* Move to next line. */
+         nScreenLine++;
+         nAbsoluteLine++;
+      }
+
+      /* Determine whether user can page up or down from this page. */
+      bCanPageDown = nCurrentPage < (nTotalLines - 1) / nPageSize;
+      bCanPageUp = nCurrentPage > 0;
+
+      /* Display prompt at bottom of screen. */
+      od_set_attrib(bPromptColor);
+      od_printf("\n\r[Page %d of %d]  ", nCurrentPage + 1,
+         ((nTotalLines - 1) / nPageSize) + 1);
+      if(bAllowSelection)
+      {
+         od_printf("Choose an option or");
+      }
+      else
+      {
+         od_printf("Available options:");
+      }
+      if(bCanPageDown)
+      {
+         od_printf(" [N]ext page,");
+      }
+      if(bCanPageUp)
+      {
+         od_printf(" [P]revious page,");
+      }
+      od_printf(" [Q]uit.");
+
+      /* Loop until the user makes a valid choice. */
+      for(;;)
+      {
+         /* Get key from user */
+         chPressed = toupper(od_get_key(TRUE));
+
+         if(chPressed == 'Q')
+         {
+            /* If user chooses to quit, then return without a selection. */
+            od_printf("\n\r");
+            return(NO_LINE);
+         }
+
+         else if(chPressed == 'P' && bCanPageUp)
+         {
+            /* Move to previous page and redraw screen. */
+            --nCurrentPage;
+            break;
+         }
+
+         else if(chPressed == 'N' && bCanPageDown)
+         {
+            /* Move to next page and redraw screen. */
+            ++nCurrentPage;
+            break;
+         }
+
+         else if(bAllowSelection
+                 && (
+                         (chPressed >= '1' && chPressed <= '9')
+                      || (chPressed >= 'A' && chPressed <= 'M')
+                    )
+                )
+         {
+            /* If user pressed a possible line key, and selection is */
+            /* enabled, try translating character to a line number.  */
+            if(chPressed >= '1' && chPressed <= '9')
+            {
+               nScreenLine = chPressed - '1';
+            }
+            else
+            {
+               nScreenLine = 9 + (chPressed - 'A');
+            }
+
+            /* Calculate absolute line number. */
+            nAbsoluteLine = nScreenLine + (nCurrentPage * nPageSize);
+
+            /* If selected line is within range, then return selected line */
+            /* number.                                                     */
+            if(nScreenLine < nPageSize && nAbsoluteLine < nTotalLines)
+            {
+               od_printf("\n\r");
+               return(nAbsoluteLine);
+            }
+         }
+      }
+   }
+}

+ 19 - 0
odoors/historic/odtips3/PAGEVIEW.H

@@ -0,0 +1,19 @@
+/* pageview.h - Include file for PagedViewer() function. */
+
+/* Manifest constant. */
+#define NO_LINE -1
+
+/* Boolean definitions. */
+#ifndef BOOL
+typedef int BOOL;
+#endif
+
+/* Prototype for the function. */
+int PagedViewer(
+   int nInitialLine,
+   int nTotalLines,
+   void (*pDisplayCallback)(int nLine, void *pCallbackData),
+   void *pCallbackData,
+   BOOL bAllowSelection,
+   char *pszTitle,
+   int nPageSize);

+ 46 - 0
odoors/historic/odtips3/SUMMARY.TXT

@@ -0,0 +1,46 @@
+                         OpenDoors tips package #3
+
+                                    ---
+
+           (C) Copyright 1994, Brian Pirie. All Rights Reserved.
+
+
+This package includes a collection of tips and utility functions to help
+those using the OpenDoors door programming toolkit. You are free to use
+these tips and included source code as you wish, without any fee or
+royalty.
+
+
+This package includes the following tip files:
+
+Tip #1.  Drawing horizontal bar graphs using od_repeat().
+         Files: tip1.txt, tip1.c
+
+Tip #2.  Transferring files using DSZ.
+         Files: tip2.txt, tip2.c
+
+Tip #3.  A generic paged viewing / selection routine, and text file
+         viewing door that uses the paged viewing routine.
+         Files: tip3.txt, pageview.c, pageview.h, fileview.c, bpfind.h
+
+Tip #4.  Command-line processing for many standard door-related command
+         line options.
+         Files: tip4.txt,tip4.c, cmdline.c, cmdline.h
+
+
+How to contact the author
+-------------------------
+
+I can be reached by any of the following means:
+
+   Internet email : [email protected]
+
+Fidonet Crashmail : 1:243/8 (you must poll for a response)
+
+Modem (24hrs/day) : +1 613 526 4466
+
+Conventional mail : Brian Pirie
+                    #1416 - 2201 Riverside Drive
+                    Ottawa, Ontario
+                    K1H 8K9
+                    Canada

+ 88 - 0
odoors/historic/odtips3/TIP1.C

@@ -0,0 +1,88 @@
+/* Includes */
+#include "opendoor.h"
+
+/* Function prototypes. */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars);
+void DrawGraphOfPercentages(int nItems, int *panPercentages,
+   char **papszTitles, char bTitleColor, char bGraphColor,
+   int nTitleWidth, int nGraphWidth);
+
+
+/* Main function - program execution begins here. */
+main()
+{
+   char *apszTitles[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+                          "Thursday", "Friday", "Saturday"};
+   int anValues[7] = {50, 75, 100, 25, 83, 0, 43};
+
+   od_printf("`bright green`Test graph:\n\r");
+
+   DrawGraphOfPercentages(7, anValues, apszTitles, 0x02, 0x0f, 20, 50);
+
+   od_get_key(TRUE);
+
+   return(0);
+}
+
+
+/* Function to draw horizontal graph of percentages with titles, to */
+/* demonstrate the use of the DrawHorizontalBar() function.         */
+/* No titles should have more than nTitleWidth characters.          */
+void DrawGraphOfPercentages(int nItems, int *panPercentages,
+   char **papszTitles, char bTitleColor, char bGraphColor,
+   int nTitleWidth, int nGraphWidth)
+{
+   int nCurrentItem;
+
+   /* Loop for each item (line) in the graph. */
+   for(nCurrentItem = 0; nCurrentItem < nItems; ++nCurrentItem)
+   {
+      /* Set display color for title text. */
+      od_set_attrib(bTitleColor);
+
+      /* Add spaces to right-align all titles. */
+      od_repeat(' ', nTitleWidth - strlen(papszTitles[nCurrentItem]));
+
+      /* Display the title. */
+      od_disp_str(papszTitles[nCurrentItem]);
+
+      /* Add space between title and graph. */
+      od_printf(" ");
+
+      /* Set display color for graph. */
+      od_set_attrib(bGraphColor);
+
+      /* Draw bar graph for this line. */
+      DrawHorizontalBar(panPercentages[nCurrentItem], 0, 100, nGraphWidth);
+
+      /* Move to the next line. */
+      od_printf("\n\r");
+   }
+}
+
+
+/* Function to draw a horizontal bar, given a value, the minimum and maximum */
+/* possible values, and the number of characters the horizontal bar should   */
+/* extended for the maximum value.                                           */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars)
+{
+   /* Determine lenght of bar */
+   int nBarChars = ((nValue - nMinValue) * nMaxChars) / nMaxValue;
+
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      /* If ANSI or AVATAR graphics are available, assume that IBM extended */
+      /* ASCII is also available. This function uses character 220 to form  */
+      /* bars that are 1/2 the height of the line. You might also want to   */
+      /* try character 119, which will form bars that are the entire height */
+      /* of the line.                                                       */
+      od_repeat(220, nBarChars);
+   }
+   else
+   {
+      /* In ASCII mode, the bar is formed by the '=' character. */
+      od_repeat('=', nBarChars);
+   }
+}

+ 55 - 0
odoors/historic/odtips3/TIP1.TXT

@@ -0,0 +1,55 @@
+Many different types of programs can be enhanced by the use of graphical
+information. Often, this graphical information can take the form of
+horizontal bar graphs.
+
+An easy way to draw horizontal bars in door programs written with
+OpenDoors, is to use the od_repeat() function. Not only does od_repeat()
+allow you to easily form a bar by repeating a particular character the
+specified number of times, but it is also a very efficient way to do so.
+od_repeat() will take advantage of terminal emulation optimizations,
+when available. For instance, a character can be repeated any number of
+times with AVATAR by sending a short 3-byte sequence that specifies the
+character and number of times to repeat.
+
+How do you calculate the number of character to use to form a bar in
+your graph? The DrawHorizontalBar() function, which is provided below,
+will do this calculation for you. Simply provide the value to be
+represented by this bar, the minimum and maximum possible values, and
+the maximum number of character to use to draw the bar. For example, if
+you are graphing percentages (which could range from 0% to 100%), and
+wanted the graph to fit in a space of 40 columns, you would use:
+
+    DrawHorizontalBar(nPercent, 0, 100, 40);
+
+The included tip1.c is a complete program which demonstrates the
+DrawHorizontalBar() function as called from another function that will
+create complete horizontal bar graphs. This second function,
+DrawGraphOfPercentages(), takes an array of titles, and array of values
+corresponding to each title, and draws a complete bar graph from this
+information.
+
+
+/* Function to draw a horizontal bar, given a value, the minimum and maximum */
+/* possible values, and the number of characters the horizontal bar should   */
+/* extended for the maximum value.                                           */
+void DrawHorizontalBar(int nValue, int nMinValue, int nMaxValue,
+   int nMaxChars)
+{
+   /* Determine lenght of bar */
+   int nBarChars = ((nValue - nMinValue) * nMaxChars) / nMaxValue;
+
+   if(od_control.user_ansi || od_control.user_avatar)
+   {
+      /* If ANSI or AVATAR graphics are available, assume that IBM extended */
+      /* ASCII is also available. This function uses character 220 to form  */
+      /* bars that are 1/2 the height of the line. You might also want to   */
+      /* try character 119, which will form bars that are the entire height */
+      /* of the line.                                                       */
+      od_repeat(220, nBarChars);
+   }
+   else
+   {
+      /* In ASCII mode, the bar is formed by the '=' character. */
+      od_repeat('=', nBarChars);
+   }
+}

+ 245 - 0
odoors/historic/odtips3/TIP2.C

@@ -0,0 +1,245 @@
+/* tip2.c - Example program to demonstrate how to send or receive files */
+/*          using DSZ, from within an OpenDoors program.                */
+
+/* Include required header files. */
+#include <stdio.h>
+#include <assert.h>
+#include "opendoor.h"
+
+/* File transfer protocol enumeration. */
+typedef enum
+{
+   XModem,
+   XModemCRC,
+   XModem1K,
+   YModem,
+   YModemG,
+   ZModem
+} eProtocol;
+
+/* Function prototypes. */
+int TransferFile(char *pszFilename, eProtocol Protocol, char bReceive);
+eProtocol ChooseProtocol(void);
+void AddParameter(char **papszArguments, int *pnCount, char *pszNewArgument);
+
+/* Manifest constants. */
+#define ARGS_ARRAY_SIZE 10
+
+/* Global variable with DSZ filename. */
+char szDSZFilename[80] = "DSZ";
+
+
+/* Program's execution begins here. */
+main()
+{
+   char chAnswer;
+   char bReceive;
+   eProtocol Protocol;
+   char szFilename[73];
+   int bSuccess;
+
+   od_printf("OpenDoors file transfer demo.\n\r\n\r");
+
+   /* Get file transfer direction from user. */
+   od_printf("Do you wish to [U]pload or [D]ownload? ");
+   chAnswer = od_get_answer("UD");
+   if(chAnswer == 'U')
+   {
+      od_printf("Upload\n\r\n\r");
+      bReceive = TRUE;
+   }
+   else
+   {
+      od_printf("Download\n\r\n\r");
+      bReceive = FALSE;
+   }
+
+   /* Get file transfer protocol from user. */
+   Protocol = ChooseProtocol();
+
+   /* Get filename. */
+   od_printf("\n\rEnter filename(s) : ");
+   od_input_str(szFilename, 72, ' ', 255);
+   od_printf("\n\r");
+
+   /* Perform file transfer. */
+   bSuccess = TransferFile(szFilename, Protocol, bReceive);
+
+   /* Display result of file transfer to user. */
+   od_clr_scr();
+   if(bSuccess)
+   {
+      od_printf("File transfer successful.\n\r");
+   }
+   else
+   {
+      od_printf("File transfer not completed.\n\r");
+   }
+
+   /* Prompt user to exit program. */
+   od_printf("Press [Enter] to return to BBS.\n\r");
+   od_get_answer("\n\r");
+
+   /* Return control to calling BBS software. */
+   od_exit(0, FALSE);
+
+   return(0);
+}
+
+
+/* Function to allow user to choose a file transfer protocol. */
+eProtocol ChooseProtocol(void)
+{
+   eProtocol Protocol;
+   char chAnswer;
+
+   od_printf("Available file transfer protocols:\n\r");
+   od_printf("    [X] XModem\n\r");
+   od_printf("    [C] XModem/CRC\n\r");
+   od_printf("    [1] XModem/1K\n\r");
+   od_printf("    [Y] YModem\n\r");
+   od_printf("    [G] YModem-G\n\r");
+   od_printf("    [Z] ZModem\n\r\n\r");
+   od_printf("Please select a protocol: ");
+
+   chAnswer = od_get_answer("XC1YGZ");
+
+   switch(chAnswer)
+   {
+      case 'X':
+         od_printf("XModem\n\r");
+         Protocol = XModem;
+         break;
+      case 'C':
+         od_printf("XModem/CRC\n\r");
+         Protocol = XModemCRC;
+         break;
+      case '1':
+         od_printf("XModem/1K\n\r");
+         Protocol = XModem1K;
+         break;
+      case 'Y':
+         od_printf("YModem\n\r");
+         Protocol = YModem;
+         break;
+      case 'G':
+         od_printf("YModem-G\n\r");
+         Protocol = YModemG;
+         break;
+      case 'Z':
+      default:
+         od_printf("ZModem\n\r");
+         Protocol = ZModem;
+         break;
+   }
+
+   return(Protocol);
+}
+
+
+/* Function to send or receive a file to/from remote system. */
+int TransferFile(char *pszFilename, eProtocol Protocol, char bReceive)
+{
+   char szPort[7];
+   char *apszArguments[ARGS_ARRAY_SIZE];
+   int nArgCount = 0;
+
+   /* Filename cannot be NULL. */
+   assert(pszFilename != NULL);
+
+   /* Ensure that we are not operating in local mode. */
+   if(od_control.baud == 0)
+   {
+      od_printf("Operating in local mode; file transfer not possible.\n\r");
+      return(FALSE);
+   }
+
+   /* Generate DSZ command line */
+
+   /* Begin with executable filename. */
+   AddParameter(apszArguments, &nArgCount, szDSZFilename);
+
+   /* Add port parameter. */
+   AddParameter(apszArguments, &nArgCount, "port");
+   sprintf(szPort, "%d", od_control.port + 1);
+   AddParameter(apszArguments, &nArgCount, szPort);
+
+   /* Restrict inbound files to current drive and directory. */
+   AddParameter(apszArguments, &nArgCount, "restrict");
+
+   /* Generate DSZ protocol parameters from specified protocol and direction. */
+   if(bReceive)
+   {
+      switch(Protocol)
+      {
+         case XModem:
+         case XModem1K:
+            AddParameter(apszArguments, &nArgCount, "rx");
+            break;
+         case XModemCRC:
+            AddParameter(apszArguments, &nArgCount, "rc");
+            break;
+         case YModem:
+            AddParameter(apszArguments, &nArgCount, "rb");
+            break;
+         case YModemG:
+            AddParameter(apszArguments, &nArgCount, "rb");
+            AddParameter(apszArguments, &nArgCount, "-g");
+            break;
+         case ZModem:
+            AddParameter(apszArguments, &nArgCount, "rz");
+            break;
+         default:
+            assert(FALSE);
+      }
+   }
+   else
+   {
+      switch(Protocol)
+      {
+         case XModem:
+         case XModemCRC:
+            AddParameter(apszArguments, &nArgCount, "sx");
+            break;
+         case XModem1K:
+            AddParameter(apszArguments, &nArgCount, "sx");
+            AddParameter(apszArguments, &nArgCount, "-k");
+            break;
+         case YModem:
+         case YModemG:
+            AddParameter(apszArguments, &nArgCount, "sb");
+            break;
+         case ZModem:
+            AddParameter(apszArguments, &nArgCount, "sz");
+            break;
+         default:
+            assert(FALSE);
+      }
+   }
+
+   /* Add filename parameter to command line. */
+   AddParameter(apszArguments, &nArgCount, pszFilename);
+
+   /* Display prompt to user providing */
+   od_printf("Begin your transfer now, or press [Ctrl]-[X] several times to abort.\n\r");
+
+   /* Execute command using the OpenDoors od_spawn() function. */
+   return(od_spawnvpe(P_WAIT, apszArguments[0], apszArguments, NULL) == 0);
+}
+
+
+/* Function to add next parameter to array of parameters to pass to */
+/* od_spawnvpe().                                                   */
+void AddParameter(char **papszArguments, int *pnCount, char *pszNewArgument)
+{
+   assert(*pnCount < ARGS_ARRAY_SIZE - 1);
+   assert(papszArguments != NULL);
+   assert(pnCount != NULL);
+   assert(pszNewArgument != NULL);
+
+   /* Add next argument to array. */
+   papszArguments[(*pnCount)++] = pszNewArgument;
+
+   /* Ensure that the array is always NULL-terminated. */
+   papszArguments[*pnCount] = NULL;
+}

+ 45 - 0
odoors/historic/odtips3/TIP2.TXT

@@ -0,0 +1,45 @@
+Often, it can be useful or necessary to send and receive files between
+a program using OpenDoors, and the remote user's system. To allow file
+uploading and downloadeding, you can either implement the common file
+transfer protocols as part of your program, or you can call an external
+file transfer program, such as DSZ. This tip demonstrates how you can
+call DSZ using OpenDoors.
+
+In order to see this program in action, you should run a terminal
+program on one machine, and connect to second machine that will run this
+example program. (You could also do this in two different windows on one
+machine.) The demonstration program will ask whether you want to upload
+or download files, and will then prompt you for the file transfer protocol
+to use and the filename to transfer. Once this is done, DSZ is invoked
+to perform the file transfer. As such, DSZ will need to be installed on
+the machine that will run the example program.
+
+The tip2.c source file provides a function that you can use to perform
+file transfers. This function is defined as follows:
+
+  int TransferFile(char *pszFilename, eProtocol Protocol, char bReceive)
+
+The first parameter should contain the name of the file or files to be
+transfered. The second parameter should specify the file transfer
+protocol to use, and should be one of the following enumerated
+constants:
+
+   XModem
+   XModemCRC
+   XModem1K
+   YModem
+   YModemG
+   ZModem
+
+The third parameter specifies whether the file is being send (FALSE) or
+received (TRUE). From the user's perspective, sending the file means
+downloading, and receiving the file means uploading.
+
+The TransferFile() function returns TRUE if the file transfer was
+completed, and FALSE if it was not.
+
+If the DSZ program is not present in the system's PATH or the current
+directory, then the global variable szDSZFilename must be set to the
+full path and filename of the DSZ program. This could be done by adding
+a custom OpenDoors configuration file line with a keyword such as
+"DSZPath".

+ 82 - 0
odoors/historic/odtips3/TIP3.TXT

@@ -0,0 +1,82 @@
+Many people have admired the paged question selection facility in the
+OpenDoors 5.00 EZVote example door. EZVote allows the user to choose
+questions from a list of up to 200 questions, by displaying one page
+of questions at a time. The user is able to page up or down in the list
+of quesitons, select a question from the list, or return to the main
+menu. A prompt at the bottom of the screen shows the current page
+number, and a list of options that are currently available. For
+instance, when displaying the first of two pages, this prompt indicates
+that the user can move to the next page, but not the previous page.
+
+This OpenDoors Tip shows a generic function that provides the same sorts
+of capabilities that are seen in the EZVote example door, in a form that
+can be re-used in many different programs. This function, named
+PagedViewer(), can be used for displaying multi-paged messages, text
+files, or for permitting selection from a potentially very long list of
+items. To use the PagedViewer() in a program, you should add the
+pageview.c file to your project file / makefile, and #include the
+pageview.h file in any source file that calls PagedViewer().
+
+The prototype for the PagedViewer() function is as follows:
+
+          int PagedViewer(
+             int nInitialLine,
+             int nTotalLines,
+             void (*pDisplayCallback)(int nLine, void *pCallbackData),
+             void *pCallbackData,
+             BOOL bAllowSelection,
+             char *pszTitle,
+             int nPageSize);
+
+The nInitialLine parameter specifies the line to begin viewing at.
+Normally this would be 0, but you may wish to pass a different value to
+this function to force the viewer to begin on a page other than the
+first. Using this parameter, you can have the user return to
+the page they were previously viewing, rather than returning to the
+first page and having to again find their original location.
+
+The nTotalLines parameter specifies the total number of lines that can
+be viewed, and can be any value greater than or equal to 0.
+
+The pDisplayCallback parameter must be a pointer to a function that the
+PagedViewer will call to display a particular line of the file at the
+current location. When PagedViewer() calls your function, it will pass
+the line number to be displayed, along with the value originally passed
+to PagedViewer() in pCallbackData. The provided function should simply
+display the text for the specified line number, without a trailing CR/LF
+sequence, and then return.
+
+The pCallbackData can point to any data that you wish PagedViewer() to
+pass to your callback function, or may be NULL if you do not wish to use
+this feature.
+
+The bAllowSelection parameter should be TRUE if the user should be able
+to make a selection from the list, and FALSE if they should not. If
+bAllowSelection is TRUE, PagedViewer() will display a letter beside each
+line, and allow the user to select a line by pressing the corresponding
+letter. If you are using PagedViewer() to display a text file or
+message, you will want to set bAllowSelection to FALSE. If you are using
+PagedViewer() to display a list of items from which the user can select,
+you will want to set bAllowSelection to TRUE.
+
+The pszTitle parameter can point to a title to be displayed at the top
+of each page, and could be something like ("Select a message"). If you
+do not wish to have a title displayed, set this parameter to NULL.
+
+The nPageSize parameter specifies the number of lines that should be
+displayed on each page. If you do not wish to have the local screen
+(with a two line status line) to be scrolled while displaying the list,
+the maximum page size you should use is 21 if no title is being
+displayed, and 19 if a title is being displayed.
+
+The included fileview.c text file viewing program demonstrates the use
+of PagedViewer(). The fileview.c door can be setup to allow the user to
+view one or more files. If setup to view multiple files, the program
+first displays a list of files that the user can select to view.
+The fileview.c program uses PagedViewer() in two places - for providing
+the list of files and for displaying the file itself. As such, the user
+can page up or down in the list of files, select a file to view, and
+then page up or down in the file. When the user selects quit while
+viewing the file, they are returned to the list of files to possibly
+select another file. When the user selects quit from the list of files,
+the door returns control to the calling BBS software.

+ 19 - 0
odoors/historic/odtips3/TIP4.C

@@ -0,0 +1,19 @@
+#include "opendoor.h"
+#include "cmdline.h"
+
+int main(int argc, char *argv[])
+{
+   /* Set function to be called if no door information file is found. */
+   od_control.od_no_file_func = NoDoorFileHandler;
+
+   /* Call command-line processing function before first call to any */
+   /* OpenDoors function! */
+   ParseStandardCommandLine(argc, argv);
+
+   /* Proceed with door normally. */
+   od_printf("Hello World!\n\r\n\r");
+   od_printf("Press [Enter] to return to BBS.\n\r");
+   od_get_answer("\n\r");
+
+   return(0);
+}

+ 50 - 0
odoors/historic/odtips3/TIP4.TXT

@@ -0,0 +1,50 @@
+It is common for BBS door programs to accept command line parameters
+that permit various door-related settings, such as the serial port, baud
+rate, and node number. This tip demonstrates how you can do this when
+working with OpenDoors. This tip is presented in the following files:
+
+   cmdline.h   - Header file for command line processing function.
+   cmdline.c   - Implementation of command line processing function.
+   tip4.c      - Example of a program that uses the command line
+		 processing function.
+
+The cmdline.c module implements the ParseStandardCommandLine() function,
+which takes as its parameters the same argc and argv parameters that are
+passed to the main() function of any C program. The
+ParseStandardCommandLine() function then sets the appropriate OpenDoors
+settings based on the following optional command-line parameters:
+
+   -L or -LOCAL      - Causes door to operate in local mode, without
+		       requiring a door information (drop) file.
+   -B x or -BPS x    - Sets the bps (baud) rate to use.
+   -P x or -PORT x   - Sets the serial port to use, were 0=COM1, 1=COM2,
+		       etc.
+   -N x or -NODE x   - Sets the node number to use.
+   -?, -H or -HELP   - Displays command-line help and exits
+   -PERSONALITY x    - Sets the sysop status line / function key
+		       personality to use.
+   -MAXTIME x	     - Sets the maximum number of minutes that any
+		       user will be permitted to access the door.
+   -ADDRESS x	     - Sets serial port address, to be used if FOSSIL
+		       driver is not being used.
+   -IRQ x	     - Sets the serial port IRQ line, to be used if
+		       FOSSIL driver is not being used.
+   -NOFOSSIL	     - Disables use of FOSSIL driver, even if it is
+		       present.
+   -NOFIFO	     - Disables use of 16550 FIFO buffers (only if
+		       FOSSIL driver is not being used).
+   -DROPFILE x	     - Door information file directory or
+		       directory+filename.
+   -USERNAME x	     - Name of user who is currently online.
+   -TIMELEFT x	     - User's time remaining online.
+   -SECURITY x	     - User's security level.
+   -LOCATION x	     - Location from which user is calling.
+
+Note that any information that is available from the door information
+file overrides information provided on the command-line. If sufficient
+parameters are provided on the command-line, the door can be operated
+without a door information file. In order to do this, cmdline.c provides
+a callback function that you can hook into od_control.od_no_file_func,
+as demonstrated in tip4.c.
+
+Case is not sensitive in the names of command line arguments.

+ 1 - 0
odoors/lbuild.bat

@@ -0,0 +1 @@
+make -fDOS.mak -DTARGET=l > out.txt

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov