]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - host_cmd.c
autocvars: update to current value after loading a savegame, to always fulfill the...
[xonotic/darkplaces.git] / host_cmd.c
index 95647886cc0d0a433bf11514e080302e4c0ed096..202e4d98837545e80f451366f854af79b9a7cca2 100644 (file)
@@ -19,17 +19,34 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 
 #include "quakedef.h"
+#include "sv_demo.h"
+#include "image.h"
+
+#include "utf8lib.h"
+
+// for secure rcon authentication
+#include "hmac.h"
+#include "mdfour.h"
+#include <time.h>
 
 int current_skill;
 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
-cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands"};
+cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
+cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
+cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
+cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
+cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
+cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
 qboolean allowcheats = false;
 
+extern qboolean host_shuttingdown;
+extern cvar_t developer_entityparsing;
+
 /*
 ==================
 Host_Quit_f
@@ -38,10 +55,12 @@ Host_Quit_f
 
 void Host_Quit_f (void)
 {
-       Sys_Quit (0);
+       if(host_shuttingdown)
+               Con_Printf("shutting down already!\n");
+       else
+               Sys_Quit (0);
 }
 
-
 /*
 ==================
 Host_Status_f
@@ -49,9 +68,12 @@ Host_Status_f
 */
 void Host_Status_f (void)
 {
+       char qcstatus[256];
        client_t *client;
-       int seconds, minutes, hours = 0, j, players;
+       int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
        void (*print) (const char *fmt, ...);
+       char ip[22];
+       int frags;
 
        if (cmd_source == src_command)
        {
@@ -68,33 +90,111 @@ void Host_Status_f (void)
 
        if (!sv.active)
                return;
+       
+       if(cmd_source == src_command)
+               SV_VM_Begin();
+       
+       in = 0;
+       if (Cmd_Argc() == 2)
+       {
+               if (strcmp(Cmd_Argv(1), "1") == 0)
+                       in = 1;
+               else if (strcmp(Cmd_Argv(1), "2") == 0)
+                       in = 2;
+       }
 
-       for (players = 0, j = 0;j < svs.maxclients;j++)
-               if (svs.clients[j].active)
+       for (players = 0, i = 0;i < svs.maxclients;i++)
+               if (svs.clients[i].active)
                        players++;
        print ("host:     %s\n", Cvar_VariableString ("hostname"));
        print ("version:  %s build %s\n", gamename, buildstring);
        print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
        print ("map:      %s\n", sv.name);
+       print ("timing:   %s\n", Host_TimingReport());
        print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
-       for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++)
+
+       if (in == 1)
+               print ("^2IP                   %%pl ping  time   frags  no   name\n");
+       else if (in == 2)
+               print ("^5IP                    no   name\n");
+
+       for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
        {
                if (!client->active)
                        continue;
-               seconds = (int)(realtime - client->connecttime);
-               minutes = seconds / 60;
-               if (minutes)
+
+               ++k;
+
+               if (in == 0 || in == 1)
                {
-                       seconds -= (minutes * 60);
-                       hours = minutes / 60;
-                       if (hours)
-                               minutes -= (hours * 60);
+                       seconds = (int)(realtime - client->connecttime);
+                       minutes = seconds / 60;
+                       if (minutes)
+                       {
+                               seconds -= (minutes * 60);
+                               hours = minutes / 60;
+                               if (hours)
+                                       minutes -= (hours * 60);
+                       }
+                       else
+                               hours = 0;
+                       
+                       packetloss = 0;
+                       if (client->netconnection)
+                               for (j = 0;j < NETGRAPH_PACKETS;j++)
+                                       if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
+                                               packetloss++;
+                       packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
+                       ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
                }
+
+               if(sv_status_privacy.integer && cmd_source != src_command)
+                       strlcpy(ip, client->netconnection ? "hidden" : "botclient" , 22);
                else
-                       hours = 0;
-               print ("#%-3u %-16.16s^%i  %3i  %2i:%02i:%02i\n", j+1, client->name, STRING_COLOR_DEFAULT, client->frags, hours, minutes, seconds);
-               print ("   %s\n", client->netconnection ? client->netconnection->address : "botclient");
+                       strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 22);
+
+               frags = client->frags;
+
+               if(sv_status_show_qcstatus.integer && prog->fieldoffsets.clientstatus >= 0)
+               {
+                       const char *str = PRVM_E_STRING(PRVM_EDICT_NUM(i + 1), prog->fieldoffsets.clientstatus);
+                       if(str && *str)
+                       {
+                               char *p;
+                               const char *q;
+                               p = qcstatus;
+                               for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
+                                       if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
+                                               *p++ = *q;
+                               *p = 0;
+                               if(*qcstatus)
+                                       frags = atoi(qcstatus);
+                       }
+               }
+               
+               if (in == 0) // default layout
+               {
+                       // LordHavoc: we must use multiple prints for ProQuake compatibility
+                       print ("#%-3u ", i+1);
+                       print ("%-16.16s ", client->name);
+                       print ("%4i  ", frags);
+                       print ("%2i:%02i:%02i\n   ", hours, minutes, seconds);
+                       print ("%s\n", ip);
+//                     print ("#%-3u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
+//                     print ("   %s\n", ip);
+               }
+               else if (in == 1) // extended layout
+               {
+                       print ("%s%-21s %2i %4i %2i:%02i:%02i %4i  #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
+               }
+               else if (in == 2) // reduced layout
+               {
+                       print ("%s%-21s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
+               }
        }
+
+       if(cmd_source == src_command)
+               SV_VM_End();
 }
 
 
@@ -225,7 +325,8 @@ void Host_Ping_f (void)
        }
 
        // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
