]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - common.c
Rework game specific hacks to have a special group for Nexuiz-derived games.
[xonotic/darkplaces.git] / common.c
index 12a42555b96b4cbc545c6a17ef14ffe135b77ddc..b119f044456f2d12ebfc3599ccff742761e470b8 100644 (file)
--- a/common.c
+++ b/common.c
@@ -19,20 +19,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 // common.c -- misc functions used in client and server
 
-#include "quakedef.h"
-
 #include <stdlib.h>
 #include <fcntl.h>
 #ifndef WIN32
 #include <unistd.h>
 #endif
 
+#include "quakedef.h"
+#include "utf8lib.h"
+
 cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
 cvar_t cmdline = {0, "cmdline","0", "contains commandline the engine was launched with"};
 
 char com_token[MAX_INPUTLINE];
 int com_argc;
 const char **com_argv;
+int com_selffd = -1;
 
 gamemode_t gamemode;
 const char *gamename;
@@ -362,7 +364,7 @@ void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol)
                MSG_WriteCoord32f (sb, f);
 }
 
-void MSG_WriteVector (sizebuf_t *sb, float *v, protocolversion_t protocol)
+void MSG_WriteVector (sizebuf_t *sb, const vec3_t v, protocolversion_t protocol)
 {
        MSG_WriteCoord (sb, v[0], protocol);
        MSG_WriteCoord (sb, v[1], protocol);
@@ -402,167 +404,175 @@ void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol)
 //
 // reading functions
 //
-int msg_readcount;
-qboolean msg_badread;
 
-void MSG_BeginReading (void)
+void MSG_InitReadBuffer (sizebuf_t *buf, unsigned char *data, int size)
 {
-       msg_readcount = 0;
-       msg_badread = false;
+       memset(buf, 0, sizeof(*buf));
+       buf->data = data;
+       buf->maxsize = buf->cursize = size;
+       MSG_BeginReading(buf);
 }
 
-int MSG_ReadLittleShort (void)
+void MSG_BeginReading(sizebuf_t *sb)
 {
-       if (msg_readcount+2 > net_message.cursize)
+       sb->readcount = 0;
+       sb->badread = false;
+}
+
+int MSG_ReadLittleShort(sizebuf_t *sb)
+{
+       if (sb->readcount+2 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 2;
-       return (short)(net_message.data[msg_readcount-2] | (net_message.data[msg_readcount-1]<<8));
+       sb->readcount += 2;
+       return (short)(sb->data[sb->readcount-2] | (sb->data[sb->readcount-1]<<8));
 }
 
-int MSG_ReadBigShort (void)
+int MSG_ReadBigShort (sizebuf_t *sb)
 {
-       if (msg_readcount+2 > net_message.cursize)
+       if (sb->readcount+2 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 2;
-       return (short)((net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1]);
+       sb->readcount += 2;
+       return (short)((sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1]);
 }
 
-int MSG_ReadLittleLong (void)
+int MSG_ReadLittleLong (sizebuf_t *sb)
 {
-       if (msg_readcount+4 > net_message.cursize)
+       if (sb->readcount+4 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 4;
-       return net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24);
+       sb->readcount += 4;
+       return sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24);
 }
 
-int MSG_ReadBigLong (void)
+int MSG_ReadBigLong (sizebuf_t *sb)
 {
-       if (msg_readcount+4 > net_message.cursize)
+       if (sb->readcount+4 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 4;
-       return (net_message.data[msg_readcount-4]<<24) + (net_message.data[msg_readcount-3]<<16) + (net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1];
+       sb->readcount += 4;
+       return (sb->data[sb->readcount-4]<<24) + (sb->data[sb->readcount-3]<<16) + (sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1];
 }
 
-float MSG_ReadLittleFloat (void)
+float MSG_ReadLittleFloat (sizebuf_t *sb)
 {
        union
        {
                float f;
                int l;
        } dat;
-       if (msg_readcount+4 > net_message.cursize)
+       if (sb->readcount+4 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 4;
-       dat.l = net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24);
+       sb->readcount += 4;
+       dat.l = sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24);
        return dat.f;
 }
 
-float MSG_ReadBigFloat (void)
+float MSG_ReadBigFloat (sizebuf_t *sb)
 {
        union
        {
                float f;
                int l;
        } dat;
-       if (msg_readcount+4 > net_message.cursize)
+       if (sb->readcount+4 > sb->cursize)
        {
-               msg_badread = true;
+               sb->badread = true;
                return -1;
        }
-       msg_readcount += 4;
-       dat.l = (net_message.data[msg_readcount-4]<<24) | (net_message.data[msg_readcount-3]<<16) | (net_message.data[msg_readcount-2]<<8) | net_message.data[msg_readcount-1];
+       sb->readcount += 4;
+       dat.l = (sb->data[sb->readcount-4]<<24) | (sb->data[sb->readcount-3]<<16) | (sb->data[sb->readcount-2]<<8) | sb->data[sb->readcount-1];
        return dat.f;
 }
 
-char *MSG_ReadString (void)
+char *MSG_ReadString (sizebuf_t *sb, char *string, size_t maxstring)
 {
-       static char string[MAX_INPUTLINE];
-       int l,c;
-       for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadByte()) != -1 && c != 0;l++)
-               string[l] = c;
+       int c;
+       size_t l = 0;
+       // read string into sbfer, but only store as many characters as will fit
+       while ((c = MSG_ReadByte(sb)) > 0)
+               if (l < maxstring - 1)
+                       string[l++] = c;
        string[l] = 0;
        return string;
 }
 
