]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - host_cmd.c
(Round 3) Break up host_cmd.c
[xonotic/darkplaces.git] / host_cmd.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22 #include "sv_demo.h"
23 #include "image.h"
24
25 #include "prvm_cmds.h"
26 #include "utf8lib.h"
27
28 // for secure rcon authentication
29 #include "hmac.h"
30 #include "mdfour.h"
31 #include <time.h>
32
33 int current_skill;
34 extern cvar_t sv_adminnick;
35 extern cvar_t sv_status_privacy;
36 extern cvar_t sv_status_show_qcstatus;
37 extern cvar_t sv_namechangetimer;
38 cvar_t rcon_password = {CVAR_CLIENT | CVAR_SERVER | 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"};
39 cvar_t rcon_secure = {CVAR_CLIENT | CVAR_SERVER | 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"};
40 cvar_t rcon_secure_challengetimeout = {CVAR_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
41 cvar_t rcon_address = {CVAR_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_CLIENT | CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 cvar_t r_fixtrans_auto = {CVAR_CLIENT, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
46
47 /*
48 ==================
49 CL_Reconnect_f
50
51 This command causes the client to wait for the signon messages again.
52 This is sent just before a server changes levels
53 ==================
54 */
55 void CL_Reconnect_f(cmd_state_t *cmd)
56 {
57         char temp[128];
58         // if not connected, reconnect to the most recent server
59         if (!cls.netcon)
60         {
61                 // if we have connected to a server recently, the userinfo
62                 // will still contain its IP address, so get the address...
63                 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
64                 if (temp[0])
65                         CL_EstablishConnection(temp, -1);
66                 else
67                         Con_Printf("Reconnect to what server?  (you have not connected to a server yet)\n");
68                 return;
69         }
70         // if connected, do something based on protocol
71         if (cls.protocol == PROTOCOL_QUAKEWORLD)
72         {
73                 // quakeworld can just re-login
74                 if (cls.qw_downloadmemory)  // don't change when downloading
75                         return;
76
77                 S_StopAllSounds();
78
79                 if (cls.state == ca_connected)
80                 {
81                         Con_Printf("Server is changing level...\n");
82                         MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
83                         MSG_WriteString(&cls.netcon->message, "new");
84                 }
85         }
86         else
87         {
88                 // netquake uses reconnect on level changes (silly)
89                 if (Cmd_Argc(cmd) != 1)
90                 {
91                         Con_Print("reconnect : wait for signon messages again\n");
92                         return;
93                 }
94                 if (!cls.signon)
95                 {
96                         Con_Print("reconnect: no signon, ignoring reconnect\n");
97                         return;
98                 }
99                 cls.signon = 0;         // need new connection messages
100         }
101 }
102
103 /*
104 =====================
105 CL_Connect_f
106
107 User command to connect to server
108 =====================
109 */
110 static void CL_Connect_f(cmd_state_t *cmd)
111 {
112         if (Cmd_Argc(cmd) < 2)
113         {
114                 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
115                 return;
116         }
117         // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
118         if(rcon_secure.integer <= 0)
119                 Cvar_SetQuick(&rcon_password, "");
120         CL_EstablishConnection(Cmd_Argv(cmd, 1), 2);
121 }
122
123
124 //============================================================================
125
126 /*
127 ======================
128 CL_Name_f
129 ======================
130 */
131 cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
132 static void CL_Name_f(cmd_state_t *cmd)
133 {
134         prvm_prog_t *prog = SVVM_prog;
135         int i, j;
136         qboolean valid_colors;
137         const char *newNameSource;
138         char newName[sizeof(host_client->name)];
139
140         if (Cmd_Argc (cmd) == 1)
141         {
142                 if (cmd->source == src_command)
143                 {
144                         Con_Printf("name: %s\n", cl_name.string);
145                 }
146                 return;
147         }
148
149         if (Cmd_Argc (cmd) == 2)
150                 newNameSource = Cmd_Argv(cmd, 1);
151         else
152                 newNameSource = Cmd_Args(cmd);
153
154         strlcpy(newName, newNameSource, sizeof(newName));
155
156         if (cmd->source == src_command)
157         {
158                 Cvar_Set (&cvars_all, "_cl_name", newName);
159                 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
160                 {
161                         Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
162                         Con_Printf("name: %s\n", cl_name.string);
163                 }
164                 return;
165         }
166
167         if (host.realtime < host_client->nametime)
168         {
169                 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
170                 return;
171         }
172
173         host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
174
175         // point the string back at updateclient->name to keep it safe
176         strlcpy (host_client->name, newName, sizeof (host_client->name));
177
178         for (i = 0, j = 0;host_client->name[i];i++)
179                 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
180                         host_client->name[j++] = host_client->name[i];
181         host_client->name[j] = 0;
182
183         if(host_client->name[0] == 1 || host_client->name[0] == 2)
184         // may interfere with chat area, and will needlessly beep; so let's add a ^7
185         {
186                 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
187                 host_client->name[sizeof(host_client->name) - 1] = 0;
188                 host_client->name[0] = STRING_COLOR_TAG;
189                 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
190         }
191
192         u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
193         if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
194         {
195                 size_t l;
196                 l = strlen(host_client->name);
197                 if(l < sizeof(host_client->name) - 1)
198                 {
199                         // duplicate the color tag to escape it
200                         host_client->name[i] = STRING_COLOR_TAG;
201                         host_client->name[i+1] = 0;
202                         //Con_DPrintf("abuse detected, adding another trailing color tag\n");
203                 }
204                 else
205                 {
206                         // remove the last character to fix the color code
207                         host_client->name[l-1] = 0;
208                         //Con_DPrintf("abuse detected, removing a trailing color tag\n");
209                 }
210         }
211
212         // find the last color tag offset and decide if we need to add a reset tag
213         for (i = 0, j = -1;host_client->name[i];i++)
214         {
215                 if (host_client->name[i] == STRING_COLOR_TAG)
216                 {
217                         if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
218                         {
219                                 j = i;
220                                 // if this happens to be a reset  tag then we don't need one
221                                 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
222                                         j = -1;
223                                 i++;
224                                 continue;
225                         }
226                         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]))
227                         {
228                                 j = i;
229                                 i += 4;
230                                 continue;
231                         }
232                         if (host_client->name[i+1] == STRING_COLOR_TAG)
233                         {
234                                 i++;
235                                 continue;
236                         }
237                 }
238         }
239         // does not end in the default color string, so add it
240         if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
241                 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
242
243         PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
244         if (strcmp(host_client->old_name, host_client->name))
245         {
246                 if (host_client->begun)
247                         SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
248                 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
249                 // send notification to all clients
250                 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
251                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
252                 MSG_WriteString (&sv.reliable_datagram, host_client->name);
253                 SV_WriteNetnameIntoDemo(host_client);
254         }
255 }
256
257 /*
258 ======================
259 CL_Playermodel_f
260 ======================
261 */
262 cvar_t cl_playermodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
263 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
264 static void CL_Playermodel_f(cmd_state_t *cmd)
265 {
266         prvm_prog_t *prog = SVVM_prog;
267         int i, j;
268         char newPath[sizeof(host_client->playermodel)];
269
270         if (Cmd_Argc (cmd) == 1)
271         {
272                 if (cmd->source == src_command)
273                 {
274                         Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
275                 }
276                 return;
277         }
278
279         if (Cmd_Argc (cmd) == 2)
280                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
281         else
282                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
283
284         for (i = 0, j = 0;newPath[i];i++)
285                 if (newPath[i] != '\r' && newPath[i] != '\n')
286                         newPath[j++] = newPath[i];
287         newPath[j] = 0;
288
289         if (cmd->source == src_command)
290         {
291                 Cvar_Set (&cvars_all, "_cl_playermodel", newPath);
292                 return;
293         }
294
295         /*
296         if (host.realtime < host_client->nametime)
297         {
298                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
299                 return;
300         }
301
302         host_client->nametime = host.realtime + 5;
303         */
304
305         // point the string back at updateclient->name to keep it safe
306         strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
307         PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
308         if (strcmp(host_client->old_model, host_client->playermodel))
309         {
310                 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
311                 /*// send notification to all clients
312                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
313                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
314                 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
315         }
316 }
317
318 /*
319 ======================
320 CL_Playerskin_f
321 ======================
322 */
323 cvar_t cl_playerskin = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
324 static void CL_Playerskin_f(cmd_state_t *cmd)
325 {
326         prvm_prog_t *prog = SVVM_prog;
327         int i, j;
328         char newPath[sizeof(host_client->playerskin)];
329
330         if (Cmd_Argc (cmd) == 1)
331         {
332                 if (cmd->source == src_command)
333                 {
334                         Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
335                 }
336                 return;
337         }
338
339         if (Cmd_Argc (cmd) == 2)
340                 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
341         else
342                 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
343
344         for (i = 0, j = 0;newPath[i];i++)
345                 if (newPath[i] != '\r' && newPath[i] != '\n')
346                         newPath[j++] = newPath[i];
347         newPath[j] = 0;
348
349         if (cmd->source == src_command)
350         {
351                 Cvar_Set (&cvars_all, "_cl_playerskin", newPath);
352                 return;
353         }
354
355         /*
356         if (host.realtime < host_client->nametime)
357         {
358                 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
359                 return;
360         }
361
362         host_client->nametime = host.realtime + 5;
363         */
364
365         // point the string back at updateclient->name to keep it safe
366         strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
367         PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
368         if (strcmp(host_client->old_skin, host_client->playerskin))
369         {
370                 //if (host_client->begun)
371                 //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
372                 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
373                 /*// send notification to all clients
374                 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
375                 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
376                 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
377         }
378 }
379
380 /*
381 ==================
382 CL_Color_f
383 ==================
384 */
385 cvar_t cl_color = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
386 static void CL_Color(cmd_state_t *cmd, int changetop, int changebottom)
387 {
388         prvm_prog_t *prog = SVVM_prog;
389         int top, bottom, playercolor;
390
391         // get top and bottom either from the provided values or the current values
392         // (allows changing only top or bottom, or both at once)
393         top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
394         bottom = changebottom >= 0 ? changebottom : cl_color.integer;
395
396         top &= 15;
397         bottom &= 15;
398         // LadyHavoc: allowing skin colormaps 14 and 15 by commenting this out
399         //if (top > 13)
400         //      top = 13;
401         //if (bottom > 13)
402         //      bottom = 13;
403
404         playercolor = top*16 + bottom;
405
406         if (cmd->source == src_command)
407         {
408                 Cvar_SetValueQuick(&cl_color, playercolor);
409                 return;
410         }
411
412         if (cls.protocol == PROTOCOL_QUAKEWORLD)
413                 return;
414
415         if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
416         {
417                 Con_DPrint("Calling SV_ChangeTeam\n");
418                 prog->globals.fp[OFS_PARM0] = playercolor;
419                 PRVM_serverglobalfloat(time) = sv.time;
420                 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
421                 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
422         }
423         else
424         {
425                 if (host_client->edict)
426                 {
427                         PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
428                         PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
429                 }
430                 host_client->colors = playercolor;
431                 if (host_client->old_colors != host_client->colors)
432                 {
433                         host_client->old_colors = host_client->colors;
434                         // send notification to all clients
435                         MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
436                         MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
437                         MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
438                 }
439         }
440 }
441
442 static void CL_Color_f(cmd_state_t *cmd)
443 {
444         int             top, bottom;
445
446         if (Cmd_Argc(cmd) == 1)
447         {
448                 if (cmd->source == src_command)
449                 {
450                         Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
451                         Con_Print("color <0-15> [0-15]\n");
452                 }
453                 return;
454         }
455
456         if (Cmd_Argc(cmd) == 2)
457                 top = bottom = atoi(Cmd_Argv(cmd, 1));
458         else
459         {
460                 top = atoi(Cmd_Argv(cmd, 1));
461                 bottom = atoi(Cmd_Argv(cmd, 2));
462         }
463         CL_Color(cmd, top, bottom);
464 }
465
466 static void CL_TopColor_f(cmd_state_t *cmd)
467 {
468         if (Cmd_Argc(cmd) == 1)
469         {
470                 if (cmd->source == src_command)
471                 {
472                         Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
473                         Con_Print("topcolor <0-15>\n");
474                 }
475                 return;
476         }
477
478         CL_Color(cmd, atoi(Cmd_Argv(cmd, 1)), -1);
479 }
480
481 static void CL_BottomColor_f(cmd_state_t *cmd)
482 {
483         if (Cmd_Argc(cmd) == 1)
484         {
485                 if (cmd->source == src_command)
486                 {
487                         Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
488                         Con_Print("bottomcolor <0-15>\n");
489                 }
490                 return;
491         }
492
493         CL_Color(cmd, -1, atoi(Cmd_Argv(cmd, 1)));
494 }
495
496 cvar_t cl_rate = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
497 cvar_t cl_rate_burstsize = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
498 static void CL_Rate_f(cmd_state_t *cmd)
499 {
500         int rate;
501
502         if (Cmd_Argc(cmd) != 2)
503         {
504                 if (cmd->source == src_command)
505                 {
506                         Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
507                         Con_Print("rate <bytespersecond>\n");
508                 }
509                 return;
510         }
511
512         rate = atoi(Cmd_Argv(cmd, 1));
513
514         if (cmd->source == src_command)
515         {
516                 Cvar_SetValue (&cvars_all, "_cl_rate", max(NET_MINRATE, rate));
517                 return;
518         }
519
520         host_client->rate = rate;
521 }
522
523 static void CL_Rate_BurstSize_f(cmd_state_t *cmd)
524 {
525         int rate_burstsize;
526
527         if (Cmd_Argc(cmd) != 2)
528         {
529                 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
530                 Con_Print("rate_burstsize <bytes>\n");
531                 return;
532         }
533
534         rate_burstsize = atoi(Cmd_Argv(cmd, 1));
535
536         if (cmd->source == src_command)
537         {
538                 Cvar_SetValue (&cvars_all, "_cl_rate_burstsize", rate_burstsize);
539                 return;
540         }
541
542         host_client->rate_burstsize = rate_burstsize;
543 }
544
545 /*
546 ======================
547 CL_PModel_f
548 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
549 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
550 ======================
551 */
552 cvar_t cl_pmodel = {CVAR_CLIENT | CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
553 static void CL_PModel_f(cmd_state_t *cmd)
554 {
555         prvm_prog_t *prog = SVVM_prog;
556         int i;
557
558         if (Cmd_Argc (cmd) == 1)
559         {
560                 if (cmd->source == src_command)
561                 {
562                         Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
563                 }
564                 return;
565         }
566         i = atoi(Cmd_Argv(cmd, 1));
567
568         if (cmd->source == src_command)
569         {
570                 if (cl_pmodel.integer == i)
571                         return;
572                 Cvar_SetValue (&cvars_all, "_cl_pmodel", i);
573                 if (cls.state == ca_connected)
574                         Cmd_ForwardToServer_f(cmd);
575                 return;
576         }
577
578         PRVM_serveredictfloat(host_client->edict, pmodel) = i;
579 }
580
581 //===========================================================================
582
583 //===========================================================================
584
585 static void CL_SendCvar_f(cmd_state_t *cmd)
586 {
587         int             i;
588         cvar_t  *c;
589         const char *cvarname;
590         client_t *old;
591         char vabuf[1024];
592
593         if(Cmd_Argc(cmd) != 2)
594                 return;
595         cvarname = Cmd_Argv(cmd, 1);
596         if (cls.state == ca_connected)
597         {
598                 c = Cvar_FindVar(&cvars_all, cvarname, CVAR_CLIENT | CVAR_SERVER);
599                 // LadyHavoc: if there is no such cvar or if it is private, send a
600                 // reply indicating that it has no value
601                 if(!c || (c->flags & CVAR_PRIVATE))
602                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
603                 else
604                         Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
605                 return;
606         }
607         if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
608                 return;
609
610         old = host_client;
611         if (cls.state != ca_dedicated)
612                 i = 1;
613         else
614                 i = 0;
615         for(;i<svs.maxclients;i++)
616                 if(svs.clients[i].active && svs.clients[i].netconnection)
617                 {
618                         host_client = &svs.clients[i];
619                         SV_ClientCommands("sendcvar %s\n", cvarname);
620                 }
621         host_client = old;
622 }
623
624 /*
625 =====================
626 CL_PQRcon_f
627
628 ProQuake rcon support
629 =====================
630 */
631 static void CL_PQRcon_f(cmd_state_t *cmd)
632 {
633         int n;
634         const char *e;
635         lhnetsocket_t *mysocket;
636
637         if (Cmd_Argc(cmd) == 1)
638         {
639                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
640                 return;
641         }
642
643         if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
644         {
645                 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
646                 return;
647         }
648
649         e = strchr(rcon_password.string, ' ');
650         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
651
652         if (cls.netcon)
653                 cls.rcon_address = cls.netcon->peeraddress;
654         else
655         {
656                 if (!rcon_address.string[0])
657                 {
658                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
659                         return;
660                 }
661                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
662         }
663         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
664         if (mysocket)
665         {
666                 sizebuf_t buf;
667                 unsigned char bufdata[64];
668                 buf.data = bufdata;
669                 SZ_Clear(&buf);
670                 MSG_WriteLong(&buf, 0);
671                 MSG_WriteByte(&buf, CCREQ_RCON);
672                 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
673                 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
674                 MSG_WriteString(&buf, Cmd_Args(cmd));
675                 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
676                 NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
677                 SZ_Clear(&buf);
678         }
679 }
680
681 //=============================================================================
682
683 // QuakeWorld commands
684
685 /*
686 =====================
687 CL_Rcon_f
688
689   Send the rest of the command line over as
690   an unconnected command.
691 =====================
692 */
693 static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
694 {
695         int i, n;
696         const char *e;
697         lhnetsocket_t *mysocket;
698
699         if (Cmd_Argc(cmd) == 1)
700         {
701                 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(cmd, 0), Cmd_Argv(cmd, 0));
702                 return;
703         }
704
705         if (!rcon_password.string || !rcon_password.string[0])
706         {
707                 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
708                 return;
709         }
710
711         e = strchr(rcon_password.string, ' ');
712         n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
713
714         if (cls.netcon)
715                 cls.rcon_address = cls.netcon->peeraddress;
716         else
717         {
718                 if (!rcon_address.string[0])
719                 {
720                         Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
721                         return;
722                 }
723                 LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
724         }
725         mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
726         if (mysocket && Cmd_Args(cmd)[0])
727         {
728                 // simply put together the rcon packet and send it
729                 if(Cmd_Argv(cmd, 0)[0] == 's' || rcon_secure.integer > 1)
730                 {
731                         if(cls.rcon_commands[cls.rcon_ringpos][0])
732                         {
733                                 char s[128];
734                                 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
735                                 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]);
736                                 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
737                                 --cls.rcon_trying;
738                         }
739                         for (i = 0;i < MAX_RCONS;i++)
740                                 if(cls.rcon_commands[i][0])
741                                         if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
742                                                 break;
743                         ++cls.rcon_trying;
744                         if(i >= MAX_RCONS)
745                                 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
746                         strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(cmd), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
747                         cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
748                         cls.rcon_timeout[cls.rcon_ringpos] = host.realtime + rcon_secure_challengetimeout.value;
749                         cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
750                 }
751                 else if(rcon_secure.integer > 0)
752                 {
753                         char buf[1500];
754                         char argbuf[1500];
755                         dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args(cmd));
756                         memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
757                         if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
758                         {
759                                 buf[40] = ' ';
760                                 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
761                                 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
762                         }
763                 }
764                 else
765                 {
766                         char buf[1500];
767                         memcpy(buf, "\377\377\377\377", 4);
768                         dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s",  n, rcon_password.string, Cmd_Args(cmd));
769                         NetConn_WriteString(mysocket, buf, &cls.rcon_address);
770                 }
771         }
772 }
773
774 /*
775 ==================
776 CL_FullServerinfo_f
777
778 Sent by server when serverinfo changes
779 ==================
780 */
781 // TODO: shouldn't this be a cvar instead?
782 static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
783 {
784         char temp[512];
785         if (Cmd_Argc(cmd) != 2)
786         {
787                 Con_Printf ("usage: fullserverinfo <complete info string>\n");
788                 return;
789         }
790
791         strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo));
792         InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
793         cl.qw_teamplay = atoi(temp);
794 }
795
796 /*
797 ==================
798 CL_FullInfo_f
799
800 Allow clients to change userinfo
801 ==================
802 Casey was here :)
803 */
804 static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
805 {
806         char key[512];
807         char value[512];
808         const char *s;
809
810         if (Cmd_Argc(cmd) != 2)
811         {
812                 Con_Printf ("fullinfo <complete info string>\n");
813                 return;
814         }
815
816         s = Cmd_Argv(cmd, 1);
817         if (*s == '\\')
818                 s++;
819         while (*s)
820         {
821                 size_t len = strcspn(s, "\\");
822                 if (len >= sizeof(key)) {
823                         len = sizeof(key) - 1;
824                 }
825                 strlcpy(key, s, len + 1);
826                 s += len;
827                 if (!*s)
828                 {
829                         Con_Printf ("MISSING VALUE\n");
830                         return;
831                 }
832                 ++s; // Skip over backslash.
833
834                 len = strcspn(s, "\\");
835                 if (len >= sizeof(value)) {
836                         len = sizeof(value) - 1;
837                 }
838                 strlcpy(value, s, len + 1);
839
840                 CL_SetInfo(key, value, false, false, false, false);
841
842                 s += len;
843                 if (!*s)
844                 {
845                         break;
846                 }
847                 ++s; // Skip over backslash.
848         }
849 }
850
851 /*
852 ==================
853 CL_SetInfo_f
854
855 Allow clients to change userinfo
856 ==================
857 */
858 static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
859 {
860         if (Cmd_Argc(cmd) == 1)
861         {
862                 InfoString_Print(cls.userinfo);
863                 return;
864         }
865         if (Cmd_Argc(cmd) != 3)
866         {
867                 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
868                 return;
869         }
870         CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false);
871 }
872
873 /*
874 ====================
875 CL_Packet_f
876
877 packet <destination> <contents>
878
879 Contents allows \n escape character
880 ====================
881 */
882 static void CL_Packet_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
883 {
884         char send[2048];
885         int i, l;
886         const char *in;
887         char *out;
888         lhnetaddress_t address;
889         lhnetsocket_t *mysocket;
890
891         if (Cmd_Argc(cmd) != 3)
892         {
893                 Con_Printf ("packet <destination> <contents>\n");
894                 return;
895         }
896
897         if (!LHNETADDRESS_FromString (&address, Cmd_Argv(cmd, 1), sv_netport.integer))
898         {
899                 Con_Printf ("Bad address\n");
900                 return;
901         }
902
903         in = Cmd_Argv(cmd, 2);
904         out = send+4;
905         send[0] = send[1] = send[2] = send[3] = -1;
906
907         l = (int)strlen (in);
908         for (i=0 ; i<l ; i++)
909         {
910                 if (out >= send + sizeof(send) - 1)
911                         break;
912                 if (in[i] == '\\' && in[i+1] == 'n')
913                 {
914                         *out++ = '\n';
915                         i++;
916                 }
917                 else if (in[i] == '\\' && in[i+1] == '0')
918                 {
919                         *out++ = '\0';
920                         i++;
921                 }
922                 else if (in[i] == '\\' && in[i+1] == 't')
923                 {
924                         *out++ = '\t';
925                         i++;
926                 }
927                 else if (in[i] == '\\' && in[i+1] == 'r')
928                 {
929                         *out++ = '\r';
930                         i++;
931                 }
932                 else if (in[i] == '\\' && in[i+1] == '"')
933                 {
934                         *out++ = '\"';
935                         i++;
936                 }
937                 else
938                         *out++ = in[i];
939         }
940
941         mysocket = NetConn_ChooseClientSocketForAddress(&address);
942         if (!mysocket)
943                 mysocket = NetConn_ChooseServerSocketForAddress(&address);
944         if (mysocket)
945                 NetConn_Write(mysocket, send, out - send, &address);
946 }
947
948 static void CL_PingPLReport_f(cmd_state_t *cmd)
949 {
950         char *errbyte;
951         int i;
952         int l = Cmd_Argc(cmd);
953         if (l > cl.maxclients)
954                 l = cl.maxclients;
955         for (i = 0;i < l;i++)
956         {
957                 cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2));
958                 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0);
959                 if(errbyte && *errbyte == ',')
960                         cl.scores[i].qw_movementloss = atoi(errbyte + 1);
961                 else
962                         cl.scores[i].qw_movementloss = 0;
963         }
964 }
965
966 //=============================================================================
967
968 /*
969 ==================
970 Host_InitCommands
971 ==================
972 */
973 void Host_InitCommands (void)
974 {
975         dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
976
977         Cvar_RegisterVariable(&cl_name);
978         Cvar_RegisterVariable(&cl_color);
979         Cvar_RegisterVariable(&cl_rate);
980         Cvar_RegisterVariable(&cl_rate_burstsize);
981         Cvar_RegisterVariable(&cl_pmodel);
982         Cvar_RegisterVariable(&cl_playermodel);
983         Cvar_RegisterVariable(&cl_playerskin);
984         Cvar_RegisterVariable(&rcon_password);
985         Cvar_RegisterVariable(&rcon_address);
986         Cvar_RegisterVariable(&rcon_secure);
987         Cvar_RegisterVariable(&rcon_secure_challengetimeout);
988         Cvar_RegisterVariable(&r_fixtrans_auto);
989         Cvar_RegisterVariable(&team);
990         Cvar_RegisterVariable(&skin);
991         Cvar_RegisterVariable(&noaim);
992
993         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "name", CL_Name_f, "change your player name");
994         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors");
995         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate", CL_Rate_f, "change your network connection speed");
996         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "rate_burstsize", CL_Rate_BurstSize_f, "change your network connection speed");
997         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "pmodel", CL_PModel_f, "(Nehahra-only) change your player model choice");
998         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playermodel", CL_Playermodel_f, "change your player model");
999         Cmd_AddCommand(CMD_CLIENT | CMD_SERVER_FROM_CLIENT, "playerskin", CL_Playerskin_f, "change your player skin number");
1000
1001         Cmd_AddCommand(CMD_CLIENT, "connect", CL_Connect_f, "connect to a server by IP address or hostname");
1002         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "reconnect", CL_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)");
1003         Cmd_AddCommand(CMD_CLIENT, "sendcvar", CL_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
1004         Cmd_AddCommand(CMD_CLIENT, "rcon", CL_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");
1005         Cmd_AddCommand(CMD_CLIENT, "srcon", CL_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");
1006         Cmd_AddCommand(CMD_CLIENT, "pqrcon", CL_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)");
1007         Cmd_AddCommand(CMD_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo");
1008         Cmd_AddCommand(CMD_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo");
1009         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string");
1010         Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "topcolor", CL_TopColor_f, "QW command to set top color without changing bottom color");
1011         Cmd_AddCommand(CMD_CLIENT, "bottomcolor", CL_BottomColor_f, "QW command to set bottom color without changing top color");
1012         Cmd_AddCommand(CMD_CLIENT, "fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
1013
1014         // commands that are only sent by server to client for execution
1015         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "pingplreport", CL_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)");
1016         Cmd_AddCommand(CMD_CLIENT_FROM_SERVER, "fullserverinfo", CL_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
1017 }
1018
1019 void Host_NoOperation_f(cmd_state_t *cmd)
1020 {
1021 }