2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 // common.c -- misc functions used in client and server
31 cvar_t registered = {CVAR_CLIENT | CVAR_SERVER, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
32 cvar_t cmdline = {CVAR_CLIENT | CVAR_SERVER, "cmdline","0", "contains commandline the engine was launched with"};
34 // FIXME: Find a better place for these.
35 cvar_t cl_playermodel = {CVAR_CLIENT | CVAR_SERVER | CVAR_USERINFO | CVAR_SAVE, "playermodel", "", "current player model in Nexuiz/Xonotic"};
36 cvar_t cl_playerskin = {CVAR_CLIENT | CVAR_SERVER | CVAR_USERINFO | CVAR_SAVE, "playerskin", "", "current player skin in Nexuiz/Xonotic"};
38 char com_token[MAX_INPUTLINE];
42 const char *gamenetworkfiltername; // same as gamename currently but with _ in place of spaces so that "getservers" packets parse correctly (this also means the
43 const char *gamedirname1;
44 const char *gamedirname2;
45 const char *gamescreenshotname;
46 const char *gameuserdirname;
47 char com_modname[MAX_OSPATH] = "";
49 //===========================================================================
51 void SZ_Clear (sizebuf_t *buf)
56 unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
60 if (buf->cursize + length > buf->maxsize)
62 if (!buf->allowoverflow)
63 Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
65 if (length > buf->maxsize)
66 Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
68 buf->overflowed = true;
69 Con_Print("SZ_GetSpace: overflow\n");
73 data = buf->data + buf->cursize;
74 buf->cursize += length;
79 void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
81 memcpy (SZ_GetSpace(buf,length),data,length);
84 // LadyHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
85 // attention, it has been eradicated from here, its only (former) use in
88 static const char *hexchar = "0123456789ABCDEF";
89 void Com_HexDumpToConsole(const unsigned char *data, int size)
93 char *cur, *flushpointer;
94 const unsigned char *d;
96 flushpointer = text + 512;
104 *cur++ = hexchar[(i >> 12) & 15];
105 *cur++ = hexchar[(i >> 8) & 15];
106 *cur++ = hexchar[(i >> 4) & 15];
107 *cur++ = hexchar[(i >> 0) & 15];
110 for (j = 0;j < 16;j++)
114 *cur++ = hexchar[(d[j] >> 4) & 15];
115 *cur++ = hexchar[(d[j] >> 0) & 15];
126 for (j = 0;j < 16;j++)
130 // color change prefix character has to be treated specially
131 if (d[j] == STRING_COLOR_TAG)
133 *cur++ = STRING_COLOR_TAG;
134 *cur++ = STRING_COLOR_TAG;
136 else if (d[j] >= (unsigned char) ' ')
146 if (cur >= flushpointer || i >= size)
155 void SZ_HexDumpToConsole(const sizebuf_t *buf)
157 Com_HexDumpToConsole(buf->data, buf->cursize);
161 //============================================================================
167 Word wraps a string. The wordWidth function is guaranteed to be called exactly
168 once for each word in the string, so it may be stateful, no idea what that
169 would be good for any more. At the beginning of the string, it will be called
170 for the char 0 to initialize a clean state, and then once with the string " "
171 (a space) so the routine knows how long a space is.
173 In case no single character fits into the given width, the wordWidth function
174 must return the width of exactly one character.
176 Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
178 The sum of the return values of the processLine function will be returned.
181 int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL)
183 // Logic is as follows:
185 // For each word or whitespace:
186 // Newline found? Output current line, advance to next line. This is not a continuation. Continue.
187 // Space found? Always add it to the current line, no matter if it fits.
188 // Word found? Check if current line + current word fits.
189 // If it fits, append it. Continue.
190 // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue.
192 qboolean isContinuation = false;
194 const char *startOfLine = string;
195 const char *cursor = string;
196 const char *end = string + length;
197 float spaceUsedInLine = 0;
198 float spaceUsedForWord;
204 wordWidth(passthroughCW, NULL, &dummy, -1);
206 spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
210 char ch = (cursor < end) ? *cursor : 0;
213 case 0: // end of string
214 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
216 case '\n': // end of line
217 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
218 isContinuation = false;
220 startOfLine = cursor;
224 spaceUsedInLine += spaceWidth;
228 while(cursor + wordLen < end)
230 switch(cursor[wordLen])
242 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
243 if(wordLen < 1) // cannot happen according to current spec of wordWidth
246 spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
248 if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
250 // we can simply append it
252 spaceUsedInLine += spaceUsedForWord;
256 // output current line
257 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
258 isContinuation = true;
259 startOfLine = cursor;
261 spaceUsedInLine = continuationWidth + spaceUsedForWord;
270 qboolean isContinuation = false;
271 float currentWordSpace = 0;
272 const char *currentWord = 0;
273 float minReserve = 0;
275 float spaceUsedInLine = 0;
276 const char *currentLine = 0;
277 const char *currentLineEnd = 0;
278 float currentLineFinalWhitespace = 0;
282 minReserve = charWidth(passthroughCW, 0);
283 minReserve += charWidth(passthroughCW, ' ');
285 if(maxWidth < continuationWidth + minReserve)
286 maxWidth = continuationWidth + minReserve;
288 charWidth(passthroughCW, 0);
290 for(p = string; p < string + length; ++p)
293 float w = charWidth(passthroughCW, c);
298 currentWordSpace = 0;
304 spaceUsedInLine = isContinuation ? continuationWidth : 0;
310 // 1. I can add the word AND a space - then just append it.
311 if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
313 currentLineEnd = p; // note: space not included here
314 currentLineFinalWhitespace = w;
315 spaceUsedInLine += currentWordSpace + w;
317 // 2. I can just add the word - then append it, output current line and go to next one.
318 else if(spaceUsedInLine + currentWordSpace <= maxWidth)
320 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
322 isContinuation = true;
324 // 3. Otherwise, output current line and go to next one, where I can add the word.
325 else if(continuationWidth + currentWordSpace + w <= maxWidth)
328 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
329 currentLine = currentWord;
330 spaceUsedInLine = continuationWidth + currentWordSpace + w;
332 currentLineFinalWhitespace = w;
333 isContinuation = true;
335 // 4. We can't even do that? Then output both current and next word as new lines.
340 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
341 isContinuation = true;
343 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
345 isContinuation = true;
351 // 1. I can add the word - then do it.
352 if(spaceUsedInLine + currentWordSpace <= maxWidth)
354 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
356 // 2. Otherwise, output current line, next one and make tabula rasa.
361 processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
362 isContinuation = true;
364 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
368 isContinuation = false;
372 currentWordSpace += w;
374 spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
376 continuationWidth + currentWordSpace > maxWidth // can't join any other line...
379 // this word cannot join ANY line...
380 // so output the current line...
383 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
384 isContinuation = true;
387 // then this word's beginning...
390 // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces
391 float pieceWidth = maxWidth - continuationWidth;
392 const char *pos = currentWord;
393 currentWordSpace = 0;
395 // reset the char width function to a state where no kerning occurs (start of word)
396 charWidth(passthroughCW, ' ');
399 float w = charWidth(passthroughCW, *pos);
400 if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
402 // print everything until it
403 result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
406 currentWordSpace = 0;
408 currentWordSpace += w;
411 // now we have a currentWord that fits... set up its next line
412 // currentWordSpace has been set
413 // currentWord has been set
414 spaceUsedInLine = continuationWidth;
415 currentLine = currentWord;
417 isContinuation = true;
421 // we have a guarantee that it will fix (see if clause)
422 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
424 // and use the rest of this word as new start of a line
425 currentWordSpace = w;
427 spaceUsedInLine = continuationWidth;
430 isContinuation = true;
439 currentWordSpace = 0;
442 if(currentLine) // Same procedure as \n
444 // Can I append the current word?
445 if(spaceUsedInLine + currentWordSpace <= maxWidth)
446 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
451 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
452 isContinuation = true;
454 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
464 COM_ParseToken_Simple
466 Parse a token out of a string
469 int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments)
473 const char *data = *datapointer;
490 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
500 // handle Windows line ending
501 if (data[0] == '\r' && data[1] == '\n')
504 if (parsecomments && data[0] == '/' && data[1] == '/')
507 while (*data && *data != '\n' && *data != '\r')
511 else if (parsecomments && data[0] == '/' && data[1] == '*')
515 while (*data && (data[0] != '*' || data[1] != '/'))
523 else if (*data == '\"')
526 for (data++;*data && *data != '\"';data++)
529 if (*data == '\\' && parsebackslash)
538 if (len < (int)sizeof(com_token) - 1)
539 com_token[len++] = c;
547 else if (*data == '\r')
549 // translate Mac line ending to UNIX
550 com_token[len++] = '\n';data++;
555 else if (*data == '\n')
558 com_token[len++] = *data++;
566 for (;!ISWHITESPACE(*data);data++)
567 if (len < (int)sizeof(com_token) - 1)
568 com_token[len++] = *data;
577 COM_ParseToken_QuakeC
579 Parse a token out of a string
582 int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline)
586 const char *data = *datapointer;
603 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
613 // handle Windows line ending
614 if (data[0] == '\r' && data[1] == '\n')
617 if (data[0] == '/' && data[1] == '/')
620 while (*data && *data != '\n' && *data != '\r')
624 else if (data[0] == '/' && data[1] == '*')
628 while (*data && (data[0] != '*' || data[1] != '/'))
636 else if (*data == '\"' || *data == '\'')
640 for (data++;*data && *data != quote;data++)
652 if (len < (int)sizeof(com_token) - 1)
653 com_token[len++] = c;
661 else if (*data == '\r')
663 // translate Mac line ending to UNIX
664 com_token[len++] = '\n';data++;
669 else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
672 com_token[len++] = *data++;
680 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
681 if (len < (int)sizeof(com_token) - 1)
682 com_token[len++] = *data;
691 COM_ParseToken_VM_Tokenize
693 Parse a token out of a string
696 int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline)
700 const char *data = *datapointer;
717 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
727 // handle Windows line ending
728 if (data[0] == '\r' && data[1] == '\n')
731 if (data[0] == '/' && data[1] == '/')
734 while (*data && *data != '\n' && *data != '\r')
738 else if (data[0] == '/' && data[1] == '*')
742 while (*data && (data[0] != '*' || data[1] != '/'))
750 else if (*data == '\"' || *data == '\'')
754 for (data++;*data && *data != quote;data++)
766 if (len < (int)sizeof(com_token) - 1)
767 com_token[len++] = c;
775 else if (*data == '\r')
777 // translate Mac line ending to UNIX
778 com_token[len++] = '\n';data++;
783 else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
786 com_token[len++] = *data++;
794 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
795 if (len < (int)sizeof(com_token) - 1)
796 com_token[len++] = *data;
805 COM_ParseToken_Console
807 Parse a token out of a string, behaving like the qwcl console
810 int COM_ParseToken_Console(const char **datapointer)
813 const char *data = *datapointer;
826 for (;ISWHITESPACE(*data);data++)
836 if (*data == '/' && data[1] == '/')
839 while (*data && *data != '\n' && *data != '\r')
843 else if (*data == '\"')
846 for (data++;*data && *data != '\"';data++)
848 // allow escaped " and \ case
849 if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
851 if (len < (int)sizeof(com_token) - 1)
852 com_token[len++] = *data;
862 for (;!ISWHITESPACE(*data);data++)
863 if (len < (int)sizeof(com_token) - 1)
864 com_token[len++] = *data;
877 Returns the position (1 to argc-1) in the program's argument list
878 where the given parameter apears, or 0 if not present
881 int COM_CheckParm (const char *parm)
885 for (i=1 ; i<sys.argc ; i++)
888 continue; // NEXTSTEP sometimes clears appkit vars.
889 if (!strcmp (parm,sys.argv[i]))
896 //===========================================================================
900 gamemode_t com_startupgamemode;
901 gamemode_t com_startupgamegroup;
903 typedef struct gamemode_info_s
905 gamemode_t mode; // this gamemode
906 gamemode_t group; // different games with same group can switch automatically when gamedirs change
907 const char* prog_name; // not null
908 const char* cmdline; // not null
909 const char* gamename; // not null
910 const char* gamenetworkfiltername; // not null
911 const char* gamedirname1; // not null
912 const char* gamedirname2; // null
913 const char* gamescreenshotname; // not nul
914 const char* gameuserdirname; // not null
917 static const gamemode_info_t gamemode_info [GAME_COUNT] =
918 {// game basegame prog_name cmdline gamename gamenetworkfilername basegame modgame screenshot userdir // commandline option
919 { GAME_NORMAL, GAME_NORMAL, "", "-quake", "DarkPlaces-Quake", "DarkPlaces-Quake", "id1", NULL, "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -quake runs the game Quake (default)
920 { GAME_HIPNOTIC, GAME_NORMAL, "hipnotic", "-hipnotic", "Darkplaces-Hipnotic", "Darkplaces-Hipnotic", "id1", "hipnotic", "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon
921 { GAME_ROGUE, GAME_NORMAL, "rogue", "-rogue", "Darkplaces-Rogue", "Darkplaces-Rogue", "id1", "rogue", "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity
922 { GAME_NEHAHRA, GAME_NORMAL, "nehahra", "-nehahra", "DarkPlaces-Nehahra", "DarkPlaces-Nehahra", "id1", "nehahra", "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -nehahra runs The Seal of Nehahra movie and game
923 { GAME_QUOTH, GAME_NORMAL, "quoth", "-quoth", "Darkplaces-Quoth", "Darkplaces-Quoth", "id1", "quoth", "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -quoth runs the Quoth mod for playing community maps made for it
924 { GAME_NEXUIZ, GAME_NEXUIZ, "nexuiz", "-nexuiz", "Nexuiz", "Nexuiz", "data", NULL, "nexuiz", "nexuiz" }, // COMMANDLINEOPTION: Game: -nexuiz runs the multiplayer game Nexuiz
925 { GAME_XONOTIC, GAME_XONOTIC, "xonotic", "-xonotic", "Xonotic", "Xonotic", "data", NULL, "xonotic", "xonotic" }, // COMMANDLINEOPTION: Game: -xonotic runs the multiplayer game Xonotic
926 { GAME_TRANSFUSION, GAME_TRANSFUSION, "transfusion", "-transfusion", "Transfusion", "Transfusion", "basetf", NULL, "transfusion", "transfusion" }, // COMMANDLINEOPTION: Game: -transfusion runs Transfusion (the recreation of Blood in Quake)
927 { GAME_GOODVSBAD2, GAME_GOODVSBAD2, "gvb2", "-goodvsbad2", "GoodVs.Bad2", "GoodVs.Bad2", "rts", NULL, "gvb2", "gvb2" }, // COMMANDLINEOPTION: Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2
928 { GAME_TEU, GAME_TEU, "teu", "-teu", "TheEvilUnleashed", "TheEvilUnleashed", "baseteu", NULL, "teu", "teu" }, // COMMANDLINEOPTION: Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces)
929 { GAME_BATTLEMECH, GAME_BATTLEMECH, "battlemech", "-battlemech", "Battlemech", "Battlemech", "base", NULL, "battlemech", "battlemech" }, // COMMANDLINEOPTION: Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech
930 { GAME_ZYMOTIC, GAME_ZYMOTIC, "zymotic", "-zymotic", "Zymotic", "Zymotic", "basezym", NULL, "zymotic", "zymotic" }, // COMMANDLINEOPTION: Game: -zymotic runs the singleplayer game Zymotic
931 { GAME_SETHERAL, GAME_SETHERAL, "setheral", "-setheral", "Setheral", "Setheral", "data", NULL, "setheral", "setheral" }, // COMMANDLINEOPTION: Game: -setheral runs the multiplayer game Setheral
932 { GAME_TENEBRAE, GAME_NORMAL, "tenebrae", "-tenebrae", "DarkPlaces-Tenebrae", "DarkPlaces-Tenebrae", "id1", "tenebrae", "dp", "darkplaces" }, // COMMANDLINEOPTION: Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented)
933 { GAME_NEOTERIC, GAME_NORMAL, "neoteric", "-neoteric", "Neoteric", "Neoteric", "id1", "neobase", "neo", "darkplaces" }, // COMMANDLINEOPTION: Game: -neoteric runs the game Neoteric
934 { GAME_OPENQUARTZ, GAME_NORMAL, "openquartz", "-openquartz", "OpenQuartz", "OpenQuartz", "id1", NULL, "openquartz", "darkplaces" }, // COMMANDLINEOPTION: Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content
935 { GAME_PRYDON, GAME_NORMAL, "prydon", "-prydon", "PrydonGate", "PrydonGate", "id1", "prydon", "prydon", "darkplaces" }, // COMMANDLINEOPTION: Game: -prydon runs the topdown point and click action-RPG Prydon Gate
936 { GAME_DELUXEQUAKE, GAME_DELUXEQUAKE, "dq", "-dq", "Deluxe Quake", "Deluxe_Quake", "basedq", "extradq", "basedq", "dq" }, // COMMANDLINEOPTION: Game: -dq runs the game Deluxe Quake
937 { GAME_THEHUNTED, GAME_THEHUNTED, "thehunted", "-thehunted", "The Hunted", "The_Hunted", "thdata", NULL, "th", "thehunted" }, // COMMANDLINEOPTION: Game: -thehunted runs the game The Hunted
938 { GAME_DEFEATINDETAIL2, GAME_DEFEATINDETAIL2, "did2", "-did2", "Defeat In Detail 2", "Defeat_In_Detail_2", "data", NULL, "did2_", "did2" }, // COMMANDLINEOPTION: Game: -did2 runs the game Defeat In Detail 2
939 { GAME_DARSANA, GAME_DARSANA, "darsana", "-darsana", "Darsana", "Darsana", "ddata", NULL, "darsana", "darsana" }, // COMMANDLINEOPTION: Game: -darsana runs the game Darsana
940 { GAME_CONTAGIONTHEORY, GAME_CONTAGIONTHEORY, "contagiontheory", "-contagiontheory", "Contagion Theory", "Contagion_Theory", "ctdata", NULL, "ct", "contagiontheory" }, // COMMANDLINEOPTION: Game: -contagiontheory runs the game Contagion Theory
941 { GAME_EDU2P, GAME_EDU2P, "edu2p", "-edu2p", "EDU2 Prototype", "EDU2_Prototype", "id1", "edu2", "edu2_p", "edu2prototype" }, // COMMANDLINEOPTION: Game: -edu2p runs the game Edu2 prototype
942 { GAME_PROPHECY, GAME_PROPHECY, "prophecy", "-prophecy", "Prophecy", "Prophecy", "gamedata", NULL, "phcy", "prophecy" }, // COMMANDLINEOPTION: Game: -prophecy runs the game Prophecy
943 { GAME_BLOODOMNICIDE, GAME_BLOODOMNICIDE, "omnicide", "-omnicide", "Blood Omnicide", "Blood_Omnicide", "kain", NULL, "omnicide", "omnicide" }, // COMMANDLINEOPTION: Game: -omnicide runs the game Blood Omnicide
944 { GAME_STEELSTORM, GAME_STEELSTORM, "steelstorm", "-steelstorm", "Steel-Storm", "Steel-Storm", "gamedata", NULL, "ss", "steelstorm" }, // COMMANDLINEOPTION: Game: -steelstorm runs the game Steel Storm
945 { GAME_STEELSTORM2, GAME_STEELSTORM2, "steelstorm2", "-steelstorm2", "Steel Storm 2", "Steel_Storm_2", "gamedata", NULL, "ss2", "steelstorm2" }, // COMMANDLINEOPTION: Game: -steelstorm2 runs the game Steel Storm 2
946 { GAME_SSAMMO, GAME_SSAMMO, "steelstorm-ammo", "-steelstormammo", "Steel Storm A.M.M.O.", "Steel_Storm_A.M.M.O.", "gamedata", NULL, "ssammo", "steelstorm-ammo" }, // COMMANDLINEOPTION: Game: -steelstormammo runs the game Steel Storm A.M.M.O.
947 { GAME_STEELSTORMREVENANTS, GAME_STEELSTORMREVENANTS, "steelstorm-revenants", "-steelstormrev", "Steel Storm: Revenants", "Steel_Storm_Revenants", "base", NULL, "ssrev", "steelstorm-revenants" }, // COMMANDLINEOPTION: Game: -steelstormrev runs the game Steel Storm: Revenants
948 { GAME_TOMESOFMEPHISTOPHELES, GAME_TOMESOFMEPHISTOPHELES, "tomesofmephistopheles","-tomesofmephistopheles", "Tomes of Mephistopheles", "Tomes_of_Mephistopheles", "gamedata", NULL, "tom", "tomesofmephistopheles" }, // COMMANDLINEOPTION: Game: -tomesofmephistopheles runs the game Tomes of Mephistopheles
949 { GAME_STRAPBOMB, GAME_STRAPBOMB, "strapbomb", "-strapbomb", "Strap-on-bomb Car", "Strap-on-bomb_Car", "id1", NULL, "strap", "strapbomb" }, // COMMANDLINEOPTION: Game: -strapbomb runs the game Strap-on-bomb Car
950 { GAME_MOONHELM, GAME_MOONHELM, "moonhelm", "-moonhelm", "MoonHelm", "MoonHelm", "data", NULL, "mh", "moonhelm" }, // COMMANDLINEOPTION: Game: -moonhelm runs the game MoonHelm
951 { GAME_VORETOURNAMENT, GAME_VORETOURNAMENT, "voretournament", "-voretournament", "Vore Tournament", "Vore_Tournament", "data", NULL, "voretournament", "voretournament" }, // COMMANDLINEOPTION: Game: -voretournament runs the multiplayer game Vore Tournament
954 static void COM_SetGameType(int index);
955 void COM_InitGameType (void)
957 char name [MAX_OSPATH];
962 COM_ToLowerString(FORCEGAME, name, sizeof (name));
964 // check executable filename for keywords, but do it SMARTLY - only check the last path element
965 FS_StripExtension(FS_FileWithoutPath(sys.argv[0]), name, sizeof (name));
966 COM_ToLowerString(name, name, sizeof (name));
968 for (i = 1;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
969 if (gamemode_info[i].prog_name && gamemode_info[i].prog_name[0] && strstr (name, gamemode_info[i].prog_name))
972 // check commandline options for keywords
973 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
974 if (COM_CheckParm (gamemode_info[i].cmdline))
977 com_startupgamemode = gamemode_info[index].mode;
978 com_startupgamegroup = gamemode_info[index].group;
979 COM_SetGameType(index);
982 void COM_ChangeGameTypeForGameDirs(void)
986 // this will not not change the gamegroup
987 // first check if a base game (single gamedir) matches
988 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
990 if (gamemode_info[i].group == com_startupgamegroup && !(gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]))
996 // now that we have a base game, see if there is a matching derivative game (two gamedirs)
999 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
1001 if (gamemode_info[i].group == com_startupgamegroup && (gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]) && !strcasecmp(fs_gamedirs[0], gamemode_info[i].gamedirname2))
1008 // we now have a good guess at which game this is meant to be...
1009 if (index >= 0 && gamemode != gamemode_info[index].mode)
1010 COM_SetGameType(index);
1013 static void COM_SetGameType(int index)
1015 static char gamenetworkfilternamebuffer[64];
1017 if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0])))
1019 gamemode = gamemode_info[index].mode;
1020 gamename = gamemode_info[index].gamename;
1021 gamenetworkfiltername = gamemode_info[index].gamenetworkfiltername;
1022 gamedirname1 = gamemode_info[index].gamedirname1;
1023 gamedirname2 = gamemode_info[index].gamedirname2;
1024 gamescreenshotname = gamemode_info[index].gamescreenshotname;
1025 gameuserdirname = gamemode_info[index].gameuserdirname;
1027 if (gamemode == com_startupgamemode)
1029 if((t = COM_CheckParm("-customgamename")) && t + 1 < sys.argc)
1030 gamename = gamenetworkfiltername = sys.argv[t+1];
1031 if((t = COM_CheckParm("-customgamenetworkfiltername")) && t + 1 < sys.argc)
1032 gamenetworkfiltername = sys.argv[t+1];
1033 if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < sys.argc)
1034 gamedirname1 = sys.argv[t+1];
1035 if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < sys.argc)
1036 gamedirname2 = *sys.argv[t+1] ? sys.argv[t+1] : NULL;
1037 if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < sys.argc)
1038 gamescreenshotname = sys.argv[t+1];
1039 if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < sys.argc)
1040 gameuserdirname = sys.argv[t+1];
1043 if (gamedirname2 && gamedirname2[0])
1044 Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2);
1046 Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1);
1047 for (i = 0;i < fs_numgamedirs;i++)
1050 Con_Printf(", with mod gamedirs");
1051 Con_Printf(" %s", fs_gamedirs[i]);
1055 if (strchr(gamenetworkfiltername, ' '))
1058 // if there are spaces in the game's network filter name it would
1059 // cause parse errors in getservers in dpmaster, so we need to replace
1060 // them with _ characters
1061 strlcpy(gamenetworkfilternamebuffer, gamenetworkfiltername, sizeof(gamenetworkfilternamebuffer));
1062 while ((s = strchr(gamenetworkfilternamebuffer, ' ')) != NULL)
1064 gamenetworkfiltername = gamenetworkfilternamebuffer;
1067 Con_Printf("gamename for server filtering: %s\n", gamenetworkfiltername);
1076 void COM_Init_Commands (void)
1079 char com_cmdline[MAX_INPUTLINE];
1081 Cvar_RegisterVariable (®istered);
1082 Cvar_RegisterVariable (&cmdline);
1083 Cvar_RegisterVariable(&cl_playermodel);
1084 Cvar_RegisterAlias(&cl_playermodel, "_cl_playermodel");
1085 Cvar_RegisterVariable(&cl_playerskin);
1086 Cvar_RegisterAlias(&cl_playerskin, "_cl_playerskin");
1088 // reconstitute the command line for the cmdline externally visible cvar
1090 for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
1093 if (strstr(sys.argv[j], " "))
1095 // arg contains whitespace, store quotes around it
1096 // This condition checks whether we can allow to put
1097 // in two quote characters.
1098 if (n >= ((int)sizeof(com_cmdline) - 2))
1100 com_cmdline[n++] = '\"';
1101 // This condition checks whether we can allow one
1102 // more character and a quote character.
1103 while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
1104 // FIXME: Doesn't quote special characters.
1105 com_cmdline[n++] = sys.argv[j][i++];
1106 com_cmdline[n++] = '\"';
1110 // This condition checks whether we can allow one
1112 while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
1113 com_cmdline[n++] = sys.argv[j][i++];
1115 if (n < ((int)sizeof(com_cmdline) - 1))
1116 com_cmdline[n++] = ' ';
1121 Cvar_SetQuick(&cmdline, com_cmdline);
1128 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
1131 char *va(char *buf, size_t buflen, const char *format, ...)
1135 va_start (argptr, format);
1136 dpvsnprintf (buf, buflen, format,argptr);
1143 //======================================
1145 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
1151 # define snprintf _snprintf
1152 # define vsnprintf _vsnprintf
1156 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
1161 va_start (args, format);
1162 result = dpvsnprintf (buffer, buffersize, format, args);
1169 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
1173 #if _MSC_VER >= 1400
1174 result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
1176 result = vsnprintf (buffer, buffersize, format, args);
1178 if (result < 0 || (size_t)result >= buffersize)
1180 buffer[buffersize - 1] = '\0';
1188 //======================================
1190 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1195 if(utf8_enable.integer)
1198 while(*in && size_out > 1)
1201 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1202 ch = u8_tolower(ch);
1203 n = u8_fromchar(ch, out, size_out);
1212 while (*in && size_out > 1)
1214 if (*in >= 'A' && *in <= 'Z')
1215 *out++ = *in++ + 'a' - 'A';
1223 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1228 if(utf8_enable.integer)
1231 while(*in && size_out > 1)
1234 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1235 ch = u8_toupper(ch);
1236 n = u8_fromchar(ch, out, size_out);
1245 while (*in && size_out > 1)
1247 if (*in >= 'a' && *in <= 'z')
1248 *out++ = *in++ + 'A' - 'a';
1256 int COM_StringBeginsWith(const char *s, const char *match)
1258 for (;*s && *match;s++, match++)
1264 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1266 int argc, commentprefixlength;
1270 tokenbufend = tokenbuf + tokenbufsize;
1272 commentprefixlength = 0;
1274 commentprefixlength = (int)strlen(commentprefix);
1275 while (*l && *l != '\n' && *l != '\r')
1277 if (!ISWHITESPACE(*l))
1279 if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1281 while (*l && *l != '\n' && *l != '\r')
1285 if (argc >= maxargc)
1287 argv[argc++] = tokenbuf;
1291 while (*l && *l != '"')
1293 if (tokenbuf >= tokenbufend)
1302 while (!ISWHITESPACE(*l))
1304 if (tokenbuf >= tokenbufend)
1309 if (tokenbuf >= tokenbufend)
1330 COM_StringLengthNoColors
1332 calculates the visible width of a color coded string.
1334 *valid is filled with TRUE if the string is a valid colored string (that is, if
1335 it does not end with an unfinished color code). If it gets filled with FALSE, a
1336 fix would be adding a STRING_COLOR_TAG at the end of the string.
1338 valid can be set to NULL if the caller doesn't care.
1340 For size_s, specify the maximum number of characters from s to use, or 0 to use
1341 all characters until the zero terminator.
1345 COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid)
1347 const char *end = size_s ? (s + size_s) : NULL;
1351 switch((s == end) ? 0 : *s)
1357 case STRING_COLOR_TAG:
1359 switch((s == end) ? 0 : *s)
1361 case STRING_COLOR_RGB_TAG_CHAR:
1362 if (s+1 != end && isxdigit(s[1]) &&
1363 s+2 != end && isxdigit(s[2]) &&
1364 s+3 != end && isxdigit(s[3]) )
1369 ++len; // STRING_COLOR_TAG
1370 ++len; // STRING_COLOR_RGB_TAG_CHAR
1372 case 0: // ends with unfinished color code!
1377 case STRING_COLOR_TAG: // escaped ^
1380 case '0': case '1': case '2': case '3': case '4':
1381 case '5': case '6': case '7': case '8': case '9': // color code
1383 default: // not a color code
1384 ++len; // STRING_COLOR_TAG
1385 ++len; // the character
1400 COM_StringDecolorize
1402 removes color codes from a string.
1404 If escape_carets is true, the resulting string will be safe for printing. If
1405 escape_carets is false, the function will just strip color codes (for logging
1408 If the output buffer size did not suffice for converting, the function returns
1409 FALSE. Generally, if escape_carets is false, the output buffer needs
1410 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1411 bytes. In any case, the function makes sure that the resulting string is
1414 For size_in, specify the maximum number of characters from in to use, or 0 to use
1415 all characters until the zero terminator.
1419 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets)
1421 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
1422 const char *end = size_in ? (in + size_in) : NULL;
1427 switch((in == end) ? 0 : *in)
1432 case STRING_COLOR_TAG:
1434 switch((in == end) ? 0 : *in)
1436 case STRING_COLOR_RGB_TAG_CHAR:
1437 if (in+1 != end && isxdigit(in[1]) &&
1438 in+2 != end && isxdigit(in[2]) &&
1439 in+3 != end && isxdigit(in[3]) )
1444 APPEND(STRING_COLOR_TAG);
1446 APPEND(STRING_COLOR_TAG);
1447 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1449 case 0: // ends with unfinished color code!
1450 APPEND(STRING_COLOR_TAG);
1451 // finish the code by appending another caret when escaping
1453 APPEND(STRING_COLOR_TAG);
1456 case STRING_COLOR_TAG: // escaped ^
1457 APPEND(STRING_COLOR_TAG);
1458 // append a ^ twice when escaping
1460 APPEND(STRING_COLOR_TAG);
1462 case '0': case '1': case '2': case '3': case '4':
1463 case '5': case '6': case '7': case '8': case '9': // color code
1465 default: // not a color code
1466 APPEND(STRING_COLOR_TAG);
1481 char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
1487 keylength = strlen(key);
1488 if (valuelength < 1 || !value)
1490 Con_Printf("InfoString_GetValue: no room in value\n");
1494 if (strchr(key, '\\'))
1496 Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
1499 if (strchr(key, '\"'))
1501 Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
1506 Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
1509 while (buffer[pos] == '\\')
1511 if (!memcmp(buffer + pos+1, key, keylength) &&
1512 (buffer[pos+1 + keylength] == 0 ||
1513 buffer[pos+1 + keylength] == '\\'))
1515 pos += 1 + (int)keylength; // Skip \key
1516 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1517 for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
1518 value[j] = buffer[pos+j];
1522 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1523 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1524 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1525 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1527 // if we reach this point the key was not found
1531 void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
1539 keylength = strlen(key);
1540 if (strchr(key, '\\') || strchr(value, '\\'))
1542 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value);
1545 if (strchr(key, '\"') || strchr(value, '\"'))
1547 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value);
1552 Con_Printf("InfoString_SetValue: can not set a key with no name\n");
1555 while (buffer[pos] == '\\')
1557 if (!memcmp(buffer + pos+1, key, keylength) &&
1558 (buffer[pos+1 + keylength] == 0 ||
1559 buffer[pos+1 + keylength] == '\\'))
1561 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1562 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1563 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1564 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1566 // if we found the key, find the end of it because we will be replacing it
1568 if (buffer[pos] == '\\')
1570 pos2 += 1 + (int)keylength; // Skip \key
1571 if (buffer[pos2] == '\\') pos2++; // Skip \ before value.
1572 for (;buffer[pos2] && buffer[pos2] != '\\';pos2++);
1574 if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2))
1576 Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value);
1581 // set the key/value and append the remaining text
1582 char tempbuffer[MAX_INPUTLINE];
1583 strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
1584 dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
1588 // just remove the key from the text
1589 strlcpy(buffer + pos, buffer + pos2, bufferlength - pos);
1593 void InfoString_Print(char *buffer)
1596 char key[MAX_INPUTLINE];
1597 char value[MAX_INPUTLINE];
1600 if (*buffer != '\\')
1602 Con_Printf("InfoString_Print: corrupt string\n");
1605 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1606 if (i < (int)sizeof(key)-1)
1609 if (*buffer != '\\')
1611 Con_Printf("InfoString_Print: corrupt string\n");
1614 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1615 if (i < (int)sizeof(value)-1)
1616 value[i++] = *buffer;
1618 // empty value is an error case
1619 Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE");
1623 //========================================================
1624 // strlcat and strlcpy, from OpenBSD
1627 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
1629 * Permission to use, copy, modify, and distribute this software for any
1630 * purpose with or without fee is hereby granted, provided that the above
1631 * copyright notice and this permission notice appear in all copies.
1633 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1634 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1635 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1636 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1637 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1638 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1639 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1642 /* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */
1643 /* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */
1646 #ifndef HAVE_STRLCAT
1648 strlcat(char *dst, const char *src, size_t siz)
1650 register char *d = dst;
1651 register const char *s = src;
1652 register size_t n = siz;
1655 /* Find the end of dst and adjust bytes left but don't go past end */
1656 while (n-- != 0 && *d != '\0')
1662 return(dlen + strlen(s));
1663 while (*s != '\0') {
1672 return(dlen + (s - src)); /* count does not include NUL */
1674 #endif // #ifndef HAVE_STRLCAT
1677 #ifndef HAVE_STRLCPY
1679 strlcpy(char *dst, const char *src, size_t siz)
1681 register char *d = dst;
1682 register const char *s = src;
1683 register size_t n = siz;
1685 /* Copy as many bytes as will fit */
1686 if (n != 0 && --n != 0) {
1688 if ((*d++ = *s++) == 0)
1693 /* Not enough room in dst, add NUL and traverse rest of src */
1696 *d = '\0'; /* NUL-terminate dst */
1701 return(s - src - 1); /* count does not include NUL */
1704 #endif // #ifndef HAVE_STRLCPY
1706 void FindFraction(double val, int *num, int *denom, int denomMax)
1711 bestdiff = fabs(val);
1715 for(i = 1; i <= denomMax; ++i)
1717 int inum = (int) floor(0.5 + val * i);
1718 double diff = fabs(val - inum / (double)i);
1728 // decodes an XPM from C syntax
1729 char **XPM_DecodeString(const char *in)
1731 static char *tokens[257];
1732 static char lines[257][512];
1735 // skip until "{" token
1736 while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1738 // now, read in succession: string, comma-or-}
1739 while(COM_ParseToken_QuakeC(&in, false))
1741 tokens[line] = lines[line];
1742 strlcpy(lines[line++], com_token, sizeof(lines[0]));
1743 if(!COM_ParseToken_QuakeC(&in, false))
1745 if(!strcmp(com_token, "}"))
1747 if(strcmp(com_token, ","))
1749 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1756 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1757 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1759 unsigned char i0 = (bytes > 0) ? in[0] : 0;
1760 unsigned char i1 = (bytes > 1) ? in[1] : 0;
1761 unsigned char i2 = (bytes > 2) ? in[2] : 0;
1762 unsigned char o0 = base64[i0 >> 2];
1763 unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1764 unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1765 unsigned char o3 = base64[i2 & 077];
1766 out[0] = (bytes > 0) ? o0 : '?';
1767 out[1] = (bytes > 0) ? o1 : '?';
1768 out[2] = (bytes > 1) ? o2 : '=';
1769 out[3] = (bytes > 2) ? o3 : '=';
1772 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1775 // expand the out-buffer
1776 blocks = (buflen + 2) / 3;
1777 if(blocks*4 > outbuflen)
1779 for(i = blocks; i > 0; )
1782 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));