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
952 { GAME_DOOMBRINGER, GAME_DOOMBRINGER, "doombringer", "-doombringer", "DOOMBRINGER", "DOOMBRINGER", "dbdata", NULL, "doombringer", "doombringer" }, // COMMANDLINEOPTION: Game: -doombringer runs the game DOOMBRINGER
955 static void COM_SetGameType(int index);
956 void COM_InitGameType (void)
958 char name [MAX_OSPATH];
963 COM_ToLowerString(FORCEGAME, name, sizeof (name));
965 // check executable filename for keywords, but do it SMARTLY - only check the last path element
966 FS_StripExtension(FS_FileWithoutPath(sys.argv[0]), name, sizeof (name));
967 COM_ToLowerString(name, name, sizeof (name));
969 for (i = 1;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
970 if (gamemode_info[i].prog_name && gamemode_info[i].prog_name[0] && strstr (name, gamemode_info[i].prog_name))
973 // check commandline options for keywords
974 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
975 if (COM_CheckParm (gamemode_info[i].cmdline))
978 com_startupgamemode = gamemode_info[index].mode;
979 com_startupgamegroup = gamemode_info[index].group;
980 COM_SetGameType(index);
983 void COM_ChangeGameTypeForGameDirs(void)
987 // this will not not change the gamegroup
988 // first check if a base game (single gamedir) matches
989 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
991 if (gamemode_info[i].group == com_startupgamegroup && !(gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]))
997 // now that we have a base game, see if there is a matching derivative game (two gamedirs)
1000 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
1002 if (gamemode_info[i].group == com_startupgamegroup && (gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]) && !strcasecmp(fs_gamedirs[0], gamemode_info[i].gamedirname2))
1009 // we now have a good guess at which game this is meant to be...
1010 if (index >= 0 && gamemode != gamemode_info[index].mode)
1011 COM_SetGameType(index);
1014 static void COM_SetGameType(int index)
1016 static char gamenetworkfilternamebuffer[64];
1018 if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0])))
1020 gamemode = gamemode_info[index].mode;
1021 gamename = gamemode_info[index].gamename;
1022 gamenetworkfiltername = gamemode_info[index].gamenetworkfiltername;
1023 gamedirname1 = gamemode_info[index].gamedirname1;
1024 gamedirname2 = gamemode_info[index].gamedirname2;
1025 gamescreenshotname = gamemode_info[index].gamescreenshotname;
1026 gameuserdirname = gamemode_info[index].gameuserdirname;
1028 if (gamemode == com_startupgamemode)
1030 if((t = COM_CheckParm("-customgamename")) && t + 1 < sys.argc)
1031 gamename = gamenetworkfiltername = sys.argv[t+1];
1032 if((t = COM_CheckParm("-customgamenetworkfiltername")) && t + 1 < sys.argc)
1033 gamenetworkfiltername = sys.argv[t+1];
1034 if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < sys.argc)
1035 gamedirname1 = sys.argv[t+1];
1036 if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < sys.argc)
1037 gamedirname2 = *sys.argv[t+1] ? sys.argv[t+1] : NULL;
1038 if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < sys.argc)
1039 gamescreenshotname = sys.argv[t+1];
1040 if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < sys.argc)
1041 gameuserdirname = sys.argv[t+1];
1044 if (gamedirname2 && gamedirname2[0])
1045 Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2);
1047 Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1);
1048 for (i = 0;i < fs_numgamedirs;i++)
1051 Con_Printf(", with mod gamedirs");
1052 Con_Printf(" %s", fs_gamedirs[i]);
1056 if (strchr(gamenetworkfiltername, ' '))
1059 // if there are spaces in the game's network filter name it would
1060 // cause parse errors in getservers in dpmaster, so we need to replace
1061 // them with _ characters
1062 strlcpy(gamenetworkfilternamebuffer, gamenetworkfiltername, sizeof(gamenetworkfilternamebuffer));
1063 while ((s = strchr(gamenetworkfilternamebuffer, ' ')) != NULL)
1065 gamenetworkfiltername = gamenetworkfilternamebuffer;
1068 Con_Printf("gamename for server filtering: %s\n", gamenetworkfiltername);
1077 void COM_Init_Commands (void)
1080 char com_cmdline[MAX_INPUTLINE];
1082 Cvar_RegisterVariable (®istered);
1083 Cvar_RegisterVariable (&cmdline);
1084 Cvar_RegisterVariable(&cl_playermodel);
1085 Cvar_RegisterAlias(&cl_playermodel, "_cl_playermodel");
1086 Cvar_RegisterVariable(&cl_playerskin);
1087 Cvar_RegisterAlias(&cl_playerskin, "_cl_playerskin");
1089 // reconstitute the command line for the cmdline externally visible cvar
1091 for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
1094 if (strstr(sys.argv[j], " "))
1096 // arg contains whitespace, store quotes around it
1097 // This condition checks whether we can allow to put
1098 // in two quote characters.
1099 if (n >= ((int)sizeof(com_cmdline) - 2))
1101 com_cmdline[n++] = '\"';
1102 // This condition checks whether we can allow one
1103 // more character and a quote character.
1104 while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
1105 // FIXME: Doesn't quote special characters.
1106 com_cmdline[n++] = sys.argv[j][i++];
1107 com_cmdline[n++] = '\"';
1111 // This condition checks whether we can allow one
1113 while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
1114 com_cmdline[n++] = sys.argv[j][i++];
1116 if (n < ((int)sizeof(com_cmdline) - 1))
1117 com_cmdline[n++] = ' ';
1122 Cvar_SetQuick(&cmdline, com_cmdline);
1129 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
1132 char *va(char *buf, size_t buflen, const char *format, ...)
1136 va_start (argptr, format);
1137 dpvsnprintf (buf, buflen, format,argptr);
1144 //======================================
1146 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
1152 # define snprintf _snprintf
1153 # define vsnprintf _vsnprintf
1157 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
1162 va_start (args, format);
1163 result = dpvsnprintf (buffer, buffersize, format, args);
1170 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
1174 #if _MSC_VER >= 1400
1175 result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
1177 result = vsnprintf (buffer, buffersize, format, args);
1179 if (result < 0 || (size_t)result >= buffersize)
1181 buffer[buffersize - 1] = '\0';
1189 //======================================
1191 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1196 if(utf8_enable.integer)
1199 while(*in && size_out > 1)
1202 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1203 ch = u8_tolower(ch);
1204 n = u8_fromchar(ch, out, size_out);
1213 while (*in && size_out > 1)
1215 if (*in >= 'A' && *in <= 'Z')
1216 *out++ = *in++ + 'a' - 'A';
1224 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1229 if(utf8_enable.integer)
1232 while(*in && size_out > 1)
1235 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1236 ch = u8_toupper(ch);
1237 n = u8_fromchar(ch, out, size_out);
1246 while (*in && size_out > 1)
1248 if (*in >= 'a' && *in <= 'z')
1249 *out++ = *in++ + 'A' - 'a';
1257 int COM_StringBeginsWith(const char *s, const char *match)
1259 for (;*s && *match;s++, match++)
1265 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1267 int argc, commentprefixlength;
1271 tokenbufend = tokenbuf + tokenbufsize;
1273 commentprefixlength = 0;
1275 commentprefixlength = (int)strlen(commentprefix);
1276 while (*l && *l != '\n' && *l != '\r')
1278 if (!ISWHITESPACE(*l))
1280 if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1282 while (*l && *l != '\n' && *l != '\r')
1286 if (argc >= maxargc)
1288 argv[argc++] = tokenbuf;
1292 while (*l && *l != '"')
1294 if (tokenbuf >= tokenbufend)
1303 while (!ISWHITESPACE(*l))
1305 if (tokenbuf >= tokenbufend)
1310 if (tokenbuf >= tokenbufend)
1331 COM_StringLengthNoColors
1333 calculates the visible width of a color coded string.
1335 *valid is filled with TRUE if the string is a valid colored string (that is, if
1336 it does not end with an unfinished color code). If it gets filled with FALSE, a
1337 fix would be adding a STRING_COLOR_TAG at the end of the string.
1339 valid can be set to NULL if the caller doesn't care.
1341 For size_s, specify the maximum number of characters from s to use, or 0 to use
1342 all characters until the zero terminator.
1346 COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid)
1348 const char *end = size_s ? (s + size_s) : NULL;
1352 switch((s == end) ? 0 : *s)
1358 case STRING_COLOR_TAG:
1360 switch((s == end) ? 0 : *s)
1362 case STRING_COLOR_RGB_TAG_CHAR:
1363 if (s+1 != end && isxdigit(s[1]) &&
1364 s+2 != end && isxdigit(s[2]) &&
1365 s+3 != end && isxdigit(s[3]) )
1370 ++len; // STRING_COLOR_TAG
1371 ++len; // STRING_COLOR_RGB_TAG_CHAR
1373 case 0: // ends with unfinished color code!
1378 case STRING_COLOR_TAG: // escaped ^
1381 case '0': case '1': case '2': case '3': case '4':
1382 case '5': case '6': case '7': case '8': case '9': // color code
1384 default: // not a color code
1385 ++len; // STRING_COLOR_TAG
1386 ++len; // the character
1401 COM_StringDecolorize
1403 removes color codes from a string.
1405 If escape_carets is true, the resulting string will be safe for printing. If
1406 escape_carets is false, the function will just strip color codes (for logging
1409 If the output buffer size did not suffice for converting, the function returns
1410 FALSE. Generally, if escape_carets is false, the output buffer needs
1411 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1412 bytes. In any case, the function makes sure that the resulting string is
1415 For size_in, specify the maximum number of characters from in to use, or 0 to use
1416 all characters until the zero terminator.
1420 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets)
1422 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
1423 const char *end = size_in ? (in + size_in) : NULL;
1428 switch((in == end) ? 0 : *in)
1433 case STRING_COLOR_TAG:
1435 switch((in == end) ? 0 : *in)
1437 case STRING_COLOR_RGB_TAG_CHAR:
1438 if (in+1 != end && isxdigit(in[1]) &&
1439 in+2 != end && isxdigit(in[2]) &&
1440 in+3 != end && isxdigit(in[3]) )
1445 APPEND(STRING_COLOR_TAG);
1447 APPEND(STRING_COLOR_TAG);
1448 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1450 case 0: // ends with unfinished color code!
1451 APPEND(STRING_COLOR_TAG);
1452 // finish the code by appending another caret when escaping
1454 APPEND(STRING_COLOR_TAG);
1457 case STRING_COLOR_TAG: // escaped ^
1458 APPEND(STRING_COLOR_TAG);
1459 // append a ^ twice when escaping
1461 APPEND(STRING_COLOR_TAG);
1463 case '0': case '1': case '2': case '3': case '4':
1464 case '5': case '6': case '7': case '8': case '9': // color code
1466 default: // not a color code
1467 APPEND(STRING_COLOR_TAG);
1482 char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
1488 keylength = strlen(key);
1489 if (valuelength < 1 || !value)
1491 Con_Printf("InfoString_GetValue: no room in value\n");
1495 if (strchr(key, '\\'))
1497 Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
1500 if (strchr(key, '\"'))
1502 Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
1507 Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
1510 while (buffer[pos] == '\\')
1512 if (!memcmp(buffer + pos+1, key, keylength) &&
1513 (buffer[pos+1 + keylength] == 0 ||
1514 buffer[pos+1 + keylength] == '\\'))
1516 pos += 1 + (int)keylength; // Skip \key
1517 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1518 for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
1519 value[j] = buffer[pos+j];
1523 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1524 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1525 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1526 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1528 // if we reach this point the key was not found
1532 void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
1540 keylength = strlen(key);
1541 if (strchr(key, '\\') || strchr(value, '\\'))
1543 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value);
1546 if (strchr(key, '\"') || strchr(value, '\"'))
1548 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value);
1553 Con_Printf("InfoString_SetValue: can not set a key with no name\n");
1556 while (buffer[pos] == '\\')
1558 if (!memcmp(buffer + pos+1, key, keylength) &&
1559 (buffer[pos+1 + keylength] == 0 ||
1560 buffer[pos+1 + keylength] == '\\'))
1562 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1563 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1564 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1565 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1567 // if we found the key, find the end of it because we will be replacing it
1569 if (buffer[pos] == '\\')
1571 pos2 += 1 + (int)keylength; // Skip \key
1572 if (buffer[pos2] == '\\') pos2++; // Skip \ before value.
1573 for (;buffer[pos2] && buffer[pos2] != '\\';pos2++);
1575 if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2))
1577 Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value);
1582 // set the key/value and append the remaining text
1583 char tempbuffer[MAX_INPUTLINE];
1584 strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
1585 dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
1589 // just remove the key from the text
1590 strlcpy(buffer + pos, buffer + pos2, bufferlength - pos);
1594 void InfoString_Print(char *buffer)
1597 char key[MAX_INPUTLINE];
1598 char value[MAX_INPUTLINE];
1601 if (*buffer != '\\')
1603 Con_Printf("InfoString_Print: corrupt string\n");
1606 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1607 if (i < (int)sizeof(key)-1)
1610 if (*buffer != '\\')
1612 Con_Printf("InfoString_Print: corrupt string\n");
1615 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1616 if (i < (int)sizeof(value)-1)
1617 value[i++] = *buffer;
1619 // empty value is an error case
1620 Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE");
1624 //========================================================
1625 // strlcat and strlcpy, from OpenBSD
1628 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
1630 * Permission to use, copy, modify, and distribute this software for any
1631 * purpose with or without fee is hereby granted, provided that the above
1632 * copyright notice and this permission notice appear in all copies.
1634 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1635 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1636 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1637 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1638 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1639 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1640 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1643 /* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */
1644 /* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */
1647 #ifndef HAVE_STRLCAT
1649 strlcat(char *dst, const char *src, size_t siz)
1651 register char *d = dst;
1652 register const char *s = src;
1653 register size_t n = siz;
1656 /* Find the end of dst and adjust bytes left but don't go past end */
1657 while (n-- != 0 && *d != '\0')
1663 return(dlen + strlen(s));
1664 while (*s != '\0') {
1673 return(dlen + (s - src)); /* count does not include NUL */
1675 #endif // #ifndef HAVE_STRLCAT
1678 #ifndef HAVE_STRLCPY
1680 strlcpy(char *dst, const char *src, size_t siz)
1682 register char *d = dst;
1683 register const char *s = src;
1684 register size_t n = siz;
1686 /* Copy as many bytes as will fit */
1687 if (n != 0 && --n != 0) {
1689 if ((*d++ = *s++) == 0)
1694 /* Not enough room in dst, add NUL and traverse rest of src */
1697 *d = '\0'; /* NUL-terminate dst */
1702 return(s - src - 1); /* count does not include NUL */
1705 #endif // #ifndef HAVE_STRLCPY
1707 void FindFraction(double val, int *num, int *denom, int denomMax)
1712 bestdiff = fabs(val);
1716 for(i = 1; i <= denomMax; ++i)
1718 int inum = (int) floor(0.5 + val * i);
1719 double diff = fabs(val - inum / (double)i);
1729 // decodes an XPM from C syntax
1730 char **XPM_DecodeString(const char *in)
1732 static char *tokens[257];
1733 static char lines[257][512];
1736 // skip until "{" token
1737 while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1739 // now, read in succession: string, comma-or-}
1740 while(COM_ParseToken_QuakeC(&in, false))
1742 tokens[line] = lines[line];
1743 strlcpy(lines[line++], com_token, sizeof(lines[0]));
1744 if(!COM_ParseToken_QuakeC(&in, false))
1746 if(!strcmp(com_token, "}"))
1748 if(strcmp(com_token, ","))
1750 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1757 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1758 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1760 unsigned char i0 = (bytes > 0) ? in[0] : 0;
1761 unsigned char i1 = (bytes > 1) ? in[1] : 0;
1762 unsigned char i2 = (bytes > 2) ? in[2] : 0;
1763 unsigned char o0 = base64[i0 >> 2];
1764 unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1765 unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1766 unsigned char o3 = base64[i2 & 077];
1767 out[0] = (bytes > 0) ? o0 : '?';
1768 out[1] = (bytes > 0) ? o1 : '?';
1769 out[2] = (bytes > 1) ? o2 : '=';
1770 out[3] = (bytes > 2) ? o3 : '=';
1773 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1776 // expand the out-buffer
1777 blocks = (buflen + 2) / 3;
1778 if(blocks*4 > outbuflen)
1780 for(i = blocks; i > 0; )
1783 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));