1 /* NetHack 3.7	pline.c	$NHDT-Date: 1606504240 2020/11/27 19:10:40 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.100 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2018. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "hack.h"
7 
8 #define BIGBUFSZ (5 * BUFSZ) /* big enough to format a 4*BUFSZ string (from
9                               * config file parsing) with modest decoration;
10                               * result will then be truncated to BUFSZ-1 */
11 
12 static void putmesg(const char *);
13 static char *You_buf(int);
14 #if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__))
15 static void execplinehandler(const char *);
16 #endif
17 
18 #ifdef USER_SOUNDS
19 extern void maybe_play_sound(const char *);
20 #endif
21 
22 #if defined(DUMPLOG) || defined(DUMPHTML)
23 
24 /* keep the most recent DUMPLOG_MSG_COUNT messages */
25 void
dumplogmsg(const char * line)26 dumplogmsg(const char *line)
27 {
28     /*
29      * TODO:
30      *  This essentially duplicates message history, which is
31      *  currently implemented in an interface-specific manner.
32      *  The core should take responsibility for that and have
33      *  this share it.
34      */
35     unsigned indx = g.saved_pline_index; /* next slot to use */
36     char *oldest = g.saved_plines[indx]; /* current content of that slot */
37 
38     if (!strncmp(line, "Unknown command", 15))
39         return;
40     if (oldest && strlen(oldest) >= strlen(line)) {
41         /* this buffer will gradually shrink until the 'else' is needed;
42            there's no pressing need to track allocation size instead */
43         Strcpy(oldest, line);
44     } else {
45         if (oldest)
46             free((genericptr_t) oldest);
47         g.saved_plines[indx] = dupstr(line);
48     }
49     g.saved_pline_index = (indx + 1) % DUMPLOG_MSG_COUNT;
50 }
51 
52 /* called during save (unlike the interface-specific message history,
53    this data isn't saved and restored); end-of-game releases saved_plines[]
54    while writing its contents to the final dump log */
55 void
dumplogfreemessages(void)56 dumplogfreemessages(void)
57 {
58     unsigned i;
59 
60     for (i = 0; i < DUMPLOG_MSG_COUNT; ++i)
61         if (g.saved_plines[i])
62             free((genericptr_t) g.saved_plines[i]), g.saved_plines[i] = 0;
63     g.saved_pline_index = 0;
64 }
65 #endif
66 
67 /* keeps windowprocs usage out of pline() */
68 static void
putmesg(const char * line)69 putmesg(const char *line)
70 {
71     int attr = ATR_NONE;
72 
73     if ((g.pline_flags & URGENT_MESSAGE) != 0
74         && (windowprocs.wincap2 & WC2_URGENT_MESG) != 0)
75         attr |= ATR_URGENT;
76     if ((g.pline_flags & SUPPRESS_HISTORY) != 0
77         && (windowprocs.wincap2 & WC2_SUPPRESS_HIST) != 0)
78         attr |= ATR_NOHISTORY;
79 
80     putstr(WIN_MESSAGE, attr, line);
81 }
82 
83 static void vpline(const char *, va_list);
84 
85 DISABLE_WARNING_FORMAT_NONLITERAL
86 
87 void
pline(const char * line,...)88 pline(const char *line, ...)
89 {
90     va_list the_args;
91 
92     va_start(the_args, line);
93     vpline(line, the_args);
94     va_end(the_args);
95 }
96 
97 static void
vpline(const char * line,va_list the_args)98 vpline(const char *line, va_list the_args)
99 {
100     static int in_pline = 0;
101     char pbuf[BIGBUFSZ]; /* will get chopped down to BUFSZ-1 if longer */
102     int ln;
103     int msgtyp;
104 #if !defined(NO_VSNPRINTF)
105     int vlen = 0;
106 #endif
107     boolean no_repeat;
108 
109     if (!line || !*line)
110         return;
111 #ifdef HANGUPHANDLING
112     if (g.program_state.done_hup)
113         return;
114 #endif
115     if (g.program_state.wizkit_wishing)
116         return;
117 
118     if (index(line, '%')) {
119 #if !defined(NO_VSNPRINTF)
120         vlen = vsnprintf(pbuf, sizeof(pbuf), line, the_args);
121 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG)
122         if (vlen >= (int) sizeof pbuf)
123             panic("%s: truncation of buffer at %zu of %d bytes",
124                   "pline", sizeof pbuf, vlen);
125 #else
126         nhUse(vlen);
127 #endif
128 #else
129         Vsprintf(pbuf, line, the_args);
130 #endif
131         line = pbuf;
132     }
133     if ((ln = (int) strlen(line)) > BUFSZ - 1) {
134         if (line != pbuf)                          /* no '%' was present */
135             (void) strncpy(pbuf, line, BUFSZ - 1); /* caveat: unterminated */
136         /* truncate, preserving the final 3 characters:
137            "___ extremely long text" -> "___ extremely l...ext"
138            (this may be suboptimal if overflow is less than 3) */
139         memcpy(pbuf + BUFSZ - 1 - 6, "...", 3);
140         /* avoid strncpy; buffers could overlap if excess is small */
141         pbuf[BUFSZ - 1 - 3] = line[ln - 3];
142         pbuf[BUFSZ - 1 - 2] = line[ln - 2];
143         pbuf[BUFSZ - 1 - 1] = line[ln - 1];
144         pbuf[BUFSZ - 1] = '\0';
145         line = pbuf;
146     }
147     msgtyp = MSGTYP_NORMAL;
148 
149 #if defined(DUMPLOG) || defined(DUMPHTML)
150     /* We hook here early to have options-agnostic output.
151      * Unfortunately, that means Norep() isn't honored (general issue) and
152      * that short lines aren't combined into one longer one (tty behavior).
153      */
154     if ((g.pline_flags & SUPPRESS_HISTORY) == 0)
155         dumplogmsg(line);
156 #endif
157     FUZLOG(line);
158     /* use raw_print() if we're called too early (or perhaps too late
159        during shutdown) or if we're being called recursively (probably
160        via debugpline() in the interface code) */
161     if (in_pline++ || !iflags.window_inited) {
162         /* [we should probably be using raw_printf("\n%s", line) here] */
163         raw_print(line);
164         iflags.last_msg = PLNMSG_UNKNOWN;
165         goto pline_done;
166     }
167 
168     no_repeat = (g.pline_flags & PLINE_NOREPEAT) ? TRUE : FALSE;
169     if ((g.pline_flags & OVERRIDE_MSGTYPE) == 0) {
170         msgtyp = msgtype_type(line, no_repeat);
171 #ifdef USER_SOUNDS
172         if (msgtyp == MSGTYP_NORMAL || msgtyp == MSGTYP_NOSHOW)
173             maybe_play_sound(line);
174 #endif
175         if ((g.pline_flags & URGENT_MESSAGE) == 0
176             && (msgtyp == MSGTYP_NOSHOW
177                 || (msgtyp == MSGTYP_NOREP && !strcmp(line, g.prevmsg))))
178             /* FIXME: we need a way to tell our caller that this message
179              * was suppressed so that caller doesn't set iflags.last_msg
180              * for something that hasn't been shown, otherwise a subsequent
181              * message which uses alternate wording based on that would be
182              * doing so out of context and probably end up seeming silly.
183              * (Not an issue for no-repeat but matters for no-show.)
184              */
185             goto pline_done;
186     }
187 
188     if (g.vision_full_recalc)
189         vision_recalc(0);
190     if (u.ux)
191         flush_screen(1); /* %% */
192 
193     putmesg(line);
194 
195 #if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__))
196     execplinehandler(line);
197 #endif
198 
199     /* this gets cleared after every pline message */
200     iflags.last_msg = PLNMSG_UNKNOWN;
201     (void) strncpy(g.prevmsg, line, BUFSZ), g.prevmsg[BUFSZ - 1] = '\0';
202     switch (msgtyp) {
203     case MSGTYP_ALERT:
204         iflags.msg_is_alert = TRUE; /* <TAB> */
205         /* FALLTHRU */
206     case MSGTYP_STOP:
207         display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */
208         break;
209     }
210     iflags.msg_is_alert = FALSE;
211 
212 pline_done:
213     --in_pline;
214 }
215 
216 RESTORE_WARNING_FORMAT_NONLITERAL
217 
218 /* pline() variant which can override MSGTYPE handling or suppress
219    message history (tty interface uses pline() to issue prompts and
220    they shouldn't be blockable via MSGTYPE=hide) */
221 void
custompline(unsigned pflags,const char * line,...)222 custompline(unsigned pflags, const char * line, ...)
223 {
224     va_list the_args;
225 
226     va_start(the_args, line);
227     g.pline_flags = pflags;
228     vpline(line, the_args);
229     g.pline_flags = 0;
230     va_end(the_args);
231 }
232 
233 void
Norep(const char * line,...)234 Norep(const char *line, ...)
235 {
236     va_list the_args;
237 
238     va_start(the_args, line);
239     g.pline_flags = PLINE_NOREPEAT;
240     vpline(line, the_args);
241     g.pline_flags = 0;
242     va_end(the_args);
243 }
244 
245 static char *
You_buf(int siz)246 You_buf(int siz)
247 {
248     if (siz > g.you_buf_siz) {
249         if (g.you_buf)
250             free((genericptr_t) g.you_buf);
251         g.you_buf_siz = siz + 10;
252         g.you_buf = (char *) alloc((unsigned) g.you_buf_siz);
253     }
254     return g.you_buf;
255 }
256 
257 void
free_youbuf(void)258 free_youbuf(void)
259 {
260     if (g.you_buf)
261         free((genericptr_t) g.you_buf), g.you_buf = (char *) 0;
262     g.you_buf_siz = 0;
263 }
264 
265 /* `prefix' must be a string literal, not a pointer */
266 #define YouPrefix(pointer, prefix, text) \
267     Strcpy((pointer = You_buf((int) (strlen(text) + sizeof prefix))), prefix)
268 
269 #define YouMessage(pointer, prefix, text) \
270     strcat((YouPrefix(pointer, prefix, text), pointer), text)
271 
272 void
You(const char * line,...)273 You(const char *line, ...)
274 {
275     va_list the_args;
276     char *tmp;
277 
278     va_start(the_args, line);
279     vpline(YouMessage(tmp, "You ", line), the_args);
280     va_end(the_args);
281 }
282 
283 void
Your(const char * line,...)284 Your(const char *line, ...)
285 {
286     va_list the_args;
287     char *tmp;
288 
289     va_start(the_args, line);
290     vpline(YouMessage(tmp, "Your ", line), the_args);
291     va_end(the_args);
292 }
293 
294 void
You_feel(const char * line,...)295 You_feel(const char *line, ...)
296 {
297     va_list the_args;
298     char *tmp;
299 
300     va_start(the_args, line);
301     if (Unaware)
302         YouPrefix(tmp, "You dream that you feel ", line);
303     else
304         YouPrefix(tmp, "You feel ", line);
305     vpline(strcat(tmp, line), the_args);
306     va_end(the_args);
307 }
308 
309 void
You_cant(const char * line,...)310 You_cant(const char *line, ...)
311 {
312     va_list the_args;
313     char *tmp;
314 
315     va_start(the_args, line);
316     vpline(YouMessage(tmp, "You can't ", line), the_args);
317     va_end(the_args);
318 }
319 
320 void
pline_The(const char * line,...)321 pline_The(const char *line, ...)
322 {
323     va_list the_args;
324     char *tmp;
325 
326     va_start(the_args, line);
327     vpline(YouMessage(tmp, "The ", line), the_args);
328     va_end(the_args);
329 }
330 
331 void
There(const char * line,...)332 There(const char *line, ...)
333 {
334     va_list the_args;
335     char *tmp;
336 
337     va_start(the_args, line);
338     vpline(YouMessage(tmp, "There ", line), the_args);
339     va_end(the_args);
340 }
341 
342 void
You_hear(const char * line,...)343 You_hear(const char *line, ...)
344 {
345     va_list the_args;
346     char *tmp;
347 
348     if (Deaf || !flags.acoustics)
349         return;
350     va_start(the_args, line);
351     if (Underwater)
352         YouPrefix(tmp, "You barely hear ", line);
353     else if (Unaware)
354         YouPrefix(tmp, "You dream that you hear ", line);
355     else
356         YouPrefix(tmp, "You hear ", line);  /* Deaf-aware */
357     vpline(strcat(tmp, line), the_args);
358     va_end(the_args);
359 }
360 
361 void
You_see(const char * line,...)362 You_see(const char *line, ...)
363 {
364     va_list the_args;
365     char *tmp;
366 
367     va_start(the_args, line);
368     if (Unaware)
369         YouPrefix(tmp, "You dream that you see ", line);
370     else if (Blind) /* caller should have caught this... */
371         YouPrefix(tmp, "You sense ", line);
372     else
373         YouPrefix(tmp, "You see ", line);
374     vpline(strcat(tmp, line), the_args);
375     va_end(the_args);
376 }
377 
378 /* Print a message inside double-quotes.
379  * The caller is responsible for checking deafness.
380  * Gods can speak directly to you in spite of deafness.
381  */
382 void
verbalize(const char * line,...)383 verbalize(const char *line, ...)
384 {
385     va_list the_args;
386     char *tmp;
387 
388     va_start(the_args, line);
389     tmp = You_buf((int) strlen(line) + sizeof "\"\"");
390     Strcpy(tmp, "\"");
391     Strcat(tmp, line);
392     Strcat(tmp, "\"");
393     vpline(tmp, the_args);
394     va_end(the_args);
395 }
396 
397 static void vraw_printf(const char *, va_list);
398 
399 void
raw_printf(const char * line,...)400 raw_printf(const char *line, ...)
401 {
402     va_list the_args;
403 
404     va_start(the_args, line);
405     vraw_printf(line, the_args);
406     va_end(the_args);
407 }
408 
409 DISABLE_WARNING_FORMAT_NONLITERAL
410 
411 static void
vraw_printf(const char * line,va_list the_args)412 vraw_printf(const char *line, va_list the_args)
413 {
414     char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
415 
416     if (index(line, '%')) {
417 #if !defined(NO_VSNPRINTF)
418         (void) vsnprintf(pbuf, sizeof(pbuf), line, the_args);
419 #else
420         Vsprintf(pbuf, line, the_args);
421 #endif
422         line = pbuf;
423     }
424     if ((int) strlen(line) > BUFSZ - 1) {
425         if (line != pbuf)
426             line = strncpy(pbuf, line, BUFSZ - 1);
427         /* unlike pline, we don't futz around to keep last few chars */
428         pbuf[BUFSZ - 1] = '\0'; /* terminate strncpy or truncate vsprintf */
429     }
430     raw_print(line);
431 #if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__))
432     execplinehandler(line);
433 #endif
434 }
435 
436 void
impossible(const char * s,...)437 impossible(const char *s, ...)
438 {
439     va_list the_args;
440     char pbuf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
441 
442     va_start(the_args, s);
443     if (g.program_state.in_impossible)
444         panic("impossible called impossible");
445 
446     g.program_state.in_impossible = 1;
447 #if !defined(NO_VSNPRINTF)
448     (void) vsnprintf(pbuf, sizeof(pbuf), s, the_args);
449 #else
450     Vsprintf(pbuf, s, the_args);
451 #endif
452     va_end(the_args);
453     pbuf[BUFSZ - 1] = '\0'; /* sanity */
454     paniclog("impossible", pbuf);
455     if (iflags.debug_fuzzer)
456         panic("%s", pbuf);
457     pline("%s", pbuf);
458     /* reuse pbuf[] */
459     Strcpy(pbuf, "Program in disorder!");
460     if (g.program_state.something_worth_saving)
461         Strcat(pbuf, "  (Saving and reloading may fix this problem.)");
462     pline("%s", pbuf);
463     pline("Please report these messages to %s.", DEVTEAM_EMAIL);
464     if (sysopt.support) {
465         pline("Alternatively, contact local support: %s", sysopt.support);
466     }
467 
468     g.program_state.in_impossible = 0;
469 }
470 
471 RESTORE_WARNING_FORMAT_NONLITERAL
472 
473 #if defined(MSGHANDLER) && (defined(POSIX_TYPES) || defined(__GNUC__))
474 static boolean use_pline_handler = TRUE;
475 
476 static void
execplinehandler(const char * line)477 execplinehandler(const char *line)
478 {
479     int f;
480     const char *args[3];
481     char *env;
482 
483     if (!use_pline_handler)
484         return;
485 
486     if (!(env = nh_getenv("NETHACK_MSGHANDLER"))) {
487         use_pline_handler = FALSE;
488         return;
489     }
490 
491     f = fork();
492     if (f == 0) { /* child */
493         args[0] = env;
494         args[1] = line;
495         args[2] = NULL;
496         (void) setgid(getgid());
497         (void) setuid(getuid());
498         (void) execv(args[0], (char *const *) args);
499         perror((char *) 0);
500         (void) fprintf(stderr, "Exec to message handler %s failed.\n", env);
501         nh_terminate(EXIT_FAILURE);
502     } else if (f > 0) {
503         int status;
504 
505         waitpid(f, &status, 0);
506     } else if (f == -1) {
507         perror((char *) 0);
508         use_pline_handler = FALSE;
509         pline("%s", "Fork to message handler failed.");
510     }
511 }
512 #endif /* MSGHANDLER && (POSIX_TYPES || __GNUC__) */
513 
514 /*
515  * varargs handling for files.c
516  */
517 static void vconfig_error_add(const char *, va_list);
518 
519 DISABLE_WARNING_FORMAT_NONLITERAL
520 
521 void
config_error_add(const char * str,...)522 config_error_add(const char *str, ...)
523 {
524     va_list the_args;
525 
526     va_start(the_args, str);
527     vconfig_error_add(str, the_args);
528     va_end(the_args);
529 }
530 
531 static void
vconfig_error_add(const char * str,va_list the_args)532 vconfig_error_add(const char *str, va_list the_args)
533 {       /* start of vconf...() or of nested block in USE_OLDARG's conf...() */
534 #if !defined(NO_VSNPRINTF)
535     int vlen = 0;
536 #endif
537     char buf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
538 
539 #if !defined(NO_VSNPRINTF)
540     vlen = vsnprintf(buf, sizeof(buf), str, the_args);
541 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG)
542     if (vlen >= (int) sizeof buf)
543         panic("%s: truncation of buffer at %zu of %d bytes",
544               "config_error_add", sizeof buf, vlen);
545 #else
546     nhUse(vlen);
547 #endif
548 #else
549     Vsprintf(buf, str, the_args);
550 #endif
551     buf[BUFSZ - 1] = '\0';
552     config_erradd(buf);
553 }
554 
555 RESTORE_WARNING_FORMAT_NONLITERAL
556 
557 /* nhassert_failed is called when an nhassert's condition is false */
558 void
nhassert_failed(const char * expression,const char * filepath,int line)559 nhassert_failed(const char *expression, const char *filepath, int line)
560 {
561     const char * filename;
562 
563     /* attempt to get filename from path.  TODO: we really need a port provided
564      * function to return a filename from a path */
565     filename = strrchr(filepath, '/');
566     filename = (filename == NULL ? strrchr(filepath, '\\') : filename);
567     filename = (filename == NULL ? filepath : filename + 1);
568 
569     impossible("nhassert(%s) failed in file '%s' at line %d", expression, filename, line);
570 }
571 
572 /*pline.c*/
573