1 /* XQF - Quake server browser and launcher
2 * Copyright (C) 1998-2000 Roman Pozlevich <roma@botik.ru>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (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. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17 */
18
19 #include <stdio.h>
20 #include <ctype.h> /* is...() */
21 #include <string.h> /* strcmp */
22 #include <sys/stat.h> /* stat. mkdir */
23 #include <unistd.h> /* stat, mkdir, unlink */
24 #include <sys/types.h> /* mkdir */
25 #include <fcntl.h> /* mkdir */
26
27 #include "xqf.h"
28 #include "game.h"
29 #include "pref.h"
30 #include "filter.h"
31 #include "utils.h"
32 #include "config.h"
33 #include "rc.h"
34
35
36 static FILE *rc;
37 static int token = -1;
38 static char *token_str = NULL;
39 static int token_int = 0;
40 static char *tptr = NULL;
41 static int state = STATE_SPACE;
42
43 static char *rcfilename = NULL;
44 static int line;
45 static int pos;
46
47
48 static struct keyword keywords[] = {
49
50 { "q1_top", KEYWORD_INT, "/" CONFIG_FILE "/Game: QS/top" },
51 { "q1_bottom", KEYWORD_INT, "/" CONFIG_FILE "/Game: QS/bottom" },
52 { "team", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/team" },
53 { "qw_skin", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/skin" },
54 { "qw_top", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/top" },
55 { "qw_bottom", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/bottom" },
56 { "q2_skin", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q2S/skin" },
57 { "rate", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/rate" },
58 { "cl_nodelta", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/cl_nodelta" },
59 { "cl_predict", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/cl_predict" },
60 { "noaim", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/noaim" },
61 { "pushlatency", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/pushlatency mode" },
62 { "noskins", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/noskins" },
63 { "w_switch", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/w_switch" },
64 { "b_switch", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/b_switch" },
65
66 { "name", KEYWORD_STRING, "/" CONFIG_FILE "/Games Config/player name" },
67 { "nosound", KEYWORD_BOOL, "/" CONFIG_FILE "/Games Config/nosound" },
68 { "nocdaudio", KEYWORD_BOOL, "/" CONFIG_FILE "/Games Config/nocdaudio" },
69
70 { "retries", KEYWORD_INT, "/" CONFIG_FILE "/Server Filter/retries" },
71 { "ping", KEYWORD_INT, "/" CONFIG_FILE "/Server Filter/ping" },
72 { "notfull", KEYWORD_BOOL, "/" CONFIG_FILE "/Server Filter/not full" },
73 { "notempty", KEYWORD_BOOL, "/" CONFIG_FILE "/Server Filter/not empty" },
74 { "nocheats", KEYWORD_BOOL, "/" CONFIG_FILE "/Server Filter/no cheats" },
75 { "nopasswd", KEYWORD_BOOL, "/" CONFIG_FILE "/Server Filter/no password" },
76
77 { "terminate", KEYWORD_BOOL, "/" CONFIG_FILE "/General/terminate" },
78 { "iconify", KEYWORD_BOOL, "/" CONFIG_FILE "/General/iconify" },
79 { "savelists", KEYWORD_BOOL, "/" CONFIG_FILE "/General/save lists" },
80 { "saveservers", KEYWORD_BOOL, "/" CONFIG_FILE "/General/save srvinfo" },
81 { "saveplayers", KEYWORD_BOOL, "/" CONFIG_FILE "/General/save players" },
82
83 { "autofavorites", KEYWORD_BOOL, "/" CONFIG_FILE "/General/refresh favorites" },
84
85 { "tb_style", KEYWORD_INT, "/" CONFIG_FILE "/Appearance/toolbar style" },
86 { "tb_tips", KEYWORD_BOOL, "/" CONFIG_FILE "/Appearance/toolbar tips" },
87
88 { "sort_on_refresh", KEYWORD_BOOL, "/" CONFIG_FILE "/Appearance/sort on refresh" },
89 { "ref_on_update", KEYWORD_BOOL, "/" CONFIG_FILE "/Appearance/refresh on update" },
90 { "alwaysresolve", KEYWORD_BOOL, "/" CONFIG_FILE "/Appearance/show hostnames" },
91 { "maxsimultaneous", KEYWORD_INT, "/" CONFIG_FILE "/QStat/maxsimultaneous" },
92 { "maxretries", KEYWORD_INT, "/" CONFIG_FILE "/QStat/maxretires" },
93
94 { "q1_custom_cfg", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QS/custom cfg" },
95 { "qw_custom_cfg", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/custom cfg" },
96 { "q2_custom_cfg", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q2S/custom cfg" },
97 { "q3_custom_cfg", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q3S/custom cfg" },
98
99 { "q1_dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QS/dir" },
100 { "q1_cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QS/cmd" },
101 { "qw_dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/dir" },
102 { "qw_cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/cmd" },
103 { "q2_dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q2S/dir" },
104 { "q2_cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q2S/cmd" },
105 { "q3_dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q3S/dir" },
106 { "q3_cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: Q3S/cmd" },
107 { "hl_dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: HLS/dir" },
108 { "hl_cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: HLS/cmd" },
109
110 /* compatibility with ancient versions */
111
112 { "top", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/top" },
113 { "bottom", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/bottom" },
114
115 { "cl_predict_players", KEYWORD_INT, "/" CONFIG_FILE "/Game: QWS/cl_predict" },
116
117 { "skin", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/skin" },
118 { "dir", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/dir" },
119 { "cmd", KEYWORD_STRING, "/" CONFIG_FILE "/Game: QWS/dir" },
120
121 { NULL, 0, NULL }
122 };
123
124
unexpected_char_error(char c)125 static void unexpected_char_error (char c) {
126 fprintf (stderr, "Unexpected character: ");
127 fprintf (stderr, (isprint (c))? "\'%c\'" : "\\%03o", c);
128 fprintf (stderr, " in file %s[line:%d,pos:%d]\n", rcfilename, line, pos);
129 }
130
131
unexpected_eof_error(void)132 static void unexpected_eof_error (void) {
133 fprintf (stderr, "Unexpected end of line in file %s[line:%d,pos:%d]\n", rcfilename, line, pos);
134 }
135
136
syntax_error(void)137 static void syntax_error (void) {
138 fprintf (stderr, "Syntax error in file %s[line:%d,pos:%d]\n", rcfilename, line, pos);
139 }
140
141
unexpected_token_error(int need_token)142 static void unexpected_token_error (int need_token) {
143 fprintf (stderr, "Syntax error in file %s[line:%d,pos:%d]\n", rcfilename, line, pos);
144 fprintf (stderr, "Skipping to the end of the line...\n");
145 }
146
147
rc_getc(FILE * rc)148 static inline int rc_getc (FILE *rc) {
149 pos++;
150 return getc (rc);
151 }
152
153
rc_ungetc(char c,FILE * rc)154 static inline int rc_ungetc (char c, FILE *rc) {
155 pos--;
156 return ungetc (c, rc);
157 }
158
159
rc_open(char * filename)160 static int rc_open (char *filename) {
161 rc = fopen (filename, "r");
162 if (rc == NULL)
163 return -1;
164
165 token_str = g_malloc (BUFFER_SIZE);
166 tptr = token_str;
167 state = STATE_SPACE;
168 token_int = 0;
169 line = pos = 1;
170 rcfilename = g_strdup (filename);
171 return 0;
172 }
173
174
rc_close(void)175 static void rc_close (void) {
176 if (rc) {
177 fclose (rc);
178 rc = NULL;
179 }
180
181 if (token_str) {
182 g_free (token_str);
183 token_str = NULL;
184 }
185
186 if (rcfilename) {
187 g_free (rcfilename);
188 rcfilename = NULL;
189 }
190 }
191
192
rc_next_token(void)193 static int rc_next_token (void) {
194 int c;
195 int num, i;
196 int sign = 1;
197
198 if (!rc)
199 return TOKEN_EOF;
200
201 if ((c = rc_getc (rc)) == EOF) {
202 rc_close ();
203 return TOKEN_EOF;
204 }
205
206 tptr = token_str;
207
208 while (1) {
209
210 switch (state) {
211
212 case STATE_SPACE:
213 if (c == ' ' || c == '\r' || c == '\t')
214 break;
215
216 if (isdigit (c)) {
217 sign = 1;
218 token_int = c - '0';
219 state = STATE_INT;
220 break;
221 }
222
223 if (isalnum (c) || c == '_') {
224 *tptr++ = c;
225 state = STATE_TOKEN;
226 break;
227 }
228
229 switch (c) {
230
231 case '\n':
232 line++;
233 pos = 1;
234 return TOKEN_EOL;
235 break;
236
237 case ',':
238 return TOKEN_COMMA;
239 break;
240
241 case '\"':
242 state = STATE_STRING;
243 break;
244
245 case '-':
246 sign = -1;
247 token_int = 0;
248 state = STATE_INT;
249 break;
250
251 case '#':
252 state = STATE_COMMENT;
253 break;
254
255 default:
256 unexpected_char_error (c);
257 break;
258
259 } /* switch */
260
261 break;
262
263 case STATE_COMMENT:
264 if (c == '\n') {
265 line++;
266 pos = 1;
267 state = STATE_SPACE;
268 return TOKEN_EOL;
269 }
270 break;
271
272 case STATE_INT:
273 if (!isdigit (c)) {
274 rc_ungetc (c, rc);
275 token_int = token_int * sign;
276 state = STATE_SPACE;
277 return TOKEN_INT;
278 }
279
280 token_int = token_int*10 + c - '0';
281 break;
282
283 case STATE_TOKEN:
284 if (!isalnum (c) && c != '_') {
285 rc_ungetc (c, rc);
286 *tptr = '\0';
287 state = STATE_SPACE;
288 return TOKEN_KEYWORD;
289 }
290
291 *tptr++ = c;
292 break;
293
294 case STATE_STRING:
295 if (c == '\"') {
296 *tptr = '\0';
297 state = STATE_SPACE;
298 return TOKEN_STRING;
299 }
300
301 if (c == '\n') {
302 unexpected_eof_error ();
303 rc_ungetc (c, rc);
304 *tptr = '\0';
305 state = STATE_SPACE;
306 return TOKEN_STRING;
307 }
308
309 if (c == '\\') {
310 if ((c = rc_getc (rc)) == EOF) {
311 *tptr = '\0';
312 rc_close ();
313 return TOKEN_STRING;
314 }
315
316 if (c >= '0' && c <='7') {
317 for (i=0, num=0; i < 3; i++) {
318 num <<= 3;
319 num |= c - '0';
320 c = rc_getc (rc);
321 if (c < '0' || c > '7')
322 break;
323 }
324
325 *tptr++ = num;
326
327 if (c == EOF) {
328 *tptr = '\0';
329 rc_close ();
330 return TOKEN_STRING;
331 }
332
333 continue;
334 } /* if (c >= '0' && c <='7') */
335
336 switch (c) {
337
338 case 'n':
339 *tptr++ = '\n';
340 break;
341
342 case 'r':
343 *tptr++ = '\r';
344 break;
345
346 case 't':
347 *tptr++ = '\t';
348 break;
349
350 default:
351 *tptr++ = c;
352 break;
353
354 } /* switch (c) */
355 break;
356
357 } /* if (c == '\\') */
358
359 *tptr++ = c;
360 break;
361
362 } /* switch (state) */
363
364 c = rc_getc (rc);
365
366 } /* while (1) */
367
368 }
369
370
rc_skip_to_eol(void)371 static void rc_skip_to_eol (void) {
372 while (token != TOKEN_EOL && token != TOKEN_EOF) {
373 token = rc_next_token ();
374 }
375 }
376
377
rc_expect_token(int need_token)378 static int rc_expect_token (int need_token) {
379 if ((token = rc_next_token ()) == need_token)
380 return TRUE;
381
382 unexpected_token_error (need_token);
383 rc_skip_to_eol ();
384 return FALSE;
385 }
386
387
rc_parse(void)388 int rc_parse (void) {
389 char *fn;
390 struct keyword *kw;
391
392 fn = file_in_dir (user_rcdir, RC_FILE);
393 rc_open (fn);
394 g_free (fn);
395
396 if (!rc)
397 return -1;
398
399 while ((token = rc_next_token ()) != TOKEN_EOF) {
400
401 switch (token) {
402
403 case TOKEN_KEYWORD:
404 for (kw = keywords; kw->name; kw++) {
405 if (strcmp (token_str, kw->name) == 0) {
406
407 switch (kw->required) {
408 case KEYWORD_INT:
409 if (rc_expect_token (TOKEN_INT))
410 config_set_int (kw->config, token_int);
411 break;
412
413 case KEYWORD_BOOL:
414 if (rc_expect_token (TOKEN_INT))
415 config_set_bool (kw->config, token_int);
416 break;
417
418 case KEYWORD_STRING:
419 if (rc_expect_token (TOKEN_STRING))
420 config_set_string (kw->config, token_str);
421 break;
422 }
423
424 break;
425 }
426 }
427 break;
428
429 case TOKEN_EOL:
430 case TOKEN_EOF:
431 break;
432
433 default:
434 syntax_error ();
435 rc_skip_to_eol ();
436 break;
437
438 } /* switch */
439 } /* while */
440
441 rc_close ();
442
443 /* Compatibility with old versions */
444
445 if (!games[Q1_SERVER].dir && games[QW_SERVER].dir) {
446 games[Q1_SERVER].dir = g_strdup (games[QW_SERVER].dir);
447 config_set_string ("/" CONFIG_FILE "/Game: QS/dir", games[Q1_SERVER].dir);
448 }
449
450 if (default_w_switch < 0) default_w_switch = 0;
451 if (default_b_switch < 0) default_b_switch = 0;
452
453 return 0;
454 }
455
456
rc_save(void)457 int rc_save (void) {
458 char *fn;
459
460 fn = file_in_dir (user_rcdir, RC_FILE);
461 unlink (fn);
462 g_free (fn);
463 return 0;
464 }
465
466
rc_migrate_dir(void)467 int rc_migrate_dir (void) {
468 int res;
469 struct stat st_buf;
470 const gchar* legacy_user_rcdir = NULL;
471 const gchar* xdg_user_rcdir = NULL;
472 const gchar* xdg_user_dir = NULL;
473
474 if (!g_get_user_name () || !g_get_home_dir () || !g_get_user_config_dir ()) {
475 fprintf(stderr, "Unable to get user name/home directory/XDG config directory\n");
476 return FALSE;
477 }
478
479 xdg_user_dir = g_get_user_config_dir ();
480 legacy_user_rcdir = file_in_dir (g_get_home_dir (), RC_DIR);
481 xdg_user_rcdir = file_in_dir (xdg_user_dir, XDG_RC_DIR);
482
483 /* if ~/.qf exists and ~/.config/xqf does not exists */
484 if (stat (legacy_user_rcdir, &st_buf) != -1 && stat (xdg_user_rcdir, &st_buf) == -1) {
485 /* if ~/.config does not exists, create it */
486 if (stat (xdg_user_dir, &st_buf) == -1) {
487 res = mkdir (xdg_user_dir, 0755);
488 if (res != 0) {
489 fprintf(stderr, "Can't create XDG user config directory %s\n", xdg_user_dir);
490 return res;
491 }
492 }
493 else {
494 /* move ~/.qf ~/.config/qf */
495 fprintf(stdout, "Moving legacy config directory %s to XDG user config directory %s\n", legacy_user_rcdir, xdg_user_rcdir);
496 res = rename(legacy_user_rcdir, xdg_user_rcdir);
497 if (res == 0) {
498 fprintf(stdout, "Legacy config directory %s succesfully moved to XDG user config directory %s\n", legacy_user_rcdir, xdg_user_rcdir);
499 }
500 else {
501 fprintf(stderr, "Error when moving legacy config directory %s to XDG user config directory %s\n", legacy_user_rcdir, xdg_user_rcdir);
502 return res;
503 }
504 }
505 }
506 else if (stat (legacy_user_rcdir, &st_buf) != -1 && stat (xdg_user_rcdir, &st_buf) != -1) {
507 fprintf(stderr, "Warning, there is an old legacy config directory %s, but XDG user config directory %s will be used\n", legacy_user_rcdir, xdg_user_rcdir);
508 }
509 return TRUE;
510 }
511
rc_check_dir(void)512 int rc_check_dir (void) {
513 struct stat st_buf;
514
515 if (stat (user_rcdir, &st_buf) == -1) {
516 return mkdir (user_rcdir, 0755);
517 }
518 else {
519 if (!S_ISDIR (st_buf.st_mode)) {
520 fprintf (stderr, "%s is not a directory\n", user_rcdir);
521 return -1;
522 }
523 }
524
525 return 0;
526 }
527