]> git.xonotic.org Git - xonotic/darkplaces.git/blob - common.c
Merge PR 'Use the text from modinfo.txt as the mod menu entry'
[xonotic/darkplaces.git] / common.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 */
21 // common.c -- misc functions used in client and server
22
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #ifndef WIN32
26 #include <unistd.h>
27 #endif
28
29 #include "quakedef.h"
30 #include "utf8lib.h"
31
32 cvar_t registered = {CF_CLIENT | CF_SERVER, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
33 cvar_t cmdline = {CF_CLIENT | CF_SERVER, "cmdline","0", "contains commandline the engine was launched with"};
34
35 // FIXME: Find a better place for these.
36 cvar_t cl_playermodel = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playermodel", "", "current player model in Nexuiz/Xonotic"};
37 cvar_t cl_playerskin = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playerskin", "", "current player skin in Nexuiz/Xonotic"};
38
39 char com_token[MAX_INPUTLINE];
40 unsigned com_token_len;
41
42 //===========================================================================
43
44 void SZ_Clear (sizebuf_t *buf)
45 {
46         buf->cursize = 0;
47 }
48
49 unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
50 {
51         unsigned char *data;
52
53         if (buf->cursize + length > buf->maxsize)
54         {
55                 if (!buf->allowoverflow)
56                         Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
57
58                 if (length > buf->maxsize)
59                         Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
60
61                 buf->overflowed = true;
62                 Con_Print("SZ_GetSpace: overflow\n");
63                 SZ_Clear (buf);
64         }
65
66         data = buf->data + buf->cursize;
67         buf->cursize += length;
68
69         return data;
70 }
71
72 void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
73 {
74         memcpy (SZ_GetSpace(buf,length),data,length);
75 }
76
77 // LadyHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
78 // attention, it has been eradicated from here, its only (former) use in
79 // all of darkplaces.
80
81 static const char *hexchar = "0123456789ABCDEF";
82 void Com_HexDumpToConsole(const unsigned char *data, int size)
83 {
84         int i, j, n;
85         char text[1024];
86         char *cur, *flushpointer;
87         const unsigned char *d;
88         cur = text;
89         flushpointer = text + 512;
90         for (i = 0;i < size;)
91         {
92                 n = 16;
93                 if (n > size - i)
94                         n = size - i;
95                 d = data + i;
96                 // print offset
97                 *cur++ = hexchar[(i >> 12) & 15];
98                 *cur++ = hexchar[(i >>  8) & 15];
99                 *cur++ = hexchar[(i >>  4) & 15];
100                 *cur++ = hexchar[(i >>  0) & 15];
101                 *cur++ = ':';
102                 // print hex
103                 for (j = 0;j < 16;j++)
104                 {
105                         if (j < n)
106                         {
107                                 *cur++ = hexchar[(d[j] >> 4) & 15];
108                                 *cur++ = hexchar[(d[j] >> 0) & 15];
109                         }
110                         else
111                         {
112                                 *cur++ = ' ';
113                                 *cur++ = ' ';
114                         }
115                         if ((j & 3) == 3)
116                                 *cur++ = ' ';
117                 }
118                 // print text
119                 for (j = 0;j < 16;j++)
120                 {
121                         if (j < n)
122                         {
123                                 // color change prefix character has to be treated specially
124                                 if (d[j] == STRING_COLOR_TAG)
125                                 {
126                                         *cur++ = STRING_COLOR_TAG;
127                                         *cur++ = STRING_COLOR_TAG;
128                                 }
129                                 else if (d[j] >= (unsigned char) ' ')
130                                         *cur++ = d[j];
131                                 else
132                                         *cur++ = '.';
133                         }
134                         else
135                                 *cur++ = ' ';
136                 }
137                 *cur++ = '\n';
138                 i += n;
139                 if (cur >= flushpointer || i >= size)
140                 {
141                         *cur++ = 0;
142                         Con_Print(text);
143                         cur = text;
144                 }
145         }
146 }
147
148 void SZ_HexDumpToConsole(const sizebuf_t *buf)
149 {
150         Com_HexDumpToConsole(buf->data, buf->cursize);
151 }
152
153
154 //============================================================================
155
156 /*
157 ==============
158 COM_Wordwrap
159
160 Word wraps a string. The wordWidth function is guaranteed to be called exactly
161 once for each word in the string, so it may be stateful, no idea what that
162 would be good for any more. At the beginning of the string, it will be called
163 for the char 0 to initialize a clean state, and then once with the string " "
164 (a space) so the routine knows how long a space is.
165
166 In case no single character fits into the given width, the wordWidth function
167 must return the width of exactly one character.
168
169 Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
170
171 The sum of the return values of the processLine function will be returned.
172 ==============
173 */
174 int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL)
175 {
176         // Logic is as follows:
177         //
178         // For each word or whitespace:
179         //   Newline found? Output current line, advance to next line. This is not a continuation. Continue.
180         //   Space found? Always add it to the current line, no matter if it fits.
181         //   Word found? Check if current line + current word fits.
182         //     If it fits, append it. Continue.
183         //     If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue.
184
185         qbool isContinuation = false;
186         float spaceWidth;
187         const char *startOfLine = string;
188         const char *cursor = string;
189         const char *end = string + length;
190         float spaceUsedInLine = 0;
191         float spaceUsedForWord;
192         int result = 0;
193         size_t wordLen;
194         size_t dummy;
195
196         dummy = 0;
197         wordWidth(passthroughCW, NULL, &dummy, -1);
198         dummy = 1;
199         spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
200
201         for(;;)
202         {
203                 char ch = (cursor < end) ? *cursor : 0;
204                 switch(ch)
205                 {
206                         case 0: // end of string
207                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
208                                 goto out;
209                         case '\n': // end of line
210                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
211                                 isContinuation = false;
212                                 ++cursor;
213                                 startOfLine = cursor;
214                                 break;
215                         case ' ': // space
216                                 ++cursor;
217                                 spaceUsedInLine += spaceWidth;
218                                 break;
219                         default: // word
220                                 wordLen = 1;
221                                 while(cursor + wordLen < end)
222                                 {
223                                         switch(cursor[wordLen])
224                                         {
225                                                 case 0:
226                                                 case '\n':
227                                                 case ' ':
228                                                         goto out_inner;
229                                                 default:
230                                                         ++wordLen;
231                                                         break;
232                                         }
233                                 }
234                                 out_inner:
235                                 spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line
236                                 if(wordLen < 1) // cannot happen according to current spec of wordWidth
237                                 {
238                                         wordLen = 1;
239                                         spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
240                                 }
241                                 if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
242                                 {
243                                         // we can simply append it
244                                         cursor += wordLen;
245                                         spaceUsedInLine += spaceUsedForWord;
246                                 }
247                                 else
248                                 {
249                                         // output current line
250                                         result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
251                                         isContinuation = true;
252                                         startOfLine = cursor;
253                                         cursor += wordLen;
254                                         spaceUsedInLine = continuationWidth + spaceUsedForWord;
255                                 }
256                 }
257         }
258         out:
259
260         return result;
261
262 /*
263         qbool isContinuation = false;
264         float currentWordSpace = 0;
265         const char *currentWord = 0;
266         float minReserve = 0;
267
268         float spaceUsedInLine = 0;
269         const char *currentLine = 0;
270         const char *currentLineEnd = 0;
271         float currentLineFinalWhitespace = 0;
272         const char *p;
273
274         int result = 0;
275         minReserve = charWidth(passthroughCW, 0);
276         minReserve += charWidth(passthroughCW, ' ');
277
278         if(maxWidth < continuationWidth + minReserve)
279                 maxWidth = continuationWidth + minReserve;
280
281         charWidth(passthroughCW, 0);
282
283         for(p = string; p < string + length; ++p)
284         {
285                 char c = *p;
286                 float w = charWidth(passthroughCW, c);
287
288                 if(!currentWord)
289                 {
290                         currentWord = p;
291                         currentWordSpace = 0;
292                 }
293
294                 if(!currentLine)
295                 {
296                         currentLine = p;
297                         spaceUsedInLine = isContinuation ? continuationWidth : 0;
298                         currentLineEnd = 0;
299                 }
300
301                 if(c == ' ')
302                 {
303                         // 1. I can add the word AND a space - then just append it.
304                         if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
305                         {
306                                 currentLineEnd = p; // note: space not included here
307                                 currentLineFinalWhitespace = w;
308                                 spaceUsedInLine += currentWordSpace + w;
309                         }
310                         // 2. I can just add the word - then append it, output current line and go to next one.
311                         else if(spaceUsedInLine + currentWordSpace <= maxWidth)
312                         {
313                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
314                                 currentLine = 0;
315                                 isContinuation = true;
316                         }
317                         // 3. Otherwise, output current line and go to next one, where I can add the word.
318                         else if(continuationWidth + currentWordSpace + w <= maxWidth)
319                         {
320                                 if(currentLineEnd)
321                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
322                                 currentLine = currentWord;
323                                 spaceUsedInLine = continuationWidth + currentWordSpace + w;
324                                 currentLineEnd = p;
325                                 currentLineFinalWhitespace = w;
326                                 isContinuation = true;
327                         }
328                         // 4. We can't even do that? Then output both current and next word as new lines.
329                         else
330                         {
331                                 if(currentLineEnd)
332                                 {
333                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
334                                         isContinuation = true;
335                                 }
336                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
337                                 currentLine = 0;
338                                 isContinuation = true;
339                         }
340                         currentWord = 0;
341                 }
342                 else if(c == '\n')
343                 {
344                         // 1. I can add the word - then do it.
345                         if(spaceUsedInLine + currentWordSpace <= maxWidth)
346                         {
347                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
348                         }
349                         // 2. Otherwise, output current line, next one and make tabula rasa.
350                         else
351                         {
352                                 if(currentLineEnd)
353                                 {
354                                         processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
355                                         isContinuation = true;
356                                 }
357                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
358                         }
359                         currentWord = 0;
360                         currentLine = 0;
361                         isContinuation = false;
362                 }
363                 else
364                 {
365                         currentWordSpace += w;
366                         if(
367                                 spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
368                                 &&
369                                 continuationWidth + currentWordSpace > maxWidth // can't join any other line...
370                         )
371                         {
372                                 // this word cannot join ANY line...
373                                 // so output the current line...
374                                 if(currentLineEnd)
375                                 {
376                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
377                                         isContinuation = true;
378                                 }
379
380                                 // then this word's beginning...
381                                 if(isContinuation)
382                                 {
383                                         // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces
384                                         float pieceWidth = maxWidth - continuationWidth;
385                                         const char *pos = currentWord;
386                                         currentWordSpace = 0;
387
388                                         // reset the char width function to a state where no kerning occurs (start of word)
389                                         charWidth(passthroughCW, ' ');
390                                         while(pos <= p)
391                                         {
392                                                 float w = charWidth(passthroughCW, *pos);
393                                                 if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
394                                                 {
395                                                         // print everything until it
396                                                         result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
397                                                         // go to here
398                                                         currentWord = pos;
399                                                         currentWordSpace = 0;
400                                                 }
401                                                 currentWordSpace += w;
402                                                 ++pos;
403                                         }
404                                         // now we have a currentWord that fits... set up its next line
405                                         // currentWordSpace has been set
406                                         // currentWord has been set
407                                         spaceUsedInLine = continuationWidth;
408                                         currentLine = currentWord;
409                                         currentLineEnd = 0;
410                                         isContinuation = true;
411                                 }
412                                 else
413                                 {
414                                         // we have a guarantee that it will fix (see if clause)
415                                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
416
417                                         // and use the rest of this word as new start of a line
418                                         currentWordSpace = w;
419                                         currentWord = p;
420                                         spaceUsedInLine = continuationWidth;
421                                         currentLine = p;
422                                         currentLineEnd = 0;
423                                         isContinuation = true;
424                                 }
425                         }
426                 }
427         }
428
429         if(!currentWord)
430         {
431                 currentWord = p;
432                 currentWordSpace = 0;
433         }
434
435         if(currentLine) // Same procedure as \n
436         {
437                 // Can I append the current word?
438                 if(spaceUsedInLine + currentWordSpace <= maxWidth)
439                         result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
440                 else
441                 {
442                         if(currentLineEnd)
443                         {
444                                 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
445                                 isContinuation = true;
446                         }
447                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
448                 }
449         }
450
451         return result;
452 */
453 }
454
455 /*
456 ==============
457 COM_ParseToken_Simple
458
459 Parse a token out of a string
460 Writes the token and its strlen to the com_token and com_token_len globals.
461 ==============
462 */
463 qbool COM_ParseToken_Simple(const char **datapointer, qbool returnnewline, qbool parsebackslash, qbool parsecomments)
464 {
465         int len;
466         int c;
467         const char *data = *datapointer;
468
469         com_token_len = len = 0;
470         com_token[0] = '\0';
471
472         if (!data)
473         {
474                 *datapointer = NULL;
475                 return false;
476         }
477
478 // skip whitespace
479 skipwhite:
480         // line endings:
481         // UNIX: \n
482         // Mac: \r
483         // Windows: \r\n
484         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
485         {
486                 if (*data == 0)
487                 {
488                         // end of file
489                         *datapointer = NULL;
490                         return false;
491                 }
492         }
493
494         // handle Windows line ending
495         if (data[0] == '\r' && data[1] == '\n')
496                 data++;
497
498         if (parsecomments && data[0] == '/' && data[1] == '/')
499         {
500                 // comment
501                 while (*data && *data != '\n' && *data != '\r')
502                         data++;
503                 goto skipwhite;
504         }
505         else if (parsecomments && data[0] == '/' && data[1] == '*')
506         {
507                 // comment
508                 data++;
509                 while (*data && (data[0] != '*' || data[1] != '/'))
510                         data++;
511                 if (*data)
512                         data++;
513                 if (*data)
514                         data++;
515                 goto skipwhite;
516         }
517         else if (*data == '\"')
518         {
519                 // quoted string
520                 for (data++;*data && *data != '\"';data++)
521                 {
522                         c = *data;
523                         if (*data == '\\' && parsebackslash)
524                         {
525                                 data++;
526                                 c = *data;
527                                 if (c == 'n')
528                                         c = '\n';
529                                 else if (c == 't')
530                                         c = '\t';
531                         }
532                         if (len < (int)sizeof(com_token) - 1)
533                                 com_token[len++] = c;
534                 }
535                 com_token[len] = '\0';
536                 com_token_len = len;
537                 if (*data == '\"')
538                         data++;
539                 *datapointer = data;
540                 return true;
541         }
542         else if (*data == '\r')
543         {
544                 // translate Mac line ending to UNIX
545                 com_token[len++] = '\n';data++;
546                 com_token[len] = '\0';
547                 com_token_len = len;
548                 *datapointer = data;
549                 return true;
550         }
551         else if (*data == '\n')
552         {
553                 // single character
554                 com_token[len++] = *data++;
555                 com_token[len] = '\0';
556                 com_token_len = len;
557                 *datapointer = data;
558                 return true;
559         }
560         else
561         {
562                 // regular word
563                 for (;!ISWHITESPACE(*data);data++)
564                         if (len < (int)sizeof(com_token) - 1)
565                                 com_token[len++] = *data;
566                 com_token[len] = '\0';
567                 com_token_len = len;
568                 *datapointer = data;
569                 return true;
570         }
571 }
572
573 /*
574 ==============
575 COM_ParseToken_QuakeC
576
577 Parse a token out of a string
578 Writes the token and its strlen to the com_token and com_token_len globals.
579 ==============
580 */
581 qbool COM_ParseToken_QuakeC(const char **datapointer, qbool returnnewline)
582 {
583         int len;
584         int c;
585         const char *data = *datapointer;
586
587         com_token_len = len = 0;
588         com_token[0] = '\0';
589
590         if (!data)
591         {
592                 *datapointer = NULL;
593                 return false;
594         }
595
596 // skip whitespace
597 skipwhite:
598         // line endings:
599         // UNIX: \n
600         // Mac: \r
601         // Windows: \r\n
602         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
603         {
604                 if (*data == 0)
605                 {
606                         // end of file
607                         *datapointer = NULL;
608                         return false;
609                 }
610         }
611
612         // handle Windows line ending
613         if (data[0] == '\r' && data[1] == '\n')
614                 data++;
615
616         if (data[0] == '/' && data[1] == '/')
617         {
618                 // comment
619                 while (*data && *data != '\n' && *data != '\r')
620                         data++;
621                 goto skipwhite;
622         }
623         else if (data[0] == '/' && data[1] == '*')
624         {
625                 // comment
626                 data++;
627                 while (*data && (data[0] != '*' || data[1] != '/'))
628                         data++;
629                 if (*data)
630                         data++;
631                 if (*data)
632                         data++;
633                 goto skipwhite;
634         }
635         else if (*data == '\"' || *data == '\'')
636         {
637                 // quoted string
638                 char quote = *data;
639                 for (data++;*data && *data != quote;data++)
640                 {
641                         c = *data;
642                         if (*data == '\\')
643                         {
644                                 data++;
645                                 c = *data;
646                                 if (c == 'n')
647                                         c = '\n';
648                                 else if (c == 't')
649                                         c = '\t';
650                         }
651                         if (len < (int)sizeof(com_token) - 1)
652                                 com_token[len++] = c;
653                 }
654                 com_token[len] = '\0';
655                 com_token_len = len;
656                 if (*data == quote)
657                         data++;
658                 *datapointer = data;
659                 return true;
660         }
661         else if (*data == '\r')
662         {
663                 // translate Mac line ending to UNIX
664                 com_token[len++] = '\n';data++;
665                 com_token[len] = '\0';
666                 com_token_len = len;
667                 *datapointer = data;
668                 return true;
669         }
670         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
671         {
672                 // single character
673                 com_token[len++] = *data++;
674                 com_token[len] = '\0';
675                 com_token_len = len;
676                 *datapointer = data;
677                 return true;
678         }
679         else
680         {
681                 // regular word
682                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
683                         if (len < (int)sizeof(com_token) - 1)
684                                 com_token[len++] = *data;
685                 com_token[len] = '\0';
686                 com_token_len = len;
687                 *datapointer = data;
688                 return true;
689         }
690 }
691
692 /*
693 ==============
694 COM_ParseToken_VM_Tokenize
695
696 Parse a token out of a string
697 Writes the token and its strlen to the com_token and com_token_len globals.
698 ==============
699 */
700 qbool COM_ParseToken_VM_Tokenize(const char **datapointer, qbool returnnewline)
701 {
702         int len;
703         int c;
704         const char *data = *datapointer;
705
706         com_token_len = len = 0;
707         com_token[0] = '\0';
708
709         if (!data)
710         {
711                 *datapointer = NULL;
712                 return false;
713         }
714
715 // skip whitespace
716 skipwhite:
717         // line endings:
718         // UNIX: \n
719         // Mac: \r
720         // Windows: \r\n
721         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
722         {
723                 if (*data == 0)
724                 {
725                         // end of file
726                         *datapointer = NULL;
727                         return false;
728                 }
729         }
730
731         // handle Windows line ending
732         if (data[0] == '\r' && data[1] == '\n')
733                 data++;
734
735         if (data[0] == '/' && data[1] == '/')
736         {
737                 // comment
738                 while (*data && *data != '\n' && *data != '\r')
739                         data++;
740                 goto skipwhite;
741         }
742         else if (data[0] == '/' && data[1] == '*')
743         {
744                 // comment
745                 data++;
746                 while (*data && (data[0] != '*' || data[1] != '/'))
747                         data++;
748                 if (*data)
749                         data++;
750                 if (*data)
751                         data++;
752                 goto skipwhite;
753         }
754         else if (*data == '\"' || *data == '\'')
755         {
756                 char quote = *data;
757                 // quoted string
758                 for (data++;*data && *data != quote;data++)
759                 {
760                         c = *data;
761                         if (*data == '\\')
762                         {
763                                 data++;
764                                 c = *data;
765                                 if (c == 'n')
766                                         c = '\n';
767                                 else if (c == 't')
768                                         c = '\t';
769                         }
770                         if (len < (int)sizeof(com_token) - 1)
771                                 com_token[len++] = c;
772                 }
773                 com_token[len] = '\0';
774                 com_token_len = len;
775                 if (*data == quote)
776                         data++;
777                 *datapointer = data;
778                 return true;
779         }
780         else if (*data == '\r')
781         {
782                 // translate Mac line ending to UNIX
783                 com_token[len++] = '\n';data++;
784                 com_token[len] = '\0';
785                 com_token_len = len;
786                 *datapointer = data;
787                 return true;
788         }
789         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
790         {
791                 // single character
792                 com_token[len++] = *data++;
793                 com_token[len] = '\0';
794                 com_token_len = len;
795                 *datapointer = data;
796                 return true;
797         }
798         else
799         {
800                 // regular word
801                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
802                         if (len < (int)sizeof(com_token) - 1)
803                                 com_token[len++] = *data;
804                 com_token[len] = '\0';
805                 com_token_len = len;
806                 *datapointer = data;
807                 return true;
808         }
809 }
810
811 /*
812 ==============
813 COM_ParseToken_Console
814
815 Parse a token out of a string, behaving like the qwcl console
816 Writes the token and its strlen to the com_token and com_token_len globals.
817 ==============
818 */
819 qbool COM_ParseToken_Console(const char **datapointer)
820 {
821         int len;
822         const char *data = *datapointer;
823
824         com_token_len = len = 0;
825         com_token[0] = '\0';
826
827         if (!data)
828         {
829                 *datapointer = NULL;
830                 return false;
831         }
832
833 // skip whitespace
834 skipwhite:
835         for (;ISWHITESPACE(*data);data++)
836         {
837                 if (*data == 0)
838                 {
839                         // end of file
840                         *datapointer = NULL;
841                         return false;
842                 }
843         }
844
845         if (*data == '/' && data[1] == '/')
846         {
847                 // comment
848                 while (*data && *data != '\n' && *data != '\r')
849                         data++;
850                 goto skipwhite;
851         }
852         else if (*data == '\"')
853         {
854                 // quoted string
855                 for (data++;*data && *data != '\"';data++)
856                 {
857                         // allow escaped " and \ case
858                         if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
859                                 data++;
860                         if (len < (int)sizeof(com_token) - 1)
861                                 com_token[len++] = *data;
862                 }
863                 com_token[len] = '\0';
864                 com_token_len = len;
865                 if (*data == '\"')
866                         data++;
867                 *datapointer = data;
868         }
869         else
870         {
871                 // regular word
872                 for (;!ISWHITESPACE(*data);data++)
873                         if (len < (int)sizeof(com_token) - 1)
874                                 com_token[len++] = *data;
875                 com_token[len] = '\0';
876                 com_token_len = len;
877                 *datapointer = data;
878         }
879
880         return true;
881 }
882
883 /*
884 ===============
885 Com_CalcRoll
886
887 Used by view and sv_user
888 ===============
889 */
890 float Com_CalcRoll (const vec3_t angles, const vec3_t velocity, const vec_t angleval, const vec_t velocityval)
891 {
892         vec3_t  forward, right, up;
893         float   sign;
894         float   side;
895
896         AngleVectors (angles, forward, right, up);
897         side = DotProduct (velocity, right);
898         sign = side < 0 ? -1 : 1;
899         side = fabs(side);
900
901         if (side < velocityval)
902                 side = side * angleval / velocityval;
903         else
904                 side = angleval;
905
906         return side*sign;
907
908 }
909
910 //===========================================================================
911
912 /*
913 ================
914 COM_Init
915 ================
916 */
917 void COM_Init_Commands (void)
918 {
919         int i, j, n;
920         char com_cmdline[MAX_INPUTLINE];
921
922         Cvar_RegisterVariable (&registered);
923         Cvar_RegisterVariable (&cmdline);
924         Cvar_RegisterVariable(&cl_playermodel);
925         Cvar_RegisterVirtual(&cl_playermodel, "_cl_playermodel");
926         Cvar_RegisterVariable(&cl_playerskin);
927         Cvar_RegisterVirtual(&cl_playerskin, "_cl_playerskin");
928
929         // reconstitute the command line for the cmdline externally visible cvar
930         n = 0;
931         for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
932         {
933                 i = 0;
934                 if (strstr(sys.argv[j], " "))
935                 {
936                         // arg contains whitespace, store quotes around it
937                         // This condition checks whether we can allow to put
938                         // in two quote characters.
939                         if (n >= ((int)sizeof(com_cmdline) - 2))
940                                 break;
941                         com_cmdline[n++] = '\"';
942                         // This condition checks whether we can allow one
943                         // more character and a quote character.
944                         while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
945                                 // FIXME: Doesn't quote special characters.
946                                 com_cmdline[n++] = sys.argv[j][i++];
947                         com_cmdline[n++] = '\"';
948                 }
949                 else
950                 {
951                         // This condition checks whether we can allow one
952                         // more character.
953                         while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
954                                 com_cmdline[n++] = sys.argv[j][i++];
955                 }
956                 if (n < ((int)sizeof(com_cmdline) - 1))
957                         com_cmdline[n++] = ' ';
958                 else
959                         break;
960         }
961         com_cmdline[n] = 0;
962         Cvar_SetQuick(&cmdline, com_cmdline);
963 }
964
965 /*
966 ============
967 va
968
969 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
970 ============
971 */
972 char *va(char *buf, size_t buflen, const char *format, ...)
973 {
974         va_list argptr;
975
976         va_start (argptr, format);
977         dpvsnprintf (buf, buflen, format,argptr);
978         va_end (argptr);
979
980         return buf;
981 }
982
983
984 //======================================
985
986 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
987
988 #undef snprintf
989 #undef vsnprintf
990
991 #ifdef WIN32
992 # define snprintf _snprintf
993 # define vsnprintf _vsnprintf
994 #endif
995
996
997 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
998 {
999         va_list args;
1000         int result;
1001
1002         va_start (args, format);
1003         result = dpvsnprintf (buffer, buffersize, format, args);
1004         va_end (args);
1005
1006         return result;
1007 }
1008
1009
1010 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
1011 {
1012         int result;
1013
1014 #if _MSC_VER >= 1400
1015         result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
1016 #else
1017         result = vsnprintf (buffer, buffersize, format, args);
1018 #endif
1019         if (result < 0 || (size_t)result >= buffersize)
1020         {
1021                 buffer[buffersize - 1] = '\0';
1022                 // we could be inside Con_Printf
1023                 if (result < 0)
1024                         Sys_Printf("dpvsnprintf: output error, buffer size %lu\n", (unsigned long)buffersize);
1025                 else
1026                         Sys_Printf("dpvsnprintf: truncated to %lu bytes: \"%s\"\n", (unsigned long)buffersize - 1, buffer);
1027                 return -1;
1028         }
1029
1030         return result;
1031 }
1032
1033
1034 //======================================
1035
1036 size_t COM_ToLowerString(const char *in, char *out, size_t size_out)
1037 {
1038         const char *out_start = out;
1039
1040         if (size_out == 0)
1041                 return 0;
1042
1043         if(utf8_enable.integer)
1044         {
1045                 *out = 0;
1046                 while(*in && size_out > 1)
1047                 {
1048                         int n;
1049                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1050                         ch = u8_tolower(ch);
1051                         n = u8_fromchar(ch, out, size_out);
1052                         out += n; // before the break so the return is correct
1053                         if(n <= 0)
1054                                 break;
1055                         size_out -= n;
1056                 }
1057                 return out - out_start;
1058         }
1059
1060         while (*in && size_out > 1)
1061         {
1062                 if (*in >= 'A' && *in <= 'Z')
1063                         *out++ = *in++ + 'a' - 'A';
1064                 else
1065                         *out++ = *in++;
1066                 size_out--;
1067         }
1068         *out = '\0';
1069         return out - out_start;
1070 }
1071
1072 size_t COM_ToUpperString(const char *in, char *out, size_t size_out)
1073 {
1074         const char *out_start = out;
1075
1076         if (size_out == 0)
1077                 return 0;
1078
1079         if(utf8_enable.integer)
1080         {
1081                 *out = 0;
1082                 while(*in && size_out > 1)
1083                 {
1084                         int n;
1085                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1086                         ch = u8_toupper(ch);
1087                         n = u8_fromchar(ch, out, size_out);
1088                         out += n; // before the break so the return is correct
1089                         if(n <= 0)
1090                                 break;
1091                         size_out -= n;
1092                 }
1093                 return out - out_start;
1094         }
1095
1096         while (*in && size_out > 1)
1097         {
1098                 if (*in >= 'a' && *in <= 'z')
1099                         *out++ = *in++ + 'A' - 'a';
1100                 else
1101                         *out++ = *in++;
1102                 size_out--;
1103         }
1104         *out = '\0';
1105         return out - out_start;
1106 }
1107
1108 int COM_StringBeginsWith(const char *s, const char *match)
1109 {
1110         for (;*s && *match;s++, match++)
1111                 if (*s != *match)
1112                         return false;
1113         return true;
1114 }
1115
1116 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1117 {
1118         int argc, commentprefixlength;
1119         char *tokenbufend;
1120         const char *l;
1121         argc = 0;
1122         tokenbufend = tokenbuf + tokenbufsize;
1123         l = *text;
1124         commentprefixlength = 0;
1125         if (commentprefix)
1126                 commentprefixlength = (int)strlen(commentprefix);
1127         while (*l && *l != '\n' && *l != '\r')
1128         {
1129                 if (!ISWHITESPACE(*l))
1130                 {
1131                         if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1132                         {
1133                                 while (*l && *l != '\n' && *l != '\r')
1134                                         l++;
1135                                 break;
1136                         }
1137                         if (argc >= maxargc)
1138                                 return -1;
1139                         argv[argc++] = tokenbuf;
1140                         if (*l == '"')
1141                         {
1142                                 l++;
1143                                 while (*l && *l != '"')
1144                                 {
1145                                         if (tokenbuf >= tokenbufend)
1146                                                 return -1;
1147                                         *tokenbuf++ = *l++;
1148                                 }
1149                                 if (*l == '"')
1150                                         l++;
1151                         }
1152                         else
1153                         {
1154                                 while (!ISWHITESPACE(*l))
1155                                 {
1156                                         if (tokenbuf >= tokenbufend)
1157                                                 return -1;
1158                                         *tokenbuf++ = *l++;
1159                                 }
1160                         }
1161                         if (tokenbuf >= tokenbufend)
1162                                 return -1;
1163                         *tokenbuf++ = 0;
1164                 }
1165                 else
1166                         l++;
1167         }
1168         // line endings:
1169         // UNIX: \n
1170         // Mac: \r
1171         // Windows: \r\n
1172         if (*l == '\r')
1173                 l++;
1174         if (*l == '\n')
1175                 l++;
1176         *text = l;
1177         return argc;
1178 }
1179
1180 /*
1181 ============
1182 COM_StringLengthNoColors
1183
1184 calculates the visible width of a color coded string.
1185
1186 *valid is filled with TRUE if the string is a valid colored string (that is, if
1187 it does not end with an unfinished color code). If it gets filled with FALSE, a
1188 fix would be adding a STRING_COLOR_TAG at the end of the string.
1189
1190 valid can be set to NULL if the caller doesn't care.
1191
1192 For size_s, specify the maximum number of characters from s to use, or 0 to use
1193 all characters until the zero terminator.
1194 ============
1195 */
1196 size_t
1197 COM_StringLengthNoColors(const char *s, size_t size_s, qbool *valid)
1198 {
1199         const char *end = size_s ? (s + size_s) : NULL;
1200         size_t len = 0;
1201         for(;;)
1202         {
1203                 switch((s == end) ? 0 : *s)
1204                 {
1205                         case 0:
1206                                 if(valid)
1207                                         *valid = true;
1208                                 return len;
1209                         case STRING_COLOR_TAG:
1210                                 ++s;
1211                                 switch((s == end) ? 0 : *s)
1212                                 {
1213                                         case STRING_COLOR_RGB_TAG_CHAR:
1214                                                 if (s+1 != end && isxdigit(s[1]) &&
1215                                                         s+2 != end && isxdigit(s[2]) &&
1216                                                         s+3 != end && isxdigit(s[3]) )
1217                                                 {
1218                                                         s+=3;
1219                                                         break;
1220                                                 }
1221                                                 ++len; // STRING_COLOR_TAG
1222                                                 ++len; // STRING_COLOR_RGB_TAG_CHAR
1223                                                 break;
1224                                         case 0: // ends with unfinished color code!
1225                                                 ++len;
1226                                                 if(valid)
1227                                                         *valid = false;
1228                                                 return len;
1229                                         case STRING_COLOR_TAG: // escaped ^
1230                                                 ++len;
1231                                                 break;
1232                                         case '0': case '1': case '2': case '3': case '4':
1233                                         case '5': case '6': case '7': case '8': case '9': // color code
1234                                                 break;
1235                                         default: // not a color code
1236                                                 ++len; // STRING_COLOR_TAG
1237                                                 ++len; // the character
1238                                                 break;
1239                                 }
1240                                 break;
1241                         default:
1242                                 ++len;
1243                                 break;
1244                 }
1245                 ++s;
1246         }
1247         // never get here
1248 }
1249
1250 /*
1251 ============
1252 COM_StringDecolorize
1253
1254 removes color codes from a string.
1255
1256 If escape_carets is true, the resulting string will be safe for printing. If
1257 escape_carets is false, the function will just strip color codes (for logging
1258 for example).
1259 Returns the number of bytes written to the *out buffer excluding the \0 terminator.
1260
1261 If the output buffer size did not suffice for converting, the function returns 0.
1262 Generally, if escape_carets is false, the output buffer needs
1263 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1264 bytes. In any case, the function makes sure that the resulting string is
1265 zero terminated.
1266
1267 For size_in, specify the maximum number of characters from in to use, or 0 to use
1268 all characters until the zero terminator.
1269 ============
1270 */
1271 size_t COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qbool escape_carets)
1272 {
1273 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return 0; } } while(0)
1274
1275         const char *out_start = out;
1276         const char *end = size_in ? (in + size_in) : NULL;
1277
1278         if(size_out < 1)
1279                 return 0;
1280         for(;;)
1281         {
1282                 switch((in == end) ? 0 : *in)
1283                 {
1284                         case 0:
1285                                 *out = '\0';
1286                                 return out - out_start;
1287                         case STRING_COLOR_TAG:
1288                                 ++in;
1289                                 switch((in == end) ? 0 : *in)
1290                                 {
1291                                         case STRING_COLOR_RGB_TAG_CHAR:
1292                                                 if (in+1 != end && isxdigit(in[1]) &&
1293                                                         in+2 != end && isxdigit(in[2]) &&
1294                                                         in+3 != end && isxdigit(in[3]) )
1295                                                 {
1296                                                         in+=3;
1297                                                         break;
1298                                                 }
1299                                                 APPEND(STRING_COLOR_TAG);
1300                                                 if(escape_carets)
1301                                                         APPEND(STRING_COLOR_TAG);
1302                                                 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1303                                                 break;
1304                                         case 0: // ends with unfinished color code!
1305                                                 APPEND(STRING_COLOR_TAG);
1306                                                 // finish the code by appending another caret when escaping
1307                                                 if(escape_carets)
1308                                                         APPEND(STRING_COLOR_TAG);
1309                                                 *out = '\0';
1310                                                 return out - out_start;
1311                                         case STRING_COLOR_TAG: // escaped ^
1312                                                 APPEND(STRING_COLOR_TAG);
1313                                                 // append a ^ twice when escaping
1314                                                 if(escape_carets)
1315                                                         APPEND(STRING_COLOR_TAG);
1316                                                 break;
1317                                         case '0': case '1': case '2': case '3': case '4':
1318                                         case '5': case '6': case '7': case '8': case '9': // color code
1319                                                 break;
1320                                         default: // not a color code
1321                                                 APPEND(STRING_COLOR_TAG);
1322                                                 APPEND(*in);
1323                                                 break;
1324                                 }
1325                                 break;
1326                         default:
1327                                 APPEND(*in);
1328                                 break;
1329                 }
1330                 ++in;
1331         }
1332         // never get here
1333 #undef APPEND
1334 }
1335
1336
1337 /*
1338 ============
1339 String Copying
1340
1341 The glibc implementation of memccpy is several times faster than old functions that
1342 copy one byte at a time (even at -O3) and its advantage increases with string length.
1343 ============
1344 */
1345 #ifdef WIN32
1346         // memccpy() is standard in POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD, C23.
1347         // Microsoft supports it, but apparently complains if we use it.
1348         #undef memccpy
1349         #define memccpy _memccpy
1350 #endif
1351
1352 /** Chain-copies a string with truncation and efficiency (compared to strlcat()).
1353  * The destination ends at an absolute pointer instead of a relative offset
1354  * and a pointer to the \0 terminator is returned on success.
1355  * Truncates, warns, and returns the end pointer on overflow or unterminated source.
1356  * Guarantees \0 termination.    end = dst + sizeof(src[])
1357  */
1358 char *dp_stpecpy(char *dst, char *end, const char *src)
1359 {
1360         char *p = (char *)memccpy(dst, src, '\0', end - dst);
1361
1362         if (p)
1363                 return p - 1;
1364         end[-1] = '\0';
1365         Con_Printf(CON_WARN "%s: src string unterminated or truncated to %lu bytes: \"%s\"\n", __func__, (unsigned long)(dst == end ? 0 : (end - dst) - 1), dst);
1366         return end;
1367 }
1368
1369 /** Copies a measured byte sequence (unterminated string) to a null-terminated string.
1370  * Returns a pointer to the \0 terminator. Guarantees \0 termination.
1371  * Compared to ustr2stp(): truncates and warns on overflow.
1372  */
1373 char *dp_ustr2stp(char *dst, size_t dsize, const char *src, size_t slen)
1374 {
1375         if (slen >= dsize)
1376         {
1377                 slen = dsize - 1;
1378                 Con_Printf(CON_WARN "%s: src string truncated to %lu bytes: \"%.*s\"\n", __func__, (unsigned long)slen, (int)slen, src);
1379         }
1380         memcpy(dst, src, slen);
1381         dst[slen] = '\0';
1382         return &dst[slen];
1383 }
1384
1385 /** Copies a string, like strlcpy() but with a better return: the number of bytes copied
1386  * excluding the \0 terminator. Truncates and warns on overflow or unterminated source,
1387  * whereas strlcpy() truncates silently and overreads (possibly segfaulting).
1388  * Guarantees \0 termination.
1389  * See also: dp_stpecpy() and dp_ustr2stp().
1390  */
1391 size_t dp__strlcpy(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1392 {
1393         char *p = (char *)memccpy(dst, src, '\0', dsize);
1394
1395         if (p)
1396                 return (p - 1) - dst;
1397         dst[dsize - 1] = '\0';
1398         Con_Printf(CON_WARN "%s:%u: src string unterminated or truncated to %lu bytes: \"%s\"\n", func, line, (unsigned long)dsize - 1, dst);
1399         return dsize - 1;
1400 }
1401
1402 /** Catenates a string, like strlcat() but with a better return: the number of bytes copied
1403  * excluding the \0 terminator. Truncates and warns on overflow or unterminated source,
1404  * whereas strlcat() truncates silently and overreads (possibly segfaulting).
1405  * Guarantees \0 termination.
1406  * Inefficient like any strcat(), please use memcpy(), dp_stpecpy() or dp_strlcpy() instead.
1407  */
1408 size_t dp__strlcat(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1409 {
1410         size_t offset;
1411         char *p = (char *)memchr(dst, '\0', dsize);
1412
1413         if (!p)
1414                 p = dst;
1415         offset = p - dst;
1416         return dp__strlcpy(p, src, dsize - offset, func, line) + offset;
1417 }
1418
1419
1420
1421 void FindFraction(double val, int *num, int *denom, int denomMax)
1422 {
1423         int i;
1424         double bestdiff;
1425         // initialize
1426         bestdiff = fabs(val);
1427         *num = 0;
1428         *denom = 1;
1429
1430         for(i = 1; i <= denomMax; ++i)
1431         {
1432                 int inum = (int) floor(0.5 + val * i);
1433                 double diff = fabs(val - inum / (double)i);
1434                 if(diff < bestdiff)
1435                 {
1436                         bestdiff = diff;
1437                         *num = inum;
1438                         *denom = i;
1439                 }
1440         }
1441 }
1442
1443 // decodes an XPM from C syntax
1444 char **XPM_DecodeString(const char *in)
1445 {
1446         static char *tokens[257];
1447         static char lines[257][512];
1448         size_t line = 0;
1449
1450         // skip until "{" token
1451         while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1452
1453         // now, read in succession: string, comma-or-}
1454         while(COM_ParseToken_QuakeC(&in, false))
1455         {
1456                 tokens[line] = lines[line];
1457                 dp_strlcpy(lines[line++], com_token, sizeof(lines[0]));
1458                 if(!COM_ParseToken_QuakeC(&in, false))
1459                         return NULL;
1460                 if(!strcmp(com_token, "}"))
1461                         break;
1462                 if(strcmp(com_token, ","))
1463                         return NULL;
1464                 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1465                         return NULL;
1466         }
1467
1468         return tokens;
1469 }
1470
1471 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1472 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1473 {
1474         unsigned char i0 = (bytes > 0) ? in[0] : 0;
1475         unsigned char i1 = (bytes > 1) ? in[1] : 0;
1476         unsigned char i2 = (bytes > 2) ? in[2] : 0;
1477         unsigned char o0 = base64[i0 >> 2];
1478         unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1479         unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1480         unsigned char o3 = base64[i2 & 077];
1481         out[0] = (bytes > 0) ? o0 : '?';
1482         out[1] = (bytes > 0) ? o1 : '?';
1483         out[2] = (bytes > 1) ? o2 : '=';
1484         out[3] = (bytes > 2) ? o3 : '=';
1485 }
1486
1487 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1488 {
1489         size_t blocks, i;
1490         // expand the out-buffer
1491         blocks = (buflen + 2) / 3;
1492         if(blocks*4 > outbuflen)
1493                 return 0;
1494         for(i = blocks; i > 0; )
1495         {
1496                 --i;
1497                 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));
1498         }
1499         return blocks * 4;
1500 }