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