-       Host_Pings_f();
+       // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
+       //Host_Pings_f();
 }
 
 /*
@@ -255,12 +356,26 @@ void Host_Map_f (void)
                return;
        }
 
+       // GAME_DELUXEQUAKE - clear warpmark (used by QC)
+       if (gamemode == GAME_DELUXEQUAKE)
+               Cvar_Set("warpmark", "");
+
        cls.demonum = -1;               // stop demo loop in case this fails
 
        CL_Disconnect ();
        Host_ShutdownServer();
 
+       if(svs.maxclients != svs.maxclients_next)
+       {
+               svs.maxclients = svs.maxclients_next;
+               if (svs.clients)
+                       Mem_Free(svs.clients);
+               svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
+       }
+
        // remove menu
+       if (key_dest == key_menu || key_dest == key_menu_grabbed)
+               MR_ToggleMenu(0);
        key_dest = key_game;
 
        svs.serverflags = 0;                    // haven't completed an episode yet
@@ -294,6 +409,8 @@ void Host_Changelevel_f (void)
        }
 
        // remove menu
+       if (key_dest == key_menu || key_dest == key_menu_grabbed)
+               MR_ToggleMenu(0);
        key_dest = key_game;
 
        SV_VM_Begin();
@@ -329,6 +446,8 @@ void Host_Restart_f (void)
        }
 
        // remove menu
+       if (key_dest == key_menu || key_dest == key_menu_grabbed)
+               MR_ToggleMenu(0);
        key_dest = key_game;
 
        allowcheats = sv_cheats.integer != 0;
@@ -408,6 +527,9 @@ void Host_Connect_f (void)
                Con_Print("connect <serveraddress> : connect to a multiplayer game\n");
                return;
        }
+       // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
+       if(rcon_secure.integer <= 0)
+               Cvar_SetQuick(&rcon_password, "");
        CL_EstablishConnection(Cmd_Argv(1));
 }
 
@@ -422,32 +544,148 @@ LOAD / SAVE GAME
 
 #define        SAVEGAME_VERSION        5
 
-/*
-===============
-Host_SavegameComment
-
-Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current
-===============
-*/
-void Host_SavegameComment (char *text)
+void Host_Savegame_to (const char *name)
 {
-       int             i;
-       char    kills[20];
+       qfile_t *f;
+       int             i, k, l, lightstyles = 64;
+       char    comment[SAVEGAME_COMMENT_LENGTH+1];
+       char    line[MAX_INPUTLINE];
+       qboolean isserver;
+       char    *s;
 
-       for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-               text[i] = ' ';
-       // LordHavoc: added min() to prevent overflow
-       memcpy (text, cl.levelname, min(strlen(cl.levelname), SAVEGAME_COMMENT_LENGTH));
-       sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
-       memcpy (text+22, kills, strlen(kills));
+       // first we have to figure out if this can be saved in 64 lightstyles
+       // (for Quake compatibility)
+       for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
+               if (sv.lightstyles[i][0])
+                       lightstyles = i+1;
+
+       isserver = !strcmp(PRVM_NAME, "server");
+
+       Con_Printf("Saving game to %s...\n", name);
+       f = FS_OpenRealFile(name, "wb", false);
+       if (!f)
+       {
+               Con_Print("ERROR: couldn't open.\n");
+               return;
+       }
+
+       FS_Printf(f, "%i\n", SAVEGAME_VERSION);
+
+       memset(comment, 0, sizeof(comment));
+       if(isserver)
+               dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog->edicts->fields.server->message), (int)prog->globals.server->killed_monsters, (int)prog->globals.server->total_monsters);
+       else
+               dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_NAME);
        // convert space to _ to make stdio happy
        // LordHavoc: convert control characters to _ as well
        for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-               if (text[i] <= ' ')
-                       text[i] = '_';
-       text[SAVEGAME_COMMENT_LENGTH] = '\0';
-}
+               if (ISWHITESPACEORCONTROL(comment[i]))
+                       comment[i] = '_';
+       comment[SAVEGAME_COMMENT_LENGTH] = '\0';
 
