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