Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.1 6/24/83 (MC830713); site ttds.UUCP Path: utzoo!watmath!clyde!akgua!mcnc!decvax!mcvax!enea!ttds!johanw From: johanw@ttds.UUCP (Johan Wide'n) Newsgroups: net.sources Subject: vttest.c: vt100 compatibility test (part 2 of 2) Message-ID: <199@ttds.UUCP> Date: Mon, 30-Apr-84 20:48:41 EDT Article-I.D.: ttds.199 Posted: Mon Apr 30 20:48:41 1984 Date-Received: Fri, 4-May-84 02:14:58 EDT Organization: Royal Inst. of Techn., Stockholm Lines: 905 This program tests vt100 lookalikes for compatibility. An smaller version of this program was posted to net.sources a few month's ago. The program was developed under TOPS-10 which explains it's non-UNIX flavour. It has been run under BSD4.2 (VAX), version 7 (PDP-11) and System III (MC68000). {decvax,philabs}!mcvax!enea!ttds!johanw Johan Widen The Royal Institute of Technology Stockholm Cut at this line ---------------------------------------------------------- : This is a shar archive. Extract with sh, not csh. echo x - Makefile cat > Makefile << '!Funky!Stuff!' # Do not define SIII or XENIX if you have v7 or BSD CFLAGS= # -DSIII -DXENIX LDFLAGS= DEST=/usr/local OBJECTS=vttest.o vttest: $(OBJECTS) $(CC) $(LDFLAGS) -o vttest $(OBJECTS) install: vttest install -s vttest $(DEST) clean: rm -f $(OBJECTS) vttest vttest.c: cat vttest.c1 vttest.c2 >vttest.c !Funky!Stuff! echo x - vttest.c2 cat > vttest.c2 << '!Funky!Stuff!' tst_reports() { /* Test of: (AnswerBack Message) SM RM (Set/Reset Mode) - LineFeed / Newline DSR (Device Status Report) DA (Device Attributes) DECREQTPARM (Request Terminal Parameters) */ int parity, nbits, xspeed, rspeed, clkmul, flags; int i, reportpos; char *report, *report2; static char *attributes[][2] = { { "\033[?1;0c", "No options (vanilla VT100)" }, { "\033[?1;1c", "STP" }, { "\033[?1;2c", "AVO (could be a VT102)" }, { "\033[?1;3c", "STP and AVO" }, { "\033[?1;4c", "GPO" }, { "\033[?1;5c", "STP and GPO" }, { "\033[?1;6c", "AVO and GPO" }, { "\033[?1;7c", "STP, AVO and GPO" }, { "\033[?12;5c", "VT125" }, { "\033[?12;7c", "VT125 with AVO" }, { "\033[?7c", "VT131" }, { "\033[?5;0c", "VK100 (GIGI)" }, { "\033[?5c", "VK100 (GIGI)" }, { "", "" } }; #ifdef UNIX sgttyNew.sg_flags &= ~ECHO; stty(0, &sgttyNew); #endif ed(2); cup(5,1); println("This is a test of the ANSWERBACK MESSAGE. (To load the A.B.M."); println("see the TEST KEYBOARD part of this program). Below here, the"); println("current answerback message in your terminal should be"); println("displayed. Finish this test with RETURN."); cup(10,1); inflush(); printf("%c", 5); /* ENQ */ report = instr(); cup(10,1); chrprint(report); cup(12,1); pause1(); ed(2); cup(1,1); println("Test of LineFeed/NewLine mode."); cup(3,1); sm("20"); #ifdef UNIX sgttyNew.sg_flags &= ~CRMOD; stty(0, &sgttyNew); #endif printf("NewLine mode set. Push the RETURN key: "); report = instr(); cup(4,1); el(0); chrprint(report); if (!strcmp(report, "\015\012")) printf(" -- OK"); else printf(" -- Not expected"); cup(6,1); rm("20"); printf("NewLine mode reset. Push the RETURN key: "); report = instr(); cup(7,1); el(0); chrprint(report); if (!strcmp(report, "\015")) printf(" -- OK"); else printf(" -- Not expected"); cup(9,1); #ifdef UNIX sgttyNew.sg_flags |= CRMOD; stty(0, &sgttyNew); #endif pause1(); ed(2); cup(1,1); printf("Test of Device Status Report 5 (report terminal status)."); cup(2,1); dsr(5); report = instr(); cup(2,1); el(0); printf("Report is: "); chrprint(report); if (!strcmp(report,"\033[0n")) printf(" -- means \"TERMINAL OK\""); else if (!strcmp(report,"\033[3n")) printf(" -- means \"TERMINAL OK\""); else printf(" -- Unknown response!"); cup(4,1); println("Test of Device Status Report 6 (report cursor position)."); cup(5,1); dsr(6); report = instr(); cup(5,1); el(0); printf("Report is: "); chrprint(report); if (!strcmp(report,"\033[5;1R")) printf(" -- OK"); else printf(" -- Unknown response!"); cup(7,1); println("Test of Device Attributes report (what are you)"); cup(8,1); da(0); report = instr(); cup(8,1); el(0); printf("Report is: "); chrprint(report); for (i = 0; *attributes[i][0] != '\0'; i++) { if (!strcmp(report,attributes[i][0])) break; } if (*attributes[i][0] == '\0') printf(" -- Unknown response, refer to the manual"); else { printf(" -- means %s", attributes[i][1]); if (i) { cup(9,1); println("Legend: STP = Processor Option"); println(" AVO = Advanced Video Option"); println(" GPO = Graphics Processor Option"); } } cup(13,1); println("Test of the \"Request Terminal Parameters\" feature, argument 0."); cup(14,1); decreqtparm(0); report = instr(); cup(14,1); el(0); printf("Report is: "); chrprint(report); if (strlen(report) < 16 || report[0] != '\033' || report[1] != '[' || report[2] != '2' || report[3] != ';') println(" -- Bad format"); else { reportpos = 4; parity = scanto(report, &reportpos, ';'); nbits = scanto(report, &reportpos, ';'); xspeed = scanto(report, &reportpos, ';'); rspeed = scanto(report, &reportpos, ';'); clkmul = scanto(report, &reportpos, ';'); flags = scanto(report, &reportpos, 'x'); if (parity == 0 || nbits == 0 || clkmul == 0) println(" -- Bad format"); else println(" -- OK"); printf( "This means: Parity %s, %s bits, xmitspeed %s, recvspeed %s.\n", lookup(paritytable, parity), lookup(nbitstable, nbits), lookup(speedtable, xspeed), lookup(speedtable, rspeed)); printf("(CLoCk MULtiplier = %d, STP option flags = %d)\n", clkmul, flags); } cup(18,1); println("Test of the \"Request Terminal Parameters\" feature, argument 1."); cup(19,1); decreqtparm(1); /* Does the same as decreqtparm(0), reports "3" */ report2 = instr(); cup(19,1); el(0); printf("Report is: "); chrprint(report2); if (strlen(report2) < 3 || report2[2] != '3') println(" -- Bad format"); else { report2[2] = '2'; if (!strcmp(report,report2)) println(" -- OK"); else println(" -- Bad format"); } cup(24,1); pause1(); #ifdef UNIX sgttyNew.sg_flags |= ECHO; stty(0, &sgttyNew); #endif } tst_vt52() { static struct rtabl { char *rcode; char *rmsg; } resp1onstable[] = { { "\033/K", " -- OK (means Standard VT52)" }, { "\033/Z", " -- OK (means VT100 emulating VT52)" }, { "", " -- Unknown response"} }; int i,j; char *response; rm("?2"); /* Reset ANSI (VT100) mode, Set VT52 mode */ esc("H"); /* Cursor home */ esc("J"); /* Erase to end of screen */ esc("H"); /* Cursor home */ for (i = 0; i <= 23; i++) { for (j = 0; j <= 9; j++) printf("%s", "FooBar "); println("Bletch"); } esc("H"); /* Cursor home */ esc("J"); /* Erase to end of screen */ vt52cup(7,47); printf("nothing more."); for (i = 1; i <= 10; i++) printf("THIS SHOULD GO AWAY! "); for (i = 1; i <= 5; i++) { vt52cup(1,1); printf("%s", "Back scroll (this should go away)"); esc("I"); /* Reverse LineFeed (with backscroll!) */ } vt52cup(12,60); esc("J"); /* Erase to end of screen */ for (i = 2; i <= 6; i++) { vt52cup(i,1); esc("K"); /* Erase to end of line */ } for (i = 2; i <= 23; i++) { vt52cup(i,70); printf("%s", "**Foobar"); } vt52cup(23,10); for (i = 23; i >= 2; i--) { printf("%s", "*"); printf("%c", 8); /* BS */ esc("I"); /* Reverse LineFeed (LineStarve) */ } vt52cup(1,70); for (i = 70; i >= 10; i--) { printf("%s", "*"); esc("D"); esc("D"); /* Cursor Left */ } vt52cup(24,10); for (i = 10; i <= 70; i++) { printf("%s", "*"); printf("%c", 8); /* BS */ esc("C"); /* Cursor Right */ } vt52cup(2,11); for (i = 2; i <= 23; i++) { printf("%s", "!"); printf("%c", 8); /* BS */ esc("B"); /* Cursor Down */ } vt52cup(23,69); for (i = 23; i >= 2; i--) { printf("%s", "!"); printf("%c", 8); /* BS */ esc("A"); /* Cursor Up */ } for (i = 2; i <= 23; i++) { vt52cup(i,71); esc("K"); /* Erase to end of line */ } vt52cup(10,16); printf("%s", "The screen should be cleared, and have a centered"); vt52cup(11,16); printf("%s", "rectangle of \"*\"s with \"!\"s on the inside to the"); vt52cup(12,16); printf("%s", "left and right. Only this, and"); vt52cup(13,16); pause1(); esc("H"); /* Cursor home */ esc("J"); /* Erase to end of screen */ printf("%s", "This is the normal character set:"); for (j = 0; j <= 1; j++) { vt52cup(3 + j, 16); for (i = 0; i <= 47; i++) printf("%c", 32 + i + 48 * j); } vt52cup(6,1); printf("%s", "This is the special graphics character set:"); esc("F"); /* Select Special Graphics character set */ for (j = 0; j <= 1; j++) { vt52cup(8 + j, 16); for (i = 0; i <= 47; i++) printf("%c", 32 + i + 48 * j); } esc("G"); /* Select ASCII character set */ vt52cup(12,1); pause1(); esc("H"); /* Cursor home */ esc("J"); /* Erase to end of screen */ println("Test of terminal response to IDENTIFY command"); esc("Z"); /* Identify */ response = instr(); println(""); printf("Response was"); esc("<"); /* Enter ANSI mode (VT100 mode) */ chrprint(response); for(i = 0; resp1onstable[i].rcode[0] != '\0'; i++) if (!strcmp(response, resp1onstable[i].rcode)) break; printf("%s", resp1onstable[i].rmsg); println(""); println(""); pause1(); } tst_bugs() { /* * Test of * VT100 "Wrap around / cursor position 80" bug. * VT100 "Toggle origin mode, forget rest" bug. * VT100 "Scroll while toggling softscroll" bug. * * - RIS (Reset to Initial State) * - DECTST (invoke terminal test) */ int i, row, col; ed (2); cup (1, 1); for (col = 1; col <= 79; col++) printf ("+"); for (row = 1; row <= 24; row++) { hvp (row, 80); printf ("*"); } cup (10, 10); printf ("This illustrates the \"wrap around bug\" which exists on a"); cup (11, 10); printf ("standard VT100. On the top of the screen there should be"); cup (12, 10); printf ("a row of +'s, and the rightmost column should be filled"); cup (13, 10); printf ("with *'s. But if the bug is present, some of the *'s may"); cup (14, 10); printf ("be placed in other places, e.g. in the leftmost column,"); cup (15, 10); printf ("and the top line of +'s may be scrolled away."); cup (17, 10); printf ("Of course, a good VT100-compatible terminal should have"); cup (18, 10); printf ("means of shutting this bug off (if it has it at all!)"); cup (20, 10); pause1(); /* * VT100 "toggle origin mode, forget rest" bug. If you try to set * (or clear) parameters and one of them is the "origin mode" * ("?6") parameter, parameters that appear after the "?6" * remain unaffected. This is also true on CIT-101 terminals. */ ed (2); sm ("?5"); /* Set reverse mode */ sm ("?3"); /* Set 132 column mode */ println("Test VT100 'Toggle origin mode, forget rest' bug, part 1."); println("The screen should be in reverse, 132 column mode."); pause1(); ed (2); rm ("?6;5;3"); /* Reset (origin, reverse, 132 col) */ println("Test VT100 'Toggle origin mode, forget rest' bug, part 2.\n"); println("The screen should be in non-reverse, 80 column mode."); printf("%s", "(Otherwise, RESET the terminal). "); pause1(); /* * Test VT100 "Scroll while toggling softscroll" bug: * The cursor may disappear entirely after you type in a few carriage * returns. It may reappear, moving UP the screen. You may see * multiple copies of some lines. You must RESET the terminal to * cure the problem. */ ed (2); cup (10, 1); printf ("Next is a test of the VT100 'Scroll while toggle softscroll'\n"); printf ("bug. The cursor may disappear, or move UP the screen, or\n"); printf ("multiple copies of some lines may appear. After this test,\n"); printf ("may want to RESET the terminal.\n\n\n"); /* * Invoke the bug */ esc ("[24H"); /* Simplified cursor movement */ sm ("?4"); /* Set soft scroll */ nel (); /* "NextLine", move down */ rm ("?4"); /* Reset soft scroll */ nel (); /* "NextLine", move down */ for (i = 1; i <= 10; i++) { /* Show the bug */ printf ("Softscroll bug test, line %d. ", i); pause1(); } /* * Get rid of the bug. */ printf ("End of softscroll test. The terminal will now be RESET. "); pause1(); ris(); #ifdef SARGASSO sleep(5000); /* Wait 5.0 seconds */ #endif #ifdef UNIX sleep(5); #endif println("The terminal is now RESET. Next, the built-in confidence test"); printf("%s", "will be invoked. "); pause1(); ed(2); dectst(1); #ifdef SARGASSO sleep(5000); /* Wait 5.0 seconds */ #endif #ifdef UNIX sleep(5); #endif cup(5,1); println("If the built-in confidence test found any errors, a code"); printf("%s", "is visible above. "); pause1(); } initterminal() { #ifdef UNIX setbuf(stdout,NULL); gtty(0,&sgttyOrg); gtty(0,&sgttyNew); sgttyNew.sg_flags |= CBREAK; stty(0,&sgttyNew); #ifdef SIII close(2); open("/dev/tty",O_RDWR|O_NDELAY); #endif #endif #ifdef SARGASSO /* Set up neccesary TOPS-10 terminal parameters */ trmop(02002, 0); /* tty no tape */ trmop(02003, 1); /* tty lc */ trmop(02005, 1); /* tty tab */ trmop(02010, 1); /* tty no crlf */ trmop(02020, 0); /* tty no tape */ trmop(02021, 1); /* tty page */ trmop(02025, 0); /* tty blanks */ trmop(02026, 1); /* tty no alt */ trmop(02040, 1); /* tty defer */ trmop(02041, `VT100`); /* tty type vt100 */ #endif /* Set up my personal prejudices */ esc("<"); /* Enter ANSI mode (if in VT52 mode) */ rm("?3"); /* 80 col mode */ sm("?7"); /* Wrap around on */ rm("?8"); /* Auto repeat off */ decstbm(0,0); /* No scroll region */ } bye () { /* Force my personal prejudices upon the poor luser */ rm("?3"); /* 80 col mode */ rm("?6"); /* Absolute origin mode */ sm("?7"); /* Wrap around on */ sm("?8"); /* Auto repeat on */ decstbm(0,0); /* No scroll region */ /* Say goodbye */ ed(2); cup(12,30); printf("That's all, folks!\n"); printf("\n\n\n"); inflush(); #ifdef UNIX stty(0,&sgttyOrg); #endif exit(); } pause1 () { printf("Push "); readnl(); } readnl() { #ifdef UNIX char ch; do { read(0,&ch,1); } while(ch != '\n'); #endif #ifdef SARGASSO while (getchar() != '\n') ; #endif } scanto(str, pos, toc) char *str; int *pos; char toc; { char c; int result = 0; while (toc != (c = str[(*pos)++])) { if (isdigit(c)) result = result * 10 + c - '0'; else break; } if (c == toc) return(result); else return(0); } char *lookup(t, k) struct table t[]; int k; { int i; for (i = 0; t[i].key != -1; i++) { if (t[i].key == k) return(t[i].msg); } return("BAD VALUE"); } menu(tableptr, header) char *tableptr[]; char *header; { int i, tablesize, choice; char choicec; ed(2); cup(1, 10); printf("%s", header); tablesize = 0; for (i = 0; *tableptr[i] != '\0'; i++) { cup(4 + 2 * tablesize, 10); printf("%d. %s", i, tableptr[i]); tablesize++; } tablesize--; cup(7 + 2 * tablesize, 10); printf("Enter choice number (0 - %d): ", tablesize); for(;;) { scanf("%1s", &choicec); readnl(); choice = choicec - '0'; if (choice >= 0 && choice <= tablesize) return (choice); printf("%9sBad choice, try again: ", ""); } } chrprint (s) char *s; { int i; printf(" "); sgr("7"); printf(" "); for (i = 0; s[i] != '\0'; i++) { if (s[i] <= ' ' || s[i] == '\177') printf("<%d> ", s[i]); else printf("%c ", s[i]); } sgr(""); } #ifdef SARGASSO sleep(t) int t; { calli(072,t); /* (HIBER) t milliseconds */ } #endif inflush () { /* * Flush input buffer, make sure no pending input character */ int val; #ifdef UNIX #ifdef XENIX while(rdchk(0)) read(0,&val,1); #else #ifdef SIII while(read(2,&val,1)); #else long l1; ioctl (0, FIONREAD, &l1); while(l1-- > 0L) read(0,&val,1); #endif #endif #endif #ifdef SARGASSO while(uuo(051,2,&val)) /* TTCALL 2, (INCHRS) */ ; #endif } char inchar() { /* * Wait until a character is typed on the terminal * then read it, without waiting for CR. */ int val, waittime; #ifdef UNIX char ch; read(0,&ch,1); val = ch; #endif #ifdef SARGASSO waittime = 0; while(!uuo(051,2,&val)) { /* TTCALL 2, (INCHRS) */ sleep(100); /* Wait 0.1 seconds */ if ((waittime += ttymode) > 600) /* Time-out, in case */ return('\177'); /* of hung in ttybin(1) */ } #endif return(val); } char *instr() { /* * Get an unfinished string from the terminal: * wait until a character is typed on the terminal, * then read it, and all other available characters. * Return a pointer to that string. */ int i, val, crflag; long l1; char ch; static char result[80]; i = 0; result[i++] = inchar(); #ifdef SARGASSO sleep(100); /* Wait 0.1 seconds */ #endif #ifdef UNIX sleep(1); /* can't sleep 0.1 seconds in vanilla UNIX */ #ifdef XENIX while(rdchk(0)) { read(0,result+i,1); if (i++ == 78) break; } #else #ifdef SIII while(read(2,result+i,1) == 1) if (i++ == 78) break; #else while(ioctl(0,FIONREAD,&l1), l1 > 0L) { while(l1-- > 0L) { read(0,result+i,1); if (i++ == 78) goto out1; } } out1: #endif #endif #endif #ifdef SARGASSO while(uuo(051,2,&val)) { /* TTCALL 2, (INCHRS) */ if (!(val == '\012' && crflag)) /* TOPS-10 adds LF to CR */ result[i++] = val; crflag = val == '\015'; if (i == 79) break; sleep(50); /* Wait 0.05 seconds */ } #endif result[i] = '\0'; return(result); } ttybin(bin) int bin; { #ifdef SARGASSO #define OPEN 050 #define IO_MOD 0000017 #define _IOPIM 2 #define _IOASC 0 #define _TOPAG 01021 #define _TOSET 01000 int v; static int arglst[] = { _IOPIM, `TTY`, 0 }; arglst[0] = bin ? _IOPIM : _IOASC; v = uuo(OPEN, 1, &arglst[0]); if (!v) { printf("OPEN failed"); exit(); } trmop(_TOPAG + _TOSET, bin ? 0 : 1); ttymode = bin; #endif } trmop(fc,arg) int fc, arg; { #ifdef SARGASSO int retvalp; int arglst[3]; arglst[0] = fc; /* function code */ arglst[1] = -1; /* UDX, -1 denotes TTY: */ arglst[2] = arg; /* Optional argument */ /* TRMOP. */ if (calli(0116, 3 // &arglst[0], &retvalp)) return (retvalp); else { printf("?Error return in TRMOP."); exit(); } #endif } println(s) char *s; { printf("%s\n", s); } esc(s) char *s; { printf("%c%s", 27, s); } esc2(s1, s2) char s1, s2; { printf("%c%s%s", 27, s1, s2); } brcstr(ps, c) char *ps, c; { printf("%c[%s%c", 27, ps, c); } brc(pn,c) int pn; char c; { char *str, *ptr; ptr = str = "[ "; ptr++; if (pn > 99) { *ptr++ = (pn / 100) + 48; pn %= 100; } if (pn > 9) { *ptr++ = (pn / 10) + 48; pn %= 10; } *ptr++ = pn + 48; *ptr++ = c; *ptr = '\0'; printf("%c%s", 27, str); } brc2(pn1, pn2 ,c) int pn1, pn2; char c; { char *str, *ptr; ptr = str = "[ "; ptr++; if (pn1 > 99) { *ptr++ = (pn1 / 100) + 48; pn1 %= 100; } if (pn1 > 9) { *ptr++ = (pn1 / 10) + 48; pn1 %= 10; } *ptr++ = pn1 + 48; *ptr++ = ';'; if (pn2 > 99) { *ptr++ = (pn2 / 100) + 48; pn2 %= 100; } if (pn2 > 9) { *ptr++ = (pn2 / 10) + 48; pn2 %= 10; } *ptr++ = pn2 + 48; *ptr++ = c; *ptr = '\0'; printf("%c%s", 27, str); } cub(pn) int pn; { /* Cursor Backward */ brc(pn,'D'); } cud(pn) int pn; { /* Cursor Down */ brc(pn,'B'); } cuf(pn) int pn; { /* Cursor Forward */ brc(pn,'C'); } cup(pn1, pn2) int pn1, pn2; { /* Cursor Position */ brc2(pn1, pn2, 'H'); } cuu(pn) int pn; { /* Cursor Up */ brc(pn,'A'); } da() { /* Device Attributes */ brc(0,'c'); } decaln() { /* Screen Alignment Display */ esc("#8"); } decdhl(lower) int lower; { /* Double Height Line (also double width) */ if (lower) esc("#4"); else esc("#3"); } decdwl() { /* Double Wide Line */ esc("#6"); } deckpam() { /* Keypad Application Mode */ esc("="); } deckpnm() { /* Keypad Numeric Mode */ esc(">"); } decll(ps) char *ps; { /* Load LEDs */ brcstr(ps, 'q'); } decrc() { /* Restore Cursor */ esc("8"); } decreqtparm(pn) int pn; { /* Request Terminal Parameters */ brc(pn,'x'); } decsc() { /* Save Cursor */ esc("7");; } decstbm(pn1, pn2) int pn1, pn2; { /* Set Top and Bottom Margins */ brc2(pn1, pn2, 'r'); } decswl() { /* Single With Line */ esc("#5"); } dectst(pn) int pn; { /* Invoke Confidence Test */ brc2(2, pn, 'y'); } dsr(pn) int pn; { /* Device Status Report */ brc(pn, 'n'); } ed(pn) int pn; { /* Erase in Display */ brc(pn, 'J'); } el(pn) int pn; { /* Erase in Line */ brc(pn,'K'); } hts() { /* Horizontal Tabulation Set */ esc("H"); } hvp(pn1, pn2) int pn1, pn2; { /* Horizontal and Vertical Position */ brc2(pn1, pn2, 'f'); } ind() { /* Index */ esc("D"); } nel() { /* Next Line */ esc("E"); } ri() { /* Reverse Index */ esc("M"); } ris() { /* Reset to Initial State */ esc("c"); } rm(ps) char *ps; { /* Reset Mode */ brcstr(ps, 'l'); } scs(g,c) int g; char c; { /* Select character Set */ printf("%c%c%c%c%c%c%c", 27, g ? ')' : '(', c, 27, g ? '(' : ')', 'B', g ? 14 : 15); } sgr(ps) char *ps; { /* Select Graphic Rendition */ brcstr(ps, 'm'); } sm(ps) char *ps; { /* Set Mode */ brcstr(ps, 'h'); } tbc(pn) int pn; { /* Tabulation Clear */ brc(pn, 'g'); } vt52cup(l,c) int l,c; { printf("%cY%c%c", 27, l + 31, c + 31); } !Funky!Stuff!