+       FS_Printf(f, "%s\n", comment);
+       if(isserver)
+       {
+               for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+                       FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
+               FS_Printf(f, "%d\n", current_skill);
+               FS_Printf(f, "%s\n", sv.name);
+               FS_Printf(f, "%f\n",sv.time);
+       }
+       else
+       {
+               for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+                       FS_Printf(f, "(dummy)\n");
+               FS_Printf(f, "%d\n", 0);
+               FS_Printf(f, "%s\n", "(dummy)");
+               FS_Printf(f, "%f\n", realtime);
+       }
+
+       // write the light styles
+       for (i=0 ; i<lightstyles ; i++)
+       {
+               if (isserver && sv.lightstyles[i][0])
+                       FS_Printf(f, "%s\n", sv.lightstyles[i]);
+               else
+                       FS_Print(f,"m\n");
+       }
+
+       PRVM_ED_WriteGlobals (f);
+       for (i=0 ; i<prog->num_edicts ; i++)
+       {
+               FS_Printf(f,"// edict %d\n", i);
+               //Con_Printf("edict %d...\n", i);
+               PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
+       }
+
+#if 1
+       FS_Printf(f,"/*\n");
+       FS_Printf(f,"// DarkPlaces extended savegame\n");
+       // darkplaces extension - extra lightstyles, support for color lightstyles
+       for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+               if (isserver && sv.lightstyles[i][0])
+                       FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
+
+       // darkplaces extension - model precaches
+       for (i=1 ; i<MAX_MODELS ; i++)
+               if (sv.model_precache[i][0])
+                       FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
+
+       // darkplaces extension - sound precaches
+       for (i=1 ; i<MAX_SOUNDS ; i++)
+               if (sv.sound_precache[i][0])
+                       FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
+
+       // darkplaces extension - save buffers
+       for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); i++)
+       {
+               prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
+               if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
+               {
+                       for(k = 0; k < stringbuffer->num_strings; k++)
+                       {
+                               if (!stringbuffer->strings[k])
+                                       continue;
+                               // Parse the string a bit to turn special characters
+                               // (like newline, specifically) into escape codes
+                               s = stringbuffer->strings[k];
+                               for (l = 0;l < (int)sizeof(line) - 2 && *s;)
+                               {       
+                                       if (*s == '\n')
+                                       {
+                                               line[l++] = '\\';
+                                               line[l++] = 'n';
+                                       }
+                                       else if (*s == '\r')
+                                       {
+                                               line[l++] = '\\';
+                                               line[l++] = 'r';
+                                       }
+                                       else if (*s == '\\')
+                                       {
+                                               line[l++] = '\\';
+                                               line[l++] = '\\';
+                                       }
+                                       else if (*s == '"')
+                                       {
+                                               line[l++] = '\\';
+                                               line[l++] = '"';
+                                       }
+                                       else
+                                               line[l++] = *s;
+                                       s++;
+                               }
+                               line[l] = '\0';
+                               FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
+                       }
+               }
+       }
+       FS_Printf(f,"*/\n");
+#endif
+
+       FS_Close (f);
+       Con_Print("done.\n");
+}
 
 /*
 ===============
@@ -457,38 +695,30 @@ Host_Savegame_f
 void Host_Savegame_f (void)
 {
        char    name[MAX_QPATH];
-       qfile_t *f;
-       int             i;
-       char    comment[SAVEGAME_COMMENT_LENGTH+1];
 
-       if (cls.state != ca_connected || !sv.active)
+       if (!sv.active)
        {
-               Con_Print("Not playing a local game.\n");
+               Con_Print("Can't save - no server running.\n");
                return;
        }
 
-       if (cl.intermission)
+       if (cl.islocalgame)
        {
-               Con_Print("Can't save in intermission.\n");
-               return;
-       }
+               // singleplayer checks
+               if (cl.intermission)
+               {
+                       Con_Print("Can't save in intermission.\n");
+                       return;
+               }
 
-       for (i = 0;i < svs.maxclients;i++)
-       {
-               if (svs.clients[i].active)
+               if (svs.clients[0].active && svs.clients[0].edict->fields.server->deadflag)
                {
-                       if (i > 0)
-                       {
-                               Con_Print("Can't save multiplayer games.\n");
-                               return;
-                       }
-                       if (svs.clients[i].edict->fields.server->deadflag)
-                       {
-                               Con_Print("Can't savegame with a dead player\n");
-                               return;
-                       }
+                       Con_Print("Can't savegame with a dead player\n");
+                       return;
                }
        }
+       else
+               Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
 
        if (Cmd_Argc() != 2)
        {
@@ -505,42 +735,9 @@ void Host_Savegame_f (void)
        strlcpy (name, Cmd_Argv(1), sizeof (name));
        FS_DefaultExtension (name, ".sav", sizeof (name));
 
-       Con_Printf("Saving game to %s...\n", name);
-       f = FS_Open (name, "wb", false, false);
-       if (!f)
-       {
-               Con_Print("ERROR: couldn't open.\n");
-               return;
-       }
-
-       FS_Printf(f, "%i\n", SAVEGAME_VERSION);
-       Host_SavegameComment (comment);
-       FS_Printf(f, "%s\n", comment);
-       for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
-               FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
-       FS_Printf(f, "%d\n", current_skill);
-       FS_Printf(f, "%s\n", sv.name);
-       FS_Printf(f, "%f\n",sv.time);
-
-       // write the light styles
-       for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
-       {
-               if (sv.lightstyles[i][0])
-                       FS_Printf(f, "%s\n", sv.lightstyles[i]);
-               else
-                       FS_Print(f,"m\n");
-       }
-
        SV_VM_Begin();
-
-       PRVM_ED_WriteGlobals (f);
-       for (i=0 ; i<prog->num_edicts ; i++)
-               PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
-
+       Host_Savegame_to(name);
        SV_VM_End();
-
-       FS_Close (f);
-       Con_Print("done.\n");
 }
 
 
@@ -549,20 +746,23 @@ void Host_Savegame_f (void)
 Host_Loadgame_f
 ===============
 */
