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