-int MSG_ReadBytes (int numbytes, unsigned char *out)
+int MSG_ReadBytes (sizebuf_t *sb, int numbytes, unsigned char *out)
 {
        int l, c;
-       for (l = 0;l < numbytes && (c = MSG_ReadByte()) != -1;l++)
+       for (l = 0;l < numbytes && (c = MSG_ReadByte(sb)) != -1;l++)
                out[l] = c;
        return l;
 }
 
-float MSG_ReadCoord13i (void)
+float MSG_ReadCoord13i (sizebuf_t *sb)
 {
-       return MSG_ReadLittleShort() * (1.0/8.0);
+       return MSG_ReadLittleShort(sb) * (1.0/8.0);
 }
 
-float MSG_ReadCoord16i (void)
+float MSG_ReadCoord16i (sizebuf_t *sb)
 {
-       return (signed short) MSG_ReadLittleShort();
+       return (signed short) MSG_ReadLittleShort(sb);
 }
 
-float MSG_ReadCoord32f (void)
+float MSG_ReadCoord32f (sizebuf_t *sb)
 {
-       return MSG_ReadLittleFloat();
+       return MSG_ReadLittleFloat(sb);
 }
 
-float MSG_ReadCoord (protocolversion_t protocol)
+float MSG_ReadCoord (sizebuf_t *sb, protocolversion_t protocol)
 {
        if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD)
-               return MSG_ReadCoord13i();
+               return MSG_ReadCoord13i(sb);
        else if (protocol == PROTOCOL_DARKPLACES1)
-               return MSG_ReadCoord32f();
+               return MSG_ReadCoord32f(sb);
        else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4)
-               return MSG_ReadCoord16i();
+               return MSG_ReadCoord16i(sb);
        else
-               return MSG_ReadCoord32f();
+               return MSG_ReadCoord32f(sb);
 }
 
-void MSG_ReadVector (float *v, protocolversion_t protocol)
+void MSG_ReadVector (sizebuf_t *sb, vec3_t v, protocolversion_t protocol)
 {
-       v[0] = MSG_ReadCoord(protocol);
-       v[1] = MSG_ReadCoord(protocol);
-       v[2] = MSG_ReadCoord(protocol);
+       v[0] = MSG_ReadCoord(sb, protocol);
+       v[1] = MSG_ReadCoord(sb, protocol);
+       v[2] = MSG_ReadCoord(sb, protocol);
 }
 
 // LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem
-float MSG_ReadAngle8i (void)
+float MSG_ReadAngle8i (sizebuf_t *sb)
 {
-       return (signed char) MSG_ReadByte () * (360.0/256.0);
+       return (signed char) MSG_ReadByte (sb) * (360.0/256.0);
 }
 
-float MSG_ReadAngle16i (void)
+float MSG_ReadAngle16i (sizebuf_t *sb)
 {
-       return (signed short)MSG_ReadShort () * (360.0/65536.0);
+       return (signed short)MSG_ReadShort (sb) * (360.0/65536.0);
 }
 
-float MSG_ReadAngle32f (void)
+float MSG_ReadAngle32f (sizebuf_t *sb)
 {
-       return MSG_ReadFloat ();
+       return MSG_ReadFloat (sb);
 }
 
-float MSG_ReadAngle (protocolversion_t protocol)
+float MSG_ReadAngle (sizebuf_t *sb, protocolversion_t protocol)
 {
        if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD)
-               return MSG_ReadAngle8i ();
+               return MSG_ReadAngle8i (sb);
        else
-               return MSG_ReadAngle16i ();
+               return MSG_ReadAngle16i (sb);
 }
 
 
@@ -605,7 +615,7 @@ void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
 // attention, it has been eradicated from here, its only (former) use in
 // all of darkplaces.
 