+
 void Host_Loadgame_f (void)
 {
        char filename[MAX_QPATH];
        char mapname[MAX_QPATH];
        float time;
        const char *start;
+       const char *end;
        const char *t;
-       const char *oldt;
        char *text;
        prvm_edict_t *ent;
-       int i;
+       int i, k;
        int entnum;
        int version;
        float spawn_parms[NUM_SPAWN_PARMS];
+       prvm_stringbuffer_t *stringbuffer;
+       size_t alloclen;
 
        if (Cmd_Argc() != 2)
        {
@@ -575,6 +775,15 @@ void Host_Loadgame_f (void)
 
        Con_Printf("Loading game from %s...\n", filename);
 
+       // stop playing demos
+       if (cls.demoplayback)
+               CL_Disconnect ();
+
+       // remove menu
+       if (key_dest == key_menu || key_dest == key_menu_grabbed)
+               MR_ToggleMenu(0);
+       key_dest = key_game;
+
        cls.demonum = -1;               // stop demo loop in case this fails
 
        t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
@@ -584,8 +793,11 @@ void Host_Loadgame_f (void)
                return;
        }
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading version\n");
+
        // version
-       COM_ParseTokenConsole(&t);
+       COM_ParseToken_Simple(&t, false, false);
        version = atoi(com_token);
        if (version != SAVEGAME_VERSION)
        {
@@ -594,32 +806,42 @@ void Host_Loadgame_f (void)
                return;
        }
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading description\n");
+
        // description
-       // this is a little hard to parse, as : is a separator in COM_ParseToken,
-       // so use the console parser instead
-       COM_ParseTokenConsole(&t);
+       COM_ParseToken_Simple(&t, false, false);
 
        for (i = 0;i < NUM_SPAWN_PARMS;i++)
        {
-               COM_ParseTokenConsole(&t);
+               COM_ParseToken_Simple(&t, false, false);
                spawn_parms[i] = atof(com_token);
        }
        // skill
-       COM_ParseTokenConsole(&t);
+       COM_ParseToken_Simple(&t, false, false);
 // this silliness is so we can load 1.06 save files, which have float skill values
        current_skill = (int)(atof(com_token) + 0.5);
        Cvar_SetValue ("skill", (float)current_skill);
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading mapname\n");
+
        // mapname
-       COM_ParseTokenConsole(&t);
+       COM_ParseToken_Simple(&t, false, false);
        strlcpy (mapname, com_token, sizeof(mapname));
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading time\n");
+
        // time
-       COM_ParseTokenConsole(&t);
+       COM_ParseToken_Simple(&t, false, false);
        time = atof(com_token);
 
        allowcheats = sv_cheats.integer != 0;
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: spawning server\n");
+
        SV_SpawnServer (mapname);
        if (!sv.active)
        {
@@ -630,49 +852,61 @@ void Host_Loadgame_f (void)
        sv.paused = true;               // pause until all clients connect
        sv.loadgame = true;
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading light styles\n");
+
 // load the light styles
 
+       SV_VM_Begin();
+       // -1 is the globals
+       entnum = -1;
+
        for (i = 0;i < MAX_LIGHTSTYLES;i++)
        {
                // light style
-               oldt = t;
-               COM_ParseTokenConsole(&t);
+               start = t;
+               COM_ParseToken_Simple(&t, false, false);
                // if this is a 64 lightstyle savegame produced by Quake, stop now
-               // we have to check this because darkplaces saves 256 lightstyle savegames
+               // we have to check this because darkplaces may save more than 64
                if (com_token[0] == '{')
                {
-                       t = oldt;
+                       t = start;
                        break;
                }
                strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
        }
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: skipping until globals\n");
+
        // now skip everything before the first opening brace
        // (this is for forward compatibility, so that older versions (at
        // least ones with this fix) can load savegames with extra data before the
        // first brace, as might be produced by a later engine version)
-       for(;;)
+       for (;;)
        {
-               oldt = t;
-               COM_ParseTokenConsole(&t);
+               start = t;
+               if (!COM_ParseToken_Simple(&t, false, false))
+                       break;
                if (com_token[0] == '{')
                {
-                       t = oldt;
+                       t = start;
                        break;
                }
        }
 
+       // unlink all entities
+       World_UnlinkAll(&sv.world);
+
 // load the edicts out of the savegame file
-       SV_VM_Begin();
-       // -1 is the globals
-       entnum = -1;
+       end = t;
        for (;;)
        {
                start = t;
-               while (COM_ParseTokenConsole(&t))
+               while (COM_ParseToken_Simple(&t, false, false))
                        if (!strcmp(com_token, "}"))
                                break;
-               if (!COM_ParseTokenConsole(&start))
+               if (!COM_ParseToken_Simple(&start, false, false))
                {
                        // end of file
                        break;
@@ -685,8 +919,14 @@ void Host_Loadgame_f (void)
 
                if (entnum == -1)
                {
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading globals\n");
+
                        // parse the global vars
                        PRVM_ED_ParseGlobals (start);
+
+                       // restore the autocvar globals
+                       Cvar_UpdateAllAutoCvars();
                }
                else
                {
@@ -697,21 +937,24 @@ void Host_Loadgame_f (void)
                                Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
                        }
                        while (entnum >= prog->max_edicts)
-                               //SV_IncreaseEdicts();
                                PRVM_MEM_IncreaseEdicts();
                        ent = PRVM_EDICT_NUM(entnum);
                        memset (ent->fields.server, 0, prog->progs->entityfields * 4);
                        ent->priv.server->free = false;
+
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
+
                        PRVM_ED_ParseEdict (start, ent);
 
                        // link it into the bsp tree
                        if (!ent->priv.server->free)
-                               SV_LinkEdict (ent, false);
+                               SV_LinkEdict(ent);
                }
 
+               end = t;
                entnum++;
        }
-       Mem_Free(text);
 
        prog->num_edicts = entnum;
        sv.time = time;
@@ -719,10 +962,116 @@ void Host_Loadgame_f (void)
        for (i = 0;i < NUM_SPAWN_PARMS;i++)
                svs.clients[0].spawn_parms[i] = spawn_parms[i];
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: skipping until extended data\n");
+
+       // read extended data if present
+       // the extended data is stored inside a /* */ comment block, which the
+       // parser intentionally skips, so we have to check for it manually here
+       if(end)
+       {
+               while (*end == '\r' || *end == '\n')
+                       end++;
+               if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
+               {
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading extended data\n");
+
+                       Con_Printf("Loading extended DarkPlaces savegame\n");
+                       t = end + 2;
+                       memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
+                       memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
+                       memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
+                       while (COM_ParseToken_Simple(&t, false, false))
+                       {
+                               if (!strcmp(com_token, "sv.lightstyles"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_LIGHTSTYLES)
+                                               strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
+                                       else
+                                               Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
+                               }
+                               else if (!strcmp(com_token, "sv.model_precache"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_MODELS)
+                                       {
+                                               strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
+                                               sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
+                                       }
+                                       else
+                                               Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
+                               }
+                               else if (!strcmp(com_token, "sv.sound_precache"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_SOUNDS)
+                                               strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
+                                       else
+                                               Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
+                               }
+                               else if (!strcmp(com_token, "sv.bufstr"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       k = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
+                                       // VorteX: nasty code, cleanup required
+                                       // create buffer at this index
+                                       if(!stringbuffer) 
+                                               stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i);
+                                       if (!stringbuffer)
+                                               Con_Printf("cant write string %i into buffer %i\n", k, i);
+                                       else
+                                       {
+                                               // code copied from VM_bufstr_set
+                                               // expand buffer
+                                               if (stringbuffer->max_strings <= i)
+                                               {
+                                                       char **oldstrings = stringbuffer->strings;
+                                                       stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128);
+                                                       while (stringbuffer->max_strings <= i)
+                                                               stringbuffer->max_strings *= 2;
+                                                       stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0]));
+                                                       if (stringbuffer->num_strings > 0)
+                                                               memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0]));
+                                                       if (oldstrings)
+                                                               Mem_Free(oldstrings);
+                                               }
+                                               // allocate string
+                                               stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1);
+                                               if(stringbuffer->strings[k])
+                                                       Mem_Free(stringbuffer->strings[k]);
+                                               stringbuffer->strings[k] = NULL;
+                                               alloclen = strlen(com_token) + 1;
+                                               stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen);
+                                               memcpy(stringbuffer->strings[k], com_token, alloclen);
+                                       }
+                               }       
+                               // skip any trailing text or unrecognized commands
+                               while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n"))
+                                       ;
+                       }
+               }
+       }
+       Mem_Free(text);
+
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: finished\n");
+
        SV_VM_End();
 
        // make sure we're connected to loopback
