home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / top2src.zip / BBSMAX.C < prev    next >
C/C++ Source or Header  |  2000-07-13  |  19KB  |  613 lines

  1. /******************************************************************************
  2. BBSMAX.C     Implements Maximus-specific BBS functions.
  3.  
  4.     Copyright 1993 - 2000 Paul J. Sidorsky
  5.  
  6.     This program is free software; you can redistribute it and/or modify
  7.     it under the terms of the GNU General Public License, version 2, as
  8.     published by the Free Software Foundation.
  9.  
  10.     This program is distributed in the hope that it will be useful,
  11.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.     GNU General Public License for more details.
  14.  
  15.     You should have received a copy of the GNU General Public License
  16.     along with this program; if not, write to the Free Software
  17.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  
  19. The module contains all of the functions for interfacing with Maximus v2.xx and
  20. v3.xx, excluding Maximus for OS/2 v3.0x.
  21. ******************************************************************************/
  22.  
  23. #include "top.h"
  24.  
  25. /* Blinking test used when displaying MECCA strings. */
  26. #define blinking (blink ? 0x80 : 0x00)
  27.  
  28. /* max_init() - Initialize pointers to Maximus functions.
  29.    Paremeters:  None.
  30.    Returns:  Nothing.
  31. */
  32. void max_init(void)
  33. {
  34.  
  35. bbs_call_loaduseron = max_loadipcstatus;
  36. bbs_call_saveuseron = max_saveipcstatus;
  37. bbs_call_processmsgs = max_processipcmsgs;
  38. bbs_call_page = max_page;
  39. bbs_call_setexistbits = max_setexistbits;
  40. bbs_call_login = max_login;
  41. bbs_call_logout = max_logout;
  42. bbs_call_openfiles = max_openfiles;
  43. bbs_call_updateopenfiles = max_openfiles;
  44. bbs_call_pageedit = max_shortpageeditor;
  45.  
  46. }
  47.  
  48. /* max_loadipcstatus() - Loads a node status from an IPC file.
  49.    Parameters:  nodenum - Node number to load the data for.
  50.                 userdata - Pointer to generic BBS data structure to fill.
  51.    Returns:  TRUE if an error occurred, FALSE if successful.
  52. */
  53. char max_loadipcstatus(XINT nodenum, bbsnodedata_typ *userdata)
  54. {
  55. char risnam[MAXPATH]; /* IPC file name. */
  56. XINT res; /* Result code. */
  57. struct _cstat statbuf; /* Maximus node status buffer. */
  58.  
  59. /* Open the IPC file and read the status information. */
  60. sprintf(risnam, "%sIPC%02X.BBS", cfg.bbsmultipath, nodenum);
  61. ipcoutfil = sh_open(risnam, O_RDONLY | O_BINARY, SH_DENYNONE, S_IREAD | S_IWRITE);
  62. if (ipcoutfil == -1)
  63.     {
  64.     return 1;
  65.     }
  66. lseek(ipcoutfil, 0, SEEK_SET);
  67. rec_locking(REC_LOCK, ipcoutfil, 0, sizeof(struct _cstat));
  68. res = read(ipcoutfil, &statbuf, sizeof(struct _cstat));
  69. rec_locking(REC_UNLOCK, ipcoutfil, 0, sizeof(struct _cstat));
  70. if (res == -1)
  71.     {
  72.     close(ipcoutfil);
  73.     return 1;
  74.     }
  75. close(ipcoutfil);
  76.  
  77. /* Copy the status information to the generic structure. */
  78. userdata->quiet = !statbuf.avail;
  79. fixname(userdata->realname, statbuf.username);
  80. fixname(userdata->handle, statbuf.username);
  81. strcpy(userdata->statdesc, statbuf.status);
  82. userdata->node = nodenum;
  83.  
  84. return 0;
  85. }
  86.  
  87. /* max_saveipcstatus() - Saves a node status to an IPC file.
  88.    Parameters:  nodenum - Node number to save the data for.
  89.                 userdata - Pointer to generic BBS data structure to use.
  90.    Returns:  TRUE if an error occurred, FALSE if successful.
  91. */
  92. char max_saveipcstatus(XINT nodenum, bbsnodedata_typ *userdata)
  93. {
  94. char winam[MAXPATH]; /* IPC file name. */
  95. XINT res; /* Result code. */
  96. struct _cstat nodeinf; /* Maximus node status buffer. */
  97. char sist = 1; /* Flag indicating that there are messages in the IPC file. */
  98.  
  99. /* Transfer the data from the generic BBS structure. */
  100. nodeinf.avail = !userdata->quiet;
  101. strcpy(nodeinf.username,
  102.        cfg.usehandles ? userdata->handle : userdata->realname);
  103. strcpy(nodeinf.status, userdata->statdesc);
  104.  
  105. /* Open the file, creating it if it doesn't exist. */
  106. sprintf(winam, "%sIPC%02X.BBS", cfg.bbsmultipath, nodenum);
  107. ipcoutfil = sh_open(winam, O_WRONLY | O_CREAT, SH_DENYNONE, S_IREAD | S_IWRITE);
  108. if (ipcoutfil == -1)
  109.     {
  110.     return 1;
  111.     }
  112.  
  113. /* If TOP created the IPC file, initialize the message data. */
  114. if (filelength(ipcoutfil) < sizeof(struct _cstat))
  115.     {
  116.     nodeinf.msgs_waiting = 0;
  117.     nodeinf.next_msgofs = sizeof(struct _cstat);
  118.     nodeinf.new_msgofs = sizeof(struct _cstat);
  119.     chsize(ipcoutfil, sizeof(struct _cstat));
  120.     sist = 0;
  121.     }
  122.  
  123. /* Save the node status information. */
  124. lseek(ipcoutfil, 0, SEEK_SET);
  125. rec_locking(REC_LOCK, ipcoutfil, 0, sizeof(struct _cstat));
  126. /* If sist is 1 then there may be messages in the file that have not been
  127.    sent, so the message data is not disturbed. */
  128. res = write(ipcoutfil, &nodeinf,
  129.             sist ? (sizeof(struct _cstat) - 10) : sizeof(struct _cstat));
  130. rec_locking(REC_UNLOCK, ipcoutfil, 0, sizeof(struct _cstat));
  131. if (res == -1)
  132.     {
  133.     close(ipcoutfil);
  134.     return 1;
  135.     }
  136.  
  137. close(ipcoutfil);
  138.  
  139. return 0;
  140. }
  141.  
  142. /* max_processipcmsgs() - Reads and displays messages from IPC file.
  143.    Parameters:  None.
  144.    Returns:  Number of messages processed.  (0 on error.)
  145. */
  146. XINT max_processipcmsgs(void)
  147. {
  148. unsigned XINT mc = 0, mcc = 0; /* Loop counter, processing counter. */
  149. XINT res, d; /* Result code, counter. */
  150. unsigned long bc; /* File position tracker. */
  151. struct ffblk ipctmp; /* File information buffer. */
  152. char pinam[256]; /* IPC file name. */
  153. char XFAR *msgdat = NULL; /* Buffer to hold incoming messages. */
  154. struct _cstat statinf; /* Maximus node status buffer. */
  155. struct _cdat msginf; /* Maximus message information buffer. */
  156.  
  157. /* Before the file is opened, the file information is read.  If the date,
  158.    time, and size of the file have not changed, it is assumed there is
  159.    nothing new in the file.  This is easier on the processor and drive. */
  160. sprintf(pinam, "%sIPC%02X.BBS", cfg.bbsmultipath, od_control.od_node);
  161. findfirst(pinam, &ipctmp, 0xFFFF);
  162. if (ipctmp.ff_fdate == ipcfildat.ff_fdate &&
  163.     ipctmp.ff_ftime == ipcfildat.ff_ftime &&
  164.     ipctmp.ff_fsize == ipcfildat.ff_fsize)
  165.     {
  166.     return 0;
  167.     }
  168.  
  169. /* Copy the new file information for use next time. */
  170. memcpy(&ipcfildat, &ipctmp, sizeof(struct ffblk));
  171.  
  172. /* Read the node status information to see how many messages there are. */
  173. lseek(ipcinfil, 0, SEEK_SET);
  174. rec_locking(REC_LOCK, ipcinfil, 0, sizeof(struct _cstat));
  175. res = read(ipcinfil, &statinf, sizeof(struct _cstat));
  176. rec_locking(REC_UNLOCK, ipcinfil, 0, sizeof(struct _cstat));
  177. if (res == -1)
  178.     {
  179.     return 0;
  180.     }
  181. if (statinf.msgs_waiting == 0)
  182.     {
  183.     return 0;
  184.     }
  185.  
  186. /* Process all incoming messages. */
  187. bc = statinf.next_msgofs;
  188. for (mc = 0; mc < statinf.msgs_waiting; mc++)
  189.     {
  190.     /* Prepare the screen if this is the first message. */
  191.     if (mc == 0)
  192.         {
  193.         delprompt(TRUE);
  194.  
  195.         if (user.pref1 & PREF1_DUALWINDOW)
  196.             {
  197.             // Different for AVT when I find out what the store/recv codes are.
  198.             od_disp_emu("\x1B" "[u", TRUE);
  199.             top_output(OUT_SCREEN, getlang("DWOutputPrefix"));
  200.             }
  201.         }
  202.  
  203.     /* Read the data for the next message. */
  204.     res = lseek(ipcinfil, bc, SEEK_SET);
  205.     if (res == -1)
  206.         {
  207.         return mcc;
  208.         }
  209.     rec_locking(REC_LOCK, ipcinfil, bc, sizeof(struct _cdat));
  210.     res = read(ipcinfil, &msginf, sizeof(struct _cdat));
  211.        rec_locking(REC_UNLOCK, ipcinfil, bc, sizeof(struct _cdat));
  212.     if (res == -1)
  213.         {
  214.         return mcc;
  215.         }
  216.  
  217.     /* Advance the file pointer to the location of the message text. */
  218.     bc += (long) sizeof(struct _cdat);
  219.  
  220.     /* Allocate a buffer for the message text. */
  221.     msgdat = malloc(msginf.len);
  222.     if (!msgdat)
  223.         {
  224.         return mcc;
  225.         }
  226.  
  227.     /* Read the message text. */
  228.     res = lseek(ipcinfil, bc, SEEK_SET);
  229.     if (res == -1)
  230.         {
  231.         dofree(msgdat);
  232.         return mcc;
  233.         }
  234.     rec_locking(REC_LOCK, ipcinfil, bc, msginf.len);
  235.     res = read(ipcinfil, msgdat, msginf.len);
  236.     rec_locking(REC_UNLOCK, ipcinfil, bc, msginf.len);
  237.     if (res == -1)
  238.         {
  239.         dofree(msgdat);
  240.         return mcc;
  241.         }
  242.  
  243.     /* Process the message based on type.  All unrecognized messages (i.e.
  244.        anything but a page) are ignored. */
  245.     switch(msginf.type)
  246.         {
  247.         case CMSG_HEY_DUDE:
  248.             max_showmeccastring(msgdat);
  249.             mcc++;
  250.             break;
  251.         }
  252.  
  253.     /* The next message is located after the text for this one. */
  254.     bc += (long) msginf.len;
  255.  
  256.     dofree(msgdat);
  257.     }
  258.  
  259. /* Clear the message data now that they've all been processed. */
  260. statinf.msgs_waiting = 0;
  261. statinf.next_msgofs = sizeof(struct _cstat);
  262. statinf.new_msgofs = sizeof(struct _cstat);
  263.  
  264. /* Write the cleared message data to the file. */
  265. lseek(ipcinfil, 0, SEEK_SET);
  266. rec_locking(REC_LOCK, ipcinfil, 0, sizeof(struct _cstat));
  267. res = write(ipcinfil, &statinf, sizeof(struct _cstat));
  268. rec_locking(REC_UNLOCK, ipcinfil, 0, sizeof(struct _cstat));
  269. if (res == -1)
  270.     {
  271.     return mcc;
  272.     }
  273.  
  274. /* Update the file information again since it's now changed. */
  275. findfirst(pinam, &ipctmp, 0xFFFF);
  276. memcpy(&ipcfildat, &ipctmp, sizeof(struct ffblk));
  277.  
  278. return mcc;
  279. }
  280.  
  281. /* max_page() - Sends a page to a Maximus node.
  282.    Parameters:  nodenum - Node number to page.
  283.                 pagebuf - Text to send.
  284.    Returns:  TRUE if successful, FALSE if an error occurred.
  285.    Notes:  pagebuf should contain the text to send, and it must also be big
  286.            enough to have the page header and footer appended.
  287. */
  288. char max_page(XINT nodenum, unsigned char *pagebuf)
  289. {
  290. unsigned char tmp[256], rpt[256]; /* Output buffer, filtered name buffer. */
  291. XINT res; /* Result code. */
  292. struct _cdat tmpmsg; /* Maximus message information buffer. */
  293.  
  294. /* rpt, which holds the user's name filtered of colour codes, is not actually
  295.    used.  I believe it got overlooked when I added configurable
  296.    handle/real-name selection. */
  297. // filter_string(rpt, od_control.user_name);
  298. /* Prepare the page header. */
  299. itoa(od_control.od_node, outnum[0], 10);
  300. strcpy(tmp, top_output(OUT_STRINGNF, getlang("MaxPageHeader"),
  301.                        cfg.usehandles ? user.handle : user.realname,
  302.                        outnum[0]));
  303. /* Inserts the page header in front of the page text. */
  304. memmove(&pagebuf[strlen(tmp)], pagebuf, strlen(tmp));
  305. memcpy(pagebuf, tmp, strlen(tmp));
  306. /* Append the page footer. */
  307. strcat(pagebuf, top_output(OUT_STRINGNF, getlang("MaxPageFooter")));
  308.  
  309. /* Set the message data. */
  310. tmpmsg.tid = od_control.od_node;
  311. tmpmsg.type = CMSG_HEY_DUDE;
  312. tmpmsg.len = strlen(pagebuf) + 1;
  313.  
  314. /* Write the message to the IPC file. */
  315. itoa(nodenum, outnum[0], 10);
  316. res = max_writeipcmsg(nodenum, &tmpmsg, pagebuf);
  317. if (!res)
  318.     {
  319.     top_output(OUT_SCREEN, getlang("CantPage"), outnum[0]);
  320.     return 0;
  321.     }
  322. else
  323.     {
  324.     top_output(OUT_SCREEN, getlang("Paged"), outnum[0]);
  325.     }
  326.  
  327. return 1;
  328. }
  329.  
  330. /* max_writeipcmsg() - Writes a new message to an IPC file.
  331.    Parameters:  nodenum - Node number to receive the message.
  332.                 msginf - Pointer to Maximus message information.
  333.                 message - String containing the message text.
  334.    Returns:  TRUE on success, FALSE on failure.
  335. */
  336. char max_writeipcmsg(XINT nodenum, struct _cdat *msginf, char *message)
  337. {
  338. XINT res; /* Result code. */
  339. char wimpath[256]; /* IPC file name. */
  340. struct _cstat statinf; /* Maximus node status buffer. */
  341.  
  342. /* Open the IPC file.  The file will not be created if it doesn't exist.
  343.    This should never be a problem under normal conditions. */
  344. sprintf(wimpath, "%sIPC%02X.BBS", cfg.bbsmultipath, nodenum);
  345. ipcoutfil = sh_open(wimpath, O_RDWR | O_BINARY, SH_DENYNONE, S_IREAD | S_IWRITE);
  346. if (ipcoutfil == -1)
  347.     {
  348.     return 0;
  349.     }
  350.  
  351. /* Read the node status information to see where to put the next message. */
  352. lseek(ipcoutfil, 0, SEEK_SET);
  353. rec_locking(REC_LOCK, ipcoutfil, 0, sizeof(struct _cstat));
  354. res = read(ipcoutfil, &statinf, sizeof(struct _cstat));
  355. rec_locking(REC_UNLOCK, ipcoutfil, 0, sizeof(struct _cstat));
  356. if (res == -1)
  357.     {
  358.     close(ipcoutfil);
  359.     return 0;
  360.     }
  361.  
  362. /* Maximus includes the null terminator in its length calculations. */
  363. msginf->len = strlen(message) + 1;
  364.  
  365. /* Extend the file size if it is too small.  This is done to make the record
  366.    locking safer. */
  367. if (filelength(ipcoutfil) < (long) statinf.new_msgofs +
  368.     (long) sizeof(struct _cdat) + (long) msginf->len)
  369.     {
  370.     chsize(ipcoutfil, (long) statinf.new_msgofs +
  371.            (long) sizeof(struct _cdat) + (long) msginf->len);
  372.     }
  373.  
  374. /* Write the message information. */
  375. res = lseek(ipcoutfil, statinf.new_msgofs, SEEK_SET);
  376. if (res == -1)
  377.     {
  378.     close(ipcoutfil);
  379.     return 0;
  380.     }
  381. rec_locking(REC_LOCK, ipcoutfil, statinf.new_msgofs, sizeof(struct _cdat));
  382. res = write(ipcoutfil, msginf, sizeof(struct _cdat));
  383. rec_locking(REC_UNLOCK, ipcoutfil, statinf.new_msgofs, sizeof(struct _cdat));
  384. if (res == -1)
  385.     {
  386.     close(ipcoutfil);
  387.     return 0;
  388.     }
  389.  
  390. /* Write the message itself. */
  391. res = lseek(ipcoutfil, statinf.new_msgofs + (long) sizeof(struct _cdat),
  392.             SEEK_SET);
  393. if (res == -1)
  394.     {
  395.     close(ipcoutfil);
  396.     return 0;
  397.     }
  398. rec_locking(REC_LOCK, ipcoutfil, statinf.new_msgofs +
  399.             (long) sizeof(struct _cdat), (long) msginf->len);
  400. res = write(ipcoutfil, message, (long) msginf->len);
  401. rec_locking(REC_UNLOCK, ipcoutfil, statinf.new_msgofs +
  402.         (long) sizeof(struct _cdat), (long) msginf->len);
  403. if (res == -1)
  404.     {
  405.     close(ipcoutfil);
  406.     return 0;
  407.     }
  408.  
  409. /* Update the message data. */
  410. statinf.msgs_waiting++;
  411. statinf.next_msgofs = (long) sizeof(struct _cstat);
  412. statinf.new_msgofs += ((long) sizeof(struct _cdat) + (long) msginf->len);
  413.  
  414. /* Write the updated data. */
  415. lseek(ipcoutfil, 0, SEEK_SET);
  416. rec_locking(REC_LOCK, ipcoutfil, 0, sizeof(struct _cstat));
  417. res = write(ipcoutfil, &statinf, sizeof(struct _cstat));
  418. rec_locking(REC_UNLOCK, ipcoutfil, 0, sizeof(struct _cstat));
  419. if (res == -1)
  420.     {
  421.     close(ipcoutfil);
  422.     return 0;
  423.     }
  424.  
  425. res = close(ipcoutfil);
  426. if (res == -1)
  427.     {
  428.     close(ipcoutfil);
  429.     return 0;
  430.     }
  431.  
  432. return 1;
  433. }
  434.  
  435. /* max_setexistbits() - Selects the node status fields to display.
  436.    Parameters:  userdata - Pointer to generic BBS data structure to set the
  437.                            bits in.
  438.    Returns:  Nothing.
  439. */
  440. void max_setexistbits(bbsnodedata_typ *userdata)
  441. {
  442.  
  443. userdata->existbits = NEX_HANDLE |
  444.                       NEX_REALNAME |
  445.                       NEX_NODE |
  446.                       NEX_STATUS |
  447.                       NEX_PAGESTAT;
  448.  
  449. }
  450.  
  451. /* max_login() - Maximus initialization when TOP is started.
  452.    Parameters:  None.
  453.    Returns:  Nothing.
  454. */
  455. void max_login(void)
  456. {
  457. /* Generic BBS data buffer, login check BBS data buffer. */
  458. bbsnodedata_typ maxtemp, logintmp;
  459. XINT mres; /* Result codes. */
  460.  
  461. /* Check if the node is already logged in Maximus. */
  462. mres = (*bbs_call_loaduseron)(od_control.od_node, &logintmp);
  463. if (!mres)
  464. {
  465.     /* Set the do not disturb flag. */
  466.     node->quiet = maxtemp.quiet = logintmp.quiet;
  467.     save_node_data(od_control.od_node, node);
  468. }
  469.  
  470.  
  471. /* Copy the user information into the generic BBS data buffer. */
  472. fixname(maxtemp.handle, user.handle);
  473. fixname(maxtemp.realname, user.realname);
  474. strcpy(maxtemp.statdesc, getlang("NodeStatus"));
  475. maxtemp.node = od_control.od_node;
  476. maxtemp.speed = od_control.baud;
  477.  
  478. /* Update the IPC file to show the user is now in TOP. */
  479. mres = (*bbs_call_saveuseron)(od_control.od_node, &maxtemp);
  480. if (!mres)
  481.     {
  482.     node_added = TRUE;
  483.     }
  484.  
  485. }
  486.  
  487. /* max_logout() - Maximus deinitialization when TOP is exited.
  488.    Parameters:  None.
  489.    Returns:  Nothing.
  490. */
  491. void max_logout(void)
  492. {
  493.  
  494. /* Nothing is done upon logout at this time. */
  495.  
  496. }
  497.  
  498. /* max_openfiles() - Opens Maximus-related files.
  499.    Parameters:  None.
  500.    Returns:  Number of errors that occurred, or 0 on success.
  501. */
  502. XINT max_openfiles(void)
  503. {
  504. unsigned char mnam[MAXPATH]; /* IPC file name. */
  505. XINT maxres = 0; /* Error counter. */
  506.  
  507. /* The IPC file for this node is kept open while TOP is being run. */
  508. sprintf(mnam, "%sIPC%02X.BBS", cfg.bbsmultipath, od_control.od_node);
  509. ipcinfil = sh_open(mnam, O_RDWR | O_CREAT | O_BINARY, SH_DENYNONE,
  510.                    S_IREAD | S_IWRITE);
  511. maxres += (ipcinfil == -1);
  512.  
  513. return maxres;
  514. }
  515.  
  516. /* max_shortpageeditor() - Editor for short (one-line) pages.
  517.    Parameters:  nodenum - Node number being paged.
  518.                 pagebuf - Pointer to buffer to hold the page text.
  519.    Returns:  TRUE if a page was entered, FALSE if the user aborted.
  520.    Notes:  Can be used with any BBS type that needs short pages.
  521. */
  522. char max_shortpageeditor(XINT nodenum, unsigned char *pagebuf)
  523.     {
  524.  
  525.     itoa(nodenum, outnum[0], 10);
  526.     /* TOP will retry if the input was censored. */
  527.     do
  528.         {
  529.         top_output(OUT_SCREEN, getlang("EnterPageLinPrompt"), outnum[0]);
  530.         od_input_str(pagebuf, 70, ' ', MAXASCII);
  531.         top_output(OUT_SCREEN, getlang("EnterPageLinSuffix"));
  532.         }
  533.     while(censorinput(pagebuf));
  534.  
  535.     /* If nothing was entered, abort. */
  536.     if (!pagebuf[0])
  537.         {
  538.         return 0;
  539.         }
  540.  
  541.     return 1;
  542.     }
  543.  
  544. /* max_showmeccastring() - Parses MECCA tokens in a string.
  545.    Parameters:  str - String to process.
  546.    Returns:  Nothing.
  547. */
  548. void max_showmeccastring(unsigned char *str)
  549.     {
  550.     XINT d; /* Counter. */
  551.     char blink; /* Blink toggle. */
  552.  
  553.     blink = (od_control.od_cur_attrib & 0x80);
  554.  
  555.     /* Loop for each character in the string. */
  556.     for (d = 0; d < strlen(str); d++)
  557.     {
  558.     unsigned XINT cccc; /* Colour code. */
  559.  
  560.         /* Handle CRLF pair. */
  561.         if (str[d] == 0x0A && str[d + 1] != 0x0D)
  562.             {
  563.             od_putch('\r'); od_putch('\n');
  564.             continue;
  565.             }
  566.         /* Handle colour code. */
  567.         if (str[d] == 0x16 && str[d + 1] == 0x01)
  568.             {
  569.             if (str[d + 2] <= 0xF)
  570.                 {
  571.                 /* Only change the foreground colour if the background is
  572.                    black. */
  573.                 cccc = od_control.od_cur_attrib -
  574.                        (od_control.od_cur_attrib % 0xF);
  575.                 cccc += str[d + 2];
  576.                 od_set_attrib(cccc + blinking);
  577.                 d += 2;
  578.                 continue;
  579.                 }
  580.             else
  581.                 {
  582.                 /* Handle both the background and foreground. */
  583.                 if (str[d + 2] == 0x10)
  584.                     {
  585.                     cccc = str[d + 3];
  586.                     if (cccc >= 0x80)
  587.                         {
  588.                         cccc -= 0x80;
  589.                         }
  590.                     od_set_attrib(cccc + blinking);
  591.                     d += 3;
  592.                     continue;
  593.                     }
  594.                 }
  595.             }
  596.         /* Handle blink code. */
  597.         if (str[d] == 0x16 && str[d + 1] == 0x02)
  598.             {
  599.             blink = !blink;
  600.             cccc = od_control.od_cur_attrib;
  601.             if (cccc >= 0x80)
  602.                 {
  603.                 cccc -= 0x80;
  604.                 }
  605.             od_set_attrib(cccc + blinking);
  606.             }
  607.  
  608.         /* Write the next character. */
  609.         od_putch(str[d]);
  610.         }
  611.  
  612.     }
  613.