1 // On-screen Display (ie. console)
2 // for the Build Engine
3 // by Jonathon Fowler (jf@jonof.id.au)
4
5 #include "build.h"
6 #include "cache1d.h"
7 #include "crc32.h"
8 #include "editor.h"
9 #include "osd.h"
10 #include "scancodes.h"
11
12 #define XXH_STATIC_LINKING_ONLY
13 #include "xxhash.h"
14
15 #include "vfs.h"
16
17 static osdsymbol_t *osd_addsymbol(const char *name);
18 static osdsymbol_t *osd_findsymbol(const char *pszName, osdsymbol_t *pSymbol);
19 static osdsymbol_t *osd_findexactsymbol(const char *pszName);
20
21 static int32_t whiteColorIdx=-1; // colour of white (used by default display routines)
22 static void _internal_drawosdchar(int, int, char, int, int);
23 static void _internal_drawosdstr(int, int, const char *, int, int, int);
24 static void _internal_drawosdcursor(int32_t,int32_t,int32_t,int32_t);
25 static int32_t _internal_getcolumnwidth(int32_t);
26 static int32_t _internal_getrowheight(int32_t);
27 static void _internal_clearbackground(int32_t,int32_t);
28 static int32_t _internal_gettime(void);
29 static void _internal_onshowosd(int32_t);
30
31 osdmain_t *osd;
32 GrowArray<char *> osdstrings;
33
34 static int osdrowscur = -1;
35 static int osdmaxrows = MAXYDIM >> 3;
36 static int osdrows;
37
38 buildvfs_FILE osdlog;
39
40 const char *osdlogfn;
41
42 static uint32_t osdkeytime = 0;
43 static uint32_t osdscrtime = 0;
44
45 #define OSD_EDIT_LINE_WIDTH (osd->draw.cols - 1 - 3)
46 #define OSDMAXERRORS 4096
47
48 static hashtable_t h_osd = { OSDMAXSYMBOLS >> 1, NULL };
49
50 // Application callbacks: these are the currently effective ones.
51 static void (*drawosdchar)(int32_t, int32_t, char, int32_t, int32_t) = _internal_drawosdchar;
52 static void (*drawosdstr)(int32_t, int32_t, const char *, int32_t, int32_t, int32_t) = _internal_drawosdstr;
53 static void (*drawosdcursor)(int32_t, int32_t, int32_t, int32_t) = _internal_drawosdcursor;
54 static int32_t (*getcolumnwidth)(int32_t) = _internal_getcolumnwidth;
55 static int32_t (*getrowheight)(int32_t) = _internal_getrowheight;
56
57 static void (*clearbackground)(int32_t,int32_t) = _internal_clearbackground;
58 static int32_t (*gettime)(void) = _internal_gettime;
59 static void (*onshowosd)(int32_t) = _internal_onshowosd;
60
61 // Application callbacks: these are the backed-up ones.
62 static void (*_drawosdchar)(int32_t, int32_t, char, int32_t, int32_t) = _internal_drawosdchar;
63 static void (*_drawosdstr)(int32_t, int32_t, const char *, int32_t, int32_t, int32_t) = _internal_drawosdstr;
64 static void (*_drawosdcursor)(int32_t, int32_t, int32_t, int32_t) = _internal_drawosdcursor;
65 static int32_t (*_getcolumnwidth)(int32_t) = _internal_getcolumnwidth;
66 static int32_t (*_getrowheight)(int32_t) = _internal_getrowheight;
67
68 static hashtable_t h_cvars = { OSDMAXSYMBOLS >> 1, NULL };
69 bool m32_osd_tryscript = false; // whether to try executing m32script on unkown command in the osd
70
71 static int osdfunc_printvar(int const cvaridx);
72
OSD_RegisterCvar(osdcvardata_t * const cvar,int (* func)(osdcmdptr_t))73 void OSD_RegisterCvar(osdcvardata_t * const cvar, int (*func)(osdcmdptr_t))
74 {
75 if (!osd)
76 OSD_Init();
77
78 osd->cvars = (osdcvar_t *)Xrealloc(osd->cvars, (osd->numcvars + 1) * sizeof(osdcvar_t));
79
80 hash_add(&h_cvars, cvar->name, osd->numcvars, 1);
81
82 switch (cvar->flags & CVAR_TYPEMASK)
83 {
84 case CVAR_FLOAT:
85 #if defined __POWERPC__ || defined GEKKO
86 osd->cvars[osd->numcvars].defaultValue.f = *cvar->f;
87 break;
88 #endif
89 case CVAR_BOOL:
90 case CVAR_INT:
91 case CVAR_UINT:
92 osd->cvars[osd->numcvars].defaultValue.u32 = *cvar->u32;
93 break;
94 case CVAR_DOUBLE:
95 osd->cvars[osd->numcvars].defaultValue.d = *cvar->d;
96 break;
97 }
98
99 osd->cvars[osd->numcvars++].pData = cvar;
100
101 OSD_RegisterFunction(cvar->name, cvar->desc, func);
102 }
103
OSD_CvarModified(const osdcvar_t * const pCvar)104 static int OSD_CvarModified(const osdcvar_t * const pCvar)
105 {
106 if (!osd || !pCvar->pData->ptr)
107 return 0;
108
109 int rv = 0;
110
111 switch (pCvar->pData->flags & CVAR_TYPEMASK)
112 {
113 case CVAR_FLOAT:
114 #if defined __POWERPC__ || defined GEKKO
115 rv = (pCvar->defaultValue.f != *pCvar->pData->f); break;
116 #endif
117 case CVAR_BOOL:
118 case CVAR_INT:
119 case CVAR_UINT:
120 rv = (pCvar->defaultValue.u32 != *pCvar->pData->u32); break;
121 case CVAR_DOUBLE:
122 rv = (pCvar->defaultValue.d != *pCvar->pData->d); break;
123 case CVAR_STRING:
124 rv = 1; break;
125 default:
126 EDUKE32_UNREACHABLE_SECTION(break);
127 }
128
129 return rv || ((pCvar->pData->flags & CVAR_MODIFIED) == CVAR_MODIFIED);
130 }
131
132 // color code format is as follows:
133 // ^## sets a color, where ## is the palette number
134 // ^S# sets a shade, range is 0-7 equiv to shades 0-14
135 // ^O resets formatting to defaults
136
OSD_StripColors(char * outBuf,const char * inBuf)137 const char * OSD_StripColors(char *outBuf, const char *inBuf)
138 {
139 const char *ptr = outBuf;
140
141 while (*inBuf)
142 {
143 if (*inBuf == '^')
144 {
145 if (isdigit(*(inBuf+1)))
146 {
147 inBuf += 2 + !!isdigit(*(inBuf+2));
148 continue;
149 }
150 else if ((Btoupper(*(inBuf+1)) == 'O'))
151 {
152 inBuf += 2;
153 continue;
154 }
155 else if ((Btoupper(*(inBuf+1)) == 'S') && isdigit(*(inBuf+2)))
156 {
157 inBuf += 3;
158 continue;
159 }
160 }
161 *(outBuf++) = *(inBuf++);
162 }
163
164 *outBuf = '\0';
165 return ptr;
166 }
167
OSD_Exec(const char * szScript)168 int OSD_Exec(const char *szScript)
169 {
170 int err = 0;
171 int32_t len = 0;
172 buildvfs_kfd handle;
173
174 if ((handle = kopen4load(szScript, 0)) == buildvfs_kfd_invalid)
175 err = 1;
176 else if ((len = kfilelength(handle)) <= 0)
177 err = 2; // blank file
178
179 if (!err)
180 OSD_Printf("Executing \"%s\"\n", szScript);
181
182 auto buf = (char *) Xmalloc(len + 1);
183
184 if (err || kread(handle, buf, len) != len)
185 {
186 if (!err) // no error message for blank file
187 OSD_Printf("Error executing \"%s\"!\n", szScript);
188
189 if (handle != buildvfs_kfd_invalid)
190 kclose(handle);
191
192 Xfree(buf);
193 return 1;
194 }
195
196 kclose(handle);
197 buf[len] = '\0';
198
199 char const *cp = strtok(buf, "\r\n");
200
201 ++osd->execdepth;
202 while (cp != NULL)
203 {
204 OSD_Dispatch(cp);
205 cp = strtok(NULL, "\r\n");
206 }
207 --osd->execdepth;
208
209 Xfree(buf);
210 return 0;
211 }
212
OSD_ParsingScript(void)213 int OSD_ParsingScript(void) { return osd->execdepth; }
OSD_OSDKey(void)214 int OSD_OSDKey(void) { return osd->keycode; }
OSD_GetCols(void)215 int OSD_GetCols(void) { return osd->draw.cols; }
OSD_IsMoving(void)216 int OSD_IsMoving(void) { return (osdrowscur != -1 && osdrowscur != osd->draw.rows); }
OSD_GetRowsCur(void)217 int OSD_GetRowsCur(void) { return osdrowscur; }
OSD_GetTextMode(void)218 int OSD_GetTextMode(void) { return osd->draw.mode; }
219
OSD_GetShadePal(const char * ch,int * shd,int * pal)220 void OSD_GetShadePal(const char *ch, int *shd, int *pal)
221 {
222 auto &t = osd->text;
223
224 if (ch < t.buf || ch >= t.buf + OSDBUFFERSIZE)
225 return;
226
227 *shd = (t.fmt[ch-t.buf] & ~0x1F)>>4;
228 *pal = (t.fmt[ch-t.buf] & ~0xE0);
229 }
230
231 // XXX: well, converting function pointers to "data pointers" (void *) is
232 // undefined behavior. See
233 // http://blog.frama-c.com/index.php?post/2013/08/24/Function-pointers-in-C
234 // Then again, my GCC just crashed (any kept on crashing until after a reboot!)
235 // when I tried to rewrite this into something different.
236
swaposdptrs(void)237 static inline void swaposdptrs(void)
238 {
239 swapptr(&_drawosdchar, &drawosdchar);
240 swapptr(&_drawosdstr, &drawosdstr);
241 swapptr(&_drawosdcursor, &drawosdcursor);
242 swapptr(&_getcolumnwidth, &getcolumnwidth);
243 swapptr(&_getrowheight, &getrowheight);
244 }
245
OSD_SetTextMode(int mode)246 void OSD_SetTextMode(int mode)
247 {
248 osd->draw.mode = (mode != 0);
249
250 if ((osd->draw.mode && drawosdchar != _internal_drawosdchar) ||
251 (!osd->draw.mode && drawosdchar == _internal_drawosdchar))
252 swaposdptrs();
253
254 if (in3dmode())
255 OSD_ResizeDisplay(xdim, ydim);
256 }
257
osdfunc_exec(osdcmdptr_t parm)258 static int osdfunc_exec(osdcmdptr_t parm)
259 {
260 if (parm->numparms != 1)
261 return OSDCMD_SHOWHELP;
262
263 if (OSD_Exec(parm->parms[0]))
264 OSD_Printf("%sexec: file \"%s\" not found.\n", osd->draw.errorfmt, parm->parms[0]);
265
266 return OSDCMD_OK;
267 }
268
osdfunc_echo(osdcmdptr_t parm)269 static int osdfunc_echo(osdcmdptr_t parm)
270 {
271 OSD_Printf("%s\n", parm->raw + 5);
272
273 return OSDCMD_OK;
274 }
275
osdfunc_fileinfo(osdcmdptr_t parm)276 static int osdfunc_fileinfo(osdcmdptr_t parm)
277 {
278 if (parm->numparms != 1) return OSDCMD_SHOWHELP;
279
280 buildvfs_kfd h;
281
282 if ((h = kopen4load(parm->parms[0],0)) == buildvfs_kfd_invalid)
283 {
284 OSD_Printf("fileinfo: File \"%s\" not found.\n", parm->parms[0]);
285 return OSDCMD_OK;
286 }
287
288 double crctime = timerGetHiTicks();
289 uint32_t crcval = 0;
290 int32_t siz = 0;
291
292 static constexpr int ReadSize = 65536;
293 auto buf = (uint8_t *)Xmalloc(ReadSize);
294
295 do
296 {
297 siz = kread(h, buf, ReadSize);
298 crcval = Bcrc32((uint8_t *)buf, siz, crcval);
299 }
300 while (siz == ReadSize);
301
302 crctime = timerGetHiTicks() - crctime;
303
304 klseek(h, 0, BSEEK_SET);
305
306 double xxhtime = timerGetHiTicks();
307
308 XXH32_state_t xxh;
309 XXH32_reset(&xxh, 0x1337);
310
311 do
312 {
313 siz = kread(h, buf, ReadSize);
314 XXH32_update(&xxh, (uint8_t *)buf, siz);
315 }
316 while (siz == ReadSize);
317
318 uint32_t const xxhash = XXH32_digest(&xxh);
319 xxhtime = timerGetHiTicks() - xxhtime;
320
321 Xfree(buf);
322
323 OSD_Printf("fileinfo: %s\n"
324 " File size: %d bytes\n"
325 " CRC-32: %08X (%.1fms)\n"
326 " xxHash: %08X (%.1fms)\n",
327 parm->parms[0], kfilelength(h),
328 crcval, crctime,
329 xxhash, xxhtime);
330
331 kclose(h);
332
333 return OSDCMD_OK;
334 }
335
_internal_drawosdchar(int x,int y,char ch,int shade,int pal)336 static void _internal_drawosdchar(int x, int y, char ch, int shade, int pal)
337 {
338 UNREFERENCED_PARAMETER(shade);
339 UNREFERENCED_PARAMETER(pal);
340
341 char st[2] = { ch, 0 };
342
343 printext256(4+(x<<3),4+(y<<3), whiteColorIdx, -1, st, 0);
344 }
345
_internal_drawosdstr(int x,int y,const char * ch,int len,int shade,int pal)346 static void _internal_drawosdstr(int x, int y, const char *ch, int len, int shade, int pal)
347 {
348 char st[1024];
349
350 UNREFERENCED_PARAMETER(shade);
351
352 if (len>1023) len=1023;
353 Bmemcpy(st,ch,len);
354 st[len]=0;
355
356 OSD_GetShadePal(ch, &shade, &pal);
357
358 {
359 int32_t colidx = whiteColorIdx >= 0 ? palookup[(uint8_t)pal][whiteColorIdx] : whiteColorIdx;
360 printext256(4+(x<<3),4+(y<<3), colidx, -1, st, 0);
361 }
362 }
363
_internal_drawosdcursor(int x,int y,int flags,int lastkeypress)364 static void _internal_drawosdcursor(int x, int y, int flags, int lastkeypress)
365 {
366 char st[2] = { '_',0 };
367
368 UNREFERENCED_PARAMETER(lastkeypress);
369
370 if (flags) st[0] = '#';
371
372 if (whiteColorIdx > -1)
373 {
374 printext256(4+(x<<3),4+(y<<3)+2, whiteColorIdx, -1, st, 0);
375 return;
376 }
377
378 // Find the palette index closest to Duke3D's brightest blue
379 // "foreground" color. (Index 79, or the last column of the 5th row,
380 // if the palette is laid out in a 16x16 pattern.)
381 for (int i=0, k=UINT8_MAX+1; i<256; i++)
382 {
383 int const j =
384 klabs(curpalette[i].r - 4*47) +
385 klabs(curpalette[i].g - 4*55) +
386 klabs(curpalette[i].b - 4*63);
387 if (j < k) { k = j; whiteColorIdx = i; }
388 }
389 }
390
_internal_getcolumnwidth(int w)391 static int _internal_getcolumnwidth(int w)
392 {
393 return w/8 - 1;
394 }
395
_internal_getrowheight(int w)396 static int _internal_getrowheight(int w)
397 {
398 return w/8;
399 }
400
_internal_clearbackground(int cols,int rows)401 static void _internal_clearbackground(int cols, int rows)
402 {
403 UNREFERENCED_PARAMETER(cols);
404 UNREFERENCED_PARAMETER(rows);
405 }
406
_internal_gettime(void)407 static int32_t _internal_gettime(void)
408 {
409 return 0;
410 }
411
_internal_onshowosd(int a)412 static void _internal_onshowosd(int a)
413 {
414 UNREFERENCED_PARAMETER(a);
415 }
416
osd_clear(int clearstrings=false)417 static void osd_clear(int clearstrings = false)
418 {
419 Bmemset(osd->text.buf, asc_Space, OSDBUFFERSIZE);
420 Bmemset(osd->text.fmt, osd->draw.textpal + (osd->draw.textshade << 5), OSDBUFFERSIZE);
421 osd->text.lines = 1;
422
423 if (clearstrings)
424 osdstrings.clear();
425 }
426
427 ////////////////////////////
428
osdfunc_alias(osdcmdptr_t parm)429 static int osdfunc_alias(osdcmdptr_t parm)
430 {
431 if (parm->numparms < 1)
432 {
433 int cnt = 0;
434
435 OSD_Printf("Alias listing:\n");
436
437 for (auto &symb : osd->symbptrs)
438 {
439 if (symb == NULL)
440 break;
441 else if (symb->func == OSD_ALIAS)
442 {
443 cnt++;
444 OSD_Printf(" %s \"%s\"\n", symb->name, symb->help);
445 }
446 }
447
448 if (cnt == 0)
449 OSD_Printf("No aliases found.\n");
450
451 return OSDCMD_OK;
452 }
453
454 for (auto &symb : osd->symbptrs)
455 {
456 if (symb == NULL)
457 break;
458 else if (!Bstrcasecmp(parm->parms[0], symb->name))
459 {
460 if (parm->numparms < 2)
461 {
462 if (symb->func == OSD_ALIAS)
463 OSD_Printf("alias %s \"%s\"\n", symb->name, symb->help);
464 else
465 OSD_Printf("%s is not an alias\n", symb->name);
466
467 return OSDCMD_OK;
468 }
469 else if (symb->func != OSD_ALIAS && symb->func != OSD_UNALIASED)
470 {
471 OSD_Printf("Cannot override a function or cvar with an alias\n");
472
473 return OSDCMD_OK;
474 }
475 }
476 }
477
478 OSD_RegisterFunction(Xstrdup(parm->parms[0]), Xstrdup(parm->parms[1]), OSD_ALIAS);
479
480 if (!osd->execdepth)
481 OSD_Printf("%s\n", parm->raw);
482
483 return OSDCMD_OK;
484 }
485
osdfunc_unalias(osdcmdptr_t parm)486 static int osdfunc_unalias(osdcmdptr_t parm)
487 {
488 if (parm->numparms < 1)
489 return OSDCMD_SHOWHELP;
490
491 for (auto symb=osd->symbols; symb!=NULL; symb=symb->next)
492 {
493 if (!Bstrcasecmp(parm->parms[0], symb->name))
494 {
495 if (parm->numparms < 2)
496 {
497 if (symb->func == OSD_ALIAS)
498 {
499 OSD_Printf("Removed alias %s (\"%s\")\n", symb->name, symb->help);
500 symb->func = OSD_UNALIASED;
501 }
502 else
503 OSD_Printf("Invalid alias %s\n", symb->name);
504
505 return OSDCMD_OK;
506 }
507 }
508 }
509
510 OSD_Printf("Invalid alias %s\n", parm->parms[0]);
511 return OSDCMD_OK;
512 }
513
osdfunc_listsymbols(osdcmdptr_t parm)514 static int osdfunc_listsymbols(osdcmdptr_t parm)
515 {
516 if (parm->numparms > 1)
517 return OSDCMD_SHOWHELP;
518
519 int maxwidth = 0;
520
521 for (auto symb=osd->symbols; symb!=NULL; symb=symb->next)
522 if (symb->func != OSD_UNALIASED && symb->help != NULL)
523 maxwidth = max<int>(maxwidth, Bstrlen(symb->name));
524
525 if (maxwidth > 0)
526 {
527 int width = 0;
528 int count = 0;
529
530 maxwidth += 3;
531
532 if (parm->numparms > 0)
533 OSD_Printf("%sSymbol listing for %s:\n", osd->draw.highlight, parm->parms[0]);
534 else
535 OSD_Printf("%sSymbol listing:\n", osd->draw.highlight);
536
537 int const parmlen = parm->numparms ? Bstrlen(parm->parms[0]) : 0;
538
539 for (auto symb=osd->symbols; symb!=NULL; symb=symb->next)
540 {
541 if (symb->func == OSD_UNALIASED || symb->help == NULL || (parm->numparms == 1 && Bstrncmp(parm->parms[0], symb->name, parmlen)))
542 continue;
543
544 int const var = hash_find(&h_cvars, symb->name);
545
546 if ((unsigned)var < OSDMAXSYMBOLS && OSD_CvarModified(&osd->cvars[var]))
547 {
548 OSD_Printf("%s*", osd->draw.highlight);
549 OSD_Printf("%-*s", maxwidth-1, symb->name);
550 }
551 else
552 OSD_Printf("%-*s", maxwidth, symb->name);
553
554 width += maxwidth;
555 count++;
556
557 if (width > osd->draw.cols - maxwidth)
558 {
559 width = 0;
560 OSD_Printf("\n");
561 }
562 }
563
564 if (width)
565 OSD_Printf("\n");
566
567 OSD_Printf("%sFound %d symbols\n", osd->draw.highlight, count);
568 }
569 return OSDCMD_OK;
570 }
571
osdfunc_help(osdcmdptr_t parm)572 static int osdfunc_help(osdcmdptr_t parm)
573 {
574 if (parm->numparms != 1)
575 return OSDCMD_SHOWHELP;
576
577 auto symb = osd_findexactsymbol(parm->parms[0]);
578
579 if (!symb)
580 OSD_Printf("Error: no help for undefined symbol \"%s\"\n", parm->parms[0]);
581 else
582 {
583 int const cvaridx = hash_findcase(&h_cvars, symb->name);
584 if (cvaridx >= 0)
585 return osdfunc_printvar(cvaridx);
586 else OSD_Printf("%s\n", symb->help);
587 }
588
589 return OSDCMD_OK;
590 }
591
osdfunc_clear(osdcmdptr_t UNUSED (parm))592 static int osdfunc_clear(osdcmdptr_t UNUSED(parm))
593 {
594 UNREFERENCED_CONST_PARAMETER(parm);
595 osd_clear();
596 return OSDCMD_OK;
597 }
598
osdfunc_history(osdcmdptr_t UNUSED (parm))599 static int osdfunc_history(osdcmdptr_t UNUSED(parm))
600 {
601 UNREFERENCED_CONST_PARAMETER(parm);
602
603 OSD_Printf("%sCommand history:\n", osd->draw.highlight);
604
605 auto &h = osd->history;
606
607 for (int i=osd->history.maxlines-1, j=0; i>=0; i--)
608 {
609 if (h.buf[i])
610 OSD_Printf("%4d \"%s\"\n", h.total - h.lines + (++j), h.buf[i]);
611 }
612
613 return OSDCMD_OK;
614 }
615
616 ////////////////////////////
617
618
619 //
620 // OSD_Cleanup() -- Cleans up the on-screen display
621 //
OSD_Cleanup(void)622 void OSD_Cleanup(void)
623 {
624 hash_free(&h_osd);
625 hash_free(&h_cvars);
626
627 for (auto &symb : osd->symbptrs)
628 DO_FREE_AND_NULL(symb);
629
630 osd->symbols = NULL;
631
632 for (auto & i : osd->history.buf)
633 DO_FREE_AND_NULL(i);
634
635 DO_FREE_AND_NULL(osd->cvars);
636
637 DO_FREE_AND_NULL(osd->editor.buf);
638 DO_FREE_AND_NULL(osd->editor.tmp);
639
640 DO_FREE_AND_NULL(osd->text.buf);
641 DO_FREE_AND_NULL(osd->text.fmt);
642
643 DO_FREE_AND_NULL(osd->version.buf);
644
645 MAYBE_FCLOSE_AND_NULL(osdlog);
646
647 for (auto s : osdstrings)
648 DO_FREE_AND_NULL(s);
649
650 DO_FREE_AND_NULL(osd);
651 }
652
653
osdcmd_cvar_set_osd(osdcmdptr_t parm)654 static int osdcmd_cvar_set_osd(osdcmdptr_t parm)
655 {
656 int const r = osdcmd_cvar_set(parm);
657
658 if (r != OSDCMD_OK)
659 return r;
660
661 if (!Bstrcasecmp(parm->name, "osdrows"))
662 {
663 osd->draw.rows = osdrows ? clamp(osdrows, 1, osdmaxrows) : (osdmaxrows >> 1);
664
665 if (osdrowscur != -1)
666 osdrowscur = osd->draw.rows;
667 }
668 else if (!Bstrcasecmp(parm->name, "osdtextmode"))
669 OSD_SetTextMode(osd->draw.mode);
670 else if (!Bstrcasecmp(parm->name, "osdhistorydepth"))
671 {
672 for (auto &i : osd->history.buf)
673 DO_FREE_AND_NULL(i);
674 }
675
676 return OSDCMD_OK;
677 }
678
osdfunc_toggle(osdcmdptr_t parm)679 static int osdfunc_toggle(osdcmdptr_t parm)
680 {
681 if (parm->numparms != 1)
682 return OSDCMD_SHOWHELP;
683
684 int i = hash_find(&h_cvars, parm->parms[0]);
685
686 if (i == -1)
687 {
688 for (i = osd->numcvars-1; i>=0; i--)
689 {
690 if (!Bstrcasecmp(parm->parms[0], osd->cvars[i].pData->name))
691 break;
692 }
693 }
694
695 if (i == -1 || (osd->cvars[i].pData->flags & CVAR_TYPEMASK) != CVAR_BOOL)
696 {
697 OSD_Printf("Bad cvar name or cvar not boolean\n");
698 return OSDCMD_OK;
699 }
700
701 *osd->cvars[i].pData->i32 = 1 - *osd->cvars[i].pData->i32;
702 osd->cvars[i].pData->flags |= CVAR_MODIFIED;
703
704 return OSDCMD_OK;
705 }
706
707 //
708 // OSD_Init() -- Initializes the on-screen display
709 //
OSD_Init(void)710 void OSD_Init(void)
711 {
712 osd = (osdmain_t *)Xcalloc(1, sizeof(osdmain_t));
713
714 mutex_init(&osd->mutex);
715
716 if (!osd->keycode)
717 osd->keycode = sc_Tilde;
718
719 osd->text.buf = (char *)Xmalloc(OSDBUFFERSIZE);
720 osd->text.fmt = (char *)Xmalloc(OSDBUFFERSIZE);
721 osd->editor.buf = (char *)Xmalloc(OSDEDITLENGTH);
722 osd->editor.tmp = (char *)Xmalloc(OSDEDITLENGTH);
723
724 Bmemset(osd->text.buf, asc_Space, OSDBUFFERSIZE);
725 Bmemset(osd->text.fmt, osd->draw.textpal + (osd->draw.textshade<<5), OSDBUFFERSIZE);
726 Bmemset(osd->symbptrs, 0, sizeof(osd->symbptrs));
727
728 osd->numsymbols = 0;
729 osd->numcvars = 0;
730 osd->text.lines = 1;
731 osd->text.maxlines = OSDDEFAULTMAXLINES; // overwritten later
732 osd->draw.cols = OSDDEFAULTCOLS;
733 osd->log.cutoff = OSDMAXERRORS;
734
735 osd->history.maxlines = OSDMINHISTORYDEPTH;
736
737 hash_init(&h_osd);
738 hash_init(&h_cvars);
739
740 static osdcvardata_t cvars_osd [] =
741 {
742 { "osdeditpal", "console input text palette", (void *) &osd->draw.editpal, CVAR_INT, 0, MAXPALOOKUPS-1 },
743 { "osdeditshade", "console input text shade", (void *) &osd->draw.editshade, CVAR_INT, 0, 7 },
744
745 { "osdtextpal", "unformatted console text palette", (void *) &osd->draw.textpal, CVAR_INT, 0, MAXPALOOKUPS-1 },
746 { "osdtextshade", "unformatted console text shade", (void *) &osd->draw.textshade, CVAR_INT, 0, 7 },
747
748 { "osdpromptpal", "console prompt palette", (void *) &osd->draw.promptpal, CVAR_INT, 0, MAXPALOOKUPS-1 },
749 { "osdpromptshade", "console prompt shade", (void *) &osd->draw.promptshade, CVAR_INT, INT8_MIN, INT8_MAX },
750
751 { "osdrows", "lines of text to display in console", (void *) &osdrows, CVAR_INT|CVAR_FUNCPTR, 0, MAXYDIM >> 3 },
752 { "osdtextmode", "console character mode: 0: sprites 1: simple glyphs", (void *) &osd->draw.mode, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
753
754 { "osdlogcutoff", "maximum number of error messages to log to the console", (void *) &osd->log.cutoff, CVAR_INT, -1, OSDMAXERRORS },
755 { "osdhistorydepth", "number of lines of command history to cycle through with the up and down cursor keys", (void *) &osd->history.maxlines, CVAR_INT|CVAR_FUNCPTR, OSDMINHISTORYDEPTH, OSDMAXHISTORYDEPTH },
756 };
757
758 for (auto & i : cvars_osd)
759 OSD_RegisterCvar(&i, (i.flags & CVAR_FUNCPTR) ? osdcmd_cvar_set_osd : osdcmd_cvar_set);
760
761 OSD_RegisterFunction("alias", "alias: creates an alias for calling multiple commands", osdfunc_alias);
762 OSD_RegisterFunction("clear", "clear: clears the console text buffer", osdfunc_clear);
763 OSD_RegisterFunction("echo", "echo [text]: echoes text to the console", osdfunc_echo);
764 OSD_RegisterFunction("exec", "exec <scriptfile>: executes a script", osdfunc_exec);
765 OSD_RegisterFunction("fileinfo", "fileinfo <file>: gets a file's information", osdfunc_fileinfo);
766 OSD_RegisterFunction("help", "help: displays help for a cvar or command; \"listsymbols\" to show all commands", osdfunc_help);
767 OSD_RegisterFunction("history", "history: displays the console command history", osdfunc_history);
768 OSD_RegisterFunction("listsymbols", "listsymbols: lists all registered functions, cvars and aliases", osdfunc_listsymbols);
769 OSD_RegisterFunction("toggle", "toggle: toggles the value of a boolean cvar", osdfunc_toggle);
770 OSD_RegisterFunction("unalias", "unalias: removes a command alias", osdfunc_unalias);
771 }
772
773
774 //
775 // OSD_SetLogFile() -- Sets the text file where printed text should be echoed
776 //
OSD_SetLogFile(const char * fn)777 void OSD_SetLogFile(const char *fn)
778 {
779 MAYBE_FCLOSE_AND_NULL(osdlog);
780 osdlogfn = NULL;
781
782 if (!osd)
783 OSD_Init();
784
785 if (!fn)
786 return;
787
788 osdlog = buildvfs_fopen_write_text(fn);
789
790 if (osdlog)
791 {
792 #ifndef USE_PHYSFS
793 #ifdef DEBUGGINGAIDS
794 const int bufmode = _IONBF;
795 #else
796 const int bufmode = _IOLBF;
797 #endif
798 setvbuf(osdlog, (char *)NULL, bufmode, BUFSIZ);
799 #endif
800 osdlogfn = fn;
801 }
802 }
803
804
805 //
806 // OSD_SetFunctions() -- Sets some callbacks which the OSD uses to understand its world
807 //
OSD_SetFunctions(void (* drawchar)(int,int,char,int,int),void (* drawstr)(int,int,const char *,int,int,int),void (* drawcursor)(int,int,int,int),int (* colwidth)(int),int (* rowheight)(int),void (* clearbg)(int,int),int32_t (* gtime)(void),void (* showosd)(int))808 void OSD_SetFunctions(void (*drawchar)(int, int, char, int, int),
809 void (*drawstr)(int, int, const char *, int, int, int),
810 void (*drawcursor)(int, int, int, int),
811 int (*colwidth)(int),
812 int (*rowheight)(int),
813 void (*clearbg)(int, int),
814 int32_t (*gtime)(void),
815 void (*showosd)(int))
816 {
817 drawosdchar = drawchar ? drawchar : _internal_drawosdchar;
818 drawosdstr = drawstr ? drawstr : _internal_drawosdstr;
819 drawosdcursor = drawcursor ? drawcursor : _internal_drawosdcursor;
820 getcolumnwidth = colwidth ? colwidth : _internal_getcolumnwidth;
821 getrowheight = rowheight ? rowheight : _internal_getrowheight;
822 clearbackground = clearbg ? clearbg : _internal_clearbackground;
823 gettime = gtime ? gtime : _internal_gettime;
824 onshowosd = showosd ? showosd : _internal_onshowosd;
825
826 if (!osd)
827 OSD_Init();
828 }
829
830
831 //
832 // OSD_SetParameters() -- Sets the parameters for presenting the text
833 //
OSD_SetParameters(int promptShade,int promptPal,int editShade,int editPal,int textShade,int textPal,char const * errorStr,char const * highlight,uint32_t flags)834 void OSD_SetParameters(int promptShade, int promptPal, int editShade, int editPal, int textShade, int textPal,
835 char const *errorStr, char const *highlight, uint32_t flags)
836 {
837 osddraw_t &draw = osd->draw;
838
839 draw.promptshade = promptShade;
840 draw.promptpal = promptPal;
841 draw.editshade = editShade;
842 draw.editpal = editPal;
843 draw.textshade = textShade;
844 draw.textpal = textPal;
845 draw.errorfmt = errorStr;
846 draw.errfmtlen = errorStr ? Bstrlen(errorStr) : -1;
847 draw.highlight = highlight;
848
849 osd->flags |= flags;
850 }
851
852
853 //
854 // OSD_CaptureKey() -- Sets the scancode for the key which activates the onscreen display
855 //
OSD_CaptureKey(uint8_t scanCode)856 void OSD_CaptureKey(uint8_t scanCode)
857 {
858 osd->keycode = scanCode;
859 }
860
861 //
862 // OSD_FindDiffPoint() -- Finds the length of the longest common prefix of 2 strings, stolen from ZDoom
863 //
OSD_FindDiffPoint(const char * str1,const char * str2)864 static int OSD_FindDiffPoint(const char *str1, const char *str2)
865 {
866 int i;
867
868 for (i = 0; Btolower(str1[i]) == Btolower(str2[i]); i++)
869 if (str1[i] == 0 || str2[i] == 0)
870 break;
871
872 return i;
873 }
874
OSD_AdjustEditorPosition(osdedit_t & e)875 static void OSD_AdjustEditorPosition(osdedit_t &e)
876 {
877 e.pos = 0;
878 while (e.buf[e.pos])
879 e.pos++;
880 e.len = e.pos;
881
882 if (e.pos < e.start)
883 {
884 e.end = e.pos;
885 e.start = e.end - OSD_EDIT_LINE_WIDTH;
886
887 if (e.start < 0)
888 {
889 e.end -= e.start;
890 e.start = 0;
891 }
892 }
893 else if (e.pos >= e.end)
894 {
895 e.start += (e.pos - e.end);
896 e.end += (e.pos - e.end);
897 }
898 }
899
OSD_HistoryPrev(void)900 static void OSD_HistoryPrev(void)
901 {
902 osdhist_t &h = osd->history;
903
904 if (h.pos >= h.lines - 1)
905 return;
906
907 osdedit_t &e = osd->editor;
908
909 Bmemcpy(e.buf, h.buf[++h.pos], OSDEDITLENGTH);
910
911 OSD_AdjustEditorPosition(e);
912 }
913
OSD_HistoryNext(void)914 static void OSD_HistoryNext(void)
915 {
916 osdhist_t &h = osd->history;
917
918 if (h.pos < 0)
919 return;
920
921 osdedit_t &e = osd->editor;
922
923 if (h.pos == 0)
924 {
925 e.len = 0;
926 e.pos = 0;
927 e.start = 0;
928 e.end = OSD_EDIT_LINE_WIDTH;
929 h.pos = -1;
930
931 return;
932 }
933
934 Bmemcpy(e.buf, h.buf[--h.pos], OSDEDITLENGTH);
935
936 OSD_AdjustEditorPosition(e);
937 }
938
OSD_HandleWheel()939 void OSD_HandleWheel()
940 {
941 if (osd->flags & OSD_CAPTURE)
942 {
943 if ((g_mouseBits & 16) && osd->draw.head < osd->text.lines - osdrowscur)
944 ++osd->draw.head;
945
946 if ((g_mouseBits & 32) && osd->draw.head > 0)
947 --osd->draw.head;
948 }
949 }
950
951 //
952 // OSD_HandleKey() -- Handles keyboard input when capturing input.
953 // Returns 0 if the key was handled internally, or the scancode if it should
954 // be passed on to the game.
955 //
956
OSD_HandleChar(char ch)957 int OSD_HandleChar(char ch)
958 {
959 if (!osd || (osd->flags & OSD_CAPTURE) != OSD_CAPTURE)
960 return ch;
961
962 osdhist_t &h = osd->history;
963 osdedit_t &ed = osd->editor;
964
965 osdsymbol_t * tabc = NULL;
966 static osdsymbol_t *lastmatch = NULL;
967
968 if (ch != asc_Tab)
969 lastmatch = NULL;
970
971 switch (ch)
972 {
973 case asc_Ctrl_A: // jump to beginning of line
974 ed.pos = 0;
975 ed.start = 0;
976 ed.end = OSD_EDIT_LINE_WIDTH;
977 return 0;
978
979 case asc_Ctrl_B: // move one character left
980 if (ed.pos > 0)
981 --ed.pos;
982 return 0;
983
984 case asc_Ctrl_C: // discard line
985 ed.buf[ed.len] = 0;
986 OSD_Printf("%s\n", ed.buf);
987
988 ed.len = 0;
989 ed.pos = 0;
990 ed.start = 0;
991 ed.end = OSD_EDIT_LINE_WIDTH;
992 ed.buf[0] = 0;
993 return 0;
994
995 case asc_Ctrl_E: // jump to end of line
996 ed.pos = ed.len;
997 ed.end = ed.pos;
998 ed.start = ed.end - OSD_EDIT_LINE_WIDTH;
999
1000 if (ed.start < 0)
1001 {
1002 ed.start = 0;
1003 ed.end = OSD_EDIT_LINE_WIDTH;
1004 }
1005 return 0;
1006
1007 case asc_Ctrl_F: // move one character right
1008 if (ed.pos < ed.len)
1009 ed.pos++;
1010 return 0;
1011
1012 case asc_BackSpace:
1013 #ifdef __APPLE__
1014 case 127: // control h, backspace
1015 #endif
1016 if (!ed.pos || !ed.len)
1017 return 0;
1018
1019 if ((osd->flags & OSD_OVERTYPE) == 0)
1020 {
1021 if (ed.pos < ed.len)
1022 Bmemmove(ed.buf + ed.pos - 1, ed.buf + ed.pos, ed.len - ed.pos);
1023 ed.len--;
1024 }
1025
1026 if (--ed.pos < ed.start)
1027 ed.start--, ed.end--;
1028 #ifndef __APPLE__
1029 fallthrough__;
1030 case 127: // handled in OSD_HandleScanCode (delete)
1031 #endif
1032 return 0;
1033
1034 case asc_Tab: // tab
1035 {
1036 int commonsize = INT_MAX;
1037
1038 if (!lastmatch)
1039 {
1040 int editPos, iter;
1041
1042 for (editPos = ed.pos; editPos > 0; editPos--)
1043 if (ed.buf[editPos - 1] == ' ')
1044 break;
1045
1046 for (iter = 0; editPos < ed.len && ed.buf[editPos] != ' '; iter++, editPos++)
1047 ed.tmp[iter] = ed.buf[editPos];
1048
1049 ed.tmp[iter] = 0;
1050
1051 if (iter > 0)
1052 {
1053 tabc = osd_findsymbol(ed.tmp, NULL);
1054
1055 if (tabc && tabc->next && osd_findsymbol(ed.tmp, tabc->next))
1056 {
1057 auto symb = tabc;
1058 int maxwidth = 0, x = 0, num = 0, diffpt;
1059
1060 while (symb && symb != lastmatch)
1061 {
1062 num++;
1063
1064 if (lastmatch)
1065 {
1066 diffpt = OSD_FindDiffPoint(symb->name, lastmatch->name);
1067
1068 if (diffpt < commonsize)
1069 commonsize = diffpt;
1070 }
1071
1072 maxwidth = max<int>(maxwidth, Bstrlen(symb->name));
1073 lastmatch = symb;
1074
1075 if (!lastmatch->next)
1076 break;
1077
1078 symb = osd_findsymbol(ed.tmp, lastmatch->next);
1079 }
1080
1081 OSD_Printf("%sFound %d possible completions for \"%s\":\n", osd->draw.highlight, num, ed.tmp);
1082 maxwidth += 3;
1083 symb = tabc;
1084 OSD_Printf(" ");
1085
1086 while (symb && (symb != lastmatch))
1087 {
1088 tabc = lastmatch = symb;
1089 OSD_Printf("%-*s", maxwidth, symb->name);
1090
1091 if (!lastmatch->next)
1092 break;
1093
1094 symb = osd_findsymbol(ed.tmp, lastmatch->next);
1095 x += maxwidth;
1096
1097 if (x > (osd->draw.cols - maxwidth))
1098 {
1099 x = 0;
1100 OSD_Printf("\n");
1101
1102 if (symb && (symb != lastmatch))
1103 OSD_Printf(" ");
1104 }
1105 }
1106
1107 if (x)
1108 OSD_Printf("\n");
1109
1110 OSD_Printf("%sPress TAB again to cycle through matches\n", osd->draw.highlight);
1111 }
1112 }
1113 }
1114 else
1115 {
1116 tabc = osd_findsymbol(ed.tmp, lastmatch->next);
1117
1118 if (!tabc)
1119 tabc = osd_findsymbol(ed.tmp, NULL); // wrap */
1120 }
1121
1122 if (tabc)
1123 {
1124 int editPos;
1125
1126 for (editPos = ed.pos; editPos > 0; editPos--)
1127 if (ed.buf[editPos - 1] == ' ')
1128 break;
1129
1130 ed.len = editPos;
1131
1132 for (int iter = 0; tabc->name[iter] && ed.len <= OSDEDITLENGTH - 1 && (ed.len < commonsize); editPos++, iter++, ed.len++)
1133 ed.buf[editPos] = tabc->name[iter];
1134
1135 ed.pos = ed.len;
1136 ed.end = ed.pos;
1137 ed.start = ed.end - OSD_EDIT_LINE_WIDTH;
1138
1139 if (ed.start < 0)
1140 {
1141 ed.start = 0;
1142 ed.end = OSD_EDIT_LINE_WIDTH;
1143 }
1144
1145 lastmatch = tabc;
1146 }
1147 return 0;
1148 }
1149
1150 case asc_Ctrl_K: // delete all to end of line
1151 Bmemset(ed.buf + ed.pos, 0, OSDEDITLENGTH - ed.pos);
1152 ed.len = ed.pos;
1153 return 0;
1154
1155 case asc_Ctrl_L: // clear screen
1156 osd_clear();
1157 return 0;
1158
1159 case asc_Enter: // control m, enter
1160 if (ed.len > 0)
1161 {
1162 ed.buf[ed.len] = 0;
1163 if (!h.buf[0] || Bstrcmp(h.buf[0], ed.buf))
1164 {
1165 DO_FREE_AND_NULL(h.buf[h.maxlines - 1]);
1166 Bmemmove(&h.buf[1], &h.buf[0], sizeof(intptr_t) * h.maxlines - 1);
1167 OSD_SetHistory(0, ed.buf);
1168
1169 if (h.lines < h.maxlines)
1170 h.lines++;
1171
1172 h.total++;
1173 }
1174
1175 if (h.exec++ == h.maxlines)
1176 OSD_Printf("Buffer full! Consider increasing \"osdhistorydepth\" beyond %d.\n", --h.exec);
1177
1178 h.pos = -1;
1179 }
1180
1181 ed.len = 0;
1182 ed.pos = 0;
1183 ed.start = 0;
1184 ed.end = OSD_EDIT_LINE_WIDTH;
1185 return 0;
1186
1187 case asc_Ctrl_N: // next (ie. down arrow)
1188 OSD_HistoryNext();
1189 return 0;
1190
1191 case asc_Ctrl_P: // previous (ie. up arrow)
1192 OSD_HistoryPrev();
1193 return 0;
1194
1195 case asc_Ctrl_U: // delete all to beginning
1196 if (ed.pos > 0 && ed.len)
1197 {
1198 if (ed.pos < ed.len)
1199 Bmemmove(ed.buf, ed.buf + ed.pos, ed.len - ed.pos);
1200
1201 ed.len -= ed.pos;
1202 ed.pos = 0;
1203 ed.start = 0;
1204 ed.end = OSD_EDIT_LINE_WIDTH;
1205 }
1206 return 0;
1207
1208 case asc_Ctrl_W: // delete one word back
1209 if (ed.pos > 0 && ed.len > 0)
1210 {
1211 int editPos = ed.pos;
1212
1213 while (editPos > 0 && ed.buf[editPos - 1] == asc_Space) editPos--;
1214 while (editPos > 0 && ed.buf[editPos - 1] != asc_Space) editPos--;
1215
1216 if (ed.pos < ed.len)
1217 Bmemmove(ed.buf + editPos, ed.buf + ed.pos, ed.len - ed.pos);
1218
1219 ed.len -= (ed.pos - editPos);
1220 ed.pos = editPos;
1221
1222 if (ed.pos < ed.start)
1223 {
1224 ed.start = ed.pos;
1225 ed.end = ed.start + OSD_EDIT_LINE_WIDTH;
1226 }
1227 }
1228 return 0;
1229
1230 default:
1231 if (ch >= asc_Space) // text char
1232 {
1233 if ((osd->flags & OSD_OVERTYPE) == 0)
1234 {
1235 if (ed.len == OSDEDITLENGTH) // buffer full, can't insert another char
1236 return 0;
1237
1238 if (ed.pos < ed.len)
1239 Bmemmove(ed.buf + ed.pos + 1, ed.buf + ed.pos, ed.len - ed.pos);
1240
1241 ed.len++;
1242 }
1243 else if (ed.pos == ed.len)
1244 ed.len++;
1245
1246 ed.buf[ed.pos++] = ch;
1247
1248 if (ed.pos > ed.end)
1249 ed.start++, ed.end++;
1250 }
1251 return 0;
1252 }
1253 return 0;
1254 }
1255
OSD_HandleScanCode(uint8_t scanCode,int keyDown)1256 int OSD_HandleScanCode(uint8_t scanCode, int keyDown)
1257 {
1258 if (!osd)
1259 return 1;
1260
1261 osddraw_t &draw = osd->draw;
1262
1263 if (scanCode == osd->keycode && (keystatus[sc_LeftShift] || (osd->flags & OSD_CAPTURE) || (osd->flags & OSD_PROTECTED) != OSD_PROTECTED))
1264 {
1265 if (keyDown)
1266 {
1267 draw.scrolling = (osdrowscur == -1) ? 1 : (osdrowscur == draw.rows) ? -1 : -draw.scrolling;
1268 osdrowscur += draw.scrolling;
1269 OSD_CaptureInput(draw.scrolling == 1);
1270 osdscrtime = timerGetTicks();
1271 }
1272 return -1;
1273 }
1274 else if ((osd->flags & OSD_CAPTURE) == 0)
1275 return 2;
1276
1277 if (!keyDown)
1278 {
1279 if (scanCode == sc_LeftShift || scanCode == sc_RightShift)
1280 osd->flags &= ~OSD_SHIFT;
1281
1282 if (scanCode == sc_LeftControl || scanCode == sc_RightControl)
1283 osd->flags &= ~OSD_CTRL;
1284
1285 return 0;
1286 }
1287
1288 osdedit_t &ed = osd->editor;
1289 osdkeytime = gettime();
1290
1291 switch (scanCode)
1292 {
1293 case sc_Escape:
1294 // OSD_ShowDisplay(0);
1295 draw.scrolling = -1;
1296 osdrowscur--;
1297 OSD_CaptureInput(0);
1298 osdscrtime = timerGetTicks();
1299 break;
1300
1301 case sc_PgUp:
1302 if (draw.head < osd->text.lines-osdrowscur)
1303 ++draw.head;
1304 break;
1305
1306 case sc_PgDn:
1307 if (draw.head > 0)
1308 --draw.head;
1309 break;
1310
1311 case sc_Home:
1312 if (osd->flags & OSD_CTRL)
1313 draw.head = osd->text.lines-1;
1314 else
1315 {
1316 ed.pos = 0;
1317 ed.start = ed.pos;
1318 ed.end = ed.start + OSD_EDIT_LINE_WIDTH;
1319 }
1320 break;
1321
1322 case sc_End:
1323 if (osd->flags & OSD_CTRL)
1324 draw.head = 0;
1325 else
1326 {
1327 ed.pos = ed.len;
1328 ed.end = ed.pos;
1329 ed.start = ed.end - OSD_EDIT_LINE_WIDTH;
1330
1331 if (ed.start < 0)
1332 {
1333 ed.start = 0;
1334 ed.end = OSD_EDIT_LINE_WIDTH;
1335 }
1336 }
1337 break;
1338
1339 case sc_Insert:
1340 osd->flags = (osd->flags & ~OSD_OVERTYPE) | (-((osd->flags & OSD_OVERTYPE) == 0) & OSD_OVERTYPE);
1341 break;
1342
1343 case sc_LeftArrow:
1344 if (ed.pos > 0)
1345 {
1346 if (osd->flags & OSD_CTRL)
1347 {
1348 while (ed.pos > 0)
1349 {
1350 if (ed.buf[ed.pos-1] != asc_Space)
1351 break;
1352
1353 --ed.pos;
1354 }
1355
1356 while (ed.pos > 0)
1357 {
1358 if (ed.buf[ed.pos-1] == asc_Space)
1359 break;
1360
1361 --ed.pos;
1362 }
1363 }
1364 else --ed.pos;
1365 }
1366
1367 if (ed.pos < ed.start)
1368 {
1369 ed.end -= (ed.start - ed.pos);
1370 ed.start -= (ed.start - ed.pos);
1371 }
1372 break;
1373
1374 case sc_RightArrow:
1375 if (ed.pos < ed.len)
1376 {
1377 if (osd->flags & OSD_CTRL)
1378 {
1379 while (ed.pos < ed.len)
1380 {
1381 if (ed.buf[ed.pos] == asc_Space)
1382 break;
1383
1384 ed.pos++;
1385 }
1386
1387 while (ed.pos < ed.len)
1388 {
1389 if (ed.buf[ed.pos] != asc_Space)
1390 break;
1391
1392 ed.pos++;
1393 }
1394 }
1395 else ed.pos++;
1396 }
1397
1398 if (ed.pos >= ed.end)
1399 {
1400 ed.start += (ed.pos - ed.end);
1401 ed.end += (ed.pos - ed.end);
1402 }
1403 break;
1404
1405 case sc_UpArrow:
1406 OSD_HistoryPrev();
1407 break;
1408
1409 case sc_DownArrow:
1410 OSD_HistoryNext();
1411 break;
1412
1413 case sc_LeftShift:
1414 case sc_RightShift:
1415 osd->flags |= OSD_SHIFT;
1416 break;
1417
1418 case sc_LeftControl:
1419 case sc_RightControl:
1420 osd->flags |= OSD_CTRL;
1421 break;
1422
1423 case sc_CapsLock:
1424 osd->flags = (osd->flags & ~OSD_CAPS) | (-((osd->flags & OSD_CAPS) == 0) & OSD_CAPS);
1425 break;
1426
1427 case sc_Delete:
1428 if (ed.pos == ed.len || !ed.len)
1429 return 0;
1430
1431 if (ed.pos <= ed.len-1)
1432 Bmemmove(ed.buf+ed.pos, ed.buf+ed.pos+1, ed.len-ed.pos-1);
1433
1434 ed.len--;
1435 break;
1436 }
1437
1438 return 0;
1439 }
1440
1441
1442 //
1443 // OSD_ResizeDisplay() -- Handles readjustment of the display when the screen resolution
1444 // changes on us.
1445 //
OSD_ResizeDisplay(int w,int h)1446 void OSD_ResizeDisplay(int w, int h)
1447 {
1448 auto &t = osd->text;
1449 auto &d = osd->draw;
1450
1451 int const newcols = getcolumnwidth(w);
1452 d.cols = newcols;
1453
1454 int const newmaxlines = OSDBUFFERSIZE / newcols;
1455 t.maxlines = newmaxlines;
1456
1457 osdmaxrows = getrowheight(h) - 2;
1458 d.rows = osdrows ? clamp(osdrows, 1, osdmaxrows) : (osdmaxrows >> 1);
1459
1460 if (osdrowscur != -1)
1461 osdrowscur = d.rows;
1462
1463 osd->editor.start = d.head = t.pos = 0;
1464 osd->editor.end = OSD_EDIT_LINE_WIDTH;
1465
1466 whiteColorIdx = -1;
1467
1468 osd_clear(false);
1469
1470 for (auto s : osdstrings)
1471 OSD_Puts(s, true);
1472 }
1473
1474
1475 //
1476 // OSD_CaptureInput()
1477 //
OSD_CaptureInput(int cap)1478 void OSD_CaptureInput(int cap)
1479 {
1480 osd->flags = (osd->flags & ~(OSD_CAPTURE|OSD_CTRL|OSD_SHIFT)) | (-cap & OSD_CAPTURE);
1481
1482 mouseGrabInput(cap == 0 ? g_mouseLockedToWindow : 0);
1483 onshowosd(cap);
1484
1485 keyFlushChars();
1486 }
1487
1488
1489 //
1490 // OSD_ShowDisplay() -- Shows or hides the onscreen display
1491 //
OSD_ShowDisplay(int onf)1492 void OSD_ShowDisplay(int onf)
1493 {
1494 osd->flags = (osd->flags & ~OSD_DRAW) | (-onf & OSD_DRAW);
1495 OSD_CaptureInput(onf);
1496 }
1497
1498
1499 //
1500 // OSD_Draw() -- Draw the onscreen display
1501 //
1502
OSD_Draw(void)1503 void OSD_Draw(void)
1504 {
1505 if (!osd)
1506 return;
1507
1508 if (osdrowscur == 0)
1509 OSD_ShowDisplay((osd->flags & OSD_DRAW) != OSD_DRAW);
1510
1511 if (osdrowscur == osd->draw.rows)
1512 osd->draw.scrolling = 0;
1513 else
1514 {
1515 if ((osdrowscur < osd->draw.rows && osd->draw.scrolling == 1) || osdrowscur < -1)
1516 {
1517 uint32_t j = (timerGetTicks() - osdscrtime);
1518
1519 while (j < UINT32_MAX)
1520 {
1521 j -= tabledivide32_noinline(200, osd->draw.rows);
1522 if (++osdrowscur > osd->draw.rows-1)
1523 break;
1524 }
1525 }
1526 else if ((osdrowscur > -1 && osd->draw.scrolling == -1) || osdrowscur > osd->draw.rows)
1527 {
1528 uint32_t j = (timerGetTicks() - osdscrtime);
1529
1530 while (j < UINT32_MAX)
1531 {
1532 j -= tabledivide32_noinline(200, osd->draw.rows);
1533 if (--osdrowscur < 1)
1534 break;
1535 }
1536 }
1537
1538 osdscrtime = timerGetTicks();
1539 }
1540
1541 if ((osd->flags & OSD_DRAW) == 0 || !osdrowscur) return;
1542
1543 int topoffs = osd->draw.head * osd->draw.cols;
1544 int row = osdrowscur - 1;
1545 int lines = min(osd->text.lines - osd->draw.head, osdrowscur);
1546
1547 videoBeginDrawing();
1548
1549 clearbackground(osd->draw.cols,osdrowscur+1);
1550
1551 for (; lines>0; lines--, row--)
1552 {
1553 // XXX: May happen, which would ensue an oob if not checked.
1554 // Last char accessed is osd->text.buf[topoffs + osd->draw.cols-1].
1555 // Reproducible by running test.lua with -Lopts=diag
1556 // and scrolling to the top.
1557 if (topoffs + osd->draw.cols-1 >= OSDBUFFERSIZE)
1558 break;
1559
1560 drawosdstr(0, row, osd->text.buf + topoffs, osd->draw.cols, osd->draw.textshade, osd->draw.textpal);
1561 topoffs += osd->draw.cols;
1562 }
1563
1564 int offset = ((osd->flags & (OSD_CAPS | OSD_SHIFT)) == (OSD_CAPS | OSD_SHIFT) && osd->draw.head > 0);
1565 int const shade = osd->draw.promptshade ? osd->draw.promptshade : (sintable[((int32_t) totalclock<<4)&2047]>>11);
1566
1567 if (osd->draw.head == osd->text.lines-1)
1568 drawosdchar(0, osdrowscur, '~', shade, osd->draw.promptpal);
1569 else if (osd->draw.head > 0)
1570 drawosdchar(0, osdrowscur, '^', shade, osd->draw.promptpal);
1571
1572 if (osd->flags & OSD_CAPS)
1573 drawosdchar((osd->draw.head > 0), osdrowscur, 'C', shade, osd->draw.promptpal);
1574
1575 if (osd->flags & OSD_SHIFT)
1576 drawosdchar(1 + (osd->flags & OSD_CAPS && osd->draw.head > 0), osdrowscur, 'H', shade, osd->draw.promptpal);
1577
1578 drawosdchar(2 + offset, osdrowscur, '>', shade, osd->draw.promptpal);
1579
1580 int const len = min(osd->draw.cols-1-3 - offset, osd->editor.len - osd->editor.start);
1581
1582 for (int x=len-1; x>=0; x--)
1583 drawosdchar(3 + x + offset, osdrowscur, osd->editor.buf[osd->editor.start+x], osd->draw.editshade<<1, osd->draw.editpal);
1584
1585 offset += 3 + osd->editor.pos - osd->editor.start;
1586
1587 drawosdcursor(offset,osdrowscur,osd->flags & OSD_OVERTYPE,osdkeytime);
1588
1589 if (osd->version.buf)
1590 drawosdstr(osd->draw.cols - osd->version.len, osdrowscur - (offset >= osd->draw.cols - osd->version.len),
1591 osd->version.buf, osd->version.len, (sintable[((int32_t) totalclock<<4)&2047]>>11), osd->version.pal);
1592
1593 videoEndDrawing();
1594 }
1595
1596
1597 //
1598 // OSD_Printf() -- Print a formatted string to the onscreen display
1599 // and write it to the log file
1600 //
1601
OSD_Printf(const char * fmt,...)1602 int OSD_Printf(const char *fmt, ...)
1603 {
1604 static char tmpstr[8192];
1605 va_list va;
1606
1607 va_start(va, fmt);
1608 int len = Bvsnprintf(tmpstr, sizeof(tmpstr), fmt, va);
1609 va_end(va);
1610
1611 osdstrings.append(Xstrdup(tmpstr));
1612 OSD_Puts(tmpstr);
1613
1614 return len;
1615 }
1616
1617
1618 //
1619 // OSD_Puts() -- Print a string to the onscreen display
1620 // and write it to the log file
1621 //
1622
OSD_LineFeed(void)1623 static inline void OSD_LineFeed(void)
1624 {
1625 auto &t = osd->text;
1626 auto &d = osd->draw;
1627
1628 Bmemmove(t.buf + d.cols, t.buf, OSDBUFFERSIZE - d.cols);
1629 Bmemset(t.buf, asc_Space, d.cols);
1630
1631 Bmemmove(t.fmt + d.cols, t.fmt, OSDBUFFERSIZE - d.cols);
1632 Bmemset(t.fmt, d.textpal, d.cols);
1633
1634 if (t.lines < t.maxlines)
1635 t.lines++;
1636 }
1637
OSD_Puts(const char * putstr,int const nolog)1638 void OSD_Puts(const char *putstr, int const nolog /*= false*/)
1639 {
1640 if (putstr[0] == 0 || !osd)
1641 return;
1642
1643 auto &t = osd->text;
1644 auto &d = osd->draw;
1645 auto &l = osd->log;
1646
1647 int textPal = d.textpal;
1648 int textShade = d.textshade;
1649
1650 static int errorCnt;
1651
1652 mutex_lock(&osd->mutex);
1653
1654 if (Bstrlen(putstr) >= (unsigned)d.errfmtlen && !Bstrncmp(putstr, d.errorfmt, d.errfmtlen) && (unsigned)++errorCnt > (unsigned)l.cutoff)
1655 {
1656 if (errorCnt >= l.cutoff + 2)
1657 {
1658 errorCnt = l.cutoff + 2;
1659 mutex_unlock(&osd->mutex);
1660 return;
1661 }
1662
1663 putstr = "\nError count exceeded \"osdlogcutoff\"!\n";
1664 }
1665 else if (!nolog && (unsigned)errorCnt < (unsigned)l.cutoff)
1666 {
1667 auto s = Xstrdup(putstr);
1668 buildvfs_fputs(OSD_StripColors(s, putstr), osdlog);
1669 Bprintf("%s", s);
1670 Xfree(s);
1671 }
1672
1673 auto s = putstr;
1674
1675 do
1676 {
1677 if (*s == '\n')
1678 {
1679 t.pos = 0;
1680 ++l.lines;
1681 OSD_LineFeed();
1682 continue;
1683 }
1684
1685 if (*s == '\r')
1686 {
1687 t.pos = 0;
1688 continue;
1689 }
1690
1691 if (*s == '^')
1692 {
1693 // palette
1694 if (isdigit(*(s+1)))
1695 {
1696 if (!isdigit(*(++s+1)))
1697 {
1698 char const smallbuf[] = { *(s), '\0' };
1699 textPal = Batoi(smallbuf);
1700 continue;
1701 }
1702
1703 char const smallbuf[] = { *(s), *(s+1), '\0' };
1704 s++;
1705 textPal = Batoi(smallbuf);
1706 continue;
1707 }
1708
1709 // shade
1710 if (Btoupper(*(s+1)) == 'S')
1711 {
1712 s++;
1713 if (isdigit(*(++s)))
1714 textShade = *s;
1715 continue;
1716 }
1717
1718 // reset
1719 if (Btoupper(*(s+1)) == 'O')
1720 {
1721 s++;
1722 textPal = d.textpal;
1723 textShade = d.textshade;
1724 continue;
1725 }
1726 }
1727
1728 t.buf[t.pos] = *s;
1729 t.fmt[t.pos] = textPal + (textShade << 5);
1730
1731 if (++t.pos == d.cols)
1732 {
1733 t.pos = 0;
1734 OSD_LineFeed();
1735 }
1736 }
1737 while (*(++s));
1738
1739 mutex_unlock(&osd->mutex);
1740 }
1741
1742
1743 //
1744 // OSD_DispatchQueued() -- Executes any commands queued in the buffer
1745 //
OSD_DispatchQueued(void)1746 void OSD_DispatchQueued(void)
1747 {
1748 if (!osd->history.exec)
1749 return;
1750
1751 int cmd = osd->history.exec - 1;
1752
1753 osd->history.exec = 0;
1754
1755 for (; cmd >= 0; cmd--)
1756 OSD_Dispatch((const char *)osd->history.buf[cmd]);
1757 }
1758
1759 //
1760 // OSD_Dispatch() -- Executes a command string
1761 //
osd_strtoken(char * s,char ** ptrptr,int * restart)1762 static char *osd_strtoken(char *s, char **ptrptr, int *restart)
1763 {
1764 *restart = 0;
1765 if (!ptrptr) return NULL;
1766
1767 // if s != NULL, we process from the start of s, otherwise
1768 // we just continue with where ptrptr points to
1769
1770 char *p = s ? s : *ptrptr;
1771
1772 if (!p) return NULL;
1773
1774 // eat up any leading whitespace
1775 while (*p == ' ') p++;
1776
1777 // a semicolon is an end of statement delimiter like a \0 is, so we signal
1778 // the caller to 'restart' for the rest of the string pointed at by *ptrptr
1779 if (*p == ';')
1780 {
1781 *restart = 1;
1782 *ptrptr = p+1;
1783 return NULL;
1784 }
1785 // or if we hit the end of the input, signal all done by nulling *ptrptr
1786 else if (*p == 0)
1787 {
1788 *ptrptr = NULL;
1789 return NULL;
1790 }
1791
1792 char *start;
1793
1794 if (*p == '\"')
1795 {
1796 // quoted string
1797 start = ++p;
1798 char *p2 = p;
1799 while (*p != 0)
1800 {
1801 if (*p == '\"')
1802 {
1803 p++;
1804 break;
1805 }
1806 else if (*p == '\\')
1807 {
1808 switch (*(++p))
1809 {
1810 case 'n':
1811 *p2 = '\n'; break;
1812 case 'r':
1813 *p2 = '\r'; break;
1814 default:
1815 *p2 = *p; break;
1816 }
1817 }
1818 else
1819 {
1820 *p2 = *p;
1821 }
1822 p2++, p++;
1823 }
1824 *p2 = 0;
1825 }
1826 else
1827 {
1828 start = p;
1829 while (*p != 0 && *p != ';' && *p != ' ') p++;
1830 }
1831
1832 // if we hit the end of input, signal all done by nulling *ptrptr
1833 if (*p == 0)
1834 {
1835 *ptrptr = NULL;
1836 }
1837 // or if we came upon a semicolon, signal caller to restart with the
1838 // string at *ptrptr
1839 else if (*p == ';')
1840 {
1841 *p = 0;
1842 *ptrptr = p+1;
1843 *restart = 1;
1844 }
1845 // otherwise, clip off the token and carry on
1846 else
1847 {
1848 *(p++) = 0;
1849 *ptrptr = p;
1850 }
1851
1852 return start;
1853 }
1854
1855 #define MAXPARMS 256
OSD_Dispatch(const char * cmd)1856 void OSD_Dispatch(const char *cmd)
1857 {
1858 char *workbuf = Xstrdup(cmd);
1859 char *state = workbuf;
1860 char *wtp;
1861
1862 int restart = 0;
1863
1864 do
1865 {
1866 char const *token;
1867
1868 if ((token = osd_strtoken(state, &wtp, &restart)) == NULL)
1869 {
1870 state = wtp;
1871 continue;
1872 }
1873
1874 // cheap hack for comments in cfgs
1875 if (token[0] == '/' && token[1] == '/')
1876 {
1877 Xfree(workbuf);
1878 return;
1879 }
1880
1881 auto const *symbol = osd_findexactsymbol(token);
1882
1883 if (symbol == NULL)
1884 {
1885 static char const s_gamefunc_[] = "gamefunc_";
1886 size_t constexpr strlen_gamefunc_ = ARRAY_SIZE(s_gamefunc_) - 1;
1887 size_t const strlen_token = Bstrlen(token);
1888
1889 if ((strlen_gamefunc_ >= strlen_token || Bstrncmp(token, s_gamefunc_, strlen_gamefunc_)) && !m32_osd_tryscript)
1890 OSD_Printf("%s\"%s\" is not a valid command or cvar\n", osd->draw.highlight, token);
1891 else if (m32_osd_tryscript)
1892 M32RunScript(cmd);
1893
1894 Xfree(workbuf);
1895 return;
1896 }
1897
1898 auto name = token;
1899 char const *parms[MAXPARMS] = {};
1900 int numparms = 0;
1901
1902 while (wtp && !restart)
1903 {
1904 token = osd_strtoken(NULL, &wtp, &restart);
1905 if (token && numparms < MAXPARMS) parms[numparms++] = token;
1906 }
1907
1908 osdfuncparm_t const ofp = { numparms, name, parms, cmd };
1909
1910 if (symbol->func == OSD_ALIAS)
1911 OSD_Dispatch(symbol->help);
1912 else if (symbol->func != OSD_UNALIASED)
1913 {
1914 switch (symbol->func(&ofp))
1915 {
1916 default:
1917 case OSDCMD_OK: break;
1918 case OSDCMD_SHOWHELP:
1919 {
1920 int const cvaridx = hash_findcase(&h_cvars, symbol->name);
1921 if (cvaridx >= 0)
1922 osdfunc_printvar(cvaridx);
1923 else OSD_Printf("%s\n", symbol->help);
1924 break;
1925 }
1926 }
1927 }
1928
1929 state = wtp;
1930 }
1931 while (wtp && restart);
1932
1933 Xfree(workbuf);
1934 }
1935
1936
1937 //
1938 // OSD_RegisterFunction() -- Registers a new function
1939 //
OSD_RegisterFunction(const char * pszName,const char * pszDesc,int (* func)(osdcmdptr_t))1940 int OSD_RegisterFunction(const char *pszName, const char *pszDesc, int (*func)(osdcmdptr_t))
1941 {
1942 if (!osd)
1943 OSD_Init();
1944
1945 auto symb = osd_findexactsymbol(pszName);
1946
1947 if (!symb) // allow reusing an alias name
1948 {
1949 symb = osd_addsymbol(pszName);
1950 symb->name = pszName;
1951 }
1952
1953 symb->help = pszDesc;
1954 symb->func = func;
1955
1956 return 0;
1957 }
1958
1959 //
1960 // OSD_SetVersionString()
1961 //
OSD_SetVersion(const char * pszVersion,int osdShade,int osdPal)1962 void OSD_SetVersion(const char *pszVersion, int osdShade, int osdPal)
1963 {
1964 DO_FREE_AND_NULL(osd->version.buf);
1965 osd->version.buf = Xstrdup(pszVersion);
1966 osd->version.len = Bstrlen(pszVersion);
1967 osd->version.shade = osdShade;
1968 osd->version.pal = osdPal;
1969 }
1970
1971 //
1972 // addnewsymbol() -- Allocates space for a new symbol and attaches it
1973 // appropriately to the lists, sorted.
1974 //
1975
osd_addsymbol(const char * pszName)1976 static osdsymbol_t *osd_addsymbol(const char *pszName)
1977 {
1978 if (osd->numsymbols >= OSDMAXSYMBOLS)
1979 return NULL;
1980
1981 auto const newsymb = (osdsymbol_t *)Xcalloc(1, sizeof(osdsymbol_t));
1982
1983 if (!osd->symbols)
1984 osd->symbols = newsymb;
1985 else
1986 {
1987 if (!Bstrcasecmp(pszName, osd->symbols->name))
1988 {
1989 auto t = osd->symbols;
1990 osd->symbols = newsymb;
1991 osd->symbols->next = t;
1992 }
1993 else
1994 {
1995 auto s = osd->symbols;
1996
1997 while (s->next)
1998 {
1999 if (Bstrcasecmp(s->next->name, pszName))
2000 break;
2001
2002 s = s->next;
2003 }
2004
2005 auto t = s->next;
2006
2007 s->next = newsymb;
2008 newsymb->next = t;
2009 }
2010 }
2011
2012 char * const lowercase = Bstrtolower(Xstrdup(pszName));
2013
2014 hash_add(&h_osd, pszName, osd->numsymbols, 1);
2015 hash_add(&h_osd, lowercase, osd->numsymbols, 1);
2016 Xfree(lowercase);
2017
2018 osd->symbptrs[osd->numsymbols++] = newsymb;
2019
2020 return newsymb;
2021 }
2022
2023
2024 //
2025 // findsymbol() -- Finds a symbol, possibly partially named
2026 //
osd_findsymbol(const char * const pszName,osdsymbol_t * pSymbol)2027 static osdsymbol_t * osd_findsymbol(const char * const pszName, osdsymbol_t *pSymbol)
2028 {
2029 if (osd->symbols == NULL)
2030 return NULL;
2031
2032 if (!pSymbol) pSymbol = osd->symbols;
2033
2034 int const nameLen = Bstrlen(pszName);
2035
2036 for (; pSymbol; pSymbol=pSymbol->next)
2037 {
2038 if (pSymbol->func != OSD_UNALIASED && pSymbol->help != NULL && !Bstrncasecmp(pszName, pSymbol->name, nameLen))
2039 return pSymbol;
2040 }
2041
2042 return NULL;
2043 }
2044
2045 //
2046 // findexactsymbol() -- Finds a symbol, complete named
2047 //
osd_findexactsymbol(const char * pszName)2048 static osdsymbol_t * osd_findexactsymbol(const char *pszName)
2049 {
2050 if (osd->symbols == NULL)
2051 return NULL;
2052
2053 int symbolNum = hash_find(&h_osd, pszName);
2054
2055 if (symbolNum < 0)
2056 {
2057 char *const lname = Xstrdup(pszName);
2058 Bstrtolower(lname);
2059 symbolNum = hash_find(&h_osd, lname);
2060 Xfree(lname);
2061 }
2062
2063 return (symbolNum >= 0) ? osd->symbptrs[symbolNum] : NULL;
2064 }
2065
osdfunc_printvar(int const cvaridx)2066 static int osdfunc_printvar(int const cvaridx)
2067 {
2068 auto& pData = *osd->cvars[cvaridx].pData;
2069
2070 switch (pData.flags & CVAR_TYPEMASK)
2071 {
2072 case CVAR_FLOAT:
2073 OSD_Printf("\"%s\" is \"%f\"\n%s [%d-%d]: %s\n", pData.name, *pData.f, pData.name, pData.min, pData.max, pData.desc);
2074 return OSDCMD_OK;
2075 case CVAR_DOUBLE:
2076 OSD_Printf("\"%s\" is \"%f\"\n%s [%d-%d]: %s\n", pData.name, *pData.d, pData.name, pData.min, pData.max, pData.desc);
2077 return OSDCMD_OK;
2078 case CVAR_INT:
2079 case CVAR_BOOL:
2080 OSD_Printf("\"%s\" is \"%d\"\n%s [%d%c%d]: %s\n", pData.name, *pData.i32, pData.name, pData.min,
2081 (pData.flags & CVAR_BOOL ? '/' : '-'), pData.max, pData.desc);
2082 return OSDCMD_OK;
2083 case CVAR_UINT:
2084 OSD_Printf("\"%s\" is \"%d\"\n%s [%d-%d]: %s\n", pData.name, *pData.u32, pData.name, pData.min, pData.max, pData.desc);
2085 return OSDCMD_OK;
2086 case CVAR_STRING:
2087 OSD_Printf("\"%s\" is \"%s\"\n%s: %s\n", pData.name, pData.string, pData.name, pData.desc);
2088 return OSDCMD_OK;
2089 default:
2090 EDUKE32_UNREACHABLE_SECTION(return OSDCMD_OK);
2091 }
2092 }
2093
osdcmd_cvar_set(osdcmdptr_t parm)2094 int osdcmd_cvar_set(osdcmdptr_t parm)
2095 {
2096 int const printValue = (parm->numparms == 0);
2097 int const cvaridx = hash_findcase(&h_cvars, parm->name);
2098
2099 Bassert(cvaridx >= 0);
2100
2101 auto &pData = *osd->cvars[cvaridx].pData;
2102
2103 if (pData.flags & CVAR_READONLY)
2104 {
2105 OSD_Printf("Cvar \"%s\" is read only.\n", pData.name);
2106 return OSDCMD_OK;
2107 }
2108
2109 if (printValue)
2110 return osdfunc_printvar(cvaridx);
2111
2112 switch (pData.flags & CVAR_TYPEMASK)
2113 {
2114 case CVAR_FLOAT:
2115 {
2116 Bsscanf(parm->parms[0], "%f", pData.f);
2117 *pData.f = clamp(*pData.f, pData.min, pData.max);
2118 pData.flags |= CVAR_MODIFIED;
2119
2120 if (!OSD_ParsingScript())
2121 OSD_Printf("%s %f", pData.name, *pData.f);
2122 }
2123 break;
2124 case CVAR_DOUBLE:
2125 {
2126 Bsscanf(parm->parms[0], "%lf", pData.d);
2127 *pData.d = clamp(*pData.d, pData.min, pData.max);
2128 pData.flags |= CVAR_MODIFIED;
2129
2130 if (!OSD_ParsingScript())
2131 OSD_Printf("%s %f", pData.name, *pData.d);
2132 }
2133 break;
2134 case CVAR_INT:
2135 case CVAR_BOOL:
2136 {
2137 *pData.i32 = clamp(Batoi(parm->parms[0]), pData.min, pData.max);
2138
2139 if ((pData.flags & CVAR_TYPEMASK) == CVAR_BOOL)
2140 *pData.i32 = (*pData.i32 != 0);
2141
2142 pData.flags |= CVAR_MODIFIED;
2143
2144 if (!OSD_ParsingScript())
2145 OSD_Printf("%s %d", pData.name, *pData.i32);
2146 }
2147 break;
2148 case CVAR_UINT:
2149 {
2150 *pData.u32 = clamp(Bstrtoul(parm->parms[0], NULL, 0), pData.min, pData.max);
2151 pData.flags |= CVAR_MODIFIED;
2152
2153 if (!OSD_ParsingScript())
2154 OSD_Printf("%s %d", pData.name, *pData.u32);
2155 }
2156 break;
2157 case CVAR_STRING:
2158 {
2159 Bstrncpy(pData.string, parm->parms[0], pData.max-1);
2160 (pData.string)[pData.max-1] = 0;
2161 pData.flags |= CVAR_MODIFIED;
2162
2163 if (!OSD_ParsingScript())
2164 OSD_Printf("%s %s", pData.name, pData.string);
2165 }
2166 break;
2167 default:
2168 EDUKE32_UNREACHABLE_SECTION(break);
2169 }
2170
2171 #ifdef USE_OPENGL
2172 if (!OSD_ParsingScript())
2173 {
2174 switch (pData.flags & (CVAR_RESTARTVID|CVAR_INVALIDATEALL|CVAR_INVALIDATEART))
2175 {
2176 case CVAR_RESTARTVID:
2177 osdcmd_restartvid(NULL);
2178 break;
2179 case CVAR_INVALIDATEALL:
2180 gltexinvalidatetype(INVALIDATE_ALL);
2181 fallthrough__;
2182 case CVAR_INVALIDATEART:
2183 gltexinvalidatetype(INVALIDATE_ART);
2184 #ifdef POLYMER
2185 if (videoGetRenderMode() == REND_POLYMER)
2186 polymer_texinvalidate();
2187 #endif
2188 break;
2189 }
2190 }
2191 #endif
2192
2193 if (!OSD_ParsingScript())
2194 OSD_Printf("\n");
2195
2196 return OSDCMD_OK;
2197 }
2198
OSD_WriteAliases(buildvfs_FILE fp)2199 void OSD_WriteAliases(buildvfs_FILE fp)
2200 {
2201 for (auto &symb : osd->symbptrs)
2202 {
2203 if (symb == NULL)
2204 break;
2205 else if (symb->func == (void *)OSD_ALIAS)
2206 {
2207 buildvfs_fputstr(fp, "alias \"");
2208 buildvfs_fputstrptr(fp, symb->name);
2209 buildvfs_fputstr(fp, "\" \"");
2210 buildvfs_fputstrptr(fp, symb->help);
2211 buildvfs_fputstr(fp, "\"\n");
2212 }
2213 }
2214 }
2215
OSD_WriteCvars(buildvfs_FILE fp)2216 void OSD_WriteCvars(buildvfs_FILE fp)
2217 {
2218 Bassert(fp);
2219
2220 char buf[64];
2221
2222 for (int i = 0; i < osd->numcvars; i++)
2223 {
2224 auto &pData = *osd->cvars[i].pData;
2225
2226 if (!(pData.flags & CVAR_NOSAVE) && OSD_CvarModified(&osd->cvars[i]))
2227 {
2228 switch (pData.flags & CVAR_TYPEMASK)
2229 {
2230 case CVAR_FLOAT:
2231 buildvfs_fputstrptr(fp, pData.name);
2232 snprintf(buf, sizeof(buf), " \"%f\"\n", *pData.f);
2233 buildvfs_fputstrptr(fp, buf);
2234 break;
2235 case CVAR_DOUBLE:
2236 buildvfs_fputstrptr(fp, pData.name);
2237 snprintf(buf, sizeof(buf), " \"%f\"\n", *pData.d);
2238 buildvfs_fputstrptr(fp, buf);
2239 break;
2240 case CVAR_INT:
2241 case CVAR_BOOL:
2242 buildvfs_fputstrptr(fp, pData.name);
2243 snprintf(buf, sizeof(buf), " \"%d\"\n", *pData.i32);
2244 buildvfs_fputstrptr(fp, buf);
2245 break;
2246 case CVAR_UINT:
2247 buildvfs_fputstrptr(fp, pData.name);
2248 snprintf(buf, sizeof(buf), " \"%u\"\n", *pData.u32);
2249 buildvfs_fputstrptr(fp, buf);
2250 break;
2251 case CVAR_STRING:
2252 if (pData.string && pData.string[0])
2253 {
2254 buildvfs_fputstrptr(fp, pData.name);
2255 buildvfs_fputstr(fp, " \"");
2256 buildvfs_fputstrptr(fp, pData.string);
2257 buildvfs_fputstr(fp, "\"\n");
2258 }
2259 break;
2260 default: EDUKE32_UNREACHABLE_SECTION(break);
2261 }
2262 }
2263 }
2264 }
2265