-       if (cls.state == ca_disconnected || !(cls.state == ca_connected && cls.netcon != NULL && LHNETADDRESS_GetAddressType(&cls.netcon->peeraddress) == LHNETADDRESSTYPE_LOOP))
+       if (sv.active && cls.state == ca_disconnected)
                CL_EstablishConnection("local:1");
 }
 
@@ -738,27 +1087,30 @@ void Host_Name_f (void)
 {
        int i, j;
        qboolean valid_colors;
+       const char *newNameSource;
        char newName[sizeof(host_client->name)];
 
        if (Cmd_Argc () == 1)
        {
-               Con_Printf("\"name\" is \"%s\"\n", cl_name.string);
+               Con_Printf("name: %s\n", cl_name.string);
                return;
        }
 
        if (Cmd_Argc () == 2)
-               strlcpy (newName, Cmd_Argv(1), sizeof (newName));
+               newNameSource = Cmd_Argv(1);
        else
-               strlcpy (newName, Cmd_Args(), sizeof (newName));
+               newNameSource = Cmd_Args();
 
-       for (i = 0, j = 0;newName[i];i++)
-               if (newName[i] != '\r' && newName[i] != '\n')
-                       newName[j++] = newName[i];
-       newName[j] = 0;
+       strlcpy(newName, newNameSource, sizeof(newName));
 
        if (cmd_source == src_command)
        {
                Cvar_Set ("_cl_name", newName);
+               if (strlen(newNameSource) >= sizeof(newName)) // overflowed
+               {
+                       Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
+                       Con_Printf("name: %s\n", cl_name.string);
+               }
                return;
        }
 
@@ -773,7 +1125,21 @@ void Host_Name_f (void)
        // point the string back at updateclient->name to keep it safe
        strlcpy (host_client->name, newName, sizeof (host_client->name));
 
-       COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
+       for (i = 0, j = 0;host_client->name[i];i++)
+               if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
+                       host_client->name[j++] = host_client->name[i];
+       host_client->name[j] = 0;
+
+       if(host_client->name[0] == 1 || host_client->name[0] == 2)
+       // may interfere with chat area, and will needlessly beep; so let's add a ^7
+       {
+               memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
+               host_client->name[sizeof(host_client->name) - 1] = 0;
+               host_client->name[0] = STRING_COLOR_TAG;
+               host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
+       }
+
+       u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
        if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
        {
                size_t l;
@@ -793,16 +1159,48 @@ void Host_Name_f (void)
                }
        }
 
