home *** CD-ROM | disk | FTP | other *** search
/ ftp.wwiv.com / ftp.wwiv.com.zip / ftp.wwiv.com / pub / BBS / SBBS1B00.ZIP / XTRN.EXE / XTRN / SDK / XSDK.C < prev    next >
Text File  |  1992-09-02  |  54KB  |  1,816 lines

  1. /* XSDK.C */
  2.  
  3. /****************************************************************************/
  4. /*            Synchronet External Program Software Development Kit            */
  5. /*                            1992 Digital Dynamics                            */
  6. /****************************************************************************/
  7.  
  8. /****************************************************************************/
  9. /* This source code file is public domain and may be modified, compiled     */
  10. /* distributed, or used in any way, in part or whole for any purposes        */
  11. /* without the consent or notification of Digital Dynamics.                 */
  12. /****************************************************************************/
  13.  
  14. /****************************************************/
  15. /* For use with Borland/Turbo C and C++ compilers.    */
  16. /* Tabstop set to 4.                                */
  17. /****************************************************/
  18.  
  19. /***************************** Revision History *****************************\
  20.  
  21.     1.0ß    Initial version for use with Synchronet v1a r6
  22.     1.0     Added bgotoxy() macro
  23.             Added mnehigh and mnelow vars for control of the mnemonic colors
  24.             Added sys_nodes and node_num variables to xtrn_sdk.c
  25.             Added MAX_NODES to xtrn_sdk.h
  26.             Added printfile() function to xtrn_sdk.c
  27.             Added rputs() (Raw put string)
  28.             Added getstr() (Get string)
  29.             Added redrwstr() (Redraw string)
  30.             Added stripattr() (String attributes)
  31.             Added sys_op var and the reading from the xtrn.dat
  32.             Removed user_min and the reading from the xtrn.dat
  33.             Changed read_xtrn_dat() to initdata()
  34.             Added ctrl-break handler to xtrn_sdk
  35.             Changed xtrn.dat format (again), adding system operator,
  36.                 guru name, user ml, tl, birthdate and sex.
  37.             Added username() function
  38.             Renamed xtrn_sdk.* to xsdk.* and xtrnvars.c to xsdkvars.c
  39.                 and xtrndefs.h to xsdkdefs.h
  40.             Added fexist() function
  41.     1.01    Ctrl-p is now switched into ctrl-^ by SBBS
  42.             Fixed relative data_dir bug in username()
  43.     1.02    Added flength() function and lowered disk activity
  44.             Lowered MAX_CARDS from 20 to 10 and made the re-shuffling happen
  45.                 less often.
  46.     1.03    Fixed bug in attr() for monochrome users
  47.     1.04    Made warning and timeout times variables (sec_warn and sec_timeout)
  48.             Added SYSOP macro
  49.             Made it so sysop won't get timeout
  50.             Added user's phone number to XTRN.DAT
  51.             Added modem and com port information to XTRN.DAT
  52.             Added ahtoul function
  53.             Changed getstr commands Ctrl-L to Ctrl-R and Ctrl-P to Ctrl-\
  54.     2.00    Added intercommunication between the external programs and users
  55.             on the BBS or other external programs written with XSDK.
  56.             Added rprintf() function
  57.             Made many changes to getstr() function
  58.     2.01    Added DESQview awareness
  59.             Removed difftime() function calls completely
  60.             Added ungetkey() function
  61.             Added memory address of last modem status register for com routines
  62.                 so we can track DCD incase user hangs up.
  63.             Added checkline function that does the checking of DCD.
  64.             Added new bug-free fdelay() routine to replace TC's delay() that
  65.                 crashes multi-taskers such as DESQview and Windows
  66.     2.02    Added external program name access for user listings.
  67.             Added last node to send message to remembering and defaulting.
  68.             Added MALLOC and FREE macros for memory model non-specific memory
  69.                 allocation.
  70.  
  71. \****************************************************************************/
  72.  
  73. #include "xsdk.h"
  74.  
  75. /****************************************************************************/
  76. /* Performs printf() using bbs bputs function                                */
  77. /****************************************************************************/
  78. void bprintf(char *fmt, ...)
  79. {
  80.     char sbuf[1024];
  81.  
  82. vsprintf(sbuf,fmt,_va_ptr);
  83. bputs(sbuf);
  84. }
  85.  
  86. /****************************************************************************/
  87. /* Performs printf() using bbs rputs function                                */
  88. /****************************************************************************/
  89. void rprintf(char *fmt, ...)
  90. {
  91.     char sbuf[1024];
  92.  
  93. vsprintf(sbuf,fmt,_va_ptr);
  94. rputs(sbuf);
  95. }
  96.  
  97. /****************************************************************************/
  98. /* Outputs a NULL terminated string locally and remotely (if applicable)     */
  99. /* Max length of str is 64 kbytes                                            */
  100. /* Called from almost every function                                        */
  101. /****************************************************************************/
  102. void bputs(char *str)
  103. {
  104.     ulong l=0;
  105.  
  106. while(str[l]) {
  107.     if(str[l]==1) {                /* ctrl-a */
  108.         ctrl_a(str[++l]);       /* skip the ctrl-a */
  109.         l++; }                    /* skip the attribute code */
  110.     else
  111.         outchar(str[l++]); }
  112. }
  113.  
  114. /****************************************************************************/
  115. /* Outputs a NULL terminated string locally and remotely (if applicable)     */
  116. /* Does not process ctrl-a codes (raw output)                                */
  117. /* Max length of str is 64 kbytes                                            */
  118. /****************************************************************************/
  119. void rputs(char *str)
  120. {
  121.     ulong l=0;
  122.  
  123. while(str[l])
  124.     outchar(str[l++]);
  125. }
  126.  
  127. /****************************************************************************/
  128. /* Returns the number of characters in 'str' not counting ctrl-ax codes        */
  129. /* or the null terminator                                                    */
  130. /****************************************************************************/
  131. int bstrlen(char *str)
  132. {
  133.     int i=0;
  134.  
  135. while(*str) {
  136.     if(*str==1)    /* ctrl-a */
  137.         str++;
  138.     else
  139.         i++;
  140.     str++; }
  141. return(i);
  142. }
  143.  
  144. /****************************************************************************/
  145. /* Outputs one character to the screen. Handles, pause, saving and            */
  146. /* restoring lines, etc.                                                    */
  147. /****************************************************************************/
  148. void outchar(char ch)
  149. {
  150.  
  151. putchar(ch);
  152.  
  153. if(ch==LF) {
  154.     lncntr++;
  155.     lbuflen=0;
  156.     tos=0; }
  157. else if(ch==FF) {
  158.     lncntr=0;
  159.     lbuflen=0;
  160.     tos=1; }
  161. else if(ch==BS) {
  162.     if(lbuflen)
  163.         lbuflen--; }
  164. else {
  165.     if(!lbuflen)
  166.         latr=curatr;
  167.     if(lbuflen>=LINE_BUFSIZE) lbuflen=0;
  168.     lbuf[lbuflen++]=ch; }
  169. if(lncntr==user_rows-1) {
  170.     lncntr=0;
  171.     pause(); }
  172. }
  173.  
  174. /****************************************************************************/
  175. /* Prints PAUSE message and waits for a key stoke                            */
  176. /****************************************************************************/
  177. void pause()
  178. {
  179.     uchar tempattrs=curatr,*msg="\1_\1r\1h[Hit a key] ";
  180.     int i,j;
  181.  
  182. lncntr=0;
  183. bputs(msg);
  184. j=bstrlen(msg);
  185. getkey(0);
  186. for(i=0;i<j;i++)
  187.     bputs("\b \b");
  188. attr(tempattrs);
  189. }
  190.  
  191.  
  192. /****************************************************************************/
  193. /* Prompts user for Y or N (yes or no) and CR is interpreted as a Y            */
  194. /* Returns 1 for Y or 0 for N                                                */
  195. /* Called from quite a few places                                            */
  196. /****************************************************************************/
  197. char yesno(char *str)
  198. {
  199.     char ch;
  200.  
  201. bprintf("\1_\1b\1h%s (Y/n) ? \1w",str);
  202. while(1) {
  203.     ch=getkey(K_UPPER);
  204.     if(ch=='Y' || ch==CR) {
  205.         bputs("Yes\r\n");
  206.         return(1); }
  207.     if(ch=='N') {
  208.         bputs("No\r\n");
  209.         return(0); } }
  210. }
  211.  
  212. /****************************************************************************/
  213. /* Prompts user for N or Y (no or yes) and CR is interpreted as a N            */
  214. /* Returns 1 for N or 0 for Y                                                */
  215. /* Called from quite a few places                                            */
  216. /****************************************************************************/
  217. char noyes(char *str)
  218. {
  219.     char ch;
  220.  
  221. bprintf("\1_\1b\1h%s (y/N) ? \1w",str);
  222. while(1) {
  223.     ch=getkey(K_UPPER);
  224.     if(ch=='N' || ch==CR) {
  225.         bputs("No\r\n");
  226.         return(1); }
  227.     if(ch=='Y') {
  228.         bputs("Yes\r\n");
  229.         return(0); } }
  230. }
  231.  
  232. /****************************************************************************/
  233. /* Outputs a string highlighting characters preceeded by a tilde with the    */
  234. /* color specified in mnehigh and the rest of the line is in color mnelow.    */
  235. /* If the user doesn't have ANSI, it puts the character following the tilde */
  236. /* in parenthesis.                                                            */
  237. /****************************************************************************/
  238. void mnemonics(char *str)
  239. {
  240.     long l;
  241.  
  242. attr(mnelow);
  243. l=0L;
  244. while(str[l]) {
  245.     if(str[l]=='~' && str[l+1]) {
  246.         if(!(user_misc&ANSI))
  247.             outchar('(');
  248.         l++;
  249.         attr(mnehigh);
  250.         outchar(str[l]);
  251.         l++;
  252.         if(!(user_misc&ANSI))
  253.             outchar(')');
  254.         attr(mnelow); }
  255.     else
  256.         outchar(str[l++]); }
  257. attr(LIGHTGRAY);
  258. }
  259.  
  260. /****************************************************************************/
  261. /* If a key has been pressed, the ASCII code is returned. If not, 0 is        */
  262. /* returned. Ctrl-P and Ctrl-U are intercepted here.                        */
  263. /****************************************************************************/
  264. char inkey()
  265. {
  266.     static in_ctrl_p;
  267.     uchar ch=0;
  268.  
  269. if(keybufbot!=keybuftop) {
  270.     ch=keybuf[keybufbot++];
  271.     if(keybufbot==KEY_BUFSIZE)
  272.         keybufbot=0; }
  273. else if(bioskey(1))
  274.     ch=(bioskey(0)&0x0ff);
  275.  
  276. if(ch==0x10 || ch==0x1e) {    /* Ctrl-P or Ctrl-^ */
  277.     if(in_ctrl_p)    /* keep from being recursive */
  278.         return(0);
  279.     in_ctrl_p=1;
  280.     SAVELINE;
  281.     CRLF;
  282.     nodemsg();
  283.     CRLF;
  284.     RESTORELINE;
  285.     lncntr=0;
  286.     in_ctrl_p=0;
  287.     return(0); }
  288.  
  289. if(ch==21) { /* Ctrl-U Users online */
  290.     SAVELINE;
  291.     CRLF;
  292.     whos_online(1);
  293.     CRLF;
  294.     RESTORELINE;
  295.     lncntr=0;
  296.     return(0); }
  297.  
  298. if(!ch && inDV)
  299.     dv_pause();
  300. return(ch);
  301. }
  302.  
  303. /****************************************************************************/
  304. /* Waits for remote or local user to hit a key. Inactivity timer is checked */
  305. /* and hangs up if inactive for 4 minutes. Returns key hit, or uppercase of */
  306. /* key hit if mode&K_UPPER or key out of KEY BUFFER. Does not print key.    */
  307. /****************************************************************************/
  308. char getkey(int mode)
  309. {
  310.     char ch,warn=0;
  311.     time_t timeout,now;
  312.  
  313. lncntr=0;
  314. timeout=time(NULL);
  315. do {
  316.     ch=inkey();
  317.     now=time(NULL);
  318.     if(ch) {
  319.         if(mode&K_NUMBER && isprint(ch) && !isdigit(ch))
  320.             continue;
  321.         if(mode&K_ALPHA && isprint(ch) && !isalpha(ch))
  322.             continue;
  323.         if(ch==LF) continue;
  324.         if(mode&K_UPPER)
  325.             return(toupper(ch));
  326.         return(ch); }
  327.     checktimeleft();
  328.     if(now-timeout>=sec_warn && !warn)     /* warning */
  329.         for(warn=0;warn<5;warn++)
  330.             outchar(7);
  331.     checkline();
  332.     } while(now-timeout<sec_timeout);
  333. bputs("\r\nInactive too long.\r\n");
  334. exit(0);
  335. return(0);    /* never gets here, but makes compiler happy */
  336. }
  337.  
  338. /****************************************************************************/
  339. /* If remote user, checks DCD to see if user has hung up or not.            */
  340. /****************************************************************************/
  341. void checkline()
  342. {
  343. if(com_port && !((*msr)&DCD)) exit(0);
  344. }
  345.  
  346. /****************************************************************************/
  347. /* Waits for remote or local user to hit a key that is contained inside str.*/
  348. /* 'str' should contain uppercase characters only. When a valid key is hit, */
  349. /* it is echoed (upper case) and is the return value.                        */
  350. /* If max is non-zero and a number is hit that is not in str, it will be    */
  351. /* returned with the high bit set. If the return of this function has the    */
  352. /* high bit set (&0x8000), just flip the bit (^0x8000) to get the number.    */
  353. /****************************************************************************/
  354. int getkeys(char *str,int max)
  355. {
  356.     uchar ch,n=0;
  357.     int i=0;
  358.  
  359. strupr(str);
  360. while(1) {
  361.     ch=getkey(K_UPPER);
  362.     if(max && ch>0x7f)    /* extended ascii chars are digits to isdigit() */
  363.         continue;
  364.     if(ch && !n && (strchr(str,ch))) {     /* return character if in string */
  365.         outchar(ch);
  366.         attr(LIGHTGRAY);
  367.         CRLF;
  368.         return(ch); }
  369.     if(ch==CR && max) {             /* return 0 if no number */
  370.         attr(LIGHTGRAY);
  371.         CRLF;
  372.         if(n)
  373.             return(i|0x8000);        /* return number plus high bit */
  374.         return(0); }
  375.     if(ch==BS && n) {
  376.         bputs("\b \b");
  377.         i/=10;
  378.         n--; }
  379.     else if(max && isdigit(ch) && (i*10)+(ch&0xf)<=max && (ch!='0' || n)) {
  380.         i*=10;
  381.         n++;
  382.         i+=ch&0xf;
  383.         outchar(ch);
  384.         if(i*10>max) {
  385.             attr(LIGHTGRAY);
  386.             CRLF;
  387.             return(i|0x8000); } } }
  388. }
  389.  
  390. /****************************************************************************/
  391. /* Hot keyed number input routine.                                            */
  392. /****************************************************************************/
  393. int getnum(int max)
  394. {
  395.     uchar ch,n=0;
  396.     int i=0;
  397.  
  398. while(1) {
  399.     ch=getkey(K_UPPER);
  400.     if(ch>0x7f)
  401.         continue;
  402.     if(ch=='Q') {
  403.         outchar('Q');
  404.         CRLF;
  405.         return(-1); }
  406.     else if(ch==3) {        /* ctrl-c */
  407.         CRLF;
  408.         return(-1); }
  409.     else if(ch==CR) {
  410.         CRLF;
  411.         return(i); }
  412.     else if(ch==BS && n) {
  413.         bputs("\b \b");
  414.         i/=10;
  415.         n--; }
  416.     else if(isdigit(ch) && (i*10)+(ch&0xf)<=max && (ch!='0' || n)) {
  417.         i*=10;
  418.         n++;
  419.         i+=ch&0xf;
  420.         outchar(ch);
  421.         if(i*10>max) {
  422.             CRLF;
  423.             return(i); } } }
  424. }
  425.  
  426. /****************************************************************************/
  427. /* Waits for remote or local user to input a CR terminated string. 'length' */
  428. /* is the maximum number of characters that getstr will allow the user to     */
  429. /* input into the string. 'mode' specifies upper case characters are echoed */
  430. /* or wordwrap or if in message input (^A sequences allowed). ^W backspaces */
  431. /* a word, ^X backspaces a line, ^Gs, BSs, TABs are processed, LFs ignored. */
  432. /* ^N non-destructive BS, ^V center line. Valid keys are echoed.            */
  433. /****************************************************************************/
  434. int getstr(char *strout, int maxlen, int mode)
  435. {
  436.     int i,l,x,z;    /* i=current position, l=length, j=printed chars */
  437.                     /* x&z=misc */
  438.     uchar ch,str1[256],str2[256],ins=0;
  439.  
  440. if(mode&K_LINE && user_misc&ANSI) {
  441.     attr(LIGHTGRAY|HIGH|(BLUE<<4));  /* white on blue */
  442.     for(i=0;i<maxlen;i++)
  443.         outchar(SP);
  444.     bprintf("\x1b[%dD",maxlen); }
  445. i=l=0;    /* i=total number of chars, j=number of printable chars */
  446. if(wordwrap[0]) {
  447.     strcpy(str1,wordwrap);
  448.     wordwrap[0]=0; }
  449. else str1[0]=0;
  450. if(mode&K_EDIT)
  451.     strcat(str1,strout);
  452. if(strlen(str1)>maxlen)
  453.     str1[maxlen]=0;
  454. rputs(str1);
  455. if(mode&K_EDIT && !(mode&K_LINE) && user_misc&ANSI) { /* destroy to eol */
  456.     bputs("\x1b[s"       /* save cursor position */
  457.         "\x1b[K"         /* delete to end of line */
  458.         "\x1b[u"); }     /* restore cursor position */
  459. i=l=strlen(str1);
  460. while((ch=getkey(mode))!=CR) {
  461.     switch(ch) {
  462.         case 1:    /* Ctrl-A for ANSI */
  463.             if(!(mode&K_MSG) || i>maxlen-3)
  464.                 break;
  465.             if(ins) {
  466.                 if(l<maxlen)
  467.                     l++;
  468.                 for(x=l;x>i;x--)
  469.                     str1[x]=str1[x-1];
  470.                 rprintf("%.*s",l-i,str1+i);
  471.                 rprintf("\x1b[%dD",l-i);
  472.                 if(i==maxlen-1)
  473.                     ins=0; }
  474.             outchar(str1[i++]=1);
  475.             break;
  476.         case 2:    /* Ctrl-B Beginning of Line */
  477.             if(user_misc&ANSI && i) {
  478.                 bprintf("\x1b[%dD",i);
  479.                 i=0; }
  480.             break;
  481.         case 4:    /* Ctrl-D Delete word right */
  482.             if(i<l) {
  483.                 x=i;
  484.                 while(x<l && str1[x]!=SP) {
  485.                     outchar(SP);
  486.                     x++; }
  487.                 while(x<l && str1[x]==SP) {
  488.                     outchar(SP);
  489.                     x++; }
  490.                 bprintf("\x1b[%dD",x-i);   /* move cursor back */
  491.                 z=i;
  492.                 while(z<l-(x-i))  {             /* move chars in string */
  493.                     outchar(str1[z]=str1[z+(x-i)]);
  494.                     z++; }
  495.                 while(z<l) {                    /* write over extra chars */
  496.                     outchar(SP);
  497.                     z++; }
  498.                 bprintf("\x1b[%dD",z-i);
  499.                 l-=x-i; }                        /* l=new length */
  500.             break;
  501.         case 5:    /* Ctrl-E End of line */
  502.             if(user_misc&ANSI && i<l) {
  503.                 bprintf("\x1b[%dC",l-i);  /* move cursor right one */
  504.                 i=l; }
  505.             break;
  506.         case 6:    /* Ctrl-F move cursor forewards */
  507.             if(i<l && (user_misc&ANSI)) {
  508.                 bputs("\x1b[C");   /* move cursor right one */
  509.                 i++; }
  510.             break;
  511.         case 7:
  512.             if(!(mode&K_MSG))
  513.                 break;
  514.              if(ins) {
  515.                 if(l<maxlen)
  516.                     l++;
  517.                 for(x=l;x>i;x--)
  518.                     str1[x]=str1[x-1];
  519.                 if(i==maxlen-1)
  520.                     ins=0; }
  521.              if(i<maxlen) {
  522.                 str1[i++]=7;
  523.                 outchar(7); }
  524.              break;
  525.         case 14:    /* Ctrl-N Next word */
  526.             if(i<l && (user_misc&ANSI)) {
  527.                 x=i;
  528.                 while(str1[i]!=SP && i<l)
  529.                     i++;
  530.                 while(str1[i]==SP && i<l)
  531.                     i++;
  532.                 bprintf("\x1b[%dC",i-x); }
  533.             break;
  534.         case 0x1c:      /* Ctrl-\ Previous word */
  535.             if(i && (user_misc&ANSI)) {
  536.                 x=i;
  537.                 while(str1[i-1]==SP && i)
  538.                     i--;
  539.                 while(str1[i-1]!=SP && i)
  540.                     i--;
  541.                 bprintf("\x1b[%dD",x-i); }
  542.             break;
  543.         case 18:    /* Ctrl-R Redraw Line */
  544.             redrwstr(str1,i,l,0);
  545.             break;
  546.         case TAB:
  547.             if(!(i%TABSIZE)) {
  548.                 if(ins) {
  549.                     if(l<maxlen)
  550.                         l++;
  551.                     for(x=l;x>i;x--)
  552.                         str1[x]=str1[x-1];
  553.                     if(i==maxlen-1)
  554.                         ins=0; }
  555.                 str1[i++]=SP;
  556.                 outchar(SP); }
  557.             while(i<maxlen && i%TABSIZE) {
  558.                 if(ins) {
  559.                     if(l<maxlen)
  560.                         l++;
  561.                     for(x=l;x>i;x--)
  562.                         str1[x]=str1[x-1];
  563.                     if(i==maxlen-1)
  564.                         ins=0; }
  565.                 str1[i++]=SP;
  566.                 outchar(SP); }
  567.             if(ins)
  568.                 redrwstr(str1,i,l,0);
  569.             break;
  570.         case BS:
  571.             if(!i)
  572.                 break;
  573.             i--;
  574.             l--;
  575.             if(i!=l) {                /* Deleting char in middle of line */
  576.                 outchar(BS);
  577.                 z=i;
  578.                 while(z<l)    {        /* move the characters in the line */
  579.                     outchar(str1[z]=str1[z+1]);
  580.                     z++; }
  581.                 outchar(SP);        /* write over the last char */
  582.                 bprintf("\x1b[%dD",(l-i)+1); }
  583.             else
  584.                 bputs("\b \b");
  585.             break;
  586.         case 22:    /* Ctrl-V     Center line */
  587.             str1[l]=0;
  588.             l=bstrlen(str1);
  589.             for(x=0;x<(maxlen-l)/2;x++)
  590.                 str2[x]=SP;
  591.             str2[x]=0;
  592.             strcat(str2,str1);
  593.             strcpy(strout,str2);
  594.             l=strlen(strout);
  595.             if(mode&K_MSG)
  596.                 redrwstr(strout,i,l,K_MSG);
  597.             else {
  598.                 while(i--)
  599.                     bputs("\b");
  600.                 bputs(strout);
  601.                 if(mode&K_LINE)
  602.                     attr(LIGHTGRAY); }
  603.             CRLF;
  604.             return(l);
  605.         case 23:    /* Ctrl-W   Delete word left */
  606.             if(i<l) {
  607.                 x=i;                            /* x=original offset */
  608.                 while(i && str1[i-1]==SP) {
  609.                     outchar(BS);
  610.                     i--; }
  611.                 while(i && str1[i-1]!=SP) {
  612.                     outchar(BS);
  613.                     i--; }
  614.                 z=i;                            /* i=z=new offset */
  615.                 while(z<l-(x-i))  {             /* move chars in string */
  616.                     outchar(str1[z]=str1[z+(x-i)]);
  617.                     z++; }
  618.                 while(z<l) {                    /* write over extra chars */
  619.                     outchar(SP);
  620.                     z++; }
  621.                 bprintf("\x1b[%dD",z-i);        /* back to new x corridnant */
  622.                 l-=x-i; }                        /* l=new length */
  623.             else {
  624.                 while(i && str1[i-1]==SP) {
  625.                     i--;
  626.                     l--;
  627.                     bputs("\b \b"); }
  628.                 while(i && str1[i-1]!=SP) {
  629.                     i--;
  630.                     l--;
  631.                     bputs("\b \b"); } }
  632.             break;
  633.         case 24:    /* Ctrl-X   Delete entire line */
  634.             while(i<l) {
  635.                 outchar(SP);
  636.                 i++; }
  637.             while(l) {
  638.                 l--;
  639.                 bputs("\b \b"); }
  640.             i=0;
  641.             break;
  642.         case 25:    /* Ctrl-Y    Delete to end of line */
  643.             if(user_misc&ANSI) {
  644.                 bputs("\x1b[s\x1b[K\x1b[u");
  645.                 l=i; }
  646.             break;
  647.         case ESC:
  648.             if(!(user_misc&ANSI))
  649.                 break;
  650.             if((ch=getkey(0x8000))!='[') {
  651.                 ungetch(ch);
  652.                 break; }
  653.             if((ch=getkey(0x8000))=='C') {
  654.                 if(i<l) {
  655.                     bputs("\x1b[C");   /* move cursor right one */
  656.                     i++; } }
  657.             else if(ch=='D') {
  658.                 if(i) {
  659.                     bputs("\x1b[D");   /* move cursor left one */
  660.                     i--; } }
  661.             else {
  662.                 while(isdigit(ch) || ch==';' || isalpha(ch)) {
  663.                     if(isalpha(ch)) {
  664.                         ch=getkey(0);
  665.                         break; }
  666.                     ch=getkey(0); }
  667.                 ungetch(ch); }
  668.             break;
  669.         case 31:    /* Ctrl-Minus        Toggles Insert/Overwrite */
  670.             if(!(user_misc&ANSI))
  671.                 break;
  672.             if(ins) {
  673.                 ins=0;
  674.                 redrwstr(str1,i,l,0); }
  675.             else if(i<l) {
  676.                 ins=1;
  677.                 bprintf("\x1b[s\x1b[%dC",80-i);         /* save pos  */
  678.                 z=curatr;                                /* and got to EOL */
  679.                 attr(z|BLINK|HIGH);
  680.                 outchar('░');
  681.                 attr(z);
  682.                 bputs("\x1b[u"); }  /* restore pos */
  683.             break;
  684.         case 0x7f:    /* Ctrl-Backspace    Reverse Cursor Movement */
  685.             if(i && (user_misc&ANSI)) {
  686.                 bputs("\x1b[D");   /* move cursor left one */
  687.                 i--; }
  688.             break;
  689.         default:
  690.             if(mode&K_WRAP && i==maxlen && ch>=SP && !ins) {
  691.                 str1[i]=0;
  692.                 if(ch==SP) {    /* don't wrap a space as last char */
  693.                     strcpy(strout,str1);
  694.                     if(stripattr(strout))
  695.                         redrwstr(strout,i,l,K_MSG);
  696.                     CRLF;
  697.                     return(i); }
  698.                 x=i-1;
  699.                 z=1;
  700.                 wordwrap[0]=ch;
  701.                 while(str1[x]!=SP && x)
  702.                     wordwrap[z++]=str1[x--];
  703.                 if(x<(maxlen/2)) {
  704.                     wordwrap[1]=0;    /* only wrap one character */
  705.                     strcpy(strout,str1);
  706.                     if(stripattr(strout))
  707.                         redrwstr(strout,i,l,K_MSG);
  708.                     CRLF;
  709.                     return(i); }
  710.                 wordwrap[z]=0;
  711.                 while(z--)
  712.                     bputs("\b \b");
  713.                 strrev(wordwrap);
  714.                 str1[x]=0;
  715.                 strcpy(strout,str1);
  716.                 if(stripattr(strout))
  717.                     redrwstr(strout,i,x,mode);
  718.                 CRLF;
  719.                 return(x); }
  720.             if(i<maxlen && ch>=SP) {
  721.                 if(mode&K_UPRLWR)
  722.                     if(!i || (i && (str1[i-1]==SP || str1[i-1]=='-'
  723.                         || str1[i-1]=='.' || str1[i-1]=='_')))
  724.                         ch=toupper(ch);
  725.                     else
  726.                         ch=tolower(ch);
  727.                 if(ins) {
  728.                     if(l<maxlen)    /* l<maxlen */
  729.                         l++;
  730.                     for(x=l;x>i;x--)
  731.                         str1[x]=str1[x-1];
  732.                     rprintf("%.*s",l-i,str1+i);
  733.                     rprintf("\x1b[%dD",l-i);
  734.                     if(i==maxlen-1) {
  735.                         bputs("  \b\b");
  736.                         ins=0; } }
  737.                 str1[i++]=ch;
  738.                 outchar(ch); } }
  739.     if(i>l)
  740.         l=i;
  741.     if(mode&K_CHAT && !l)
  742.         return(0); }
  743. if(i>l)
  744.     l=i;
  745. str1[l]=0;
  746. strcpy(strout,str1);
  747. if(stripattr(strout) || ins)
  748.     redrwstr(strout,i,l,K_MSG);
  749. if(mode&K_LINE) attr(LIGHTGRAY);
  750. if(!(mode&K_NOCRLF)) {
  751.     outchar(CR);
  752.     if(!(mode&K_MSG))
  753.         outchar(LF); }
  754. return(l);
  755. }
  756.  
  757. /****************************************************************************/
  758. /* Redraws str using i as current cursor position and l as length           */
  759. /****************************************************************************/
  760. void redrwstr(char *strin, int i, int l, char mode)
  761. {
  762.     char str[81],c;
  763.  
  764. sprintf(str,"%-*.*s",l,l,strin);
  765. c=i;
  766. while(c--)
  767.     outchar(BS);
  768. if(mode&K_MSG)
  769.     bputs(str);
  770. else
  771.     rputs(str);
  772. if(user_misc&ANSI) {
  773.     bputs("\x1b[s");
  774.     bputs("\x1b[K");
  775.     bputs("\x1b[u");
  776.     if(i<l)
  777.         bprintf("\x1b[%dD",l-i); }
  778. else {
  779.     while(c<79)    { /* clear to end of line */
  780.         outchar(SP);
  781.         c++; }
  782.     while(c>l) { /* back space to end of string */
  783.         outchar(BS);
  784.         c--; } }
  785. }
  786.  
  787. /****************************************************************************/
  788. /* Strips invalid Ctrl-Ax sequences from str                                */
  789. /* Returns number of ^A's in line                                           */
  790. /****************************************************************************/
  791. char stripattr(char *strin)
  792. {
  793.     uchar str[81];
  794.     uchar a,c,d,e;
  795.  
  796. e=strlen(strin);
  797. for(a=c=d=0;c<e;c++) {
  798.     if(strin[c]==1) {
  799.         a++;
  800.         switch(toupper(strin[c+1])) {
  801.             case '-':    /* clear         */
  802.             case '_':    /* clear        */
  803.             case 'B':    /* blue     fg     */
  804.             case 'C':    /* cyan     fg     */
  805.             case 'G':    /* green    fg     */
  806.             case 'H':    /* high     fg     */
  807.             case 'I':    /* blink            */
  808.             case 'K':    /* black     fg     */
  809.             case 'L':    /* cls             */
  810.             case 'M':    /* magenta  fg     */
  811.             case 'N':    /* normal          */
  812.             case 'P':    /* pause           */
  813.             case 'R':    /* red      fg     */
  814.             case 'W':    /* white    fg     */
  815.             case 'Y':    /* yellow   fg     */
  816.             case '0':    /* black     bg     */
  817.             case '1':    /* red       bg     */
  818.             case '2':    /* green     bg     */
  819.             case '3':   /* brown    bg     */
  820.             case '4':    /* blue      bg     */
  821.             case '5':   /* magenta     bg     */
  822.             case '6':    /* cyan        bg     */
  823.             case '7':    /* white       bg     */
  824.                 break;
  825.             default:
  826.                 c++;
  827.                 continue; } }
  828.     str[d++]=strin[c]; }
  829. str[d]=0;
  830. strcpy(strin,str);
  831. return(a);
  832. }
  833.  
  834. /***************************************************************************/
  835. /* Changes local and remote text attributes accounting for monochrome      */
  836. /***************************************************************************/
  837. void attr(char atr)
  838. {
  839.  
  840. if(!(user_misc&ANSI))
  841.     return;
  842. if(!(user_misc&COLOR)) {  /* eliminate colors if user doesn't have them */
  843.     if(atr&LIGHTGRAY)        /* if any bits set, set all */
  844.         atr|=LIGHTGRAY;
  845.     if(atr&(LIGHTGRAY<<4))
  846.         atr|=(LIGHTGRAY<<4);
  847.     if(atr&LIGHTGRAY && atr&(LIGHTGRAY<<4))
  848.         atr&=~LIGHTGRAY; }    /* if background is solid, forground is black */
  849. if(curatr==atr) /* attribute hasn't changed. don't send codes */
  850.     return;
  851.  
  852. if((!(atr&HIGH) && curatr&HIGH)    || (!(atr&BLINK) && curatr&BLINK)
  853.     || atr==LIGHTGRAY) {
  854.     bprintf("\x1b[0m");
  855.     curatr=LIGHTGRAY; }
  856.  
  857. if(atr==LIGHTGRAY) {                 /* no attributes */
  858.     curatr=atr;
  859.     return; }
  860.  
  861. if(atr&BLINK) {                        /* special attributes */
  862.     if(!(curatr&BLINK))
  863.         bprintf("\x1b[5m"); }
  864. if(atr&HIGH) {
  865.     if(!(curatr&HIGH))
  866.         bprintf("\x1b[1m"); }
  867.  
  868. if((atr&0x7)==BLACK) {                /* foreground colors */
  869.     if((curatr&0x7)!=BLACK)
  870.         bprintf("\x1b[30m"); }
  871. else if((atr&0x7)==RED) {
  872.     if((curatr&0x7)!=RED)
  873.         bprintf("\x1b[31m"); }
  874. else if((atr&0x7)==GREEN) {
  875.     if((curatr&0x7)!=GREEN)
  876.         bprintf("\x1b[32m"); }
  877. else if((atr&0x7)==BROWN) {
  878.     if((curatr&0x7)!=BROWN)
  879.         bprintf("\x1b[33m"); }
  880. else if((atr&0x7)==BLUE) {
  881.     if((curatr&0x7)!=BLUE)
  882.         bprintf("\x1b[34m"); }
  883. else if((atr&0x7)==MAGENTA) {
  884.     if((curatr&0x7)!=MAGENTA)
  885.         bprintf("\x1b[35m"); }
  886. else if((atr&0x7)==CYAN) {
  887.     if((curatr&0x7)!=CYAN)
  888.         bprintf("\x1b[36m"); }
  889. else if((atr&0x7)==LIGHTGRAY) {
  890.     if((curatr&0x7)!=LIGHTGRAY)
  891.         bprintf("\x1b[37m"); }
  892.  
  893. if((atr&0x70)==(BLACK<<4)) {        /* background colors */
  894.     if((curatr&0x70)!=(BLACK<<4))
  895.         bprintf("\x1b[40m"); }
  896. else if((atr&0x70)==(RED<<4)) {
  897.     if((curatr&0x70)!=(RED<<4))
  898.         bprintf("\x1b[41m"); }
  899. else if((atr&0x70)==(GREEN<<4)) {
  900.     if((curatr&0x70)!=(GREEN<<4))
  901.         bprintf("\x1b[42m"); }
  902. else if((atr&0x70)==(BROWN<<4)) {
  903.     if((curatr&0x70)!=(BROWN<<4))
  904.         bprintf("\x1b[43m"); }
  905. else if((atr&0x70)==(BLUE<<4)) {
  906.     if((curatr&0x70)!=(BLUE<<4))
  907.         bprintf("\x1b[44m"); }
  908. else if((atr&0x70)==(MAGENTA<<4)) {
  909.     if((curatr&0x70)!=(MAGENTA<<4))
  910.         bprintf("\x1b[45m"); }
  911. else if((atr&0x70)==(CYAN<<4)) {
  912.     if((curatr&0x70)!=(CYAN<<4))
  913.         bprintf("\x1b[46m"); }
  914. else if((atr&0x70)==(LIGHTGRAY<<4)) {
  915.     if((curatr&0x70)!=(LIGHTGRAY<<4))
  916.         bprintf("\x1b[47m"); }
  917.  
  918. curatr=atr;
  919. }
  920.  
  921. /****************************************************************************/
  922. /* Peform clear screen                                                        */
  923. /****************************************************************************/
  924. void cls()
  925. {
  926.     int i;
  927.  
  928. if(user_misc&ANSI)
  929.     bprintf("\x1b[2J");
  930. else {
  931.     outchar(FF);
  932.     clrscr(); }
  933. tos=1;
  934. }
  935.  
  936.  
  937. /****************************************************************************/
  938. /* performs the correct attribute modifications for the Ctrl-A code            */
  939. /****************************************************************************/
  940. void ctrl_a(char x)
  941. {
  942.     char atr=curatr;
  943.  
  944. switch(toupper(x)) {
  945.     case '-':                                /* turn off all attributes if */
  946.         if(atr&(HIGH|BLINK|(LIGHTGRAY<<4)))    /* high intensity, blink or */
  947.             attr(LIGHTGRAY);                /* background bits are set */
  948.         break;
  949.     case '_':                                /* turn off all attributes if */
  950.         if(atr&(BLINK|(LIGHTGRAY<<4)))        /* blink or background is set */
  951.             attr(LIGHTGRAY);
  952.         break;
  953.     case 'E':    /* Elite Text */
  954.         /* not used */
  955.         break;
  956.     case 'O':    /* r0dent Output */
  957.         /* not used */
  958.         break;
  959.     case 'P':    /* Pause */
  960.         pause();
  961.         break;
  962.     case 'L':    /* CLS (form feed) */
  963.         cls();
  964.         break;
  965.     case 'H':     /* High intensity */
  966.         atr|=HIGH;
  967.         attr(atr);
  968.         break;
  969.     case 'I':    /* Blink */
  970.         atr|=BLINK;
  971.         attr(atr);
  972.         break;
  973.     case 'N':     /* Normal */
  974.         attr(LIGHTGRAY);
  975.         break;
  976.     case 'R':
  977.         atr=(atr&0xf8)|RED;
  978.         attr(atr);
  979.         break;
  980.     case 'G':
  981.         atr=(atr&0xf8)|GREEN;
  982.         attr(atr);
  983.         break;
  984.     case 'B':
  985.         atr=(atr&0xf8)|BLUE;
  986.         attr(atr);
  987.         break;
  988.     case 'W':    /* White */
  989.         atr=(atr&0xf8)|LIGHTGRAY;
  990.         attr(atr);
  991.         break;
  992.     case 'C':
  993.         atr=(atr&0xf8)|CYAN;
  994.         attr(atr);
  995.         break;
  996.     case 'M':
  997.         atr=(atr&0xf8)|MAGENTA;
  998.         attr(atr);
  999.         break;
  1000.     case 'Y':
  1001.         atr=(atr&0xf8)|BROWN;
  1002.         attr(atr);
  1003.         break;
  1004.     case 'K':    /* Black */
  1005.         atr=(atr&0xf8)|BLACK;
  1006.         attr(atr);
  1007.         break;
  1008.     case '0':    /* Black Background */
  1009.         atr=(atr&0x8f)|(BLACK<<4);
  1010.         attr(atr);
  1011.         break;
  1012.     case '1':    /* Red Background */
  1013.         atr=(atr&0x8f)|(RED<<4);
  1014.         attr(atr);
  1015.         break;
  1016.     case '2':    /* Green Background */
  1017.         atr=(atr&0x8f)|(GREEN<<4);
  1018.         attr(atr);
  1019.         break;
  1020.     case '3':    /* Yellow Background */
  1021.         atr=(atr&0x8f)|(BROWN<<4);
  1022.         attr(atr);
  1023.         break;
  1024.     case '4':    /* Blue Background */
  1025.         atr=(atr&0x8f)|(BLUE<<4);
  1026.         attr(atr);
  1027.         break;
  1028.     case '5':    /* Magenta Background */
  1029.         atr=(atr&0x8f)|(MAGENTA<<4);
  1030.         attr(atr);
  1031.         break;
  1032.     case '6':    /* Cyan Background */
  1033.         atr=(atr&0x8f)|(CYAN<<4);
  1034.         attr(atr);
  1035.         break;
  1036.     case '7':    /* White Background */
  1037.         atr=(atr&0x8f)|(LIGHTGRAY<<4);
  1038.         attr(atr);
  1039.         break; }
  1040. }
  1041.  
  1042. /****************************************************************************/
  1043. /* Network open function. Opens all files DENYALL and retries LOOP_NOPEN    */
  1044. /* number of times if the attempted file is already open or denying access  */
  1045. /* for some other reason.    All files are opened in BINARY mode.            */
  1046. /****************************************************************************/
  1047. int nopen(char *str, int access)
  1048. {
  1049.     char count=0;
  1050.     int file,share;
  1051.  
  1052. if(access==O_RDONLY) share=O_DENYWRITE;
  1053.     else share=O_DENYALL;
  1054. while(((file=open(str,O_BINARY|share|access,S_IWRITE))==-1)
  1055.     && errno==EACCES && count++<LOOP_NOPEN)
  1056.     if(count>10)
  1057.         fdelay(50);
  1058. if(count)
  1059.     bprintf("\r\n!$!$!$ NOPEN COLLISION - File: %s Count: %d\r\n"
  1060.         ,str,count);
  1061. if(file==-1 && errno==EACCES)
  1062.     bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7");
  1063. return(file);
  1064. }
  1065.  
  1066. int cbreakh()
  1067. {
  1068. return(1);
  1069. }
  1070.  
  1071. /****************************************************************************/
  1072. /* Reads data from XTRN.DAT in the node directory and fills the appropriate */
  1073. /* global variables.                                                        */
  1074. /* Initializes starttime variable with current time.                        */
  1075. /****************************************************************************/
  1076. void initdata()
  1077. {
  1078.     char str[256];
  1079.     int i;
  1080.     FILE *stream;
  1081.  
  1082. ctrlbrk(cbreakh);
  1083.  
  1084. inDV = 0;                        /* DESQview detection                */
  1085. _CX = 0x4445; /* 'DE' */        /* set CX to 4445H; DX to 5351H     */
  1086. _DX = 0x5351; /* 'SQ' */        /* (an invalid date)                */
  1087. _AX = 0x2B01;                    /* DOS' set data function           */
  1088. geninterrupt( 0x21 );            /* call DOS                         */
  1089. if ( _AL != 0xFF )                /* if DOS didn't see as invalid     */
  1090.     _AX = _BX;                    /* AH=major ver; AL=minor ver        */
  1091. else                            /* DOS saw as invalid                */
  1092.     _AX = 0;                    /* no desqview                        */
  1093. inDV = _AX;                     /* Save version in inDV flag        */
  1094.  
  1095. sprintf(str,"%sXTRN.DAT",node_dir);
  1096. if((stream=fopen(str,"rt"))==NULL) {
  1097.     printf("Can't open %s\n",str);
  1098.     exit(1); }
  1099. fgets(str,81,stream);            /* username */
  1100. sprintf(user_name,"%.25s",str);
  1101. truncsp(user_name);
  1102. fgets(str,81,stream);            /* system name */
  1103. sprintf(sys_name,"%.40s",str);
  1104. truncsp(sys_name);
  1105. fgets(str,81,stream);            /* system operator */
  1106. sprintf(sys_op,"%.40s",str);
  1107. truncsp(sys_op);
  1108. fgets(str,81,stream);            /* system guru */
  1109. sprintf(sys_guru,"%.40s",str);
  1110. truncsp(sys_guru);
  1111. fgets(str,81,stream);            /* ctrl dir */
  1112. sprintf(ctrl_dir,"%.40s",str);
  1113. truncsp(ctrl_dir);
  1114. fgets(str,81,stream);            /* data dir */
  1115. sprintf(data_dir,"%.40s",str);
  1116. truncsp(data_dir);
  1117. fgets(str,81,stream);            /* total nodes */
  1118. sys_nodes=atoi(str);
  1119. fgets(str,81,stream);            /* current node */
  1120. node_num=atoi(str);
  1121. fgets(str,81,stream);            /* time left */
  1122. timeleft=atoi(str);
  1123. fgets(str,81,stream);            /* ANSI? (Yes, Mono, or No) */
  1124. user_misc=0;
  1125. if(str[0]=='Y')
  1126.     user_misc|=(ANSI|COLOR);
  1127. else if(str[0]=='M')
  1128.     user_misc|=ANSI;
  1129. fgets(str,81,stream);            /* screen lines */
  1130. user_rows=atoi(str);
  1131. fgets(str,81,stream);            /* credits */
  1132. user_cdt=atol(str);
  1133. fgets(str,81,stream);            /* main level */
  1134. user_ml=atoi(str);
  1135. fgets(str,81,stream);            /* transfer level */
  1136. user_tl=atoi(str);
  1137. fgets(str,81,stream);            /* birthdate */
  1138. sprintf(user_birth,"%.8s",str);
  1139. fgets(str,81,stream);            /* sex */
  1140. user_sex=str[0];
  1141. fgets(str,81,stream);            /* user number */
  1142. user_number=atoi(str);
  1143. fgets(str,81,stream);            /* user phone number */
  1144. sprintf(user_phone,"%.12s",str);
  1145. fgets(str,81,stream);            /* com port (0 if local or no modem) */
  1146. com_port=atoi(str);
  1147. fgets(str,81,stream);            /* com (UART) irq */
  1148. com_irq=atoi(str);
  1149. fgets(str,81,stream);            /* com (UART) base address in hex */
  1150. com_base=(uint)ahtoul(str);
  1151. fgets(str,81,stream);            /* com rate */
  1152. com_rate=(uint)atoi(str);
  1153. fgets(str,81,stream);            /* hardware flow control (Y/N) */
  1154. if(toupper(str[0])=='Y')
  1155.     mdm_misc|=MDM_FLOWCTRL;
  1156. fgets(str,81,stream);            /* locked DTE rate (Y/N) */
  1157. if(toupper(str[0])=='Y')
  1158.     mdm_misc|=MDM_STAYHIGH;
  1159. fgets(str,81,stream);            /* modem initialization string */
  1160. sprintf(mdm_init,"%.40s",str);
  1161. fgets(str,81,stream);            /* modem special init string */
  1162. sprintf(mdm_spec,"%.40s",str);
  1163. fgets(str,81,stream);            /* modem terminal mode string */
  1164. sprintf(mdm_term,"%.40s",str);
  1165. fgets(str,81,stream);            /* modem dial string */
  1166. sprintf(mdm_dial,"%.40s",str);
  1167. fgets(str,81,stream);            /* modem off-hook string */
  1168. sprintf(mdm_offh,"%.40s",str);
  1169. fgets(str,81,stream);            /* modem answer string */
  1170. sprintf(mdm_answ,"%.40s",str);
  1171. fgets(str,81,stream);            /* memory address of modem status register */
  1172. msr=(uint far *)atol(str);
  1173. if(!fgets(str,81,stream))        /* total number of external programs */
  1174.     total_xtrns=0;
  1175. else
  1176.     total_xtrns=atoi(str);
  1177. if(total_xtrns && (xtrn=(char **)MALLOC(sizeof(char *)*total_xtrns))==NULL) {
  1178.     printf("Allocation error 1: %u\r\n",sizeof(char *)*total_xtrns);
  1179.     exit(1); }
  1180. for(i=0;i<total_xtrns;i++) {
  1181.     fgets(str,81,stream);
  1182.     truncsp(str);
  1183.     if((xtrn[i]=(char *)MALLOC(strlen(str)+1))==NULL) {
  1184.         printf("Allocation error 2 (%u): %u\r\n",i,strlen(str)+1);
  1185.         exit(1); }
  1186.     strcpy(xtrn[i],str); }
  1187. fclose(stream);
  1188. starttime=time(NULL);            /* initialize start time stamp */
  1189. fdelay(0);                        /* calibrate delay function */
  1190. wordwrap[0]=0;                    /* set wordwrap to null */
  1191. attr(LIGHTGRAY);                /* initialize color and curatr to plain */
  1192. mnehigh=LIGHTGRAY|HIGH;         /* mnemonics highlight color */
  1193. mnelow=GREEN;                    /* mnemonics normal text color */
  1194. sec_warn=60;                    /* seconds till inactivity warning */
  1195. sec_timeout=120;                /* seconds till inactivity timeout */
  1196. tos=lncntr=0;                    /* init topofscreen and linecounter to 0 */
  1197. lastnodemsg=0;                    /* Last node to send message to */
  1198.  
  1199. sprintf(str,"%s%s",ctrl_dir,"NODE.DAB");
  1200. if((nodefile=open(str,O_BINARY|O_DENYNONE|O_RDWR))==-1) {
  1201.     sprintf(str,"%s%sNODE.DAB",node_dir,ctrl_dir);
  1202.     if((nodefile=open(str,O_BINARY|O_DENYNONE|O_RDWR))==-1) {
  1203.         bprintf("\r\n\7Error opening %s\r\n",str);
  1204.         exit(1); } }
  1205. if(lock(nodefile,0L,1) || unlock(nodefile,0L,1)) {
  1206.     printf("\r\n\7File locking failed. Did you run SHARE?\r\n");
  1207.     exit(1); }
  1208.  
  1209. sprintf(str,"%sUSER\\NAME.DAT",data_dir);
  1210. if((i=nopen(str,O_RDONLY))==-1) {
  1211.     sprintf(str,"%s%sUSER\\NAME.DAT",node_dir,data_dir);
  1212.     if((i=nopen(str,O_RDONLY))==-1) {
  1213.         printf("\r\n\7Error opening %s\r\n",str);
  1214.         exit(1); } }
  1215. memset(str,0,30);
  1216. read(i,str,26);
  1217. close(i);
  1218. if(str[25]==CR)     /* Version 1b */
  1219.     name_len=25;
  1220. else                /* Version 1a */
  1221.     name_len=30;
  1222. }
  1223.  
  1224. /****************************************************************************/
  1225. /* Truncates white-space chars off end of 'str' and terminates at first tab */
  1226. /****************************************************************************/
  1227. void truncsp(char *str)
  1228. {
  1229.     char c;
  1230.  
  1231. str[strcspn(str,"\t")]=0;
  1232. c=strlen(str);
  1233. while(c && str[c-1]<=SP) c--;
  1234. str[c]=0;
  1235. }
  1236.  
  1237. /****************************************************************************/
  1238. /* Checks the amount of time inside the external program against the amount */
  1239. /* of time the user had left online before running the external program and */
  1240. /* prints a message and exits the program if time has run out.                */
  1241. /****************************************************************************/
  1242. void checktimeleft()
  1243. {
  1244. if(!SYSOP && time(NULL)-starttime>timeleft) {
  1245.     bputs("\1_\n\1r\1hTime's up.\n");
  1246.     exit(0);  }
  1247. }
  1248.  
  1249. /****************************************************************************/
  1250. /* Prints a file remotely and locally, interpreting ^A sequences.            */
  1251. /* 'str' is the path of the file to print                                   */
  1252. /****************************************************************************/
  1253. void printfile(char *str)
  1254. {
  1255.     char *buf;
  1256.     int file;
  1257.     ulong length;
  1258.  
  1259. strupr(str);
  1260. if(!tos)
  1261.     CRLF;
  1262. if((file=nopen(str,O_RDONLY))==-1) {
  1263.     bprintf("File not Found: %s\r\n",str);
  1264.     return; }
  1265. length=filelength(file);
  1266. if((buf=MALLOC(length+1L))==NULL) {
  1267.     close(file);
  1268.     bprintf("\7\r\nPRINTFILE: Error allocating %lu bytes of memory for %s.\r\n"
  1269.         ,length+1L,str);
  1270.     return; }
  1271. buf[read(file,buf,length)]=0;
  1272. close(file);
  1273. bputs(buf);
  1274. FREE(buf);
  1275. }
  1276.  
  1277. /****************************************************************************/
  1278. /* Returns a char pointer to the name of the user that corresponds to        */
  1279. /* usernumber. Takes value directly from database.                            */
  1280. /****************************************************************************/
  1281. char *username(uint usernumber)
  1282. {
  1283.     static    char name[26];
  1284.     char    str[128];
  1285.     int     i,file;
  1286.  
  1287. strcpy(name,"UNKNOWN USER");
  1288. if(!usernumber) {
  1289.     bputs("\7username: called with zero usernumber\r\n");
  1290.     return(name); }
  1291. sprintf(str,"%sUSER\\NAME.DAT",data_dir);
  1292. if((file=nopen(str,O_RDONLY))==-1) {
  1293.     sprintf(str,"%s%sUSER\\NAME.DAT",node_dir,data_dir); /* in case data_dir */
  1294.     if((file=nopen(str,O_RDONLY))==-1) {                    /* is relative */
  1295.         bprintf("\7username: couldn't open %s\r\n",str);
  1296.         return(name); } }
  1297. if(filelength(file)<(long)(usernumber-1)*((long)name_len+2L)) {
  1298.     close(file);
  1299.     return(name); }
  1300. lseek(file,(long)(usernumber-1)*((long)name_len+2L),SEEK_SET);
  1301. read(file,name,25);
  1302. close(file);
  1303. for(i=0;i<25;i++)
  1304.     if(name[i]==3)
  1305.         break;
  1306. name[i]=0;
  1307. if(!name[0])
  1308.     strcpy(name,"DELETED USER");
  1309. return(name);
  1310. }
  1311.  
  1312. /****************************************************************************/
  1313. /* Checks the disk drive for the existance of a file. Returns 1 if it         */
  1314. /* exists, 0 if it doesn't.                                                    */
  1315. /* Called from upload                                                        */
  1316. /****************************************************************************/
  1317. char fexist(char *filespec)
  1318. {
  1319.     struct ffblk f;
  1320.  
  1321. if(findfirst(filespec,&f,0)==NULL)
  1322.     return(1);
  1323. return(0);
  1324. }
  1325.  
  1326. /****************************************************************************/
  1327. /* Returns the length of the first file found that matches 'filespec'       */
  1328. /* -1 if the file doesn't exist.                                            */
  1329. /****************************************************************************/
  1330. long flength(char *filespec)
  1331. {
  1332.     struct ffblk f;
  1333.  
  1334. if(findfirst(filespec,&f,0)==NULL)
  1335.     return(f.ff_fsize);
  1336. return(-1L);
  1337. }
  1338.  
  1339. /****************************************************************************/
  1340. /* Returns in 'string' a character representation of the number in l with   */
  1341. /* commas. Maximum value of l is 4 gigabytes.                                */
  1342. /****************************************************************************/
  1343. char *ultoac(ulong l, char *string)
  1344. {
  1345.     char str[81];
  1346.     char i,j,k;
  1347.  
  1348. ultoa(l,str,10);
  1349. i=strlen(str)-1;
  1350. j=i/3+1+i;
  1351. string[j--]=0;
  1352. for(k=1;i>-1;k++) {
  1353.     string[j--]=str[i--];
  1354.     if(j>0 && !(k%3))
  1355.         string[j--]=','; }
  1356. return(string);
  1357. }
  1358.  
  1359. /****************************************************************************/
  1360. /* Converts an ASCII Hex string into a ulong                                */
  1361. /****************************************************************************/
  1362. ulong ahtoul(char *str)
  1363. {
  1364.     ulong l,val=0;
  1365.  
  1366. while((l=(*str++)|0x20)!=0x20)
  1367.     val=(l&0xf)+(l>>6&1)*9+val*16;
  1368. return(val);
  1369. }
  1370.  
  1371. /****************************************************************************/
  1372. /* Reads the data for node number 'number' into the structure 'node'        */
  1373. /* from NODE.DAB                                                            */
  1374. /* if lockit is non-zero, locks this node's record. putnodedat() unlocks it */
  1375. /****************************************************************************/
  1376. void getnodedat(uchar number, node_t *node, char lockit)
  1377. {
  1378.     char str[256];
  1379.     int count=0;
  1380.  
  1381. number--;    /* make zero based */
  1382. while(count<LOOP_NODEDAB) {
  1383.     lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
  1384.     if(lockit
  1385.         && lock(nodefile,(long)number*sizeof(node_t),sizeof(node_t))==-1) {
  1386.         count++;
  1387.         continue; }
  1388.     if(read(nodefile,node,sizeof(node_t))==sizeof(node_t))
  1389.         break;
  1390.     count++; }
  1391. if(count==LOOP_NODEDAB)
  1392.     bprintf("\7Error unlocking and reading NODE.DAB\r\n");
  1393. }
  1394.  
  1395. /****************************************************************************/
  1396. /* Write the data from the structure 'node' into NODE.DAB                      */
  1397. /* getnodedat(num,&node,1); must have been called before calling this func  */
  1398. /*          NOTE: ------^   the indicates the node record has been locked   */
  1399. /****************************************************************************/
  1400. void putnodedat(uchar number, node_t node)
  1401. {
  1402.     char str[256];
  1403.     int count;
  1404.  
  1405. number--;    /* make zero based */
  1406. lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
  1407. if(write(nodefile,&node,sizeof(node_t))!=sizeof(node_t)) {
  1408.     unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
  1409.     bprintf("\7Error writing NODE.DAB for node %u\r\n",number+1);
  1410.     return; }
  1411. unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
  1412. }
  1413.  
  1414. /****************************************************************************/
  1415. /* Checks for messages waiting for this node or interruption.                */
  1416. /****************************************************************************/
  1417. void nodesync()
  1418. {
  1419.     node_t node;
  1420.  
  1421. getnodedat(node_num,&node,0);
  1422.  
  1423. if(node.misc&NODE_MSGW)
  1424.     getsmsg(user_number);      /* getsmsg clears MSGW flag */
  1425.  
  1426. if(node.misc&NODE_INTR)
  1427.     exit(0);
  1428.  
  1429. }
  1430.  
  1431. /****************************************************************************/
  1432. /* Displays the information for node number 'number' contained in 'node'    */
  1433. /****************************************************************************/
  1434. void printnodedat(uchar number, node_t node)
  1435. {
  1436.     char hour,mer[3],tmp[256];
  1437.     int i;
  1438.  
  1439. attr(LIGHTGRAY|HIGH);
  1440. bprintf("Node %2d: ",number);
  1441. attr(GREEN);
  1442. switch(node.status) {
  1443.     case NODE_WFC:
  1444.         bputs("Waiting for call");
  1445.         break;
  1446.     case NODE_OFFLINE:
  1447.         bputs("Offline");
  1448.         break;
  1449.     case NODE_NETTING:
  1450.         bputs("Networking");
  1451.         break;
  1452.     case NODE_LOGON:
  1453.         bputs("At logon prompt");
  1454.         break;
  1455.     case NODE_EVENT_WAITING:
  1456.         bputs("Waiting for all nodes to become inactive");
  1457.         break;
  1458.     case NODE_EVENT_LIMBO:
  1459.         bprintf("Waiting for node %d to finish external event",node.aux);
  1460.         break;
  1461.     case NODE_EVENT_RUNNING:
  1462.         bputs("Running external event");
  1463.         break;
  1464.     case NODE_NEWUSER:
  1465.         attr(GREEN|HIGH);
  1466.         bputs("New user");
  1467.         attr(GREEN);
  1468.         bputs(" applying for access ");
  1469.         if(!node.connection)
  1470.             bputs("Locally");
  1471.         else
  1472.             bprintf("at %ubps",node.connection);
  1473.         break;
  1474.     case NODE_QUIET:
  1475.         if(!SYSOP) {
  1476.             bputs("Waiting for call");
  1477.             break; }
  1478.     case NODE_INUSE:
  1479.         attr(GREEN|HIGH);
  1480.         if(node.misc&NODE_ANON && !SYSOP)
  1481.             bputs("UNKNOWN USER");
  1482.         else
  1483.             bputs(username(node.useron));
  1484.         attr(GREEN);
  1485.         bputs(" ");
  1486.         switch(node.action) {
  1487.             case NODE_MAIN:
  1488.                 bputs("at main menu");
  1489.                 break;
  1490.             case NODE_RMSG:
  1491.                 bputs("reading messages");
  1492.                 break;
  1493.             case NODE_RMAL:
  1494.                 bputs("reading mail");
  1495.                 break;
  1496.             case NODE_RSML:
  1497.                 bputs("reading sent mail");
  1498.                 break;
  1499.             case NODE_RTXT:
  1500.                 bputs("reading text files");
  1501.                 break;
  1502.             case NODE_PMSG:
  1503.                 bputs("posting message");
  1504.                 break;
  1505.             case NODE_SMAL:
  1506.                 bputs("sending mail");
  1507.                 break;
  1508.             case NODE_AMSG:
  1509.                 bputs("posting auto-message");
  1510.                 break;
  1511.             case NODE_XTRN:
  1512.                 if(!node.aux)
  1513.                     bputs("at external program menu");
  1514.                 else {
  1515.                     bputs("running ");
  1516.                     i=node.aux-1;
  1517.                     if(i>=total_xtrns || !xtrn[i][0])
  1518.                         bputs("external program");
  1519.                     else
  1520.                         bputs(xtrn[i]); }
  1521.                 break;
  1522.             case NODE_DFLT:
  1523.                 bputs("changing defaults");
  1524.                 break;
  1525.             case NODE_XFER:
  1526.                 bputs("at transfer menu");
  1527.                 break;
  1528.             case NODE_DLNG:
  1529.                 bprintf("downloading");
  1530.                 break;
  1531.             case NODE_ULNG:
  1532.                 bputs("uploading");
  1533.                 break;
  1534.             case NODE_BXFR:
  1535.                 bputs("in a bidirectional file transfer");
  1536.                 break;
  1537.             case NODE_LFIL:
  1538.                 bputs("listing files");
  1539.                 break;
  1540.             case NODE_LOGN:
  1541.                 bputs("logging on");
  1542.                 break;
  1543.             case NODE_LCHT:
  1544.                 bprintf("in local chat with %s",sys_op);
  1545.                 break;
  1546.             case NODE_MCHT:
  1547.                 if(node.aux) {
  1548.                     bprintf("in multinode chat channel %d",node.aux&0xff);
  1549.                     if(node.aux&0x1f00)  /* password */
  1550.                         outchar('*'); }
  1551.                 else
  1552.                     bputs("in multinode global chat channel");
  1553.                 break;
  1554.             case NODE_PCHT:
  1555.                 bprintf("in private chat with node %u",node.aux);
  1556.                 break;
  1557.             case NODE_GCHT:
  1558.                 bprintf("chatting with %s",sys_guru);
  1559.                 break;
  1560.             case NODE_CHAT:
  1561.                 bputs("in chat section");
  1562.                 break;
  1563.             case NODE_TQWK:
  1564.                 bputs("transferring QWK packet");
  1565.                 break;
  1566.             case NODE_SYSP:
  1567.                 bputs("performing sysop activities");
  1568.                 break;
  1569.             default:
  1570.                 bputs(itoa(node.action,tmp,10));
  1571.                 break;  }
  1572.         if(!node.connection)
  1573.             bputs(" locally");
  1574.         else
  1575.             bprintf(" at %ubps",node.connection);
  1576.         if(node.action==NODE_DLNG) {
  1577.             if((node.aux/60)>12) {
  1578.                 hour=(node.aux/60)-12;
  1579.                 strcpy(mer,"pm"); }
  1580.             else {
  1581.                 if((node.aux/60)==0)    /* 12 midnite */
  1582.                     hour=12;
  1583.                 else hour=node.aux/60;
  1584.                 strcpy(mer,"am"); }
  1585.             bprintf(" ETA %02d:%02d %s"
  1586.                 ,hour,node.aux-((node.aux/60)*60),mer); }
  1587.         break; }
  1588. if(node.misc&(NODE_LOCK|NODE_POFF|NODE_AOFF|NODE_MSGW)) {
  1589.     bputs(" (");
  1590.     if(node.misc&NODE_AOFF)
  1591.         outchar('A');
  1592.     if(node.misc&NODE_LOCK)
  1593.         outchar('L');
  1594.     if(node.misc&NODE_MSGW)
  1595.         outchar('M');
  1596.     if(node.misc&NODE_POFF)
  1597.         outchar('P');
  1598.     outchar(')'); }
  1599. if(SYSOP && ((node.misc
  1600.     &(NODE_ANON|NODE_UDAT|NODE_INTR|NODE_RRUN|NODE_EVENT|NODE_DOWN))
  1601.     || node.status==NODE_QUIET)) {
  1602.     bputs(" [");
  1603.     if(node.misc&NODE_ANON)
  1604.         outchar('A');
  1605.     if(node.misc&NODE_INTR)
  1606.         outchar('I');
  1607.     if(node.misc&NODE_RRUN)
  1608.         outchar('R');
  1609.     if(node.misc&NODE_UDAT)
  1610.         outchar('U');
  1611.     if(node.status==NODE_QUIET)
  1612.         outchar('Q');
  1613.     if(node.misc&NODE_EVENT)
  1614.         outchar('E');
  1615.     if(node.misc&NODE_DOWN)
  1616.         outchar('D');
  1617.     outchar(']'); }
  1618. if(node.errors && SYSOP) {
  1619.     attr(RED|HIGH|BLINK);
  1620.     bprintf(" %d error%c",node.errors, node.errors>1 ? 's' : '\0' ); }
  1621. CRLF;
  1622. }
  1623.  
  1624. /****************************************************************************/
  1625. /* Prints short messages waiting for 'usernumber', if any...                */
  1626. /* then deletes them.                                                        */
  1627. /****************************************************************************/
  1628. void getsmsg(int usernumber)
  1629. {
  1630.     char str[256], *buf;
  1631.     int file;
  1632.     long length;
  1633.     node_t node;
  1634.  
  1635. sprintf(str,"%sMSGS\\%4.4u.MSG",data_dir,usernumber);
  1636. if(!fexist(str) || !flength(str)) {
  1637.     sprintf(str,"%s%sMSGS\\%4.4u.MSG",node_dir,data_dir,usernumber);
  1638.     if(!fexist(str) || !flength(str))
  1639.         return; }
  1640. if((file=nopen(str,O_RDONLY))==-1) {
  1641.     bprintf("\7Error opening %s for read access\r\n",str);
  1642.     return; }
  1643. length=filelength(file);
  1644. if((buf=MALLOC(length+1))==NULL) {
  1645.     close(file);
  1646.     bprintf("\7Error allocating %u bytes of memory for %s\r\n",length+1,str);
  1647.     return; }
  1648. if(read(file,buf,length)!=length) {
  1649.     close(file);
  1650.     FREE(buf);
  1651.     bprintf("\7Error reading %u bytes from %s\r\n",length,str);
  1652.     return; }
  1653. close(file);
  1654. if((file=nopen(str,O_WRONLY|O_TRUNC))==-1) {
  1655.     FREE(buf);
  1656.     bprintf("\7Error opening %s for write/truncate access\r\n",str);
  1657.     return; }
  1658. close(file);
  1659. buf[length]=0;
  1660. getnodedat(node_num,&node,0);
  1661. if(node.action==NODE_MAIN || node.action==NODE_XFER) {
  1662.     CRLF; }
  1663. if(node.misc&NODE_MSGW) {
  1664.     getnodedat(node_num,&node,1);
  1665.     node.misc&=~NODE_MSGW;
  1666.     putnodedat(node_num,node); }
  1667. bputs(buf);
  1668. FREE(buf);
  1669. }
  1670.  
  1671. /****************************************************************************/
  1672. /* Creates a short message for 'usernumber' than contains 'strin'            */
  1673. /****************************************************************************/
  1674. void putsmsg(int usernumber, char *strin)
  1675. {
  1676.     char str[256];
  1677.     int file,i;
  1678.     struct ffblk ff;
  1679.     node_t node;
  1680.  
  1681. sprintf(str,"%sMSGS\\%4.4u.MSG",data_dir,usernumber);
  1682. if((file=nopen(str,O_WRONLY|O_CREAT|O_APPEND))==-1) {
  1683.     sprintf(str,"%s%sMSGS\\%4.4u.MSG",node_dir,data_dir,usernumber);
  1684.     if((file=nopen(str,O_WRONLY|O_CREAT|O_APPEND))==-1) {
  1685.         bprintf("\7Error opening/creating %s for creat/append access\r\n",str);
  1686.         return; } }
  1687. if(write(file,strin,strlen(strin))!=strlen(strin)) {
  1688.     close(file);
  1689.     bprintf("\7Error writing %u bytes to %s\r\n",strlen(strin),str);
  1690.     return; }
  1691. close(file);
  1692. for(i=1;i<=sys_nodes;i++) {        /* flag node if user on that msg waiting */
  1693.     getnodedat(i,&node,0);
  1694.     if(node.useron==usernumber
  1695.         && (node.status==NODE_INUSE || node.status==NODE_QUIET)
  1696.         && !(node.misc&NODE_MSGW)) {
  1697.         getnodedat(i,&node,1);
  1698.         node.misc|=NODE_MSGW;
  1699.         putnodedat(i,node); } }
  1700. }
  1701.  
  1702. /****************************************************************************/
  1703. /* This function lists users that are online.                                */
  1704. /* If listself is true, it will list the current node.                        */
  1705. /* Returns number of active nodes (not including current node).             */
  1706. /****************************************************************************/
  1707. int whos_online(char listself)
  1708. {
  1709.     int i,j;
  1710.     node_t node;
  1711.  
  1712. CRLF;
  1713. for(j=0,i=1;i<=sys_nodes;i++) {
  1714.     getnodedat(i,&node,0);
  1715.     if(i==node_num) {
  1716.         if(listself)
  1717.             printnodedat(i,node);
  1718.         continue; }
  1719.     if(node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET)) {
  1720.         printnodedat(i,node);
  1721.         if(!lastnodemsg)
  1722.             lastnodemsg=i;
  1723.         j++; } }
  1724. if(!j)
  1725.     bputs("\1nNo other active nodes.\r\n");
  1726. return(j);
  1727. }
  1728.  
  1729. /****************************************************************************/
  1730. /* Sending single line messages between nodes                               */
  1731. /****************************************************************************/
  1732. void nodemsg()
  1733. {
  1734.     char    str[256],line[256],buf[512];
  1735.     int     i,j,usernumber;
  1736.     node_t    thisnode;
  1737.     node_t     node;
  1738.  
  1739. getnodedat(node_num,&thisnode,0);
  1740. wordwrap[0]=0;
  1741. if(lastnodemsg) {
  1742.     getnodedat(lastnodemsg,&node,0);
  1743.     if(node.status!=NODE_INUSE)
  1744.         lastnodemsg=0; }
  1745. if(!whos_online(0))
  1746.     return;
  1747. bprintf("\r\nngNumber of node to send message to, whAngll, "
  1748.     "or whQnguit [%u]: wh",lastnodemsg);
  1749. i=getkeys("QA",sys_nodes);
  1750. if(i==-1)
  1751.     return;
  1752. if(i&0x8000 || !i) {
  1753.     if(!i)
  1754.         i=lastnodemsg;
  1755.     else {
  1756.         i^=0x8000;
  1757.         lastnodemsg=i; }
  1758.     getnodedat(i,&node,0);
  1759.     usernumber=node.useron;
  1760.     if(node.status!=NODE_INUSE && !SYSOP)
  1761.         bprintf("\r\n_whNode %d is not in use.\r\n",i);
  1762.     else if(i==node_num)
  1763.         bputs("\r\nThere's no need to send a message to yourself.\r\n");
  1764.     else if(node.misc&NODE_POFF && !SYSOP)
  1765.         bprintf("\r\nrhiDon't bug %s.n\r\n"
  1766.             ,node.misc&NODE_ANON ? "UNKNOWN USER"
  1767.             : username(node.useron));
  1768.     else {
  1769.         bputs("_yhMessage: ");
  1770.         if(!getstr(line,70,K_LINE))
  1771.             return;
  1772.         sprintf(buf
  1773.             ,"\7_whNode %2d: g%sng sent you a message:\r\nwh4%sn\r\n"
  1774.             ,node_num
  1775.             ,thisnode.misc&NODE_ANON ? "UNKNOWN USER" : user_name,line);
  1776.         putsmsg(usernumber,buf); } }
  1777. else if(i=='A') {
  1778.     bputs("_yhMessage: ");
  1779.     if(!getstr(line,70,K_LINE))
  1780.         return;
  1781.     sprintf(buf
  1782.         ,"\7_whNode %2d: g%sng sent all nodes a message:\r\n"
  1783.             "wh4%sn\r\n"
  1784.         ,node_num
  1785.         ,thisnode.misc&NODE_ANON ? "UNKNOWN USER" : user_name,line);
  1786.     for(i=1;i<=sys_nodes;i++) {
  1787.         if(i==node_num)
  1788.             continue;
  1789.         getnodedat(i,&node,0);
  1790.         if((node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET))
  1791.             && (SYSOP || !(node.misc&NODE_POFF)))
  1792.             putsmsg(node.useron,buf); } }
  1793.  
  1794. }
  1795.  
  1796. /****************************************************************************/
  1797. /* Pass control back to DESQview, giving up rest of time slice                */
  1798. /****************************************************************************/
  1799. void dv_pause(void)
  1800. {
  1801. _AX = 0x1000;                        /* PAUSE DV API Call    */
  1802. geninterrupt( 0x15 );                /* DV API CALL          */
  1803. }
  1804.  
  1805. /****************************************************************************/
  1806. /* Puts a character into the input buffer                                    */
  1807. /****************************************************************************/
  1808. void ungetkey(char ch)
  1809. {
  1810.  
  1811. keybuf[keybuftop++]=ch;
  1812. if(keybuftop==KEY_BUFSIZE)
  1813.     keybuftop=0;
  1814. }
  1815.  
  1816.