-static char *hexchar = "0123456789ABCDEF";
+static const char *hexchar = "0123456789ABCDEF";
 void Com_HexDumpToConsole(const unsigned char *data, int size)
 {
        int i, j, n;
@@ -732,9 +742,7 @@ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, flo
                {
                        case 0: // end of string
                                result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
-                               isContinuation = false;
                                goto out;
-                               break;
                        case '\n': // end of line
                                result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
                                isContinuation = false;
@@ -988,7 +996,7 @@ COM_ParseToken_Simple
 Parse a token out of a string
 ==============
 */
-int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash)
+int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments)
 {
        int len;
        int c;
@@ -1023,14 +1031,14 @@ skipwhite:
        if (data[0] == '\r' && data[1] == '\n')
                data++;
 
-       if (data[0] == '/' && data[1] == '/')
+       if (parsecomments && data[0] == '/' && data[1] == '/')
        {
                // comment
                while (*data && *data != '\n' && *data != '\r')
                        data++;
                goto skipwhite;
        }
-       else if (data[0] == '/' && data[1] == '*')
+       else if (parsecomments && data[0] == '/' && data[1] == '*')
        {
                // comment
                data++;
@@ -1313,7 +1321,7 @@ skipwhite:
        else
        {
                // regular word
-               for (;!ISWHITESPACE(*data) && *data != ',' && *data != ';' && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
+               for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
                        if (len < (int)sizeof(com_token) - 1)
                                com_token[len++] = *data;
                com_token[len] = 0;
@@ -1419,127 +1427,153 @@ int COM_CheckParm (const char *parm)
 
 // Game mods
 
+gamemode_t com_startupgamemode;
+gamemode_t com_startupgamegroup;
+
 typedef struct gamemode_info_s
 {
-       const char* prog_name;
-       const char* cmdline;
-       const char* gamename;
-       const char* gamedirname1;
-       const char* gamedirname2;
-       const char* gamescreenshotname;
-       const char* gameuserdirname;
+       gamemode_t mode; // this gamemode
+       gamemode_t group; // different games with same group can switch automatically when gamedirs change
+       const char* prog_name; // not null
+       const char* cmdline; // not null
+       const char* gamename; // not null
+       const char* gamedirname1; // not null
+       const char* gamedirname2; // null
+       const char* gamescreenshotname; // not nul
+       const char* gameuserdirname; // not null
 } gamemode_info_t;
 
 static const gamemode_info_t gamemode_info [GAME_COUNT] =
-{// prog_name          cmdline                 gamename                                basegame        modgame                 screenshotprefix        userdir
-
-// GAME_NORMAL
-// COMMANDLINEOPTION: Game: -quake runs the game Quake (default)
-{ "",                          "-quake",               "DarkPlaces-Quake",             "id1",          NULL,                   "dp",                   "darkplaces" },
-// GAME_HIPNOTIC
-// COMMANDLINEOPTION: Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon
-{ "hipnotic",          "-hipnotic",    "Darkplaces-Hipnotic",  "id1",          "hipnotic",             "dp",                   "darkplaces" },
-// GAME_ROGUE
-// COMMANDLINEOPTION: Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity
-{ "rogue",                     "-rogue",               "Darkplaces-Rogue",             "id1",          "rogue",                "dp",                   "darkplaces" },
-// GAME_NEHAHRA
-// COMMANDLINEOPTION: Game: -nehahra runs The Seal of Nehahra movie and game
-{ "nehahra",           "-nehahra",             "DarkPlaces-Nehahra",   "id1",          "nehahra",              "dp",                   "darkplaces" },
-// GAME_NEXUIZ
-// COMMANDLINEOPTION: Game: -nexuiz runs the multiplayer game Nexuiz
-{ "nexuiz",                    "-nexuiz",              "Nexuiz",                               "data",         NULL,                   "nexuiz",               "nexuiz" },
-// GAME_TRANSFUSION
-// COMMANDLINEOPTION: Game: -transfusion runs Transfusion (the recreation of Blood in Quake)
-{ "transfusion",       "-transfusion", "Transfusion",                  "basetf",       NULL,                   "transfusion",  "transfusion" },
-// GAME_GOODVSBAD2
-// COMMANDLINEOPTION: Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2
-{ "gvb2",                      "-goodvsbad2",  "GoodVs.Bad2",                  "rts",          NULL,                   "gvb2",                 "gvb2" },
-// GAME_TEU
-// COMMANDLINEOPTION: Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces)
-{ "teu",                       "-teu",                 "TheEvilUnleashed",             "baseteu",      NULL,                   "teu",                  "teu" },
-// GAME_BATTLEMECH
-// COMMANDLINEOPTION: Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech
-{ "battlemech",                "-battlemech",  "Battlemech",                   "base",         NULL,                   "battlemech",   "battlemech" },
-// GAME_ZYMOTIC
-// COMMANDLINEOPTION: Game: -zymotic runs the singleplayer game Zymotic
-{ "zymotic",           "-zymotic",             "Zymotic",                              "basezym",              NULL,                   "zymotic",              "zymotic" },
-// GAME_SETHERAL
-// COMMANDLINEOPTION: Game: -setheral runs the multiplayer game Setheral
-{ "setheral",          "-setheral",    "Setheral",                             "data",         NULL,                   "setheral",             "setheral" },
-// GAME_SOM
-// COMMANDLINEOPTION: Game: -som runs the multiplayer game Son Of Man
-{ "som",                       "-som",                 "Son of Man",                   "id1",          "sonofman",             "som",                  "darkplaces" },
-// GAME_TENEBRAE
-// COMMANDLINEOPTION: Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented)
-{ "tenebrae",          "-tenebrae",    "DarkPlaces-Tenebrae",  "id1",          "tenebrae",             "dp",                   "darkplaces" },
-// GAME_NEOTERIC
-// COMMANDLINEOPTION: Game: -neoteric runs the game Neoteric
-{ "neoteric",          "-neoteric",    "Neoteric",                             "id1",          "neobase",              "neo",                  "darkplaces" },
-// GAME_OPENQUARTZ
-// COMMANDLINEOPTION: Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content
-{ "openquartz",                "-openquartz",  "OpenQuartz",                   "id1",          NULL,                   "openquartz",   "darkplaces" },
-// GAME_PRYDON
-// COMMANDLINEOPTION: Game: -prydon runs the topdown point and click action-RPG Prydon Gate
-{ "prydon",                    "-prydon",              "PrydonGate",                   "id1",          "prydon",               "prydon",               "darkplaces" },
-// GAME_DELUXEQUAKE
-// COMMANDLINEOPTION: Game: -dq runs the game Deluxe Quake
-{ "dq",        "-dq",  "Deluxe Quake",         "basedq",               "extradq",              "basedq",               "dq" },
-// GAME_THEHUNTED
-// COMMANDLINEOPTION: Game: -thehunted runs the game The Hunted
-{ "thehunted",         "-thehunted",   "The Hunted",                   "thdata",       NULL,                   "th",                   "thehunted" },
-// GAME_DEFEATINDETAIL2
-// COMMANDLINEOPTION: Game: -did2 runs the game Defeat In Detail 2
-{ "did2",                      "-did2",                "Defeat In Detail 2",   "data",         NULL,                   "did2_",                "did2" },
-// GAME_DARSANA
-// COMMANDLINEOPTION: Game: -darsana runs the game Darsana
-{ "darsana",           "-darsana",     "Darsana",                      "ddata",        NULL,                   "darsana",                      "darsana" },
-// GAME_CONTAGIONTHEORY
-// COMMANDLINEOPTION: Game: -contagiontheory runs the game Contagion Theory
-{ "contagiontheory",           "-contagiontheory",     "Contagion Theory",                     "ctdata",       NULL,                   "ct",                   "contagiontheory" },
-// GAME_EDU2P
-// COMMANDLINEOPTION: Game: -edu2p runs the game Edu2 prototype
-{ "edu2p", "-edu2p", "EDU2 Prototype", "id1", "edu2", "edu2_p", "edu2prototype" },
-// GAME_BLADEMASTER
-// COMMANDLINEOPTION: Game: -blademaster runs the game Prophecy: Return of the BladeMaster
-{ "blademaster", "-blademaster", "Prophecy: Return of the BladeMaster", "basebm", NULL, "blademaster", "blademaster" },
-// GAME_PROPHECY
-// COMMANDLINEOPTION: Game: -prophecy runs the game Quake (default)
-{ "prophecy",                          "-prophecy",            "Prophecy",             "data",         NULL,                   "prophecy",                     "prophecy" },
-// GAME_BLOODOMNICIDE
-// COMMANDLINEOPTION: Game: -omnicide runs the game Blood Omnicide
-{ "omnicide", "-omnicide", "Blood Omnicide", "kain", NULL, "omnicide", "omnicide" },
+{// game                               basegame                                prog_name                       cmdline                         gamename                                basegame        modgame                 screenshot              userdir                            // commandline option
+{ GAME_NORMAL,                 GAME_NORMAL,                    "",                                     "-quake",                       "DarkPlaces-Quake",             "id1",          NULL,                   "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -quake runs the game Quake (default)
+{ GAME_HIPNOTIC,               GAME_NORMAL,                    "hipnotic",                     "-hipnotic",            "Darkplaces-Hipnotic",  "id1",          "hipnotic",             "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon
+{ GAME_ROGUE,                  GAME_NORMAL,                    "rogue",                        "-rogue",                       "Darkplaces-Rogue",             "id1",          "rogue",                "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity
+{ GAME_NEHAHRA,                        GAME_NORMAL,                    "nehahra",                      "-nehahra",                     "DarkPlaces-Nehahra",   "id1",          "nehahra",              "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -nehahra runs The Seal of Nehahra movie and game
+{ GAME_QUOTH,                  GAME_NORMAL,                    "quoth",                        "-quoth",                       "Darkplaces-Quoth",             "id1",          "quoth",                "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -quoth runs the Quoth mod for playing community maps made for it
+{ GAME_NEXUIZ,                 GAME_NEXUIZ,                    "nexuiz",                       "-nexuiz",                      "Nexuiz",                               "data",         NULL,                   "nexuiz",               "nexuiz"                        }, // COMMANDLINEOPTION: Game: -nexuiz runs the multiplayer game Nexuiz
+{ GAME_XONOTIC,                        GAME_XONOTIC,                   "xonotic",                      "-xonotic",                     "Xonotic",                              "data",         NULL,                   "xonotic",              "xonotic"                       }, // COMMANDLINEOPTION: Game: -xonotic runs the multiplayer game Xonotic
+{ GAME_TRANSFUSION,            GAME_TRANSFUSION,               "transfusion",          "-transfusion",         "Transfusion",                  "basetf",       NULL,                   "transfusion",  "transfusion"           }, // COMMANDLINEOPTION: Game: -transfusion runs Transfusion (the recreation of Blood in Quake)
+{ GAME_GOODVSBAD2,             GAME_GOODVSBAD2,                "gvb2",                         "-goodvsbad2",          "GoodVs.Bad2",                  "rts",          NULL,                   "gvb2",                 "gvb2"                          }, // COMMANDLINEOPTION: Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2
+{ GAME_TEU,                            GAME_TEU,                               "teu",                          "-teu",                         "TheEvilUnleashed",             "baseteu",      NULL,                   "teu",                  "teu"                           }, // COMMANDLINEOPTION: Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces)
+{ GAME_BATTLEMECH,             GAME_BATTLEMECH,                "battlemech",           "-battlemech",          "Battlemech",                   "base",         NULL,                   "battlemech",   "battlemech"            }, // COMMANDLINEOPTION: Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech
+{ GAME_ZYMOTIC,                        GAME_ZYMOTIC,                   "zymotic",                      "-zymotic",                     "Zymotic",                              "basezym",      NULL,                   "zymotic",              "zymotic"                       }, // COMMANDLINEOPTION: Game: -zymotic runs the singleplayer game Zymotic
+{ GAME_SETHERAL,               GAME_SETHERAL,                  "setheral",                     "-setheral",            "Setheral",                             "data",         NULL,                   "setheral",             "setheral"                      }, // COMMANDLINEOPTION: Game: -setheral runs the multiplayer game Setheral
+{ GAME_TENEBRAE,               GAME_NORMAL,                    "tenebrae",                     "-tenebrae",            "DarkPlaces-Tenebrae",  "id1",          "tenebrae",             "dp",                   "darkplaces"            }, // COMMANDLINEOPTION: Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented)
+{ GAME_NEOTERIC,               GAME_NORMAL,                    "neoteric",                     "-neoteric",            "Neoteric",                             "id1",          "neobase",              "neo",                  "darkplaces"            }, // COMMANDLINEOPTION: Game: -neoteric runs the game Neoteric
+{ GAME_OPENQUARTZ,             GAME_NORMAL,                    "openquartz",           "-openquartz",          "OpenQuartz",                   "id1",          NULL,                   "openquartz",   "darkplaces"            }, // COMMANDLINEOPTION: Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content
+{ GAME_PRYDON,                 GAME_NORMAL,                    "prydon",                       "-prydon",                      "PrydonGate",                   "id1",          "prydon",               "prydon",               "darkplaces"            }, // COMMANDLINEOPTION: Game: -prydon runs the topdown point and click action-RPG Prydon Gate
+{ GAME_DELUXEQUAKE,            GAME_DELUXEQUAKE,               "dq",                           "-dq",                          "Deluxe Quake",                 "basedq",       "extradq",              "basedq",               "dq"                            }, // COMMANDLINEOPTION: Game: -dq runs the game Deluxe Quake
+{ GAME_THEHUNTED,              GAME_THEHUNTED,                 "thehunted",            "-thehunted",           "The Hunted",                   "thdata",       NULL,                   "th",                   "thehunted"                     }, // COMMANDLINEOPTION: Game: -thehunted runs the game The Hunted
+{ GAME_DEFEATINDETAIL2,        GAME_DEFEATINDETAIL2,   "did2",                         "-did2",                        "Defeat In Detail 2",   "data",         NULL,                   "did2_",                "did2"                          }, // COMMANDLINEOPTION: Game: -did2 runs the game Defeat In Detail 2
+{ GAME_DARSANA,                        GAME_DARSANA,                   "darsana",                      "-darsana",                     "Darsana",                              "ddata",        NULL,                   "darsana",              "darsana"                       }, // COMMANDLINEOPTION: Game: -darsana runs the game Darsana
+{ GAME_CONTAGIONTHEORY,        GAME_CONTAGIONTHEORY,   "contagiontheory",      "-contagiontheory",     "Contagion Theory",             "ctdata",       NULL,                   "ct",                   "contagiontheory"       }, // COMMANDLINEOPTION: Game: -contagiontheory runs the game Contagion Theory
+{ GAME_EDU2P,                  GAME_EDU2P,                             "edu2p",                        "-edu2p",                       "EDU2 Prototype",               "id1",          "edu2",                 "edu2_p",               "edu2prototype"         }, // COMMANDLINEOPTION: Game: -edu2p runs the game Edu2 prototype
+{ GAME_PROPHECY,               GAME_PROPHECY,                  "prophecy",             "-prophecy",            "Prophecy",                     "gamedata",             NULL,                   "phcy",         "prophecy"                      }, // COMMANDLINEOPTION: Game: -prophecy runs the game Prophecy
+{ GAME_BLOODOMNICIDE,  GAME_BLOODOMNICIDE,             "omnicide",                     "-omnicide",            "Blood Omnicide",               "kain",         NULL,                   "omnicide",             "omnicide"                      }, // COMMANDLINEOPTION: Game: -omnicide runs the game Blood Omnicide
+{ GAME_STEELSTORM,             GAME_STEELSTORM,                "steelstorm",           "-steelstorm",          "Steel-Storm",                  "gamedata",     NULL,                   "ss",                   "steelstorm"            }, // COMMANDLINEOPTION: Game: -steelstorm runs the game Steel Storm
+{ GAME_STEELSTORM2,            GAME_STEELSTORM2,               "steelstorm2",          "-steelstorm2",         "Steel Storm 2",                        "gamedata",     NULL,                   "ss2",                  "steelstorm2"           }, // COMMANDLINEOPTION: Game: -steelstorm2 runs the game Steel Storm 2
+{ GAME_TOMESOFMEPHISTOPHELES,          GAME_TOMESOFMEPHISTOPHELES,             "tomesofmephistopheles",                "-tomesofmephistopheles",               "Tomes of Mephistopheles",                      "gamedata",     NULL,                   "tom",                  "tomesofmephistopheles"         }, // COMMANDLINEOPTION: Game: -steelstorm runs the game Steel Storm
+{ GAME_STRAPBOMB,              GAME_STRAPBOMB,                 "strapbomb",            "-strapbomb",           "Strap-on-bomb Car",    "id1",          NULL,                   "strap",                "strapbomb"                     }, // COMMANDLINEOPTION: Game: -strapbomb runs the game Strap-on-bomb Car
+{ GAME_MOONHELM,               GAME_MOONHELM,                  "moonhelm",                     "-moonhelm",            "MoonHelm",                             "data",         NULL,                   "mh",                   "moonhelm"                      }, // COMMANDLINEOPTION: Game: -moonhelm runs the game MoonHelm
+{ GAME_VORETOURNAMENT,         GAME_VORETOURNAMENT,            "voretournament",               "-voretournament",      "Vore Tournament",                      "data",         NULL,                   "voretournament",       "voretournament"        }, // COMMANDLINEOPTION: Game: -voretournament runs the multiplayer game Vore Tournament
 };
 
+static void COM_SetGameType(int index);
 void COM_InitGameType (void)
 {
        char name [MAX_OSPATH];
-       unsigned int i;
+       int i;
+       int index = 0;
 
-       FS_StripExtension (com_argv[0], name, sizeof (name));
-       COM_ToLowerString (name, name, sizeof (name));
+#ifdef FORCEGAME
+       COM_ToLowerString(FORCEGAME, name, sizeof (name));
+#else
+       // check executable filename for keywords, but do it SMARTLY - only check the last path element
+       FS_StripExtension(FS_FileWithoutPath(com_argv[0]), name, sizeof (name));
+       COM_ToLowerString(name, name, sizeof (name));
+#endif
+       for (i = 1;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
+               if (gamemode_info[i].prog_name && gamemode_info[i].prog_name[0] && strstr (name, gamemode_info[i].prog_name))
+                       index = i;
+
+       // check commandline options for keywords
+       for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
+               if (COM_CheckParm (gamemode_info[i].cmdline))
+                       index = i;
 
-       // Check the binary name; default to GAME_NORMAL (0)
-       gamemode = GAME_NORMAL;
-       for (i = 1; i < sizeof (gamemode_info) / sizeof (gamemode_info[0]); i++)
-               if (strstr (name, gamemode_info[i].prog_name))
+       com_startupgamemode = gamemode_info[index].mode;
+       com_startupgamegroup = gamemode_info[index].group;
+       COM_SetGameType(index);
+}
+
+void COM_ChangeGameTypeForGameDirs(void)
+{
+       int i;
+       int index = -1;
+       // this will not not change the gamegroup
+       // first check if a base game (single gamedir) matches
+       for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
+       {
+               if (gamemode_info[i].group == com_startupgamegroup && !(gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]))
                {
-                       gamemode = (gamemode_t)i;
+                       index = i;
                        break;
                }
-
-       // Look for a command-line option
-       for (i = 0; i < sizeof (gamemode_info) / sizeof (gamemode_info[0]); i++)
-               if (COM_CheckParm (gamemode_info[i].cmdline))
+       }
+       // now that we have a base game, see if there is a matching derivative game (two gamedirs)
+       if (fs_numgamedirs)
+       {
+               for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
                {
-                       gamemode = (gamemode_t)i;
-                       break;
+                       if (gamemode_info[i].group == com_startupgamegroup && (gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]) && !strcasecmp(fs_gamedirs[0], gamemode_info[i].gamedirname2))
+                       {
+                               index = i;
+                               break;
+                       }
                }
+       }
+       // we now have a good guess at which game this is meant to be...
+       if (index >= 0 && gamemode != gamemode_info[index].mode)
+               COM_SetGameType(index);
+}
+
+static void COM_SetGameType(int index)
+{
+       int i, t;
+       if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0])))
+               index = 0;
+       gamemode = gamemode_info[index].mode;
+       gamename = gamemode_info[index].gamename;
+       gamedirname1 = gamemode_info[index].gamedirname1;
+       gamedirname2 = gamemode_info[index].gamedirname2;
+       gamescreenshotname = gamemode_info[index].gamescreenshotname;
+       gameuserdirname = gamemode_info[index].gameuserdirname;
 
-       gamename = gamemode_info[gamemode].gamename;
-       gamedirname1 = gamemode_info[gamemode].gamedirname1;
-       gamedirname2 = gamemode_info[gamemode].gamedirname2;
-       gamescreenshotname = gamemode_info[gamemode].gamescreenshotname;
-       gameuserdirname = gamemode_info[gamemode].gameuserdirname;
+       if (gamemode == com_startupgamemode)
+       {
+               if((t = COM_CheckParm("-customgamename")) && t + 1 < com_argc)
+                       gamename = com_argv[t+1];
+               if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < com_argc)
+                       gamedirname1 = com_argv[t+1];
+               if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < com_argc)
+                       gamedirname2 = *com_argv[t+1] ? com_argv[t+1] : NULL;
+               if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < com_argc)
+                       gamescreenshotname = com_argv[t+1];
+               if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < com_argc)
+                       gameuserdirname = com_argv[t+1];
+       }
+
+       if (gamedirname2 && gamedirname2[0])
+               Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2);
+       else
+               Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1);
+       for (i = 0;i < fs_numgamedirs;i++)
+       {
+               if (i == 0)
+                       Con_Printf(", with mod gamedirs");
+               Con_Printf(" %s", fs_gamedirs[i]);
+       }
+       Con_Printf("\n");
 }
 
 