+       // find the last color tag offset and decide if we need to add a reset tag
+       for (i = 0, j = -1;host_client->name[i];i++)
+       {
+               if (host_client->name[i] == STRING_COLOR_TAG)
+               {
+                       if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
+                       {
+                               j = i;
+                               // if this happens to be a reset  tag then we don't need one
+                               if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
+                                       j = -1;
+                               i++;
+                               continue;
+                       }
+                       if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4]))
+                       {
+                               j = i;
+                               i += 4;
+                               continue;
+                       }
+                       if (host_client->name[i+1] == STRING_COLOR_TAG)
+                       {
+                               i++;
+                               continue;
+                       }
+               }
+       }
+       // does not end in the default color string, so add it
+       if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
+               memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
+
        host_client->edict->fields.server->netname = PRVM_SetEngineString(host_client->name);
        if (strcmp(host_client->old_name, host_client->name))
        {
                if (host_client->spawned)
-                       SV_BroadcastPrintf("%s^%i changed name to %s\n", host_client->old_name, STRING_COLOR_DEFAULT, host_client->name);
+                       SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
                strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
                // send notification to all clients
                MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
                MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
                MSG_WriteString (&sv.reliable_datagram, host_client->name);
+               SV_WriteNetnameIntoDemo(host_client);
        }
 }
 
@@ -967,13 +1365,13 @@ void Host_Say(qboolean teamonly)
        }
        // note this uses the chat prefix \001
        if (!fromServer && !teamonly)
-               dpsnprintf (text, sizeof(text), "\001%s" STRING_COLOR_DEFAULT_STR ": %s", host_client->name, p1);
+               dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
        else if (!fromServer && teamonly)
-               dpsnprintf (text, sizeof(text), "\001(%s" STRING_COLOR_DEFAULT_STR "): %s", host_client->name, p1);
+               dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
        else if(*(sv_adminnick.string))
-               dpsnprintf (text, sizeof(text), "\001<%s" STRING_COLOR_DEFAULT_STR "> %s", sv_adminnick.string, p1);
+               dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
        else
-               dpsnprintf (text, sizeof(text), "\001<%s" STRING_COLOR_DEFAULT_STR "> %s", hostname.string, p1);
+               dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
        p2 = text + strlen(text);
        while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
        {
@@ -1035,11 +1433,11 @@ void Host_Tell_f(void)
 
        // note this uses the chat prefix \001
        if (!fromServer)
-               sprintf (text, "\001%s tells you: ", host_client->name);
+               dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
        else if(*(sv_adminnick.string))
-               sprintf (text, "\001<%s tells you> ", sv_adminnick.string);
+               dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
        else
-               sprintf (text, "\001<%s tells you> ", hostname.string);
+               dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
 
        p1 = Cmd_Args();
        p2 = p1 + strlen(p1);
@@ -1245,7 +1643,7 @@ void Host_BottomColor_f(void)
        Host_Color(-1, atoi(Cmd_Argv(1)));
 }
 
-cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "10000", "internal storage cvar for current rate (changed by rate command)"};
+cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
 void Host_Rate_f(void)
 {
        int rate;
@@ -1361,6 +1759,7 @@ void Host_PreSpawn_f (void)
                SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
                MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
                MSG_WriteByte (&host_client->netconnection->message, 2);
+               host_client->sendsignon = 0;            // enable unlimited sends again
        }
 
        // reset the name change timer because the client will send name soon
@@ -1397,9 +1796,6 @@ void Host_Spawn_f (void)
        if (sv.loadgame)
        {
                // loaded games are fully initialized already
-               // if this is the last client to be connected, unpause
-               sv.paused = false;
-
                if (prog->funcoffsets.RestoreGame)
                {
                        Con_DPrint("Calling RestoreGame\n");
@@ -1422,8 +1818,8 @@ void Host_Spawn_f (void)
                prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
                PRVM_ExecuteProgram (prog->globals.server->ClientConnect, "QC function ClientConnect is missing");
 
-               if (svs.maxclients > 1 || cls.state == ca_dedicated)
-                       Con_Printf("%s^%i entered the game\n", host_client->name, STRING_COLOR_DEFAULT);
+               if (cls.state == ca_dedicated)
+                       Con_Printf("%s connected\n", host_client->name);
 
                PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing");
        }
@@ -1490,7 +1886,6 @@ void Host_Spawn_f (void)
                MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[0], sv.protocol);
                MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[1], sv.protocol);
                MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
