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