@@ -1587,25 +1621,18 @@ void COM_Init_Commands (void)
 ============
 va
 
-does a varargs printf into a temp buffer, so I don't need to have
-varargs versions of all text functions.
-FIXME: make this buffer size safe someday
+varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
 ============
 */
-char *va(const char *format, ...)
+char *va(char *buf, size_t buflen, const char *format, ...)
 {
        va_list argptr;
-       // LordHavoc: now cycles through 8 buffers to avoid problems in most cases
-       static char string[8][1024], *s;
-       static int stringindex = 0;
 
-       s = string[stringindex];
-       stringindex = (stringindex + 1) & 7;
        va_start (argptr, format);
-       dpvsnprintf (s, sizeof (string[0]), format,argptr);
+       dpvsnprintf (buf, buflen, format,argptr);
        va_end (argptr);
 
-       return s;
+       return buf;
 }
 
 
@@ -1661,6 +1688,23 @@ void COM_ToLowerString (const char *in, char *out, size_t size_out)
        if (size_out == 0)
                return;
 
+       if(utf8_enable.integer)
+       {
+               *out = 0;
+               while(*in && size_out > 1)
+               {
+                       int n;
+                       Uchar ch = u8_getchar_utf8_enabled(in, &in);
+                       ch = u8_tolower(ch);
+                       n = u8_fromchar(ch, out, size_out);
+                       if(n <= 0)
+                               break;
+                       out += n;
+                       size_out -= n;
+               }
+               return;
+       }
+
        while (*in && size_out > 1)
        {
                if (*in >= 'A' && *in <= 'Z')
@@ -1677,6 +1721,23 @@ void COM_ToUpperString (const char *in, char *out, size_t size_out)
        if (size_out == 0)
                return;
 
+       if(utf8_enable.integer)
+       {
+               *out = 0;
+               while(*in && size_out > 1)
+               {
+                       int n;
+                       Uchar ch = u8_getchar_utf8_enabled(in, &in);
+                       ch = u8_toupper(ch);
+                       n = u8_fromchar(ch, out, size_out);
+                       if(n <= 0)
+                               break;
+                       out += n;
+                       size_out -= n;
+               }
+               return;
+       }
+
        while (*in && size_out > 1)
        {
                if (*in >= 'a' && *in <= 'z')
@@ -1913,97 +1974,33 @@ COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out,
 #undef APPEND
 }
 
-// written by Elric, thanks Elric!
-char *SearchInfostring(const char *infostring, const char *key)
-{
-       static char value [MAX_INPUTLINE];
-       char crt_key [MAX_INPUTLINE];
-       size_t value_ind, key_ind;
-       char c;
-
-       if (*infostring++ != '\\')
-               return NULL;
-
-       value_ind = 0;
-       for (;;)
-       {
-               key_ind = 0;
-
-               // Get the key name
-               for (;;)
-               {
-                       c = *infostring++;
-
-                       if (c == '\0')
-                               return NULL;
-                       if (c == '\\' || key_ind == sizeof (crt_key) - 1)
-                       {
-                               crt_key[key_ind] = '\0';
-                               break;
-                       }
-
-                       crt_key[key_ind++] = c;
-               }
-
-               // If it's the key we are looking for, save it in "value"
-               if (!strcmp(crt_key, key))
-               {
-                       for (;;)
-                       {
-                               c = *infostring++;
-
-                               if (c == '\0' || c == '\\' || value_ind == sizeof (value) - 1)
-                               {
-                                       value[value_ind] = '\0';
-                                       return value;
-                               }
-
-                               value[value_ind++] = c;
-                       }
-               }
-
-               // Else, skip the value
-               for (;;)
-               {
-                       c = *infostring++;
-
-                       if (c == '\0')
-                               return NULL;
-                       if (c == '\\')
-                               break;
-               }
-       }
-}
-
-void InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
+char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
 {
        int pos = 0, j;
        size_t keylength;
        if (!key)
                key = "";
-       if (!value)
-               value = "";
        keylength = strlen(key);
        if (valuelength < 1 || !value)
        {
                Con_Printf("InfoString_GetValue: no room in value\n");
-               return;
+               return NULL;
        }
        value[0] = 0;
        if (strchr(key, '\\'))
        {
                Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
-               return;
+               return NULL;
        }
        if (strchr(key, '\"'))
        {
                Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
-               return;
+               return NULL;
        }
        if (!key[0])
        {
                Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
-               return;
+               return NULL;
        }
        while (buffer[pos] == '\\')
        {
@@ -2014,12 +2011,13 @@ void InfoString_GetValue(const char *buffer, const char *key, char *value, size_
                        for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
                                value[j] = buffer[pos+j];
                        value[j] = 0;
-                       return;
+                       return value;
                }
                for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
                for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
        }
        // if we reach this point the key was not found
+       return NULL;
 }
 
 void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
@@ -2068,7 +2066,7 @@ void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, con
        if (value && value[0])
        {
                // set the key/value and append the remaining text
-               char tempbuffer[4096];
+               char tempbuffer[MAX_INPUTLINE];
                strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
                dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
        }
@@ -2082,8 +2080,8 @@ void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, con
 void InfoString_Print(char *buffer)
 {
        int i;
-       char key[2048];
-       char value[2048];
+       char key[MAX_INPUTLINE];
+       char value[MAX_INPUTLINE];
        while (*buffer)
        {
                if (*buffer != '\\')
@@ -2213,3 +2211,62 @@ void FindFraction(double val, int *num, int *denom, int denomMax)
                }
        }
 }