-               sv.loadgame = false; // we're basically done with loading now
        }
        else
        {
@@ -1514,6 +1909,20 @@ Host_Begin_f
 void Host_Begin_f (void)
 {
        host_client->spawned = true;
+
+       // LordHavoc: note: this code also exists in SV_DropClient
+       if (sv.loadgame)
+       {
+               int i;
+               for (i = 0;i < svs.maxclients;i++)
+                       if (svs.clients[i].active && !svs.clients[i].spawned)
+                               break;
+               if (i == svs.maxclients)
+               {
+                       Con_Printf("Loaded game, everyone rejoined - unpausing\n");
+                       sv.paused = sv.loadgame = false; // we're basically done with loading now
+               }
+       }
 }
 
 //===========================================================================
@@ -1577,7 +1986,7 @@ void Host_Kick_f (void)
                if (Cmd_Argc() > 2)
                {
                        message = Cmd_Args();
-                       COM_ParseTokenConsole(&message);
+                       COM_ParseToken_Simple(&message, false, false);
                        if (byNumber)
                        {
                                message++;                                                      // skip the #
@@ -1781,7 +2190,7 @@ Host_Viewmodel_f
 void Host_Viewmodel_f (void)
 {
        prvm_edict_t    *e;
-       model_t *m;
+       dp_model_t      *m;
 
        if (!sv.active)
                return;
@@ -1792,7 +2201,7 @@ void Host_Viewmodel_f (void)
        if (!e)
                return;
 
-       m = Mod_ForName (Cmd_Argv(1), false, true, false);
+       m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
        if (!m || !m->loaded || !m->Draw)
        {
                Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
@@ -1812,7 +2221,7 @@ void Host_Viewframe_f (void)
 {
        prvm_edict_t    *e;
        int             f;
-       model_t *m;
+       dp_model_t      *m;
 
        if (!sv.active)
                return;
@@ -1832,7 +2241,7 @@ void Host_Viewframe_f (void)
 }
 
 
-void PrintFrameName (model_t *m, int frame)
+void PrintFrameName (dp_model_t *m, int frame)
 {
        if (m->animscenes)
                Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
@@ -1848,7 +2257,7 @@ Host_Viewnext_f
 void Host_Viewnext_f (void)
 {
        prvm_edict_t    *e;
-       model_t *m;
+       dp_model_t      *m;
 
        if (!sv.active)
                return;
@@ -1875,7 +2284,7 @@ Host_Viewprev_f
 void Host_Viewprev_f (void)
 {
        prvm_edict_t    *e;
-       model_t *m;
+       dp_model_t      *m;
 
        if (!sv.active)
                return;
@@ -1913,7 +2322,7 @@ void Host_Startdemos_f (void)
 {
        int             i, c;
 
-       if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo"))
+       if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
                return;
 
        c = Cmd_Argc() - 1;
@@ -1922,7 +2331,7 @@ void Host_Startdemos_f (void)
                Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
                c = MAX_DEMOS;
        }
-       Con_Printf("%i demo(s) in loop\n", c);
+       Con_DPrintf("%i demo(s) in loop\n", c);
 
        for (i=1 ; i<c+1 ; i++)
                strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
@@ -1989,9 +2398,9 @@ void Host_SendCvar_f (void)
                // LordHavoc: if there is no such cvar or if it is private, send a
                // reply indicating that it has no value
                if(!c || (c->flags & CVAR_PRIVATE))
-                       Cmd_ForwardStringToServer(va("sentcvar %s\n", cvarname));
+                       Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
                else
-                       Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"\n", c->name, c->string));
+                       Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
                return;
        }
        if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand)
@@ -2006,7 +2415,7 @@ void Host_SendCvar_f (void)
                if(svs.clients[i].active && svs.clients[i].netconnection)
                {
                        host_client = &svs.clients[i];
-                       Host_ClientCommands(va("sendcvar %s\n", cvarname));
+                       Host_ClientCommands("sendcvar %s\n", cvarname);
                }
        host_client = old;
 }
@@ -2017,30 +2426,79 @@ static void MaxPlayers_f(void)
 
        if (Cmd_Argc() != 2)
        {
-               Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients);
+               Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
                return;
        }
 
        if (sv.active)
        {
                Con_Print("maxplayers can not be changed while a server is running.\n");
-               return;
+               Con_Print("It will be changed on next server startup (\"map\" command).\n");
        }
 
        n = atoi(Cmd_Argv(1));
        n = bound(1, n, MAX_SCOREBOARD);
        Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
 
-       if (svs.clients)
-               Mem_Free(svs.clients);
-       svs.maxclients = n;
-       svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
+       svs.maxclients_next = n;
        if (n == 1)
                Cvar_Set ("deathmatch", "0");
        else
                Cvar_Set ("deathmatch", "1");
 }
 
+/*
+=====================
+Host_PQRcon_f
+
+ProQuake rcon support
+=====================
+*/
+void Host_PQRcon_f (void)
+{
+       int n;
+       const char *e;
+       lhnetaddress_t to;
+       lhnetsocket_t *mysocket;
+       char peer_address[64];
+
+       if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
+       {
+               Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
+               return;
+       }
+
+       e = strchr(rcon_password.string, ' ');
+       n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
+
+       if (cls.netcon)
+       {
+               InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
+       }
+       else
+       {
+               if (!rcon_address.string[0])
+               {
+                       Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
+                       return;
+               }
+               strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
+       }
+       LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
+       mysocket = NetConn_ChooseClientSocketForAddress(&to);
+       if (mysocket)
+       {
+               SZ_Clear(&net_message);
+               MSG_WriteLong (&net_message, 0);
+               MSG_WriteByte (&net_message, CCREQ_RCON);
+               SZ_Write(&net_message, (void*)rcon_password.string, n);
+               MSG_WriteString (&net_message, Cmd_Args());
+               StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
+               NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
+               SZ_Clear (&net_message);
+       }
+}
+
 //=============================================================================
 
 // QuakeWorld commands
@@ -2055,7 +2513,8 @@ Host_Rcon_f
 */
 void Host_Rcon_f (void) // credit: taken from QuakeWorld
 {
-       int i;
+       int i, n;
+       const char *e;
        lhnetaddress_t to;
        lhnetsocket_t *mysocket;
 
@@ -2065,14 +2524,8 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld
                return;
        }
 
-       for (i = 0;rcon_password.string[i];i++)
-       {
-               if (rcon_password.string[i] <= ' ')
-               {
-                       Con_Printf("rcon_password is not allowed to have any whitespace.\n");
-                       return;
-               }
-       }
+       e = strchr(rcon_password.string, ' ');
+       n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
 
        if (cls.netcon)
                to = cls.netcon->peeraddress;
@@ -2086,10 +2539,48 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld
                LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
        }
        mysocket = NetConn_ChooseClientSocketForAddress(&to);
