1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 1991-2001 by
5  *
6  *      Bj�rn Stabell        <bjoern@xpilot.org>
7  *      Ken Ronny Schouten   <ken@xpilot.org>
8  *      Bert Gijsbers        <bert@xpilot.org>
9  *      Dick Balaska         <dick@xpilot.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25 
26 #include "xpserver.h"
27 
28 static char    *FileName;
29 static int      LineNumber;
30 
31 
32 /*
33  * Skips to the end of the line.
34  */
toeol(char ** map_ptr)35 static void toeol(char **map_ptr)
36 {
37     int ich;
38 
39     while (**map_ptr) {
40 	ich = **map_ptr;
41 	(*map_ptr)++;
42 	if (ich == '\n') {
43 	    ++LineNumber;
44 	    return;
45 	}
46     }
47 }
48 
49 
50 /*
51  * Skips to the first non-whitespace character, returning that character.
52  */
skipspace(char ** map_ptr)53 static int skipspace(char **map_ptr)
54 {
55     int ich;
56 
57     while (**map_ptr) {
58 	ich = **map_ptr;
59 	(*map_ptr)++;
60 	if (ich == '\n') {
61 	    ++LineNumber;
62 	    return ich;
63 	}
64 	if (!isascii(ich) || !isspace(ich))
65 	    return ich;
66     }
67     return '\0';
68 }
69 
70 
71 /*
72  * Read in a multiline value.
73  */
getMultilineValue(char ** map_ptr,char * delimiter)74 static char *getMultilineValue(char **map_ptr, char *delimiter)
75 {
76     char *s = XMALLOC(char, 32768);
77     size_t i = 0, slen = 32768;
78     char *bol;
79     int ich;
80 
81     bol = s;
82     for (;;) {
83 	ich = **map_ptr;
84 	(*map_ptr)++;
85 	if (ich == '\0') {
86 	    s = (char *)realloc(s, i + 1);
87 	    s[i] = '\0';
88 	    return s;
89 	}
90 	if (i == slen) {
91 	    char *t = s;
92 
93 	    slen += (slen / 2) + 8192;
94 	    s = (char *)realloc(s, slen);
95 	    bol += s - t;
96 	}
97 	if (ich == '\n') {
98 	    s[i] = 0;
99 	    if (delimiter && !strcmp(bol, delimiter)) {
100 		char *t = s;
101 
102 		s = (char *)realloc(s, (size_t) (bol - s + 1));
103 		s[bol - t] = '\0';
104 		return s;
105 	    }
106 	    bol = &s[i + 1];
107 	    ++LineNumber;
108 	}
109 	s[i++] = ich;
110     }
111 }
112 
113 
114 /*
115  * Parse a standard line from a defaults file, in the form Name: value.
116  * Name must start at the beginning of the line with an alphabetic character.
117  * Whitespace within name is ignored. Value may contain any character other
118  * than # or newline, but leading and trailing whitespace are discarded.
119  *
120  * Characters after a # are ignored - this can be used for comments.
121  *
122  * If value begins with \override:, the override flag is set when
123  * Option_set_value is called, so that this value will override
124  * an existing value.
125  *
126  * If value starts with \multiline:, then the rest of the line is used
127  * as a delimiter, and subsequent lines are read and saved as the value
128  * until the delimiter is encountered.   No interpretation is done on
129  * the text in the multiline sequence, so # does not serve as a comment
130  * character there, and newlines and whitespace are not discarded.
131  *
132  * Lines can also be in the form "define: name value".
133  * And similarly "define: name \multiline: TAG".
134  *
135  * Names can be macros which are expanded inplace with the keyword expand like:
136  * expand: name
137  *
138  */
139 #define EXPAND				\
140     if (i == slen)			\
141 	s = (char *)realloc(s, slen *= 2);
142 
parseLine(char ** map_ptr,optOrigin opt_origin)143 static void parseLine(char **map_ptr, optOrigin opt_origin)
144 {
145     int ich, override = 0, multiline = 0;
146     char *value, *head, *name, *s = XMALLOC(char, 128), *p;
147     size_t slen = 128, i = 0;
148 
149     ich = **map_ptr;
150     (*map_ptr)++;
151 
152     /*
153      * Skip blank lines...
154      */
155     if (ich == '\n') {
156 	++LineNumber;
157 	free(s);
158 	return;
159     }
160     /*
161      * Skip leading space...
162      */
163     if (isascii(ich) && isspace(ich)) {
164 	ich = skipspace(map_ptr);
165 	if (ich == '\n') {
166 	    free(s);
167 	    return;
168 	}
169     }
170     /*
171      * Skip lines that start with comment character...
172      */
173     if (ich == '#') {
174 	toeol(map_ptr);
175 	free(s);
176 	return;
177     }
178     /*
179      * Skip lines that start with the end of the file... :')
180      */
181     if (ich == '\0') {
182 	free(s);
183 	return;
184     }
185     /*
186      *** I18nize? *** */
187     if (!isascii(ich) || !isalpha(ich)) {
188 	error("%s line %d: Names must start with an alphabetic.\n",
189 	      FileName, LineNumber);
190 	toeol(map_ptr);
191 	free(s);
192 	return;
193     }
194     s[i++] = ich;
195     do {
196 	ich = **map_ptr;
197 	(*map_ptr)++;
198 
199 	if (ich == '\n' || ich == '#' || ich == '\0') {
200 	    error("%s line %d: No colon found on line.\n",
201 		  FileName, LineNumber);
202 	    if (ich == '#')
203 		toeol(map_ptr);
204 	    else
205 		++LineNumber;
206 	    free(s);
207 	    return;
208 	}
209 	if (isascii(ich) && isspace(ich))
210 	    continue;
211 	if (ich == ':')
212 	    break;
213 	EXPAND;
214 	s[i++] = ich;
215     } while (1);
216 
217     ich = skipspace(map_ptr);
218 
219     EXPAND;
220     s[i++] = '\0';
221     name = s;
222 
223     slen = 128;
224     s = XMALLOC(char, slen);
225     i = 0;
226     do {
227 	EXPAND;
228 	s[i++] = ich;
229 	ich = **map_ptr;
230 	(*map_ptr)++;
231     } while (ich != '\0' && ich != '#' && ich != '\n');
232 
233     if (ich == '\n')
234 	++LineNumber;
235 
236     if (ich == '#')
237 	toeol(map_ptr);
238 
239     EXPAND;
240     s[i++] = 0;
241     head = value = s;
242     s = value + strlen(value) - 1;
243     while (s >= value && isascii(*s) && isspace(*s))
244 	--s;
245     *++s = 0;
246 
247     /*
248      * Deal with 'define: MACRO \multiline: TAG'.
249      */
250     if (strcmp(name, "define") == 0) {
251 	p = value;
252 	while (*p && isascii(*p) && !isspace(*p))
253 	    p++;
254 	*p++ = '\0';
255 
256 	/*
257 	 * name becomes value
258 	 */
259 	free(name);
260 	name = XMALLOC(char, (size_t) (p - value + 1));
261 	memcpy(name, value, (size_t) (p - value));
262 	name[p - value] = '\0';
263 
264 	/*
265 	 * Move value to \multiline
266 	 */
267 	while (*p && isspace(*p))
268 	    p++;
269 	value = p;
270     }
271 
272     if (!strncmp(value, "\\override:", 10)) {
273 	override = 1;
274 	value += 10;
275     }
276     while (*value && isascii(*value) && isspace(*value))
277 	++value;
278     if (!strncmp(value, "\\multiline:", 11)) {
279 	multiline = 1;
280 	value += 11;
281     }
282     while (*value && isascii(*value) && isspace(*value))
283 	++value;
284     if (!*value) {
285 	error("%s line %d: no value specified.\n", FileName, LineNumber);
286 	free(name);
287 	free(head);
288 	return;
289     }
290     if (multiline)
291 	value = getMultilineValue(map_ptr, value);
292 
293     /*
294      * Deal with 'expand: MACRO'.
295      */
296     if (strcmp(name, "expand") == 0)
297 	expandKeyword(value);
298 
299 #ifdef REGIONS			/* not yet */
300     /*
301      * Deal with 'region: \multiline: TAG'.
302      */
303     else if (strcmp(name, "region") == 0) {
304 	if (!multiline) {	/* Must be multiline. */
305 	    error("regions must use '\\multiline:'.\n");
306 	    free(name);
307 	    free(head);
308 	    return;
309 	}
310 	p = value;
311 	while (*p)
312 	    parseLine(&p, opt_origin);
313     }
314 #endif
315     else
316 	Option_set_value(name, value, override, opt_origin);
317 
318     if (multiline)
319 	free(value);
320     free(name);
321     free(head);
322     return;
323 }
324 
325 #undef EXPAND
326 
327 /*
328  * Parse a file containing defaults (and possibly a map).
329  */
parseOpenFile(FILE * ifile,optOrigin opt_origin)330 static bool parseOpenFile(FILE * ifile, optOrigin opt_origin)
331 {
332     int n;
333     size_t map_offset, map_size;
334     char *map_buf;
335 
336     LineNumber = 1;
337 
338     /*
339      * In case first map fails and this is another
340      */
341     is_polygon_map = false;
342 
343     /*
344      * First try the xp2 map format
345      */
346     if (isXp2MapFile(ifile)) {
347 	is_polygon_map = true;
348 	return parseXp2MapFile(FileName, opt_origin);
349     }
350 
351     /*
352      * Using a 200 map sample, the average map size is 37k. This chunk
353      * size could be increased to avoid lots of reallocs.
354      */
355 #define MAP_CHUNK_SIZE	8192
356 
357     map_offset = 0;
358     map_size = 2 * MAP_CHUNK_SIZE;
359     map_buf = XMALLOC(char, map_size + 1);
360     if (!map_buf) {
361 	error("Not enough memory to read the map!");
362 	return false;
363     }
364 
365     for (;;) {
366 	n = fread(&map_buf[map_offset], 1, map_size - map_offset, ifile);
367 	if (n < 0) {
368 	    error("Error reading map!");
369 	    free(map_buf);
370 	    return false;
371 	}
372 	if (n == 0)
373 	    break;
374 	map_offset += n;
375 
376 	if (map_size - map_offset < MAP_CHUNK_SIZE) {
377 	    map_size += (map_size / 2) + MAP_CHUNK_SIZE;
378 	    map_buf = (char *)realloc(map_buf, map_size + 1);
379 	    if (!map_buf) {
380 		error("Not enough memory to read the map!");
381 		return false;
382 	    }
383 	}
384     }
385 
386     map_buf = (char *)realloc(map_buf, map_offset + 1);
387     map_buf[map_offset] = '\0';	/* EOF */
388 
389     if (isdigit(*map_buf)) {
390 	warn("%s is in old (v1.x) format, please convert it with mapmapper",
391 	     FileName);
392 	free(map_buf);
393 	return false;
394     } else {
395 	/*
396 	 * Parse all the lines in the file.
397 	 */
398 	char *map_ptr = map_buf;
399 
400 	while (*map_ptr)
401 	    parseLine(&map_ptr, opt_origin);
402     }
403 
404     free(map_buf);
405 
406     return true;
407 }
408 
409 
copyFilename(const char * file)410 static int copyFilename(const char *file)
411 {
412     XFREE(FileName);
413     FileName = xp_strdup(file);
414     return (FileName != 0);
415 }
416 
417 
fileOpen(const char * file)418 static FILE *fileOpen(const char *file)
419 {
420     FILE *fp = fopen(file, "r");
421 
422     if (fp) {
423 	if (!copyFilename(file)) {
424 	    fclose(fp);
425 	    fp = NULL;
426 	}
427     }
428     return fp;
429 }
430 
431 
fileClose(FILE * fp)432 static void fileClose(FILE *fp)
433 {
434     fclose(fp);
435     XFREE(FileName);
436 }
437 
438 
439 /*
440  * Test if filename has the XPilot map extension.
441  */
hasMapExtension(const char * filename)442 static bool hasMapExtension(const char *filename)
443 {
444     int             fnlen = strlen(filename);
445     if (fnlen > 4 && !strcmp(&filename[fnlen - 4], ".xp2"))
446 	return true;
447     if (fnlen > 3 && !strcmp(&filename[fnlen - 3], ".xp"))
448 	return true;
449     if (fnlen > 4 && !strcmp(&filename[fnlen - 4], ".map"))
450 	return true;
451     return false;
452 }
453 
454 
455 /*
456  * Test if filename has a directory component.
457  */
hasDirectoryPrefix(const char * filename)458 static bool hasDirectoryPrefix(const char *filename)
459 {
460     static const char sep = '/';
461 
462     return (strchr(filename, sep) != NULL ? true : false);
463 }
464 
465 
466 /*
467  * Combine a directory and a file.
468  * Returns new path as dynamically allocated memory.
469  */
fileJoin(const char * dir,const char * file)470 static char *fileJoin(const char *dir, const char *file)
471 {
472     static const char sep = '/';
473     char *path;
474 
475     path = XMALLOC(char, strlen(dir) + 1 + strlen(file) + 1);
476     if (path)
477 	sprintf(path, "%s%c%s", dir, sep, file);
478     return path;
479 }
480 
481 
482 /*
483  * Combine a file and a filename extension.
484  * Returns new path as dynamically allocated memory.
485  */
fileAddExtension(const char * file,const char * ext)486 static char *fileAddExtension(const char *file, const char *ext)
487 {
488     char *path;
489 
490     path = XMALLOC(char, strlen(file) + strlen(ext) + 1);
491     if (path)
492 	sprintf(path, "%s%s", file, ext);
493     return path;
494 }
495 
496 
497 #ifdef CONF_COMPRESSED_MAPS
498 static bool     usePclose = false;
499 
500 
isCompressed(const char * filename)501 static bool isCompressed(const char *filename)
502 {
503     int fnlen = strlen(filename);
504     int celen = strlen(Conf_zcat_ext());
505 
506     if (fnlen > celen && !strcmp(&filename[fnlen - celen], Conf_zcat_ext()))
507 	return true;
508     return false;
509 }
510 
511 
closeCompressedFile(FILE * fp)512 static void closeCompressedFile(FILE * fp)
513 {
514     if (usePclose) {
515 	pclose(fp);
516 	usePclose = false;
517 	XFREE(FileName);
518     } else
519 	fileClose(fp);
520 }
521 
522 
openCompressedFile(const char * filename)523 static FILE *openCompressedFile(const char *filename)
524 {
525     FILE *fp = NULL;
526     char *cmdline = NULL;
527     char *newname = NULL;
528 
529     usePclose = false;
530     if (!isCompressed(filename)) {
531 	if (access(filename, 4) == 0)
532 	    return fileOpen(filename);
533 	newname = fileAddExtension(filename, Conf_zcat_ext());
534 	if (!newname)
535 	    return NULL;
536 	filename = newname;
537     }
538     if (access(filename, 4) == 0) {
539 	cmdline = XMALLOC(char,
540 			  strlen(CONF_ZCAT_FORMAT) + strlen(filename) + 1);
541 	if (cmdline) {
542 	    sprintf(cmdline, CONF_ZCAT_FORMAT, filename);
543 	    fp = popen(cmdline, "r");
544 	    if (fp) {
545 		usePclose = true;
546 		if (!copyFilename(filename)) {
547 		    closeCompressedFile(fp);
548 		    fp = NULL;
549 		}
550 	    }
551 	}
552     }
553     XFREE(newname);
554     XFREE(cmdline);
555     return fp;
556 }
557 
558 #else
559 
isCompressed(const char * filename)560 static bool isCompressed(const char *filename)
561 {
562     return false;
563 }
564 
closeCompressedFile(FILE * fp)565 static void closeCompressedFile(FILE * fp)
566 {
567     fileClose(fp);
568 }
569 
openCompressedFile(const char * filename)570 static FILE *openCompressedFile(const char *filename)
571 {
572     return fileOpen(filename);
573 }
574 #endif
575 
576 /*
577  * Open a map file.
578  * Filename argument need not contain map filename extension
579  * or compress filename extension.
580  * The search order should be:
581  *      filename
582  *      filename.gz                   if CONF_COMPRESSED_MAPS is true
583  *      filename.xp2
584  *      filename.xp2.gz               if CONF_COMPRESSED_MAPS is true
585  *      filename.xp
586  *      filename.xp.gz                if CONF_COMPRESSED_MAPS is true
587  *      filename.map
588  *      filename.map.gz               if CONF_COMPRESSED_MAPS is true
589  *      CONF_MAPDIR filename
590  *      CONF_MAPDIR filename.gz       if CONF_COMPRESSED_MAPS is true
591  *      CONF_MAPDIR filename.xp2
592  *      CONF_MAPDIR filename.xp2.gz   if CONF_COMPRESSED_MAPS is true
593  *      CONF_MAPDIR filename.xp
594  *      CONF_MAPDIR filename.xp.gz    if CONF_COMPRESSED_MAPS is true
595  *      CONF_MAPDIR filename.map
596  *      CONF_MAPDIR filename.map.gz   if CONF_COMPRESSED_MAPS is true
597  */
openMapFile(const char * filename)598 static FILE *openMapFile(const char *filename)
599 {
600     FILE *fp = NULL;
601     char *newname, *newpath;
602 
603     fp = openCompressedFile(filename);
604     if (fp)
605 	return fp;
606     if (!isCompressed(filename)) {
607 	if (!hasMapExtension(filename)) {
608 	    newname = fileAddExtension(filename, ".xp2");
609 	    fp = openCompressedFile(newname);
610 	    free(newname);
611 	    if (fp)
612 		return fp;
613 	    newname = fileAddExtension(filename, ".xp");
614 	    fp = openCompressedFile(newname);
615 	    free(newname);
616 	    if (fp)
617 		return fp;
618 	    newname = fileAddExtension(filename, ".map");
619 	    fp = openCompressedFile(newname);
620 	    free(newname);
621 	    if (fp)
622 		return fp;
623 	}
624     }
625     if (!hasDirectoryPrefix(filename)) {
626 	newpath = fileJoin(Conf_mapdir(), filename);
627 	if (!newpath)
628 	    return NULL;
629 	if (hasDirectoryPrefix(newpath))
630 	    /*
631 	     * call recursively.
632 	     */
633 	    fp = openMapFile(newpath);
634 	free(newpath);
635 	if (fp)
636 	    return fp;
637     }
638     return NULL;
639 }
640 
641 
closeMapFile(FILE * fp)642 static void closeMapFile(FILE *fp)
643 {
644     closeCompressedFile(fp);
645 }
646 
647 
openDefaultsFile(const char * filename)648 static FILE *openDefaultsFile(const char *filename)
649 {
650     return fileOpen(filename);
651 }
652 
653 
closeDefaultsFile(FILE * fp)654 static void closeDefaultsFile(FILE *fp)
655 {
656     fileClose(fp);
657 }
658 
659 
660 /*
661  * Parse a file containing defaults.
662  */
parseDefaultsFile(const char * filename)663 bool parseDefaultsFile(const char *filename)
664 {
665     FILE *ifile;
666     bool result;
667 
668     if ((ifile = openDefaultsFile(filename)) == NULL)
669 	return false;
670 
671     result = parseOpenFile(ifile, OPT_DEFAULTS);
672     closeDefaultsFile(ifile);
673 
674     return true;
675 }
676 
677 
678 /*
679  * Parse a file containing password.
680  */
parsePasswordFile(const char * filename)681 bool parsePasswordFile(const char *filename)
682 {
683     FILE *ifile;
684     bool result;
685 
686     if ((ifile = openDefaultsFile(filename)) == NULL)
687 	return false;
688 
689     result = parseOpenFile(ifile, OPT_PASSWORD);
690     closeDefaultsFile(ifile);
691 
692     return true;
693 }
694 
695 
696 /*
697  * Parse a file containing a map.
698  */
parseMapFile(const char * filename)699 bool parseMapFile(const char *filename)
700 {
701     FILE *ifile;
702     bool result;
703 
704     if ((ifile = openMapFile(filename)) == NULL)
705 	return false;
706 
707     result = parseOpenFile(ifile, OPT_MAP);
708     closeMapFile(ifile);
709 
710     return result;
711 }
712 
713 
expandKeyword(const char * keyword)714 void expandKeyword(const char *keyword)
715 {
716     optOrigin expand_origin;
717     char *p;
718 
719     p = Option_get_value(keyword, &expand_origin);
720     if (p == NULL)
721 	warn("Can't expand '%s' because it has not been defined.\n",
722 	     keyword);
723     else {
724 	while (*p)
725 	    parseLine(&p, expand_origin);
726     }
727 }
728