+
+// decodes an XPM from C syntax
+char **XPM_DecodeString(const char *in)
+{
+       static char *tokens[257];
+       static char lines[257][512];
+       size_t line = 0;
+
+       // skip until "{" token
+       while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
+
+       // now, read in succession: string, comma-or-}
+       while(COM_ParseToken_QuakeC(&in, false))
+       {
+               tokens[line] = lines[line];
+               strlcpy(lines[line++], com_token, sizeof(lines[0]));
+               if(!COM_ParseToken_QuakeC(&in, false))
+                       return NULL;
+               if(!strcmp(com_token, "}"))
+                       break;
+               if(strcmp(com_token, ","))
+                       return NULL;
+               if(line >= sizeof(tokens) / sizeof(tokens[0]))
+                       return NULL;
+       }
+
+       return tokens;
+}
+
+static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
+{
+       unsigned char i0 = (bytes > 0) ? in[0] : 0;
+       unsigned char i1 = (bytes > 1) ? in[1] : 0;
+       unsigned char i2 = (bytes > 2) ? in[2] : 0;
+       unsigned char o0 = base64[i0 >> 2];
+       unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
+       unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
+       unsigned char o3 = base64[i2 & 077];
+       out[0] = (bytes > 0) ? o0 : '?';
+       out[1] = (bytes > 0) ? o1 : '?';
+       out[2] = (bytes > 1) ? o2 : '=';
+       out[3] = (bytes > 2) ? o3 : '=';
+}
+
+size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
+{
+       size_t blocks, i;
+       // expand the out-buffer
+       blocks = (buflen + 2) / 3;
+       if(blocks*4 > outbuflen)
+               return 0;
+       for(i = blocks; i > 0; )
+       {
+               --i;
+               base64_3to4(buf + 3*i, buf + 4*i, buflen - 3*i);
+       }
+       return blocks * 4;
+}