1 /*
2 This program is free software; you can redistribute it and/or
3 modify it under the terms of the GNU General Public License
4 as published by the Free Software Foundation; either version 2
5 of the License, or (at your option) any later version.
6 
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 
11 See the included (GNU.txt) GNU General Public License for more details.
12 
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 
17 
18 */
19 
20 // sv_demo_misc.c - misc demo related stuff, helpers
21 
22 #ifndef CLIENTONLY
23 #include "qwsvdef.h"
24 #ifndef SERVERONLY
25 #include "pcre.h"
26 #endif
27 
28 #define MAX_DEMOINFO_SIZE (1024 * 200)
29 static char chartbl[256];
30 
31 /*
32 ====================
33 CleanName_Init
34 
35 sets chararcter table for quake text->filename translation
36 ====================
37 */
CleanName_Init(void)38 void CleanName_Init (void)
39 {
40 	int i;
41 
42 	for (i = 0; i < 256; i++)
43 		chartbl[i] = (((i&127) < 'a' || (i&127) > 'z') && ((i&127) < '0' || (i&127) > '9')) ? '_' : (i&127);
44 
45 	// special cases
46 
47 	// numbers
48 	for (i = 18; i < 29; i++)
49 		chartbl[i] = chartbl[i + 128] = i + 30;
50 
51 	// allow lowercase only
52 	for (i = 'A'; i <= 'Z'; i++)
53 		chartbl[i] = chartbl[i+128] = i + 'a' - 'A';
54 
55 	// brackets
56 	chartbl[29] = chartbl[29+128] = chartbl[128] = '(';
57 	chartbl[31] = chartbl[31+128] = chartbl[130] = ')';
58 	chartbl[16] = chartbl[16 + 128]= '[';
59 	chartbl[17] = chartbl[17 + 128] = ']';
60 
61 	// dot
62 	chartbl[5] = chartbl[14] = chartbl[15] = chartbl[28] = chartbl[46] = '.';
63 	chartbl[5 + 128] = chartbl[14 + 128] = chartbl[15 + 128] = chartbl[28 + 128] = chartbl[46 + 128] = '.';
64 
65 	// !
66 	chartbl[33] = chartbl[33 + 128] = '!';
67 
68 	// #
69 	chartbl[35] = chartbl[35 + 128] = '#';
70 
71 	// %
72 	chartbl[37] = chartbl[37 + 128] = '%';
73 
74 	// &
75 	chartbl[38] = chartbl[38 + 128] = '&';
76 
77 	// '
78 	chartbl[39] = chartbl[39 + 128] = '\'';
79 
80 	// (
81 	chartbl[40] = chartbl[40 + 128] = '(';
82 
83 	// )
84 	chartbl[41] = chartbl[41 + 128] = ')';
85 
86 	// +
87 	chartbl[43] = chartbl[43 + 128] = '+';
88 
89 	// -
90 	chartbl[45] = chartbl[45 + 128] = '-';
91 
92 	// @
93 	chartbl[64] = chartbl[64 + 128] = '@';
94 
95 	// ^
96 	//	chartbl[94] = chartbl[94 + 128] = '^';
97 
98 
99 	chartbl[91] = chartbl[91 + 128] = '[';
100 	chartbl[93] = chartbl[93 + 128] = ']';
101 
102 	chartbl[16] = chartbl[16 + 128] = '[';
103 	chartbl[17] = chartbl[17 + 128] = ']';
104 
105 	chartbl[123] = chartbl[123 + 128] = '{';
106 	chartbl[125] = chartbl[125 + 128] = '}';
107 }
108 
109 /*
110 ====================
111 SV_CleanName
112 
113 Cleans the demo name, removes restricted chars, makes name lowercase
114 ====================
115 */
SV_CleanName(unsigned char * name)116 char *SV_CleanName (unsigned char *name)
117 {
118 	static char text[1024];
119 	char *out = text;
120 
121 	if (!name || !*name)
122 	{
123 		*out = '\0';
124 		return text;
125 	}
126 
127 	*out = chartbl[*name++];
128 
129 	while (*name && ((out - text) < (int) sizeof(text)))
130 		if (*out == '_' && chartbl[*name] == '_')
131 			name++;
132 		else *++out = chartbl[*name++];
133 
134 	*++out = 0;
135 	return text;
136 }
137 
138 /*
139 ====================
140 SV_DirSizeCheck
141 
142 Deletes sv_demoClearOld files from demo dir if out of space
143 ====================
144 */
SV_DirSizeCheck(void)145 qbool SV_DirSizeCheck (void)
146 {
147 	dir_t	dir;
148 	file_t	*list;
149 	int	n;
150 
151 	if ((int)sv_demoMaxDirSize.value)
152 	{
153 		dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), ".*", SORT_NO/*BY_DATE*/);
154 		if ((float)dir.size > sv_demoMaxDirSize.value * 1024)
155 		{
156 			if ((int)sv_demoClearOld.value <= 0)
157 			{
158 				Con_Printf("Insufficient directory space, increase sv_demoMaxDirSize\n");
159 				return false;
160 			}
161 			list = dir.files;
162 			n = (int) sv_demoClearOld.value;
163 			Con_Printf("Clearing %d old demos\n", n);
164 			// HACK!!! HACK!!! HACK!!!
165 			if ((int)sv_demotxt.value) // if our server record demos and txts, then to remove
166 				n <<= 1;  // 50 demos, we have to remove 50 demos and 50 txts = 50*2 = 100 files
167 
168 			qsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date);
169 			for (; list->name[0] && n > 0; list++)
170 			{
171 				if (list->isdir)
172 					continue;
173 				Sys_remove(va("%s/%s/%s", fs_gamedir, sv_demoDir.string, list->name));
174 				//Con_Printf("Remove %d - %s/%s/%s\n", n, fs_gamedir, sv_demoDir.string, list->name);
175 				n--;
176 			}
177 
178 			// force cache rebuild.
179 			FS_FlushFSHash();
180 		}
181 	}
182 	return true;
183 }
184 
Run_sv_demotxt_and_sv_onrecordfinish(const char * dest_name,const char * dest_path,qbool destroyfiles)185 void Run_sv_demotxt_and_sv_onrecordfinish (const char *dest_name, const char *dest_path, qbool destroyfiles)
186 {
187 	char path[MAX_OSPATH];
188 
189 	snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, dest_path, dest_name);
190 	strlcpy(path + strlen(path) - 3, "txt", MAX_OSPATH - strlen(path) + 3);
191 
192 	if ((int)sv_demotxt.value && !destroyfiles) // dont keep txt's for deleted demos
193 	{
194 		FILE *f;
195 		char *text;
196 
197 		if (sv_demotxt.value == 2)
198 		{
199 			if ((f = fopen (path, "a+t")))
200 				fclose(f); // at least made empty file, but do not owerwite
201 		}
202 		else if ((f = fopen (path, "w+t")))
203 		{
204 			text = SV_PrintTeams();
205 			fwrite(text, strlen(text), 1, f);
206 			fflush(f);
207 			fclose(f);
208 		}
209 	}
210 
211 	if (sv_onrecordfinish.string[0] && !destroyfiles) // dont gzip deleted demos
212 	{
213 		extern redirect_t sv_redirected;
214 		redirect_t old = sv_redirected;
215 		char *p;
216 
217 		if ((p = strchr(sv_onrecordfinish.string, ' ')) != NULL)
218 			*p = 0; // strip parameters
219 
220 		strlcpy(path, dest_name, sizeof(path));
221 #ifdef SERVERONLY
222 		COM_StripExtension(path);
223 #else
224 		COM_StripExtension(path, path, sizeof(path));
225 #endif
226 
227 		sv_redirected = RD_NONE; // onrecord script is called always from the console
228 		Cmd_TokenizeString(va("script %s \"%s\" \"%s\" %s", sv_onrecordfinish.string, dest_path, path, p != NULL ? p+1 : ""));
229 
230 		if (p)
231 			*p = ' '; // restore params
232 
233 		SV_Script_f();
234 
235 		sv_redirected = old;
236 	}
237 
238 	// force cache rebuild.
239 	FS_FlushFSHash();
240 }
241 
SV_PrintTeams(void)242 char *SV_PrintTeams (void)
243 {
244 	char			*teams[MAX_CLIENTS];
245 	int				i, j, numcl = 0, numt = 0, scores;
246 	client_t		*clients[MAX_CLIENTS];
247 	char			buf[2048];
248 	static char		lastscores[2048];
249 	extern cvar_t	teamplay;
250 	date_t			date;
251 	SV_TimeOfDay(&date, "%a %b %d, %H:%M:%S %Y");
252 
253 	// count teams and players
254 	for (i=0; i < MAX_CLIENTS; i++)
255 	{
256 		if (svs.clients[i].state != cs_spawned)
257 			continue;
258 		if (svs.clients[i].spectator)
259 			continue;
260 
261 		clients[numcl++] = &svs.clients[i];
262 		for (j = 0; j < numt; j++)
263 			if (!strcmp(svs.clients[i].team, teams[j]))
264 				break;
265 		if (j != numt)
266 			continue;
267 
268 		teams[numt++] = svs.clients[i].team;
269 	}
270 
271 	// create output
272 	lastscores[0] = 0;
273 	snprintf(buf, sizeof(buf),
274 		"date %s\nmap %s\nteamplay %d\ndeathmatch %d\ntimelimit %d\n",
275 		date.str, sv.mapname, (int)teamplay.value, (int)deathmatch.value,
276 		(int)timelimit.value);
277 	if (numcl == 2) // duel
278 	{
279 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
280 			"player1: %s (%i)\nplayer2: %s (%i)\n",
281 			clients[0]->name, clients[0]->old_frags,
282 			clients[1]->name, clients[1]->old_frags);
283 		snprintf(lastscores, sizeof(lastscores), "duel: %s vs %s @ %s - %i:%i\n",
284 			clients[0]->name, clients[1]->name, sv.mapname,
285 			clients[0]->old_frags, clients[1]->old_frags);
286 	}
287 	else if (!(int)teamplay.value) // ffa
288 	{
289 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "players:\n");
290 		snprintf(lastscores, sizeof(lastscores), "ffa:");
291 		for (i = 0; i < numcl; i++)
292 		{
293 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
294 				"  %s (%i)\n", clients[i]->name, clients[i]->old_frags);
295 			snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),
296 				"  %s(%i)", clients[i]->name, clients[i]->old_frags);
297 		}
298 		snprintf(lastscores + strlen(lastscores),
299 			sizeof(lastscores) - strlen(lastscores), " @ %s\n", sv.mapname);
300 	}
301 	else
302 	{ // teamplay
303 		snprintf(lastscores, sizeof(lastscores), "tp:");
304 		for (j = 0; j < numt; j++)
305 		{
306 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
307 				"team[%i] %s:\n", j, teams[j]);
308 			snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),
309 				"%s[", teams[j]);
310 			scores = 0;
311 			for (i = 0; i < numcl; i++)
312 				if (!strcmp(clients[i]->team, teams[j]))
313 				{
314 					snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
315 						"  %s (%i)\n", clients[i]->name, clients[i]->old_frags);
316 					snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),
317 						" %s(%i) ", clients[i]->name, clients[i]->old_frags);
318 					scores += clients[i]->old_frags;
319 				}
320 			snprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),
321 				"](%i)  ", scores);
322 
323 		}
324 		snprintf(lastscores + strlen(lastscores),
325 			sizeof(lastscores) - strlen(lastscores), "@ %s\n", sv.mapname);
326 	}
327 
328 	Q_normalizetext(buf);
329 	Q_normalizetext(lastscores);
330 	strlcat(lastscores, buf, sizeof(lastscores));
331 	return lastscores;
332 }
333 
334 
SV_DemoList(qbool use_regex)335 void SV_DemoList (qbool use_regex)
336 {
337 	mvddest_t *d;
338 	dir_t	dir;
339 	file_t	*list;
340 	float	free_space;
341 	int		i, j, n;
342 	int		files[MAX_DIRFILES + 1];
343 
344 	int	r;
345 	pcre	*preg;
346 	const char	*errbuf;
347 
348 	memset(files, 0, sizeof(files));
349 
350 	Con_Printf("Listing content of %s/%s/%s\n", fs_gamedir, sv_demoDir.string, sv_demoRegexp.string);
351 	dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);
352 	list = dir.files;
353 	if (!list->name[0])
354 	{
355 		Con_Printf("no demos\n");
356 	}
357 
358 	for (i = 1, n = 0; list->name[0]; list++, i++)
359 	{
360 		for (j = 1; j < Cmd_Argc(); j++)
361 		{
362 			if (use_regex)
363 			{
364 				if (!(preg = pcre_compile(Q_normalizetext(Cmd_Argv(j)),	PCRE_CASELESS, &errbuf, &r, NULL)))
365 				{
366 					Con_Printf("Sys_listdir: pcre_compile(%s) error: %s at offset %d\n",
367 					           Cmd_Argv(j), errbuf, r);
368 					pcre_free(preg);
369 					break;
370 				}
371 				switch (r = pcre_exec(preg, NULL, list->name,
372 				                      strlen(list->name), 0, 0, NULL, 0))
373 				{
374 				case 0:
375 					pcre_free(preg);
376 					continue;
377 				case PCRE_ERROR_NOMATCH:
378 					break;
379 				default:
380 					Con_Printf("Sys_listdir: pcre_exec(%s, %s) error code: %d\n",
381 					           Cmd_Argv(j), list->name, r);
382 				}
383 				pcre_free(preg);
384 				break;
385 			}
386 			else
387 				if (strstr(list->name, Cmd_Argv(j)) == NULL)
388 					break;
389 		}
390 
391 		if (Cmd_Argc() == j)
392 		{
393 			files[n++] = i;
394 		}
395 	}
396 
397 	list = dir.files;
398 	for (j = (GameStarted() && n > 100) ? n - 100 : 0; files[j]; j++)
399 	{
400 		i = files[j];
401 
402 		if ((d = DestByName(list[i - 1].name)))
403 			Con_Printf("*%4d: %s (%dk)\n", i, list[i - 1].name, d->totalsize / 1024);
404 		else
405 			Con_Printf("%4d: %s (%dk)\n", i, list[i - 1].name, list[i - 1].size / 1024);
406 	}
407 
408 	for (d = demo.dest; d; d = d->nextdest)
409 	{
410 		if (d->desttype == DEST_STREAM)
411 			continue; // streams are not saved on to HDD, so inogre it...
412 		dir.size += d->totalsize;
413 	}
414 
415 	Con_Printf("\ndirectory size: %.1fMB\n", (float)dir.size / (1024 * 1024));
416 	if ((int)sv_demoMaxDirSize.value)
417 	{
418 		free_space = (sv_demoMaxDirSize.value * 1024 - dir.size) / (1024 * 1024);
419 		if (free_space < 0)
420 			free_space = 0;
421 		Con_Printf("space available: %.1fMB\n", free_space);
422 	}
423 }
424 
SV_DemoList_f(void)425 void SV_DemoList_f (void)
426 {
427 	SV_DemoList (false);
428 }
429 
SV_DemoListRegex_f(void)430 void SV_DemoListRegex_f (void)
431 {
432 	SV_DemoList (true);
433 }
434 
SV_MVDNum(int num)435 char *SV_MVDNum (int num)
436 {
437 	file_t	*list;
438 	dir_t	dir;
439 
440 	if (!num)
441 		return NULL;
442 
443 	// last recorded demo's names for command "cmd dl . .." (maximum 15 dots)
444 	if (num & 0xFF000000)
445 	{
446 		char *name = demo.lastdemosname[(demo.lastdemospos - (num >> 24) + 1) & 0xF];
447 		char *name2;
448 		int c;
449 
450 		if (!(name2 = quote(name)))
451 			return NULL;
452 		if ((c = strlen(name2)) > 5)
453 			name2[c - 5] = '\0'; // crop quoted extension '\.mvd'
454 
455 		dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string),
456 						  va("^%s%s", name2, sv_demoRegexp.string), SORT_NO);
457 		list = dir.files;
458 		if (dir.numfiles > 1)
459 		{
460 			Con_Printf("SV_MVDNum: where are %d demos with name: %s%s\n",
461 						dir.numfiles, name2, sv_demoRegexp.string);
462 		}
463 		if (!dir.numfiles)
464 		{
465 			Con_Printf("SV_MVDNum: where are no demos with name: %s%s\n",
466 						name2, sv_demoRegexp.string);
467 			return NULL;
468 		}
469 		Q_free(name2);
470 		//Con_Printf("%s", dir.files[0].name);
471 		return dir.files[0].name;
472 	}
473 
474 	dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);
475 	list = dir.files;
476 
477 	if (num & 0x00800000)
478 	{
479 		num |= 0xFF000000;
480 		num += dir.numfiles;
481 	}
482 	else
483 	{
484 		--num;
485 	}
486 
487 	if (num > dir.numfiles)
488 		return NULL;
489 
490 	while (list->name[0] && num)
491 	{
492 		list++;
493 		num--;
494 	}
495 
496 	return list->name[0] ? list->name : NULL;
497 }
498 
499 #define OVECCOUNT 3
SV_MVDName2Txt(const char * name)500 char *SV_MVDName2Txt (const char *name)
501 {
502 	char	s[MAX_OSPATH];
503 	int		len;
504 
505 	int		r, ovector[OVECCOUNT];
506 	pcre	*preg;
507 	const char	*errbuf;
508 
509 	if (!name)
510 		return NULL;
511 
512 	if (!*name)
513 		return NULL;
514 
515 	strlcpy(s, name, MAX_OSPATH);
516 	len = strlen(s);
517 
518 	if (!(preg = pcre_compile(sv_demoRegexp.string, PCRE_CASELESS, &errbuf, &r, NULL)))
519 	{
520 		Con_Printf("SV_MVDName2Txt: pcre_compile(%s) error: %s at offset %d\n",
521 					sv_demoRegexp.string, errbuf, r);
522 		pcre_free(preg);
523 		return NULL;
524 	}
525 	r = pcre_exec(preg, NULL, s, len, 0, 0, ovector, OVECCOUNT);
526 	pcre_free(preg);
527 	if (r < 0)
528 	{
529 		switch (r)
530 		{
531 		case PCRE_ERROR_NOMATCH:
532 			return NULL;
533 		default:
534 			Con_Printf("SV_MVDName2Txt: pcre_exec(%s, %s) error code: %d\n",
535 						sv_demoRegexp.string, s, r);
536 			return NULL;
537 		}
538 	}
539 	else
540 	{
541 		if (ovector[0] + 5 > MAX_OSPATH)
542 			len = MAX_OSPATH - 5;
543 		else
544 			len = ovector[0];
545 	}
546 	s[len++] = '.';
547 	s[len++] = 't';
548 	s[len++] = 'x';
549 	s[len++] = 't';
550 	s[len]   = '\0';
551 
552 	//Con_Printf("%d) %s, %s\n", r, name, s);
553 	return va("%s", s);
554 }
555 
SV_MVDTxTNum(int num)556 static char *SV_MVDTxTNum (int num)
557 {
558 	return SV_MVDName2Txt (SV_MVDNum(num));
559 }
560 
561 
SV_MVDRemove_f(void)562 void SV_MVDRemove_f (void)
563 {
564 	char name[MAX_DEMO_NAME], *ptr;
565 	char path[MAX_OSPATH];
566 	int i;
567 
568 	if (Cmd_Argc() != 2)
569 	{
570 		Con_Printf("rmdemo <demoname> - removes the demo\nrmdemo *<token>   - removes demo with <token> in the name\nrmdemo *          - removes all demos\n");
571 		return;
572 	}
573 
574 	ptr = Cmd_Argv(1);
575 	if (*ptr == '*')
576 	{
577 		dir_t dir;
578 		file_t *list;
579 
580 		// remove all demos with specified token
581 		ptr++;
582 
583 		dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);
584 		list = dir.files;
585 		for (i = 0;list->name[0]; list++)
586 		{
587 			if (strstr(list->name, ptr))
588 			{
589 				if (sv.mvdrecording && DestByName(list->name)/*!strcmp(list->name, demo.name)*/)
590 					SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest
591 
592 				// stop recording first;
593 				snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, list->name);
594 				if (!Sys_remove(path))
595 				{
596 					Con_Printf("removing %s...\n", list->name);
597 					i++;
598 				}
599 
600 				Sys_remove(SV_MVDName2Txt(path));
601 			}
602 		}
603 
604 		if (i)
605 		{
606 			Con_Printf("%d demos removed\n", i);
607 		}
608 		else
609 		{
610 			Con_Printf("no match found\n");
611 		}
612 
613 		// force cache rebuild.
614 		FS_FlushFSHash();
615 
616 		return;
617 	}
618 
619 	strlcpy(name, Cmd_Argv(1), MAX_DEMO_NAME);
620 	COM_DefaultExtension(name, ".mvd");
621 
622 	snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name);
623 
624 	if (sv.mvdrecording && DestByName(name) /*!strcmp(name, demo.name)*/)
625 		SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest
626 
627 	if (!Sys_remove(path))
628 	{
629 		Con_Printf("demo %s successfully removed\n", name);
630 
631 		if (*sv_ondemoremove.string)
632 		{
633 			extern redirect_t sv_redirected;
634 			redirect_t old = sv_redirected;
635 
636 			sv_redirected = RD_NONE; // this script is called always from the console
637 			Cmd_TokenizeString(va("script %s \"%s\" \"%s\"", sv_ondemoremove.string, sv_demoDir.string, name));
638 			SV_Script_f();
639 
640 			sv_redirected = old;
641 		}
642 	}
643 	else
644 		Con_Printf("unable to remove demo %s\n", name);
645 
646 	Sys_remove(SV_MVDName2Txt(path));
647 
648 	// force cache rebuild.
649 	FS_FlushFSHash();
650 }
651 
SV_MVDRemoveNum_f(void)652 void SV_MVDRemoveNum_f (void)
653 {
654 	int		num;
655 	char	*val, *name;
656 	char path[MAX_OSPATH];
657 
658 	if (Cmd_Argc() != 2)
659 	{
660 		Con_Printf("rmdemonum <#>\n");
661 		return;
662 	}
663 
664 	val = Cmd_Argv(1);
665 	if ((num = Q_atoi(val)) == 0 && val[0] != '0')
666 	{
667 		Con_Printf("rmdemonum <#>\n");
668 		return;
669 	}
670 
671 	name = SV_MVDNum(num);
672 
673 	if (name != NULL)
674 	{
675 		if (sv.mvdrecording && DestByName(name)/*!strcmp(name, demo.name)*/)
676 			SV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest
677 
678 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name);
679 		if (!Sys_remove(path))
680 		{
681 			Con_Printf("demo %s successfully removed\n", name);
682 			if (*sv_ondemoremove.string)
683 			{
684 				extern redirect_t sv_redirected;
685 				redirect_t old = sv_redirected;
686 
687 				sv_redirected = RD_NONE; // this script is called always from the console
688 				Cmd_TokenizeString(va("script %s \"%s\" \"%s\"", sv_ondemoremove.string, sv_demoDir.string, name));
689 				SV_Script_f();
690 
691 				sv_redirected = old;
692 			}
693 		}
694 		else
695 			Con_Printf("unable to remove demo %s\n", name);
696 
697 		Sys_remove(SV_MVDName2Txt(path));
698 
699 		// force cache rebuild.
700 		FS_FlushFSHash();
701 	}
702 	else
703 		Con_Printf("invalid demo num\n");
704 }
705 
SV_MVDInfoAdd_f(void)706 void SV_MVDInfoAdd_f (void)
707 {
708 	char *name, *args, path[MAX_OSPATH];
709 	FILE *f;
710 
711 	if (Cmd_Argc() < 3)
712 	{
713 		Con_Printf("usage:demoInfoAdd <demonum> <info string>\n<demonum> = * for currently recorded demo\n");
714 		return;
715 	}
716 
717 	if (!strcmp(Cmd_Argv(1), "*") || !strcmp(Cmd_Argv(1), "**")) {
718 		const char* demoname = SV_MVDDemoName();
719 
720 		if (!sv.mvdrecording || !demoname) {
721 			Con_Printf("Not recording demo!\n");
722 			return;
723 		}
724 
725 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname));
726 	}
727 	else {
728 		name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));
729 
730 		if (!name) {
731 			Con_Printf("invalid demo num\n");
732 			return;
733 		}
734 
735 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name);
736 	}
737 
738 	if ((f = fopen(path, !strcmp(Cmd_Argv(1), "**") ? "a+b" : "a+t")) == NULL)
739 	{
740 		Con_Printf("failed to open the file\n");
741 		return;
742 	}
743 
744 	if (!strcmp(Cmd_Argv(1), "**"))
745 	{
746 		// put content of one file to another
747 		FILE *src;
748 
749 		snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, Cmd_Argv(2));
750 
751 		if ((src = fopen(path, "rb")) == NULL) // open src
752 		{
753 			Con_Printf("failed to open input file\n");
754 		}
755 		else
756 		{
757 			byte buf[1024 * 200] = { 0 }; // 200 kb
758 			size_t sz = fread((void*)buf, 1, sizeof(buf), src); // read from src
759 
760 			if (sz <= 0) {
761 				Con_Printf("failed to read or empty input file\n");
762 			}
763 			else {
764 				// write to f
765 				if (sz != fwrite((void*)buf, 1, sz, f)) {
766 					Con_Printf("failed write to file\n");
767 				}
768 			}
769 
770 			fclose(src); // close src
771 		}
772 	}
773 	else
774 	{
775 		// skip demonum
776 		args = Cmd_Args();
777 
778 		while (*args > 32) args++;
779 		while (*args && *args <= 32) args++;
780 
781 		fwrite(args, strlen(args), 1, f);
782 		fwrite("\n", 1, 1, f);
783 	}
784 
785 	fflush(f);
786 	fclose(f);
787 
788 	// force cache rebuild.
789 	FS_FlushFSHash();
790 }
791 
792 // Put content of one file into .mvd
SV_MVDEmbedInfo_f(void)793 void SV_MVDEmbedInfo_f(void)
794 {
795 	// put content of one file to another
796 	FILE* src;
797 	byte* buf;
798 	size_t sz;
799 	char name[MAX_OSPATH];
800 	char path[MAX_OSPATH];
801 
802 	if (Cmd_Argc() == 1) {
803 		Con_Printf("Embeds contents of a file into mvd/qtv stream\n");
804 		Con_Printf("Usage: %s <filename>\n", Cmd_Argv(0));
805 		return;
806 	}
807 
808 	// No config files, no sub-directories
809 	strlcpy(name, Cmd_Argv(1), sizeof(name));
810 	snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, Cmd_Argv(1));
811 	if (FS_UnsafeFilename(path) || !strcasecmp(COM_FileExtension(name), "cfg")) {
812 		Con_Printf("Unsafe filename detected - cancelled\n");
813 		return;
814 	}
815 
816 	if ((src = fopen(path, "rb")) == NULL) {
817 		Con_Printf("failed to open input file\n");
818 		return;
819 	}
820 
821 	buf = Q_malloc(MAX_DEMOINFO_SIZE);
822 	sz = fread((void*)buf, 1, MAX_DEMOINFO_SIZE, src);
823 	fclose(src);
824 	if (sz <= 0) {
825 		Con_Printf("failed to read or empty input file\n");
826 		Q_free(buf);
827 		return;
828 	}
829 
830 	Con_Printf("Embedding (%s):\n", path);
831 
832 	// embed in .mvd/qtv (sanity check limits)
833 	if (sz < 2 * 1024 * 1024) {
834 		mvdhidden_block_header_t header;
835 		byte* data = buf;
836 		short block_number = 1;
837 
838 		while (sz > 0) {
839 			int prefix_length = sizeof_mvdhidden_block_header_t_range0 + sizeof(block_number);
840 			int length = (int)min(sz + prefix_length, MAX_MVD_SIZE);
841 
842 			if (MVDWrite_HiddenBlockBegin(length)) {
843 				length -= prefix_length;
844 
845 				sz -= length;
846 				if (sz == 0) {
847 					block_number = 0;
848 				}
849 
850 				header.length = LittleLong(length + sizeof(short));
851 				header.type_id = LittleShort(mvdhidden_demoinfo);
852 				MVD_SZ_Write(&header.length, sizeof(header.length));
853 				MVD_SZ_Write(&header.type_id, sizeof(header.type_id));
854 				MVD_SZ_Write(&block_number, sizeof(block_number));
855 				MVD_SZ_Write(data, length);
856 
857 				data += length;
858 				++block_number;
859 			}
860 			else {
861 				Con_Printf("failed write to mvd/qtv\n");
862 				Q_free(buf);
863 				break;
864 			}
865 		}
866 	}
867 
868 	Q_free(buf);
869 }
870 
SV_MVDInfoRemove_f(void)871 void SV_MVDInfoRemove_f (void)
872 {
873 	char *name, path[MAX_OSPATH];
874 
875 	if (Cmd_Argc() < 2)
876 	{
877 		Con_Printf("usage:demoInfoRemove <demonum>\n<demonum> = * for currently recorded demo\n");
878 		return;
879 	}
880 
881 	if (!strcmp(Cmd_Argv(1), "*"))
882 	{
883 		if (!sv.mvdrecording || !demo.dest)
884 		{
885 			Con_Printf("Not recording demo!\n");
886 			return;
887 		}
888 
889 //		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name));
890 // FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir
891 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name));
892 	}
893 	else
894 	{
895 		name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));
896 
897 		if (!name)
898 		{
899 			Con_Printf("invalid demo num\n");
900 			return;
901 		}
902 
903 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name);
904 	}
905 
906 	if (Sys_remove(path))
907 		Con_Printf("failed to remove the file %s\n", path);
908 	else
909 		Con_Printf("file %s removed\n", path);
910 
911 	// force cache rebuild.
912 	FS_FlushFSHash();
913 }
914 
SV_MVDInfo_f(void)915 void SV_MVDInfo_f (void)
916 {
917 	unsigned char buf[512];
918 	FILE *f = NULL;
919 	char *name, path[MAX_OSPATH];
920 
921 	if (Cmd_Argc() < 2)
922 	{
923 		Con_Printf("usage: demoinfo <demonum>\n<demonum> = * for currently recorded demo\n");
924 		return;
925 	}
926 
927 	if (!strcmp(Cmd_Argv(1), "*"))
928 	{
929 		if (!sv.mvdrecording || !demo.dest)
930 		{
931 			Con_Printf("Not recording demo!\n");
932 			return;
933 		}
934 
935 //		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name));
936 // FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir
937 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name));
938 	}
939 	else
940 	{
941 		name = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));
942 
943 		if (!name)
944 		{
945 			Con_Printf("invalid demo num\n");
946 			return;
947 		}
948 
949 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, name);
950 	}
951 
952 	if ((f = fopen(path, "rt")) == NULL)
953 	{
954 		Con_Printf("(empty)\n");
955 		return;
956 	}
957 
958 	while (!feof(f))
959 	{
960 		buf[fread (buf, 1, sizeof(buf) - 1, f)] = 0;
961 		Con_Printf("%s", Q_yelltext(buf));
962 	}
963 
964 	fclose(f);
965 }
966 
967 #define MAXDEMOS			10
968 #define MAXDEMOS_RD_PACKET	100
SV_LastScores_f(void)969 void SV_LastScores_f (void)
970 {
971 	int		demos = MAXDEMOS, i;
972 	char	buf[512];
973 	FILE	*f = NULL;
974 	char	path[MAX_OSPATH];
975 	dir_t	dir;
976 	extern redirect_t sv_redirected;
977 
978 	if (Cmd_Argc() > 2)
979 	{
980 		Con_Printf("usage: lastscores [<numlastdemos>]\n<numlastdemos> = '0' for all demos\n<numlastdemos> = '' for last %i demos\n", MAXDEMOS);
981 		return;
982 	}
983 
984 	if (Cmd_Argc() == 2)
985 		if ((demos = Q_atoi(Cmd_Argv(1))) <= 0)
986 			demos = MAXDEMOS;
987 
988 	dir = Sys_listdir(va("%s/%s", fs_gamedir, sv_demoDir.string),
989 	                  sv_demoRegexp.string, SORT_BY_DATE);
990 	if (!dir.numfiles)
991 	{
992 		Con_Printf("No demos.\n");
993 		return;
994 	}
995 
996 	if (demos > dir.numfiles)
997 		demos = dir.numfiles;
998 
999 	if (demos > MAXDEMOS && GameStarted())
1000 		Con_Printf("<numlastdemos> was decreased to %i: match is in progress.\n",
1001 					demos = MAXDEMOS);
1002 
1003 	if (demos > MAXDEMOS_RD_PACKET && sv_redirected == RD_PACKET)
1004 		Con_Printf("<numlastdemos> was decreased to %i: command from connectionless packet.\n",
1005 					demos = MAXDEMOS_RD_PACKET);
1006 
1007 	Con_Printf("List of %d last demos:\n", demos);
1008 
1009 	for (i = dir.numfiles - demos; i < dir.numfiles; )
1010 	{
1011 		snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string,
1012 					SV_MVDName2Txt(dir.files[i].name));
1013 
1014 		Con_Printf("%i. ", ++i);
1015 		if ((f = fopen(path, "rt")) == NULL)
1016 			Con_Printf("(empty)\n");
1017 		else
1018 		{
1019 			if (!feof(f))
1020 			{
1021 				char *nl;
1022 
1023 				buf[fread (buf, 1, sizeof(buf) - 1, f)] = 0;
1024 				if ((nl = strchr(buf, '\n')))
1025 					nl[0] = 0;
1026 				Con_Printf("%s\n", Q_yelltext((unsigned char*)buf));
1027 			}
1028 			else
1029 				Con_Printf("(empty)\n");
1030 			fclose(f);
1031 		}
1032 	}
1033 }
1034 
1035 // easyrecord helpers
1036 
Dem_CountPlayers(void)1037 int Dem_CountPlayers (void)
1038 {
1039 	int	i, count;
1040 
1041 	count = 0;
1042 	for (i = 0; i < MAX_CLIENTS ; i++)
1043 	{
1044 		if (svs.clients[i].name[0] && !svs.clients[i].spectator)
1045 			count++;
1046 	}
1047 
1048 	return count;
1049 }
1050 
Dem_Team(int num)1051 char *Dem_Team (int num)
1052 {
1053 	int i;
1054 	static char *lastteam[2];
1055 	qbool first = true;
1056 	client_t *client;
1057 	static int index1 = 0;
1058 
1059 	index1 = 1 - index1;
1060 
1061 	for (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++)
1062 	{
1063 		if (!client->name[0] || client->spectator)
1064 			continue;
1065 
1066 		if (first || strcmp(lastteam[index1], client->team))
1067 		{
1068 			first = false;
1069 			num--;
1070 			lastteam[index1] = client->team;
1071 		}
1072 	}
1073 
1074 	if (num)
1075 		return "";
1076 
1077 	return lastteam[index1];
1078 }
1079 
Dem_PlayerName(int num)1080 char *Dem_PlayerName (int num)
1081 {
1082 	int i;
1083 	client_t *client;
1084 
1085 	for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++)
1086 	{
1087 		if (!client->name[0] || client->spectator)
1088 			continue;
1089 
1090 		if (!--num)
1091 			return client->name;
1092 	}
1093 
1094 	return "";
1095 }
1096 
Dem_PlayerNameTeam(char * t)1097 char *Dem_PlayerNameTeam (char *t)
1098 {
1099 	int	i;
1100 	client_t *client;
1101 	static char	n[1024];
1102 	int	sep;
1103 
1104 	n[0] = 0;
1105 
1106 	sep = 0;
1107 
1108 	for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++)
1109 	{
1110 		if (!client->name[0] || client->spectator)
1111 			continue;
1112 
1113 		if (strcmp(t, client->team)==0)
1114 		{
1115 			if (sep >= 1)
1116 				strlcat (n, "_", sizeof(n));
1117 			//				snprintf (n, sizeof(n), "%s_", n);
1118 			strlcat (n, client->name, sizeof(n));
1119 			//			snprintf (n, sizeof(n),"%s%s", n, client->name);
1120 			sep++;
1121 		}
1122 	}
1123 
1124 	return n;
1125 }
1126 
Dem_CountTeamPlayers(char * t)1127 int Dem_CountTeamPlayers (char *t)
1128 {
1129 	int	i, count;
1130 
1131 	count = 0;
1132 	for (i = 0; i < MAX_CLIENTS ; i++)
1133 	{
1134 		if (svs.clients[i].name[0] && !svs.clients[i].spectator)
1135 			if (strcmp(&svs.clients[i].team[0], t)==0)
1136 				count++;
1137 	}
1138 
1139 	return count;
1140 }
1141 
quote(char * str)1142 char *quote (char *str)
1143 {
1144 	char *out, *s;
1145 	if (!str)
1146 		return NULL;
1147 	if (!*str)
1148 		return NULL;
1149 
1150 	s = out = (char *) Q_malloc (strlen(str) * 2 + 1);
1151 	while (*str)
1152 	{
1153 		if (!isdigit(*str) && !isalpha(*str))
1154 			*s++ = '\\';
1155 		*s++ = *str++;
1156 	}
1157 	*s = '\0';
1158 	return out;
1159 }
1160 
1161 #endif // !CLIENTONLY
1162