-       if (mysocket)
+       if (mysocket && Cmd_Args()[0])
        {
                // simply put together the rcon packet and send it
-               NetConn_WriteString(mysocket, va("\377\377\377\377rcon %s %s", rcon_password.string, Cmd_Args()), &to);
+               if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
+               {
+                       if(cls.rcon_commands[cls.rcon_ringpos][0])
+                       {
+                               char s[128];
+                               LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
+                               Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]);
+                               cls.rcon_commands[cls.rcon_ringpos][0] = 0;
+                               --cls.rcon_trying;
+                       }
+                       for (i = 0;i < MAX_RCONS;i++)
+                               if(cls.rcon_commands[i][0])
+                                       if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
+                                               break;
+                       ++cls.rcon_trying;
+                       if(i >= MAX_RCONS)
+                               NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
+                       strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
+                       cls.rcon_addresses[cls.rcon_ringpos] = to;
+                       cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
+                       cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
+               }
+               else if(rcon_secure.integer > 0)
+               {
+                       char buf[1500];
+                       char argbuf[1500];
+                       dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
+                       memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
+                       if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
+                       {
+                               buf[40] = ' ';
+                               strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
+                               NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
+                       }
+               }
+               else
+               {
+                       NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
+               }
        }
 }
 
@@ -2281,7 +2772,7 @@ void Host_Packet_f (void) // credit: taken from QuakeWorld
 
        in = Cmd_Argv(2);
        out = send+4;
-       send[0] = send[1] = send[2] = send[3] = 0xff;
+       send[0] = send[1] = send[2] = send[3] = -1;
 
        l = (int)strlen (in);
        for (i=0 ; i<l ; i++)
@@ -2318,6 +2809,8 @@ void Host_Packet_f (void) // credit: taken from QuakeWorld
        }
 
        mysocket = NetConn_ChooseClientSocketForAddress(&address);
+       if (!mysocket)
+               mysocket = NetConn_ChooseServerSocketForAddress(&address);
        if (mysocket)
                NetConn_Write(mysocket, send, out - send, &address);
 }
@@ -2331,7 +2824,7 @@ Send back ping and packet loss update for all current players to this player
 */
 void Host_Pings_f (void)
 {
-       int             i, j, ping, packetloss;
+       int             i, j, ping, packetloss, movementloss;
        char temp[128];
 
        if (!host_client->netconnection)
@@ -2345,11 +2838,18 @@ void Host_Pings_f (void)
        for (i = 0;i < svs.maxclients;i++)
        {
                packetloss = 0;
+               movementloss = 0;
                if (svs.clients[i].netconnection)
+               {
                        for (j = 0;j < NETGRAPH_PACKETS;j++)
-                               if (svs.clients[i].netconnection->incoming_unreliablesize[j] == NETGRAPH_LOSTPACKET)
+                               if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
                                        packetloss++;
-               packetloss = packetloss * 100 / NETGRAPH_PACKETS;
+                       for (j = 0;j < NETGRAPH_PACKETS;j++)
+                               if (svs.clients[i].movement_count[j] < 0)
+                                       movementloss++;
+               }
+               packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
+               movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
                ping = (int)floor(svs.clients[i].ping*1000+0.5);
                ping = bound(0, ping, 9999);
                if (sv.protocol == PROTOCOL_QUAKEWORLD)
@@ -2363,7 +2863,10 @@ void Host_Pings_f (void)
                else
                {
                        // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
-                       dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
+                       if(movementloss)
+                               dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
+                       else
+                               dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
                        MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
                }
        }
@@ -2373,6 +2876,7 @@ void Host_Pings_f (void)
 
 void Host_PingPLReport_f(void)
 {
+       char *errbyte;
        int i;
        int l = Cmd_Argc();
        if (l > cl.maxclients)
@@ -2380,7 +2884,11 @@ void Host_PingPLReport_f(void)
        for (i = 0;i < l;i++)
        {
                cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
-               cl.scores[i].qw_packetloss = atoi(Cmd_Argv(1+i*2+1));
+               cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
+               if(errbyte && *errbyte == ',')
+                       cl.scores[i].qw_movementloss = atoi(errbyte + 1);
+               else
+                       cl.scores[i].qw_movementloss = 0;
        }
 }
 
@@ -2465,7 +2973,11 @@ void Host_InitCommands (void)
 
        Cvar_RegisterVariable (&rcon_password);
        Cvar_RegisterVariable (&rcon_address);
-       Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
+       Cvar_RegisterVariable (&rcon_secure);
+       Cvar_RegisterVariable (&rcon_secure_challengetimeout);
+       Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP");
+       Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP");
+       Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
        Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
        Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
        Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
@@ -2478,11 +2990,19 @@ void Host_InitCommands (void)
        Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
        Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
 
+       Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
+       Cvar_RegisterVariable (&r_fixtrans_auto);
+
        Cvar_RegisterVariable (&team);
        Cvar_RegisterVariable (&skin);
        Cvar_RegisterVariable (&noaim);
 
        Cvar_RegisterVariable(&sv_cheats);
        Cvar_RegisterVariable(&sv_adminnick);
+       Cvar_RegisterVariable(&sv_status_privacy);
+       Cvar_RegisterVariable(&sv_status_show_qcstatus);
 }
 
+void Host_NoOperation_f(void)
+{
+}