1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
5 * Holger Schemel *
6 * Detmolder Strasse 189 *
7 * 33604 Bielefeld *
8 * Germany *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
11 * setup.c *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "platform.h"
21
22 #if !defined(PLATFORM_WIN32)
23 #include <pwd.h>
24 #include <sys/param.h>
25 #endif
26
27 #include "setup.h"
28 #include "joystick.h"
29 #include "text.h"
30 #include "misc.h"
31 #include "hash.h"
32
33
34 #define NUM_LEVELCLASS_DESC 8
35
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
37 {
38 "Tutorial Levels",
39 "Classic Originals",
40 "Contributions",
41 "Private Levels",
42 "Boulderdash",
43 "Emerald Mine",
44 "Supaplex",
45 "DX Boulderdash"
46 };
47
48
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
58 FC_BLUE)
59
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
69 9)
70
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
75 FC_BLUE)
76
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
81 9)
82
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
86
87 #define MAX_COOKIE_LEN 256
88
89
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
93
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
96
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
100
101
102 /* ------------------------------------------------------------------------- */
103 /* file functions */
104 /* ------------------------------------------------------------------------- */
105
getLevelClassDescription(TreeInfo * ti)106 static char *getLevelClassDescription(TreeInfo *ti)
107 {
108 int position = ti->sort_priority / 100;
109
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
112 else
113 return "Unknown Level Class";
114 }
115
getUserLevelDir(char * level_subdir)116 static char *getUserLevelDir(char *level_subdir)
117 {
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
121
122 checked_free(userlevel_dir);
123
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
126 else
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
128
129 return userlevel_dir;
130 }
131
getScoreDir(char * level_subdir)132 static char *getScoreDir(char *level_subdir)
133 {
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
137
138 checked_free(score_dir);
139
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
142 else
143 score_dir = getPath2(data_dir, score_subdir);
144
145 return score_dir;
146 }
147
getLevelSetupDir(char * level_subdir)148 static char *getLevelSetupDir(char *level_subdir)
149 {
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153
154 checked_free(levelsetup_dir);
155
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158 else
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160
161 return levelsetup_dir;
162 }
163
getCacheDir()164 static char *getCacheDir()
165 {
166 static char *cache_dir = NULL;
167
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170
171 return cache_dir;
172 }
173
getLevelDirFromTreeInfo(TreeInfo * node)174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
175 {
176 static char *level_dir = NULL;
177
178 if (node == NULL)
179 return options.level_directory;
180
181 checked_free(level_dir);
182
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
185
186 return level_dir;
187 }
188
getCurrentLevelDir()189 char *getCurrentLevelDir()
190 {
191 return getLevelDirFromTreeInfo(leveldir_current);
192 }
193
getTapeDir(char * level_subdir)194 static char *getTapeDir(char *level_subdir)
195 {
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
199
200 checked_free(tape_dir);
201
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
204 else
205 tape_dir = getPath2(data_dir, tape_subdir);
206
207 return tape_dir;
208 }
209
getSolutionTapeDir()210 static char *getSolutionTapeDir()
211 {
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
215
216 checked_free(tape_dir);
217
218 tape_dir = getPath2(data_dir, tape_subdir);
219
220 return tape_dir;
221 }
222
getDefaultGraphicsDir(char * graphics_subdir)223 static char *getDefaultGraphicsDir(char *graphics_subdir)
224 {
225 static char *graphics_dir = NULL;
226
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
229
230 checked_free(graphics_dir);
231
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
233
234 return graphics_dir;
235 }
236
getDefaultSoundsDir(char * sounds_subdir)237 static char *getDefaultSoundsDir(char *sounds_subdir)
238 {
239 static char *sounds_dir = NULL;
240
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
243
244 checked_free(sounds_dir);
245
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
247
248 return sounds_dir;
249 }
250
getDefaultMusicDir(char * music_subdir)251 static char *getDefaultMusicDir(char *music_subdir)
252 {
253 static char *music_dir = NULL;
254
255 if (music_subdir == NULL)
256 return options.music_directory;
257
258 checked_free(music_dir);
259
260 music_dir = getPath2(options.music_directory, music_subdir);
261
262 return music_dir;
263 }
264
getClassicArtworkSet(int type)265 static char *getClassicArtworkSet(int type)
266 {
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
270 }
271
getClassicArtworkDir(int type)272 static char *getClassicArtworkDir(int type)
273 {
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
280 }
281
getUserGraphicsDir()282 static char *getUserGraphicsDir()
283 {
284 static char *usergraphics_dir = NULL;
285
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
288
289 return usergraphics_dir;
290 }
291
getUserSoundsDir()292 static char *getUserSoundsDir()
293 {
294 static char *usersounds_dir = NULL;
295
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
298
299 return usersounds_dir;
300 }
301
getUserMusicDir()302 static char *getUserMusicDir()
303 {
304 static char *usermusic_dir = NULL;
305
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
308
309 return usermusic_dir;
310 }
311
getSetupArtworkDir(TreeInfo * ti)312 static char *getSetupArtworkDir(TreeInfo *ti)
313 {
314 static char *artwork_dir = NULL;
315
316 checked_free(artwork_dir);
317
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
319
320 return artwork_dir;
321 }
322
setLevelArtworkDir(TreeInfo * ti)323 char *setLevelArtworkDir(TreeInfo *ti)
324 {
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
327
328 if (ti == NULL || leveldir_current == NULL)
329 return NULL;
330
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
333
334 checked_free(*artwork_path_ptr);
335
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 {
338 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
339 }
340 else
341 {
342 /*
343 No (or non-existing) artwork configured in "levelinfo.conf". This would
344 normally result in using the artwork configured in the setup menu. But
345 if an artwork subdirectory exists (which might contain custom artwork
346 or an artwork configuration file), this level artwork must be treated
347 as relative to the default "classic" artwork, not to the artwork that
348 is currently configured in the setup menu.
349
350 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352 the real "classic" artwork from the original R'n'D (like "gfx_classic").
353 */
354
355 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
356
357 checked_free(*artwork_set_ptr);
358
359 if (fileExists(dir))
360 {
361 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
363 }
364 else
365 {
366 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367 *artwork_set_ptr = NULL;
368 }
369
370 free(dir);
371 }
372
373 return *artwork_set_ptr;
374 }
375
getLevelArtworkSet(int type)376 inline static char *getLevelArtworkSet(int type)
377 {
378 if (leveldir_current == NULL)
379 return NULL;
380
381 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
382 }
383
getLevelArtworkDir(int type)384 inline static char *getLevelArtworkDir(int type)
385 {
386 if (leveldir_current == NULL)
387 return UNDEFINED_FILENAME;
388
389 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
390 }
391
getTapeFilename(int nr)392 char *getTapeFilename(int nr)
393 {
394 static char *filename = NULL;
395 char basename[MAX_FILENAME_LEN];
396
397 checked_free(filename);
398
399 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
401
402 return filename;
403 }
404
getSolutionTapeFilename(int nr)405 char *getSolutionTapeFilename(int nr)
406 {
407 static char *filename = NULL;
408 char basename[MAX_FILENAME_LEN];
409
410 checked_free(filename);
411
412 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413 filename = getPath2(getSolutionTapeDir(), basename);
414
415 if (!fileExists(filename))
416 {
417 static char *filename_sln = NULL;
418
419 checked_free(filename_sln);
420
421 sprintf(basename, "%03d.sln", nr);
422 filename_sln = getPath2(getSolutionTapeDir(), basename);
423
424 if (fileExists(filename_sln))
425 return filename_sln;
426 }
427
428 return filename;
429 }
430
getScoreFilename(int nr)431 char *getScoreFilename(int nr)
432 {
433 static char *filename = NULL;
434 char basename[MAX_FILENAME_LEN];
435
436 checked_free(filename);
437
438 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
439 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
440
441 return filename;
442 }
443
getSetupFilename()444 char *getSetupFilename()
445 {
446 static char *filename = NULL;
447
448 checked_free(filename);
449
450 filename = getPath2(getSetupDir(), SETUP_FILENAME);
451
452 return filename;
453 }
454
getEditorSetupFilename()455 char *getEditorSetupFilename()
456 {
457 static char *filename = NULL;
458
459 checked_free(filename);
460 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
461
462 if (fileExists(filename))
463 return filename;
464
465 checked_free(filename);
466 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
467
468 return filename;
469 }
470
getHelpAnimFilename()471 char *getHelpAnimFilename()
472 {
473 static char *filename = NULL;
474
475 checked_free(filename);
476
477 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
478
479 return filename;
480 }
481
getHelpTextFilename()482 char *getHelpTextFilename()
483 {
484 static char *filename = NULL;
485
486 checked_free(filename);
487
488 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
489
490 return filename;
491 }
492
getLevelSetInfoFilename()493 char *getLevelSetInfoFilename()
494 {
495 static char *filename = NULL;
496 char *basenames[] =
497 {
498 "README",
499 "README.TXT",
500 "README.txt",
501 "Readme",
502 "Readme.txt",
503 "readme",
504 "readme.txt",
505
506 NULL
507 };
508 int i;
509
510 for (i = 0; basenames[i] != NULL; i++)
511 {
512 checked_free(filename);
513 filename = getPath2(getCurrentLevelDir(), basenames[i]);
514
515 if (fileExists(filename))
516 return filename;
517 }
518
519 return NULL;
520 }
521
getLevelSetTitleMessageBasename(int nr,boolean initial)522 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
523 {
524 static char basename[32];
525
526 sprintf(basename, "%s_%d.txt",
527 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
528
529 return basename;
530 }
531
getLevelSetTitleMessageFilename(int nr,boolean initial)532 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
533 {
534 static char *filename = NULL;
535 char *basename;
536 boolean skip_setup_artwork = FALSE;
537
538 checked_free(filename);
539
540 basename = getLevelSetTitleMessageBasename(nr, initial);
541
542 if (!gfx.override_level_graphics)
543 {
544 /* 1st try: look for special artwork in current level series directory */
545 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
546 if (fileExists(filename))
547 return filename;
548
549 free(filename);
550
551 /* 2nd try: look for message file in current level set directory */
552 filename = getPath2(getCurrentLevelDir(), basename);
553 if (fileExists(filename))
554 return filename;
555
556 free(filename);
557
558 /* check if there is special artwork configured in level series config */
559 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
560 {
561 /* 3rd try: look for special artwork configured in level series config */
562 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
563 if (fileExists(filename))
564 return filename;
565
566 free(filename);
567
568 /* take missing artwork configured in level set config from default */
569 skip_setup_artwork = TRUE;
570 }
571 }
572
573 if (!skip_setup_artwork)
574 {
575 /* 4th try: look for special artwork in configured artwork directory */
576 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
577 if (fileExists(filename))
578 return filename;
579
580 free(filename);
581 }
582
583 /* 5th try: look for default artwork in new default artwork directory */
584 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
585 if (fileExists(filename))
586 return filename;
587
588 free(filename);
589
590 /* 6th try: look for default artwork in old default artwork directory */
591 filename = getPath2(options.graphics_directory, basename);
592 if (fileExists(filename))
593 return filename;
594
595 return NULL; /* cannot find specified artwork file anywhere */
596 }
597
getCorrectedArtworkBasename(char * basename)598 static char *getCorrectedArtworkBasename(char *basename)
599 {
600 char *basename_corrected = basename;
601
602 #if defined(PLATFORM_MSDOS)
603 if (program.filename_prefix != NULL)
604 {
605 int prefix_len = strlen(program.filename_prefix);
606
607 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
608 basename_corrected = &basename[prefix_len];
609
610 /* if corrected filename is still longer than standard MS-DOS filename
611 size (8 characters + 1 dot + 3 characters file extension), shorten
612 filename by writing file extension after 8th basename character */
613 if (strlen(basename_corrected) > 8 + 1 + 3)
614 {
615 static char *msdos_filename = NULL;
616
617 checked_free(msdos_filename);
618
619 msdos_filename = getStringCopy(basename_corrected);
620 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
621
622 basename_corrected = msdos_filename;
623 }
624 }
625 #endif
626
627 return basename_corrected;
628 }
629
getCustomImageFilename(char * basename)630 char *getCustomImageFilename(char *basename)
631 {
632 static char *filename = NULL;
633 boolean skip_setup_artwork = FALSE;
634
635 checked_free(filename);
636
637 basename = getCorrectedArtworkBasename(basename);
638
639 if (!gfx.override_level_graphics)
640 {
641 /* 1st try: look for special artwork in current level series directory */
642 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
643 if (fileExists(filename))
644 return filename;
645
646 free(filename);
647
648 /* check if there is special artwork configured in level series config */
649 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
650 {
651 /* 2nd try: look for special artwork configured in level series config */
652 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
653 if (fileExists(filename))
654 return filename;
655
656 free(filename);
657
658 /* take missing artwork configured in level set config from default */
659 skip_setup_artwork = TRUE;
660 }
661 }
662
663 if (!skip_setup_artwork)
664 {
665 /* 3rd try: look for special artwork in configured artwork directory */
666 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
667 if (fileExists(filename))
668 return filename;
669
670 free(filename);
671 }
672
673 /* 4th try: look for default artwork in new default artwork directory */
674 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
675 if (fileExists(filename))
676 return filename;
677
678 free(filename);
679
680 /* 5th try: look for default artwork in old default artwork directory */
681 filename = getPath2(options.graphics_directory, basename);
682 if (fileExists(filename))
683 return filename;
684
685 #if defined(CREATE_SPECIAL_EDITION)
686 free(filename);
687
688 if (options.debug)
689 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
690
691 /* 6th try: look for fallback artwork in old default artwork directory */
692 /* (needed to prevent errors when trying to access unused artwork files) */
693 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
694 if (fileExists(filename))
695 return filename;
696 #endif
697
698 return NULL; /* cannot find specified artwork file anywhere */
699 }
700
getCustomSoundFilename(char * basename)701 char *getCustomSoundFilename(char *basename)
702 {
703 static char *filename = NULL;
704 boolean skip_setup_artwork = FALSE;
705
706 checked_free(filename);
707
708 basename = getCorrectedArtworkBasename(basename);
709
710 if (!gfx.override_level_sounds)
711 {
712 /* 1st try: look for special artwork in current level series directory */
713 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
714 if (fileExists(filename))
715 return filename;
716
717 free(filename);
718
719 /* check if there is special artwork configured in level series config */
720 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
721 {
722 /* 2nd try: look for special artwork configured in level series config */
723 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
724 if (fileExists(filename))
725 return filename;
726
727 free(filename);
728
729 /* take missing artwork configured in level set config from default */
730 skip_setup_artwork = TRUE;
731 }
732 }
733
734 if (!skip_setup_artwork)
735 {
736 /* 3rd try: look for special artwork in configured artwork directory */
737 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
738 if (fileExists(filename))
739 return filename;
740
741 free(filename);
742 }
743
744 /* 4th try: look for default artwork in new default artwork directory */
745 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
746 if (fileExists(filename))
747 return filename;
748
749 free(filename);
750
751 /* 5th try: look for default artwork in old default artwork directory */
752 filename = getPath2(options.sounds_directory, basename);
753 if (fileExists(filename))
754 return filename;
755
756 #if defined(CREATE_SPECIAL_EDITION)
757 free(filename);
758
759 if (options.debug)
760 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
761
762 /* 6th try: look for fallback artwork in old default artwork directory */
763 /* (needed to prevent errors when trying to access unused artwork files) */
764 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
765 if (fileExists(filename))
766 return filename;
767 #endif
768
769 return NULL; /* cannot find specified artwork file anywhere */
770 }
771
getCustomMusicFilename(char * basename)772 char *getCustomMusicFilename(char *basename)
773 {
774 static char *filename = NULL;
775 boolean skip_setup_artwork = FALSE;
776
777 checked_free(filename);
778
779 basename = getCorrectedArtworkBasename(basename);
780
781 if (!gfx.override_level_music)
782 {
783 /* 1st try: look for special artwork in current level series directory */
784 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
785 if (fileExists(filename))
786 return filename;
787
788 free(filename);
789
790 /* check if there is special artwork configured in level series config */
791 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
792 {
793 /* 2nd try: look for special artwork configured in level series config */
794 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
795 if (fileExists(filename))
796 return filename;
797
798 free(filename);
799
800 /* take missing artwork configured in level set config from default */
801 skip_setup_artwork = TRUE;
802 }
803 }
804
805 if (!skip_setup_artwork)
806 {
807 /* 3rd try: look for special artwork in configured artwork directory */
808 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
809 if (fileExists(filename))
810 return filename;
811
812 free(filename);
813 }
814
815 /* 4th try: look for default artwork in new default artwork directory */
816 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
817 if (fileExists(filename))
818 return filename;
819
820 free(filename);
821
822 /* 5th try: look for default artwork in old default artwork directory */
823 filename = getPath2(options.music_directory, basename);
824 if (fileExists(filename))
825 return filename;
826
827 #if defined(CREATE_SPECIAL_EDITION)
828 free(filename);
829
830 if (options.debug)
831 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
832
833 /* 6th try: look for fallback artwork in old default artwork directory */
834 /* (needed to prevent errors when trying to access unused artwork files) */
835 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
836 if (fileExists(filename))
837 return filename;
838 #endif
839
840 return NULL; /* cannot find specified artwork file anywhere */
841 }
842
getCustomArtworkFilename(char * basename,int type)843 char *getCustomArtworkFilename(char *basename, int type)
844 {
845 if (type == ARTWORK_TYPE_GRAPHICS)
846 return getCustomImageFilename(basename);
847 else if (type == ARTWORK_TYPE_SOUNDS)
848 return getCustomSoundFilename(basename);
849 else if (type == ARTWORK_TYPE_MUSIC)
850 return getCustomMusicFilename(basename);
851 else
852 return UNDEFINED_FILENAME;
853 }
854
getCustomArtworkConfigFilename(int type)855 char *getCustomArtworkConfigFilename(int type)
856 {
857 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
858 }
859
getCustomArtworkLevelConfigFilename(int type)860 char *getCustomArtworkLevelConfigFilename(int type)
861 {
862 static char *filename = NULL;
863
864 checked_free(filename);
865
866 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
867
868 return filename;
869 }
870
getCustomMusicDirectory(void)871 char *getCustomMusicDirectory(void)
872 {
873 static char *directory = NULL;
874 boolean skip_setup_artwork = FALSE;
875
876 checked_free(directory);
877
878 if (!gfx.override_level_music)
879 {
880 /* 1st try: look for special artwork in current level series directory */
881 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
882 if (fileExists(directory))
883 return directory;
884
885 free(directory);
886
887 /* check if there is special artwork configured in level series config */
888 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
889 {
890 /* 2nd try: look for special artwork configured in level series config */
891 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
892 if (fileExists(directory))
893 return directory;
894
895 free(directory);
896
897 /* take missing artwork configured in level set config from default */
898 skip_setup_artwork = TRUE;
899 }
900 }
901
902 if (!skip_setup_artwork)
903 {
904 /* 3rd try: look for special artwork in configured artwork directory */
905 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
906 if (fileExists(directory))
907 return directory;
908
909 free(directory);
910 }
911
912 /* 4th try: look for default artwork in new default artwork directory */
913 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
914 if (fileExists(directory))
915 return directory;
916
917 free(directory);
918
919 /* 5th try: look for default artwork in old default artwork directory */
920 directory = getStringCopy(options.music_directory);
921 if (fileExists(directory))
922 return directory;
923
924 return NULL; /* cannot find specified artwork file anywhere */
925 }
926
InitTapeDirectory(char * level_subdir)927 void InitTapeDirectory(char *level_subdir)
928 {
929 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
930 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
931 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
932 }
933
InitScoreDirectory(char * level_subdir)934 void InitScoreDirectory(char *level_subdir)
935 {
936 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
937 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
938 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
939 }
940
941 static void SaveUserLevelInfo();
942
InitUserLevelDirectory(char * level_subdir)943 void InitUserLevelDirectory(char *level_subdir)
944 {
945 if (!fileExists(getUserLevelDir(level_subdir)))
946 {
947 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
948 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
949 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
950
951 SaveUserLevelInfo();
952 }
953 }
954
InitLevelSetupDirectory(char * level_subdir)955 void InitLevelSetupDirectory(char *level_subdir)
956 {
957 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
958 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
959 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
960 }
961
InitCacheDirectory()962 void InitCacheDirectory()
963 {
964 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
965 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
966 }
967
968
969 /* ------------------------------------------------------------------------- */
970 /* some functions to handle lists of level and artwork directories */
971 /* ------------------------------------------------------------------------- */
972
newTreeInfo()973 TreeInfo *newTreeInfo()
974 {
975 return checked_calloc(sizeof(TreeInfo));
976 }
977
newTreeInfo_setDefaults(int type)978 TreeInfo *newTreeInfo_setDefaults(int type)
979 {
980 TreeInfo *ti = newTreeInfo();
981
982 setTreeInfoToDefaults(ti, type);
983
984 return ti;
985 }
986
pushTreeInfo(TreeInfo ** node_first,TreeInfo * node_new)987 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
988 {
989 node_new->next = *node_first;
990 *node_first = node_new;
991 }
992
numTreeInfo(TreeInfo * node)993 int numTreeInfo(TreeInfo *node)
994 {
995 int num = 0;
996
997 while (node)
998 {
999 num++;
1000 node = node->next;
1001 }
1002
1003 return num;
1004 }
1005
validLevelSeries(TreeInfo * node)1006 boolean validLevelSeries(TreeInfo *node)
1007 {
1008 return (node != NULL && !node->node_group && !node->parent_link);
1009 }
1010
getFirstValidTreeInfoEntry(TreeInfo * node)1011 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1012 {
1013 if (node == NULL)
1014 return NULL;
1015
1016 if (node->node_group) /* enter level group (step down into tree) */
1017 return getFirstValidTreeInfoEntry(node->node_group);
1018 else if (node->parent_link) /* skip start entry of level group */
1019 {
1020 if (node->next) /* get first real level series entry */
1021 return getFirstValidTreeInfoEntry(node->next);
1022 else /* leave empty level group and go on */
1023 return getFirstValidTreeInfoEntry(node->node_parent->next);
1024 }
1025 else /* this seems to be a regular level series */
1026 return node;
1027 }
1028
getTreeInfoFirstGroupEntry(TreeInfo * node)1029 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1030 {
1031 if (node == NULL)
1032 return NULL;
1033
1034 if (node->node_parent == NULL) /* top level group */
1035 return *node->node_top;
1036 else /* sub level group */
1037 return node->node_parent->node_group;
1038 }
1039
numTreeInfoInGroup(TreeInfo * node)1040 int numTreeInfoInGroup(TreeInfo *node)
1041 {
1042 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1043 }
1044
posTreeInfo(TreeInfo * node)1045 int posTreeInfo(TreeInfo *node)
1046 {
1047 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1048 int pos = 0;
1049
1050 while (node_cmp)
1051 {
1052 if (node_cmp == node)
1053 return pos;
1054
1055 pos++;
1056 node_cmp = node_cmp->next;
1057 }
1058
1059 return 0;
1060 }
1061
getTreeInfoFromPos(TreeInfo * node,int pos)1062 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1063 {
1064 TreeInfo *node_default = node;
1065 int pos_cmp = 0;
1066
1067 while (node)
1068 {
1069 if (pos_cmp == pos)
1070 return node;
1071
1072 pos_cmp++;
1073 node = node->next;
1074 }
1075
1076 return node_default;
1077 }
1078
getTreeInfoFromIdentifier(TreeInfo * node,char * identifier)1079 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1080 {
1081 if (identifier == NULL)
1082 return NULL;
1083
1084 while (node)
1085 {
1086 if (node->node_group)
1087 {
1088 TreeInfo *node_group;
1089
1090 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1091
1092 if (node_group)
1093 return node_group;
1094 }
1095 else if (!node->parent_link)
1096 {
1097 if (strEqual(identifier, node->identifier))
1098 return node;
1099 }
1100
1101 node = node->next;
1102 }
1103
1104 return NULL;
1105 }
1106
cloneTreeNode(TreeInfo ** node_top,TreeInfo * node_parent,TreeInfo * node,boolean skip_sets_without_levels)1107 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1108 TreeInfo *node, boolean skip_sets_without_levels)
1109 {
1110 TreeInfo *node_new;
1111
1112 if (node == NULL)
1113 return NULL;
1114
1115 if (!node->parent_link && !node->level_group &&
1116 skip_sets_without_levels && node->levels == 0)
1117 return cloneTreeNode(node_top, node_parent, node->next,
1118 skip_sets_without_levels);
1119
1120 #if 1
1121 node_new = getTreeInfoCopy(node); /* copy complete node */
1122 #else
1123 node_new = newTreeInfo();
1124
1125 *node_new = *node; /* copy complete node */
1126 #endif
1127
1128 node_new->node_top = node_top; /* correct top node link */
1129 node_new->node_parent = node_parent; /* correct parent node link */
1130
1131 if (node->level_group)
1132 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1133 skip_sets_without_levels);
1134
1135 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1136 skip_sets_without_levels);
1137
1138 return node_new;
1139 }
1140
cloneTree(TreeInfo ** ti_new,TreeInfo * ti,boolean skip_empty_sets)1141 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1142 {
1143 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1144
1145 *ti_new = ti_cloned;
1146 }
1147
adjustTreeGraphicsForEMC(TreeInfo * node)1148 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1149 {
1150 boolean settings_changed = FALSE;
1151
1152 while (node)
1153 {
1154 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1155 !strEqual(node->graphics_set, node->graphics_set_ecs))
1156 {
1157 setString(&node->graphics_set, node->graphics_set_ecs);
1158 settings_changed = TRUE;
1159 }
1160 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1161 !strEqual(node->graphics_set, node->graphics_set_aga))
1162 {
1163 setString(&node->graphics_set, node->graphics_set_aga);
1164 settings_changed = TRUE;
1165 }
1166
1167 if (node->node_group != NULL)
1168 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1169
1170 node = node->next;
1171 }
1172
1173 return settings_changed;
1174 }
1175
dumpTreeInfo(TreeInfo * node,int depth)1176 void dumpTreeInfo(TreeInfo *node, int depth)
1177 {
1178 int i;
1179
1180 printf("Dumping TreeInfo:\n");
1181
1182 while (node)
1183 {
1184 for (i = 0; i < (depth + 1) * 3; i++)
1185 printf(" ");
1186
1187 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1188 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1189
1190 if (node->node_group != NULL)
1191 dumpTreeInfo(node->node_group, depth + 1);
1192
1193 node = node->next;
1194 }
1195 }
1196
sortTreeInfoBySortFunction(TreeInfo ** node_first,int (* compare_function)(const void *,const void *))1197 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1198 int (*compare_function)(const void *,
1199 const void *))
1200 {
1201 int num_nodes = numTreeInfo(*node_first);
1202 TreeInfo **sort_array;
1203 TreeInfo *node = *node_first;
1204 int i = 0;
1205
1206 if (num_nodes == 0)
1207 return;
1208
1209 /* allocate array for sorting structure pointers */
1210 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1211
1212 /* writing structure pointers to sorting array */
1213 while (i < num_nodes && node) /* double boundary check... */
1214 {
1215 sort_array[i] = node;
1216
1217 i++;
1218 node = node->next;
1219 }
1220
1221 /* sorting the structure pointers in the sorting array */
1222 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1223 compare_function);
1224
1225 /* update the linkage of list elements with the sorted node array */
1226 for (i = 0; i < num_nodes - 1; i++)
1227 sort_array[i]->next = sort_array[i + 1];
1228 sort_array[num_nodes - 1]->next = NULL;
1229
1230 /* update the linkage of the main list anchor pointer */
1231 *node_first = sort_array[0];
1232
1233 free(sort_array);
1234
1235 /* now recursively sort the level group structures */
1236 node = *node_first;
1237 while (node)
1238 {
1239 if (node->node_group != NULL)
1240 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1241
1242 node = node->next;
1243 }
1244 }
1245
sortTreeInfo(TreeInfo ** node_first)1246 void sortTreeInfo(TreeInfo **node_first)
1247 {
1248 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1249 }
1250
1251
1252 /* ========================================================================= */
1253 /* some stuff from "files.c" */
1254 /* ========================================================================= */
1255
1256 #if defined(PLATFORM_WIN32)
1257 #ifndef S_IRGRP
1258 #define S_IRGRP S_IRUSR
1259 #endif
1260 #ifndef S_IROTH
1261 #define S_IROTH S_IRUSR
1262 #endif
1263 #ifndef S_IWGRP
1264 #define S_IWGRP S_IWUSR
1265 #endif
1266 #ifndef S_IWOTH
1267 #define S_IWOTH S_IWUSR
1268 #endif
1269 #ifndef S_IXGRP
1270 #define S_IXGRP S_IXUSR
1271 #endif
1272 #ifndef S_IXOTH
1273 #define S_IXOTH S_IXUSR
1274 #endif
1275 #ifndef S_IRWXG
1276 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1277 #endif
1278 #ifndef S_ISGID
1279 #define S_ISGID 0
1280 #endif
1281 #endif /* PLATFORM_WIN32 */
1282
1283 /* file permissions for newly written files */
1284 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1285 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1286 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1287
1288 #define MODE_W_PRIVATE (S_IWUSR)
1289 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1290 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1291
1292 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1293 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1294
1295 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1296 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1297
getHomeDir()1298 char *getHomeDir()
1299 {
1300 static char *dir = NULL;
1301
1302 #if defined(PLATFORM_WIN32)
1303 if (dir == NULL)
1304 {
1305 dir = checked_malloc(MAX_PATH + 1);
1306
1307 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1308 strcpy(dir, ".");
1309 }
1310 #elif defined(PLATFORM_UNIX)
1311 if (dir == NULL)
1312 {
1313 if ((dir = getenv("HOME")) == NULL)
1314 {
1315 struct passwd *pwd;
1316
1317 if ((pwd = getpwuid(getuid())) != NULL)
1318 dir = getStringCopy(pwd->pw_dir);
1319 else
1320 dir = ".";
1321 }
1322 }
1323 #else
1324 dir = ".";
1325 #endif
1326
1327 return dir;
1328 }
1329
getCommonDataDir(void)1330 char *getCommonDataDir(void)
1331 {
1332 static char *common_data_dir = NULL;
1333
1334 #if defined(PLATFORM_WIN32)
1335 if (common_data_dir == NULL)
1336 {
1337 char *dir = checked_malloc(MAX_PATH + 1);
1338
1339 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1340 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1341 common_data_dir = getPath2(dir, program.userdata_subdir);
1342 else
1343 common_data_dir = options.rw_base_directory;
1344 }
1345 #else
1346 if (common_data_dir == NULL)
1347 common_data_dir = options.rw_base_directory;
1348 #endif
1349
1350 return common_data_dir;
1351 }
1352
getPersonalDataDir(void)1353 char *getPersonalDataDir(void)
1354 {
1355 static char *personal_data_dir = NULL;
1356
1357 #if defined(PLATFORM_MACOSX)
1358 if (personal_data_dir == NULL)
1359 personal_data_dir = getPath2(getHomeDir(), "Documents");
1360 #else
1361 if (personal_data_dir == NULL)
1362 personal_data_dir = getHomeDir();
1363 #endif
1364
1365 return personal_data_dir;
1366 }
1367
getUserGameDataDir(void)1368 char *getUserGameDataDir(void)
1369 {
1370 static char *user_game_data_dir = NULL;
1371
1372 if (user_game_data_dir == NULL)
1373 user_game_data_dir = getPath2(getPersonalDataDir(),
1374 program.userdata_subdir);
1375
1376 return user_game_data_dir;
1377 }
1378
updateUserGameDataDir()1379 void updateUserGameDataDir()
1380 {
1381 #if defined(PLATFORM_MACOSX)
1382 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1383 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1384
1385 /* convert old Unix style game data directory to Mac OS X style, if needed */
1386 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1387 {
1388 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1389 {
1390 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1391 userdata_dir_old, userdata_dir_new);
1392
1393 /* continue using Unix style data directory -- this should not happen */
1394 program.userdata_path = getPath2(getPersonalDataDir(),
1395 program.userdata_subdir_unix);
1396 }
1397 }
1398
1399 free(userdata_dir_old);
1400 #endif
1401 }
1402
getSetupDir()1403 char *getSetupDir()
1404 {
1405 return getUserGameDataDir();
1406 }
1407
posix_umask(mode_t mask)1408 static mode_t posix_umask(mode_t mask)
1409 {
1410 #if defined(PLATFORM_UNIX)
1411 return umask(mask);
1412 #else
1413 return 0;
1414 #endif
1415 }
1416
posix_mkdir(const char * pathname,mode_t mode)1417 static int posix_mkdir(const char *pathname, mode_t mode)
1418 {
1419 #if defined(PLATFORM_WIN32)
1420 return mkdir(pathname);
1421 #else
1422 return mkdir(pathname, mode);
1423 #endif
1424 }
1425
posix_process_running_setgid()1426 static boolean posix_process_running_setgid()
1427 {
1428 #if defined(PLATFORM_UNIX)
1429 return (getgid() != getegid());
1430 #else
1431 return FALSE;
1432 #endif
1433 }
1434
createDirectory(char * dir,char * text,int permission_class)1435 void createDirectory(char *dir, char *text, int permission_class)
1436 {
1437 /* leave "other" permissions in umask untouched, but ensure group parts
1438 of USERDATA_DIR_MODE are not masked */
1439 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1440 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1441 mode_t last_umask = posix_umask(0);
1442 mode_t group_umask = ~(dir_mode & S_IRWXG);
1443 int running_setgid = posix_process_running_setgid();
1444
1445 /* if we're setgid, protect files against "other" */
1446 /* else keep umask(0) to make the dir world-writable */
1447
1448 if (running_setgid)
1449 posix_umask(last_umask & group_umask);
1450 else
1451 dir_mode |= MODE_W_ALL;
1452
1453 if (!fileExists(dir))
1454 if (posix_mkdir(dir, dir_mode) != 0)
1455 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1456
1457 if (permission_class == PERMS_PUBLIC && !running_setgid)
1458 chmod(dir, dir_mode);
1459
1460 posix_umask(last_umask); /* restore previous umask */
1461 }
1462
InitUserDataDirectory()1463 void InitUserDataDirectory()
1464 {
1465 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1466 }
1467
SetFilePermissions(char * filename,int permission_class)1468 void SetFilePermissions(char *filename, int permission_class)
1469 {
1470 int running_setgid = posix_process_running_setgid();
1471 int perms = (permission_class == PERMS_PRIVATE ?
1472 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1473
1474 if (permission_class == PERMS_PUBLIC && !running_setgid)
1475 perms |= MODE_W_ALL;
1476
1477 chmod(filename, perms);
1478 }
1479
getCookie(char * file_type)1480 char *getCookie(char *file_type)
1481 {
1482 static char cookie[MAX_COOKIE_LEN + 1];
1483
1484 if (strlen(program.cookie_prefix) + 1 +
1485 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1486 return "[COOKIE ERROR]"; /* should never happen */
1487
1488 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1489 program.cookie_prefix, file_type,
1490 program.version_major, program.version_minor);
1491
1492 return cookie;
1493 }
1494
getFileVersionFromCookieString(const char * cookie)1495 int getFileVersionFromCookieString(const char *cookie)
1496 {
1497 const char *ptr_cookie1, *ptr_cookie2;
1498 const char *pattern1 = "_FILE_VERSION_";
1499 const char *pattern2 = "?.?";
1500 const int len_cookie = strlen(cookie);
1501 const int len_pattern1 = strlen(pattern1);
1502 const int len_pattern2 = strlen(pattern2);
1503 const int len_pattern = len_pattern1 + len_pattern2;
1504 int version_major, version_minor;
1505
1506 if (len_cookie <= len_pattern)
1507 return -1;
1508
1509 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1510 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1511
1512 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1513 return -1;
1514
1515 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1516 ptr_cookie2[1] != '.' ||
1517 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1518 return -1;
1519
1520 version_major = ptr_cookie2[0] - '0';
1521 version_minor = ptr_cookie2[2] - '0';
1522
1523 return VERSION_IDENT(version_major, version_minor, 0, 0);
1524 }
1525
checkCookieString(const char * cookie,const char * template)1526 boolean checkCookieString(const char *cookie, const char *template)
1527 {
1528 const char *pattern = "_FILE_VERSION_?.?";
1529 const int len_cookie = strlen(cookie);
1530 const int len_template = strlen(template);
1531 const int len_pattern = strlen(pattern);
1532
1533 if (len_cookie != len_template)
1534 return FALSE;
1535
1536 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1537 return FALSE;
1538
1539 return TRUE;
1540 }
1541
1542 /* ------------------------------------------------------------------------- */
1543 /* setup file list and hash handling functions */
1544 /* ------------------------------------------------------------------------- */
1545
getFormattedSetupEntry(char * token,char * value)1546 char *getFormattedSetupEntry(char *token, char *value)
1547 {
1548 int i;
1549 static char entry[MAX_LINE_LEN];
1550
1551 /* if value is an empty string, just return token without value */
1552 if (*value == '\0')
1553 return token;
1554
1555 /* start with the token and some spaces to format output line */
1556 sprintf(entry, "%s:", token);
1557 for (i = strlen(entry); i < token_value_position; i++)
1558 strcat(entry, " ");
1559
1560 /* continue with the token's value */
1561 strcat(entry, value);
1562
1563 return entry;
1564 }
1565
newSetupFileList(char * token,char * value)1566 SetupFileList *newSetupFileList(char *token, char *value)
1567 {
1568 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1569
1570 new->token = getStringCopy(token);
1571 new->value = getStringCopy(value);
1572
1573 new->next = NULL;
1574
1575 return new;
1576 }
1577
freeSetupFileList(SetupFileList * list)1578 void freeSetupFileList(SetupFileList *list)
1579 {
1580 if (list == NULL)
1581 return;
1582
1583 checked_free(list->token);
1584 checked_free(list->value);
1585
1586 if (list->next)
1587 freeSetupFileList(list->next);
1588
1589 free(list);
1590 }
1591
getListEntry(SetupFileList * list,char * token)1592 char *getListEntry(SetupFileList *list, char *token)
1593 {
1594 if (list == NULL)
1595 return NULL;
1596
1597 if (strEqual(list->token, token))
1598 return list->value;
1599 else
1600 return getListEntry(list->next, token);
1601 }
1602
setListEntry(SetupFileList * list,char * token,char * value)1603 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1604 {
1605 if (list == NULL)
1606 return NULL;
1607
1608 if (strEqual(list->token, token))
1609 {
1610 checked_free(list->value);
1611
1612 list->value = getStringCopy(value);
1613
1614 return list;
1615 }
1616 else if (list->next == NULL)
1617 return (list->next = newSetupFileList(token, value));
1618 else
1619 return setListEntry(list->next, token, value);
1620 }
1621
addListEntry(SetupFileList * list,char * token,char * value)1622 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1623 {
1624 if (list == NULL)
1625 return NULL;
1626
1627 if (list->next == NULL)
1628 return (list->next = newSetupFileList(token, value));
1629 else
1630 return addListEntry(list->next, token, value);
1631 }
1632
1633 #ifdef DEBUG
printSetupFileList(SetupFileList * list)1634 static void printSetupFileList(SetupFileList *list)
1635 {
1636 if (!list)
1637 return;
1638
1639 printf("token: '%s'\n", list->token);
1640 printf("value: '%s'\n", list->value);
1641
1642 printSetupFileList(list->next);
1643 }
1644 #endif
1645
1646 #ifdef DEBUG
1647 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1648 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1649 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1650 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1651 #else
1652 #define insert_hash_entry hashtable_insert
1653 #define search_hash_entry hashtable_search
1654 #define change_hash_entry hashtable_change
1655 #define remove_hash_entry hashtable_remove
1656 #endif
1657
get_hash_from_key(void * key)1658 unsigned int get_hash_from_key(void *key)
1659 {
1660 /*
1661 djb2
1662
1663 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1664 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1665 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1666 it works better than many other constants, prime or not) has never been
1667 adequately explained.
1668
1669 If you just want to have a good hash function, and cannot wait, djb2
1670 is one of the best string hash functions i know. It has excellent
1671 distribution and speed on many different sets of keys and table sizes.
1672 You are not likely to do better with one of the "well known" functions
1673 such as PJW, K&R, etc.
1674
1675 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1676 */
1677
1678 char *str = (char *)key;
1679 unsigned int hash = 5381;
1680 int c;
1681
1682 while ((c = *str++))
1683 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1684
1685 return hash;
1686 }
1687
keys_are_equal(void * key1,void * key2)1688 static int keys_are_equal(void *key1, void *key2)
1689 {
1690 return (strEqual((char *)key1, (char *)key2));
1691 }
1692
newSetupFileHash()1693 SetupFileHash *newSetupFileHash()
1694 {
1695 SetupFileHash *new_hash =
1696 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1697
1698 if (new_hash == NULL)
1699 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1700
1701 return new_hash;
1702 }
1703
freeSetupFileHash(SetupFileHash * hash)1704 void freeSetupFileHash(SetupFileHash *hash)
1705 {
1706 if (hash == NULL)
1707 return;
1708
1709 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1710 }
1711
getHashEntry(SetupFileHash * hash,char * token)1712 char *getHashEntry(SetupFileHash *hash, char *token)
1713 {
1714 if (hash == NULL)
1715 return NULL;
1716
1717 return search_hash_entry(hash, token);
1718 }
1719
setHashEntry(SetupFileHash * hash,char * token,char * value)1720 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1721 {
1722 char *value_copy;
1723
1724 if (hash == NULL)
1725 return;
1726
1727 value_copy = getStringCopy(value);
1728
1729 /* change value; if it does not exist, insert it as new */
1730 if (!change_hash_entry(hash, token, value_copy))
1731 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1732 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1733 }
1734
removeHashEntry(SetupFileHash * hash,char * token)1735 char *removeHashEntry(SetupFileHash *hash, char *token)
1736 {
1737 if (hash == NULL)
1738 return NULL;
1739
1740 return remove_hash_entry(hash, token);
1741 }
1742
1743 #if 0
1744 static void printSetupFileHash(SetupFileHash *hash)
1745 {
1746 BEGIN_HASH_ITERATION(hash, itr)
1747 {
1748 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1749 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1750 }
1751 END_HASH_ITERATION(hash, itr)
1752 }
1753 #endif
1754
1755 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1756 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1757 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1758
1759 static boolean token_value_separator_found = FALSE;
1760 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1761 static boolean token_value_separator_warning = FALSE;
1762 #endif
1763 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1764 static boolean token_already_exists_warning = FALSE;
1765 #endif
1766
getTokenValueFromSetupLineExt(char * line,char ** token_ptr,char ** value_ptr,char * filename,char * line_raw,int line_nr,boolean separator_required)1767 static boolean getTokenValueFromSetupLineExt(char *line,
1768 char **token_ptr, char **value_ptr,
1769 char *filename, char *line_raw,
1770 int line_nr,
1771 boolean separator_required)
1772 {
1773 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1774 char *token, *value, *line_ptr;
1775
1776 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1777 if (line_raw == NULL)
1778 {
1779 strncpy(line_copy, line, MAX_LINE_LEN);
1780 line_copy[MAX_LINE_LEN] = '\0';
1781 line = line_copy;
1782
1783 strcpy(line_raw_copy, line_copy);
1784 line_raw = line_raw_copy;
1785 }
1786
1787 /* cut trailing comment from input line */
1788 for (line_ptr = line; *line_ptr; line_ptr++)
1789 {
1790 if (*line_ptr == '#')
1791 {
1792 *line_ptr = '\0';
1793 break;
1794 }
1795 }
1796
1797 /* cut trailing whitespaces from input line */
1798 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1799 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1800 *line_ptr = '\0';
1801
1802 /* ignore empty lines */
1803 if (*line == '\0')
1804 return FALSE;
1805
1806 /* cut leading whitespaces from token */
1807 for (token = line; *token; token++)
1808 if (*token != ' ' && *token != '\t')
1809 break;
1810
1811 /* start with empty value as reliable default */
1812 value = "";
1813
1814 token_value_separator_found = FALSE;
1815
1816 /* find end of token to determine start of value */
1817 for (line_ptr = token; *line_ptr; line_ptr++)
1818 {
1819 #if 1
1820 /* first look for an explicit token/value separator, like ':' or '=' */
1821 if (*line_ptr == ':' || *line_ptr == '=')
1822 #else
1823 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1824 #endif
1825 {
1826 *line_ptr = '\0'; /* terminate token string */
1827 value = line_ptr + 1; /* set beginning of value */
1828
1829 token_value_separator_found = TRUE;
1830
1831 break;
1832 }
1833 }
1834
1835 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1836 /* fallback: if no token/value separator found, also allow whitespaces */
1837 if (!token_value_separator_found && !separator_required)
1838 {
1839 for (line_ptr = token; *line_ptr; line_ptr++)
1840 {
1841 if (*line_ptr == ' ' || *line_ptr == '\t')
1842 {
1843 *line_ptr = '\0'; /* terminate token string */
1844 value = line_ptr + 1; /* set beginning of value */
1845
1846 token_value_separator_found = TRUE;
1847
1848 break;
1849 }
1850 }
1851
1852 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1853 if (token_value_separator_found)
1854 {
1855 if (!token_value_separator_warning)
1856 {
1857 Error(ERR_INFO_LINE, "-");
1858
1859 if (filename != NULL)
1860 {
1861 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1862 Error(ERR_INFO, "- config file: '%s'", filename);
1863 }
1864 else
1865 {
1866 Error(ERR_WARN, "missing token/value separator(s):");
1867 }
1868
1869 token_value_separator_warning = TRUE;
1870 }
1871
1872 if (filename != NULL)
1873 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1874 else
1875 Error(ERR_INFO, "- line: '%s'", line_raw);
1876 }
1877 #endif
1878 }
1879 #endif
1880
1881 /* cut trailing whitespaces from token */
1882 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1883 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1884 *line_ptr = '\0';
1885
1886 /* cut leading whitespaces from value */
1887 for (; *value; value++)
1888 if (*value != ' ' && *value != '\t')
1889 break;
1890
1891 #if 0
1892 if (*value == '\0')
1893 value = "true"; /* treat tokens without value as "true" */
1894 #endif
1895
1896 *token_ptr = token;
1897 *value_ptr = value;
1898
1899 return TRUE;
1900 }
1901
getTokenValueFromSetupLine(char * line,char ** token,char ** value)1902 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1903 {
1904 /* while the internal (old) interface does not require a token/value
1905 separator (for downwards compatibility with existing files which
1906 don't use them), it is mandatory for the external (new) interface */
1907
1908 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1909 }
1910
1911 #if 1
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)1912 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1913 boolean top_recursion_level, boolean is_hash)
1914 {
1915 static SetupFileHash *include_filename_hash = NULL;
1916 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1917 char *token, *value, *line_ptr;
1918 void *insert_ptr = NULL;
1919 boolean read_continued_line = FALSE;
1920 FILE *file;
1921 int line_nr = 0, token_count = 0, include_count = 0;
1922
1923 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1924 token_value_separator_warning = FALSE;
1925 #endif
1926
1927 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1928 token_already_exists_warning = FALSE;
1929 #endif
1930
1931 if (!(file = fopen(filename, MODE_READ)))
1932 {
1933 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1934
1935 return FALSE;
1936 }
1937
1938 /* use "insert pointer" to store list end for constant insertion complexity */
1939 if (!is_hash)
1940 insert_ptr = setup_file_data;
1941
1942 /* on top invocation, create hash to mark included files (to prevent loops) */
1943 if (top_recursion_level)
1944 include_filename_hash = newSetupFileHash();
1945
1946 /* mark this file as already included (to prevent including it again) */
1947 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1948
1949 while (!feof(file))
1950 {
1951 /* read next line of input file */
1952 if (!fgets(line, MAX_LINE_LEN, file))
1953 break;
1954
1955 /* check if line was completely read and is terminated by line break */
1956 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1957 line_nr++;
1958
1959 /* cut trailing line break (this can be newline and/or carriage return) */
1960 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1961 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1962 *line_ptr = '\0';
1963
1964 /* copy raw input line for later use (mainly debugging output) */
1965 strcpy(line_raw, line);
1966
1967 if (read_continued_line)
1968 {
1969 #if 0
1970 /* !!! ??? WHY ??? !!! */
1971 /* cut leading whitespaces from input line */
1972 for (line_ptr = line; *line_ptr; line_ptr++)
1973 if (*line_ptr != ' ' && *line_ptr != '\t')
1974 break;
1975 #endif
1976
1977 /* append new line to existing line, if there is enough space */
1978 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1979 strcat(previous_line, line_ptr);
1980
1981 strcpy(line, previous_line); /* copy storage buffer to line */
1982
1983 read_continued_line = FALSE;
1984 }
1985
1986 /* if the last character is '\', continue at next line */
1987 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1988 {
1989 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1990 strcpy(previous_line, line); /* copy line to storage buffer */
1991
1992 read_continued_line = TRUE;
1993
1994 continue;
1995 }
1996
1997 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1998 line_raw, line_nr, FALSE))
1999 continue;
2000
2001 if (*token)
2002 {
2003 if (strEqual(token, "include"))
2004 {
2005 if (getHashEntry(include_filename_hash, value) == NULL)
2006 {
2007 char *basepath = getBasePath(filename);
2008 char *basename = getBaseName(value);
2009 char *filename_include = getPath2(basepath, basename);
2010
2011 #if 0
2012 Error(ERR_INFO, "[including file '%s']", filename_include);
2013 #endif
2014
2015 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2016
2017 free(basepath);
2018 free(basename);
2019 free(filename_include);
2020
2021 include_count++;
2022 }
2023 else
2024 {
2025 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2026 }
2027 }
2028 else
2029 {
2030 if (is_hash)
2031 {
2032 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2033 char *old_value =
2034 getHashEntry((SetupFileHash *)setup_file_data, token);
2035
2036 if (old_value != NULL)
2037 {
2038 if (!token_already_exists_warning)
2039 {
2040 Error(ERR_INFO_LINE, "-");
2041 Error(ERR_WARN, "duplicate token(s) found in config file:");
2042 Error(ERR_INFO, "- config file: '%s'", filename);
2043
2044 token_already_exists_warning = TRUE;
2045 }
2046
2047 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2048 Error(ERR_INFO, " old value: '%s'", old_value);
2049 Error(ERR_INFO, " new value: '%s'", value);
2050 }
2051 #endif
2052
2053 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2054 }
2055 else
2056 {
2057 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2058 }
2059
2060 token_count++;
2061 }
2062 }
2063 }
2064
2065 fclose(file);
2066
2067 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2068 if (token_value_separator_warning)
2069 Error(ERR_INFO_LINE, "-");
2070 #endif
2071
2072 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2073 if (token_already_exists_warning)
2074 Error(ERR_INFO_LINE, "-");
2075 #endif
2076
2077 if (token_count == 0 && include_count == 0)
2078 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2079
2080 if (top_recursion_level)
2081 freeSetupFileHash(include_filename_hash);
2082
2083 return TRUE;
2084 }
2085
2086 #else
2087
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)2088 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2089 boolean top_recursion_level, boolean is_hash)
2090 {
2091 static SetupFileHash *include_filename_hash = NULL;
2092 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2093 char *token, *value, *line_ptr;
2094 void *insert_ptr = NULL;
2095 boolean read_continued_line = FALSE;
2096 FILE *file;
2097 int line_nr = 0;
2098 int token_count = 0;
2099
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101 token_value_separator_warning = FALSE;
2102 #endif
2103
2104 if (!(file = fopen(filename, MODE_READ)))
2105 {
2106 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2107
2108 return FALSE;
2109 }
2110
2111 /* use "insert pointer" to store list end for constant insertion complexity */
2112 if (!is_hash)
2113 insert_ptr = setup_file_data;
2114
2115 /* on top invocation, create hash to mark included files (to prevent loops) */
2116 if (top_recursion_level)
2117 include_filename_hash = newSetupFileHash();
2118
2119 /* mark this file as already included (to prevent including it again) */
2120 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2121
2122 while (!feof(file))
2123 {
2124 /* read next line of input file */
2125 if (!fgets(line, MAX_LINE_LEN, file))
2126 break;
2127
2128 /* check if line was completely read and is terminated by line break */
2129 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2130 line_nr++;
2131
2132 /* cut trailing line break (this can be newline and/or carriage return) */
2133 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2134 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2135 *line_ptr = '\0';
2136
2137 /* copy raw input line for later use (mainly debugging output) */
2138 strcpy(line_raw, line);
2139
2140 if (read_continued_line)
2141 {
2142 /* cut leading whitespaces from input line */
2143 for (line_ptr = line; *line_ptr; line_ptr++)
2144 if (*line_ptr != ' ' && *line_ptr != '\t')
2145 break;
2146
2147 /* append new line to existing line, if there is enough space */
2148 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2149 strcat(previous_line, line_ptr);
2150
2151 strcpy(line, previous_line); /* copy storage buffer to line */
2152
2153 read_continued_line = FALSE;
2154 }
2155
2156 /* if the last character is '\', continue at next line */
2157 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2158 {
2159 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2160 strcpy(previous_line, line); /* copy line to storage buffer */
2161
2162 read_continued_line = TRUE;
2163
2164 continue;
2165 }
2166
2167 /* cut trailing comment from input line */
2168 for (line_ptr = line; *line_ptr; line_ptr++)
2169 {
2170 if (*line_ptr == '#')
2171 {
2172 *line_ptr = '\0';
2173 break;
2174 }
2175 }
2176
2177 /* cut trailing whitespaces from input line */
2178 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2179 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2180 *line_ptr = '\0';
2181
2182 /* ignore empty lines */
2183 if (*line == '\0')
2184 continue;
2185
2186 /* cut leading whitespaces from token */
2187 for (token = line; *token; token++)
2188 if (*token != ' ' && *token != '\t')
2189 break;
2190
2191 /* start with empty value as reliable default */
2192 value = "";
2193
2194 token_value_separator_found = FALSE;
2195
2196 /* find end of token to determine start of value */
2197 for (line_ptr = token; *line_ptr; line_ptr++)
2198 {
2199 #if 1
2200 /* first look for an explicit token/value separator, like ':' or '=' */
2201 if (*line_ptr == ':' || *line_ptr == '=')
2202 #else
2203 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2204 #endif
2205 {
2206 *line_ptr = '\0'; /* terminate token string */
2207 value = line_ptr + 1; /* set beginning of value */
2208
2209 token_value_separator_found = TRUE;
2210
2211 break;
2212 }
2213 }
2214
2215 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2216 /* fallback: if no token/value separator found, also allow whitespaces */
2217 if (!token_value_separator_found)
2218 {
2219 for (line_ptr = token; *line_ptr; line_ptr++)
2220 {
2221 if (*line_ptr == ' ' || *line_ptr == '\t')
2222 {
2223 *line_ptr = '\0'; /* terminate token string */
2224 value = line_ptr + 1; /* set beginning of value */
2225
2226 token_value_separator_found = TRUE;
2227
2228 break;
2229 }
2230 }
2231
2232 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2233 if (token_value_separator_found)
2234 {
2235 if (!token_value_separator_warning)
2236 {
2237 Error(ERR_INFO_LINE, "-");
2238 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2239 Error(ERR_INFO, "- config file: '%s'", filename);
2240
2241 token_value_separator_warning = TRUE;
2242 }
2243
2244 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2245 }
2246 #endif
2247 }
2248 #endif
2249
2250 /* cut trailing whitespaces from token */
2251 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2252 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2253 *line_ptr = '\0';
2254
2255 /* cut leading whitespaces from value */
2256 for (; *value; value++)
2257 if (*value != ' ' && *value != '\t')
2258 break;
2259
2260 #if 0
2261 if (*value == '\0')
2262 value = "true"; /* treat tokens without value as "true" */
2263 #endif
2264
2265 if (*token)
2266 {
2267 if (strEqual(token, "include"))
2268 {
2269 if (getHashEntry(include_filename_hash, value) == NULL)
2270 {
2271 char *basepath = getBasePath(filename);
2272 char *basename = getBaseName(value);
2273 char *filename_include = getPath2(basepath, basename);
2274
2275 #if 0
2276 Error(ERR_INFO, "[including file '%s']", filename_include);
2277 #endif
2278
2279 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2280
2281 free(basepath);
2282 free(basename);
2283 free(filename_include);
2284 }
2285 else
2286 {
2287 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2288 }
2289 }
2290 else
2291 {
2292 if (is_hash)
2293 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2294 else
2295 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2296
2297 token_count++;
2298 }
2299 }
2300 }
2301
2302 fclose(file);
2303
2304 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2305 if (token_value_separator_warning)
2306 Error(ERR_INFO_LINE, "-");
2307 #endif
2308
2309 if (token_count == 0)
2310 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2311
2312 if (top_recursion_level)
2313 freeSetupFileHash(include_filename_hash);
2314
2315 return TRUE;
2316 }
2317 #endif
2318
saveSetupFileHash(SetupFileHash * hash,char * filename)2319 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2320 {
2321 FILE *file;
2322
2323 if (!(file = fopen(filename, MODE_WRITE)))
2324 {
2325 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2326
2327 return;
2328 }
2329
2330 BEGIN_HASH_ITERATION(hash, itr)
2331 {
2332 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2333 HASH_ITERATION_VALUE(itr)));
2334 }
2335 END_HASH_ITERATION(hash, itr)
2336
2337 fclose(file);
2338 }
2339
loadSetupFileList(char * filename)2340 SetupFileList *loadSetupFileList(char *filename)
2341 {
2342 SetupFileList *setup_file_list = newSetupFileList("", "");
2343 SetupFileList *first_valid_list_entry;
2344
2345 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2346 {
2347 freeSetupFileList(setup_file_list);
2348
2349 return NULL;
2350 }
2351
2352 first_valid_list_entry = setup_file_list->next;
2353
2354 /* free empty list header */
2355 setup_file_list->next = NULL;
2356 freeSetupFileList(setup_file_list);
2357
2358 return first_valid_list_entry;
2359 }
2360
loadSetupFileHash(char * filename)2361 SetupFileHash *loadSetupFileHash(char *filename)
2362 {
2363 SetupFileHash *setup_file_hash = newSetupFileHash();
2364
2365 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2366 {
2367 freeSetupFileHash(setup_file_hash);
2368
2369 return NULL;
2370 }
2371
2372 return setup_file_hash;
2373 }
2374
checkSetupFileHashIdentifier(SetupFileHash * setup_file_hash,char * filename,char * identifier)2375 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2376 char *filename, char *identifier)
2377 {
2378 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2379
2380 if (value == NULL)
2381 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2382 else if (!checkCookieString(value, identifier))
2383 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2384 }
2385
2386
2387 /* ========================================================================= */
2388 /* setup file stuff */
2389 /* ========================================================================= */
2390
2391 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2392 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2393 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2394
2395 /* level directory info */
2396 #define LEVELINFO_TOKEN_IDENTIFIER 0
2397 #define LEVELINFO_TOKEN_NAME 1
2398 #define LEVELINFO_TOKEN_NAME_SORTING 2
2399 #define LEVELINFO_TOKEN_AUTHOR 3
2400 #define LEVELINFO_TOKEN_YEAR 4
2401 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2402 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2403 #define LEVELINFO_TOKEN_TESTED_BY 7
2404 #define LEVELINFO_TOKEN_LEVELS 8
2405 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2406 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2407 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2408 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2409 #define LEVELINFO_TOKEN_READONLY 13
2410 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2411 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2413 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2414 #define LEVELINFO_TOKEN_MUSIC_SET 18
2415 #define LEVELINFO_TOKEN_FILENAME 19
2416 #define LEVELINFO_TOKEN_FILETYPE 20
2417 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2418 #define LEVELINFO_TOKEN_HANDICAP 22
2419 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2420
2421 #define NUM_LEVELINFO_TOKENS 24
2422
2423 static LevelDirTree ldi;
2424
2425 static struct TokenInfo levelinfo_tokens[] =
2426 {
2427 /* level directory info */
2428 { TYPE_STRING, &ldi.identifier, "identifier" },
2429 { TYPE_STRING, &ldi.name, "name" },
2430 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2431 { TYPE_STRING, &ldi.author, "author" },
2432 { TYPE_STRING, &ldi.year, "year" },
2433 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2434 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2435 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2436 { TYPE_INTEGER, &ldi.levels, "levels" },
2437 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2438 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2439 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2440 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2441 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2442 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2443 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2444 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2445 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2446 { TYPE_STRING, &ldi.music_set, "music_set" },
2447 { TYPE_STRING, &ldi.level_filename, "filename" },
2448 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2449 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2450 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2451 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2452 };
2453
2454 static struct TokenInfo artworkinfo_tokens[] =
2455 {
2456 /* artwork directory info */
2457 { TYPE_STRING, &ldi.identifier, "identifier" },
2458 { TYPE_STRING, &ldi.subdir, "subdir" },
2459 { TYPE_STRING, &ldi.name, "name" },
2460 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2461 { TYPE_STRING, &ldi.author, "author" },
2462 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2463 { TYPE_STRING, &ldi.basepath, "basepath" },
2464 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2465 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2466 { TYPE_INTEGER, &ldi.color, "color" },
2467 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2468
2469 { -1, NULL, NULL },
2470 };
2471
setTreeInfoToDefaults(TreeInfo * ti,int type)2472 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2473 {
2474 ti->type = type;
2475
2476 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2477 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2478 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2479 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2480 NULL);
2481
2482 ti->node_parent = NULL;
2483 ti->node_group = NULL;
2484 ti->next = NULL;
2485
2486 ti->cl_first = -1;
2487 ti->cl_cursor = -1;
2488
2489 ti->subdir = NULL;
2490 ti->fullpath = NULL;
2491 ti->basepath = NULL;
2492 ti->identifier = NULL;
2493 ti->name = getStringCopy(ANONYMOUS_NAME);
2494 ti->name_sorting = NULL;
2495 ti->author = getStringCopy(ANONYMOUS_NAME);
2496 ti->year = NULL;
2497
2498 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2499 ti->latest_engine = FALSE; /* default: get from level */
2500 ti->parent_link = FALSE;
2501 ti->in_user_dir = FALSE;
2502 ti->user_defined = FALSE;
2503 ti->color = 0;
2504 ti->class_desc = NULL;
2505
2506 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2507
2508 if (ti->type == TREE_TYPE_LEVEL_DIR)
2509 {
2510 ti->imported_from = NULL;
2511 ti->imported_by = NULL;
2512 ti->tested_by = NULL;
2513
2514 ti->graphics_set_ecs = NULL;
2515 ti->graphics_set_aga = NULL;
2516 ti->graphics_set = NULL;
2517 ti->sounds_set = NULL;
2518 ti->music_set = NULL;
2519 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2520 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2521 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2522
2523 ti->level_filename = NULL;
2524 ti->level_filetype = NULL;
2525
2526 ti->special_flags = NULL;
2527
2528 ti->levels = 0;
2529 ti->first_level = 0;
2530 ti->last_level = 0;
2531 ti->level_group = FALSE;
2532 ti->handicap_level = 0;
2533 ti->readonly = TRUE;
2534 ti->handicap = TRUE;
2535 ti->skip_levels = FALSE;
2536 }
2537 }
2538
setTreeInfoToDefaultsFromParent(TreeInfo * ti,TreeInfo * parent)2539 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2540 {
2541 if (parent == NULL)
2542 {
2543 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2544
2545 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2546
2547 return;
2548 }
2549
2550 /* copy all values from the parent structure */
2551
2552 ti->type = parent->type;
2553
2554 ti->node_top = parent->node_top;
2555 ti->node_parent = parent;
2556 ti->node_group = NULL;
2557 ti->next = NULL;
2558
2559 ti->cl_first = -1;
2560 ti->cl_cursor = -1;
2561
2562 ti->subdir = NULL;
2563 ti->fullpath = NULL;
2564 ti->basepath = NULL;
2565 ti->identifier = NULL;
2566 ti->name = getStringCopy(ANONYMOUS_NAME);
2567 ti->name_sorting = NULL;
2568 ti->author = getStringCopy(parent->author);
2569 ti->year = getStringCopy(parent->year);
2570
2571 ti->sort_priority = parent->sort_priority;
2572 ti->latest_engine = parent->latest_engine;
2573 ti->parent_link = FALSE;
2574 ti->in_user_dir = parent->in_user_dir;
2575 ti->user_defined = parent->user_defined;
2576 ti->color = parent->color;
2577 ti->class_desc = getStringCopy(parent->class_desc);
2578
2579 ti->infotext = getStringCopy(parent->infotext);
2580
2581 if (ti->type == TREE_TYPE_LEVEL_DIR)
2582 {
2583 ti->imported_from = getStringCopy(parent->imported_from);
2584 ti->imported_by = getStringCopy(parent->imported_by);
2585 ti->tested_by = getStringCopy(parent->tested_by);
2586
2587 ti->graphics_set_ecs = NULL;
2588 ti->graphics_set_aga = NULL;
2589 ti->graphics_set = NULL;
2590 ti->sounds_set = NULL;
2591 ti->music_set = NULL;
2592 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2593 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2594 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2595
2596 ti->level_filename = NULL;
2597 ti->level_filetype = NULL;
2598
2599 ti->special_flags = getStringCopy(parent->special_flags);
2600
2601 ti->levels = 0;
2602 ti->first_level = 0;
2603 ti->last_level = 0;
2604 ti->level_group = FALSE;
2605 ti->handicap_level = 0;
2606 #if 1
2607 ti->readonly = parent->readonly;
2608 #else
2609 ti->readonly = TRUE;
2610 #endif
2611 ti->handicap = TRUE;
2612 ti->skip_levels = FALSE;
2613 }
2614 }
2615
getTreeInfoCopy(TreeInfo * ti)2616 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2617 {
2618 TreeInfo *ti_copy = newTreeInfo();
2619
2620 /* copy all values from the original structure */
2621
2622 ti_copy->type = ti->type;
2623
2624 ti_copy->node_top = ti->node_top;
2625 ti_copy->node_parent = ti->node_parent;
2626 ti_copy->node_group = ti->node_group;
2627 ti_copy->next = ti->next;
2628
2629 ti_copy->cl_first = ti->cl_first;
2630 ti_copy->cl_cursor = ti->cl_cursor;
2631
2632 ti_copy->subdir = getStringCopy(ti->subdir);
2633 ti_copy->fullpath = getStringCopy(ti->fullpath);
2634 ti_copy->basepath = getStringCopy(ti->basepath);
2635 ti_copy->identifier = getStringCopy(ti->identifier);
2636 ti_copy->name = getStringCopy(ti->name);
2637 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2638 ti_copy->author = getStringCopy(ti->author);
2639 ti_copy->year = getStringCopy(ti->year);
2640 ti_copy->imported_from = getStringCopy(ti->imported_from);
2641 ti_copy->imported_by = getStringCopy(ti->imported_by);
2642 ti_copy->tested_by = getStringCopy(ti->tested_by);
2643
2644 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2645 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2646 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2647 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2648 ti_copy->music_set = getStringCopy(ti->music_set);
2649 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2650 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2651 ti_copy->music_path = getStringCopy(ti->music_path);
2652
2653 ti_copy->level_filename = getStringCopy(ti->level_filename);
2654 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2655
2656 ti_copy->special_flags = getStringCopy(ti->special_flags);
2657
2658 ti_copy->levels = ti->levels;
2659 ti_copy->first_level = ti->first_level;
2660 ti_copy->last_level = ti->last_level;
2661 ti_copy->sort_priority = ti->sort_priority;
2662
2663 ti_copy->latest_engine = ti->latest_engine;
2664
2665 ti_copy->level_group = ti->level_group;
2666 ti_copy->parent_link = ti->parent_link;
2667 ti_copy->in_user_dir = ti->in_user_dir;
2668 ti_copy->user_defined = ti->user_defined;
2669 ti_copy->readonly = ti->readonly;
2670 ti_copy->handicap = ti->handicap;
2671 ti_copy->skip_levels = ti->skip_levels;
2672
2673 ti_copy->color = ti->color;
2674 ti_copy->class_desc = getStringCopy(ti->class_desc);
2675 ti_copy->handicap_level = ti->handicap_level;
2676
2677 ti_copy->infotext = getStringCopy(ti->infotext);
2678
2679 return ti_copy;
2680 }
2681
freeTreeInfo(TreeInfo * ti)2682 static void freeTreeInfo(TreeInfo *ti)
2683 {
2684 if (ti == NULL)
2685 return;
2686
2687 checked_free(ti->subdir);
2688 checked_free(ti->fullpath);
2689 checked_free(ti->basepath);
2690 checked_free(ti->identifier);
2691
2692 checked_free(ti->name);
2693 checked_free(ti->name_sorting);
2694 checked_free(ti->author);
2695 checked_free(ti->year);
2696
2697 checked_free(ti->class_desc);
2698
2699 checked_free(ti->infotext);
2700
2701 if (ti->type == TREE_TYPE_LEVEL_DIR)
2702 {
2703 checked_free(ti->imported_from);
2704 checked_free(ti->imported_by);
2705 checked_free(ti->tested_by);
2706
2707 checked_free(ti->graphics_set_ecs);
2708 checked_free(ti->graphics_set_aga);
2709 checked_free(ti->graphics_set);
2710 checked_free(ti->sounds_set);
2711 checked_free(ti->music_set);
2712
2713 checked_free(ti->graphics_path);
2714 checked_free(ti->sounds_path);
2715 checked_free(ti->music_path);
2716
2717 checked_free(ti->level_filename);
2718 checked_free(ti->level_filetype);
2719
2720 checked_free(ti->special_flags);
2721 }
2722
2723 checked_free(ti);
2724 }
2725
setSetupInfo(struct TokenInfo * token_info,int token_nr,char * token_value)2726 void setSetupInfo(struct TokenInfo *token_info,
2727 int token_nr, char *token_value)
2728 {
2729 int token_type = token_info[token_nr].type;
2730 void *setup_value = token_info[token_nr].value;
2731
2732 if (token_value == NULL)
2733 return;
2734
2735 /* set setup field to corresponding token value */
2736 switch (token_type)
2737 {
2738 case TYPE_BOOLEAN:
2739 case TYPE_SWITCH:
2740 *(boolean *)setup_value = get_boolean_from_string(token_value);
2741 break;
2742
2743 case TYPE_SWITCH3:
2744 *(int *)setup_value = get_switch3_from_string(token_value);
2745 break;
2746
2747 case TYPE_KEY:
2748 *(Key *)setup_value = getKeyFromKeyName(token_value);
2749 break;
2750
2751 case TYPE_KEY_X11:
2752 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2753 break;
2754
2755 case TYPE_INTEGER:
2756 *(int *)setup_value = get_integer_from_string(token_value);
2757 break;
2758
2759 case TYPE_STRING:
2760 checked_free(*(char **)setup_value);
2761 *(char **)setup_value = getStringCopy(token_value);
2762 break;
2763
2764 default:
2765 break;
2766 }
2767 }
2768
compareTreeInfoEntries(const void * object1,const void * object2)2769 static int compareTreeInfoEntries(const void *object1, const void *object2)
2770 {
2771 const TreeInfo *entry1 = *((TreeInfo **)object1);
2772 const TreeInfo *entry2 = *((TreeInfo **)object2);
2773 int class_sorting1, class_sorting2;
2774 int compare_result;
2775
2776 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2777 {
2778 class_sorting1 = LEVELSORTING(entry1);
2779 class_sorting2 = LEVELSORTING(entry2);
2780 }
2781 else
2782 {
2783 class_sorting1 = ARTWORKSORTING(entry1);
2784 class_sorting2 = ARTWORKSORTING(entry2);
2785 }
2786
2787 if (entry1->parent_link || entry2->parent_link)
2788 compare_result = (entry1->parent_link ? -1 : +1);
2789 else if (entry1->sort_priority == entry2->sort_priority)
2790 {
2791 char *name1 = getStringToLower(entry1->name_sorting);
2792 char *name2 = getStringToLower(entry2->name_sorting);
2793
2794 compare_result = strcmp(name1, name2);
2795
2796 free(name1);
2797 free(name2);
2798 }
2799 else if (class_sorting1 == class_sorting2)
2800 compare_result = entry1->sort_priority - entry2->sort_priority;
2801 else
2802 compare_result = class_sorting1 - class_sorting2;
2803
2804 return compare_result;
2805 }
2806
createParentTreeInfoNode(TreeInfo * node_parent)2807 static void createParentTreeInfoNode(TreeInfo *node_parent)
2808 {
2809 TreeInfo *ti_new;
2810
2811 if (node_parent == NULL)
2812 return;
2813
2814 ti_new = newTreeInfo();
2815 setTreeInfoToDefaults(ti_new, node_parent->type);
2816
2817 ti_new->node_parent = node_parent;
2818 ti_new->parent_link = TRUE;
2819
2820 setString(&ti_new->identifier, node_parent->identifier);
2821 setString(&ti_new->name, ".. (parent directory)");
2822 setString(&ti_new->name_sorting, ti_new->name);
2823
2824 setString(&ti_new->subdir, "..");
2825 setString(&ti_new->fullpath, node_parent->fullpath);
2826
2827 ti_new->sort_priority = node_parent->sort_priority;
2828 ti_new->latest_engine = node_parent->latest_engine;
2829
2830 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2831
2832 pushTreeInfo(&node_parent->node_group, ti_new);
2833 }
2834
2835
2836 /* -------------------------------------------------------------------------- */
2837 /* functions for handling level and custom artwork info cache */
2838 /* -------------------------------------------------------------------------- */
2839
LoadArtworkInfoCache()2840 static void LoadArtworkInfoCache()
2841 {
2842 InitCacheDirectory();
2843
2844 if (artworkinfo_cache_old == NULL)
2845 {
2846 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2847
2848 /* try to load artwork info hash from already existing cache file */
2849 artworkinfo_cache_old = loadSetupFileHash(filename);
2850
2851 /* if no artwork info cache file was found, start with empty hash */
2852 if (artworkinfo_cache_old == NULL)
2853 artworkinfo_cache_old = newSetupFileHash();
2854
2855 free(filename);
2856 }
2857
2858 if (artworkinfo_cache_new == NULL)
2859 artworkinfo_cache_new = newSetupFileHash();
2860 }
2861
SaveArtworkInfoCache()2862 static void SaveArtworkInfoCache()
2863 {
2864 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2865
2866 InitCacheDirectory();
2867
2868 saveSetupFileHash(artworkinfo_cache_new, filename);
2869
2870 free(filename);
2871 }
2872
getCacheTokenPrefix(char * prefix1,char * prefix2)2873 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2874 {
2875 static char *prefix = NULL;
2876
2877 checked_free(prefix);
2878
2879 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2880
2881 return prefix;
2882 }
2883
2884 /* (identical to above function, but separate string buffer needed -- nasty) */
getCacheToken(char * prefix,char * suffix)2885 static char *getCacheToken(char *prefix, char *suffix)
2886 {
2887 static char *token = NULL;
2888
2889 checked_free(token);
2890
2891 token = getStringCat2WithSeparator(prefix, suffix, ".");
2892
2893 return token;
2894 }
2895
getFileTimestampString(char * filename)2896 static char *getFileTimestampString(char *filename)
2897 {
2898 #if 1
2899 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2900 #else
2901 struct stat file_status;
2902
2903 if (stat(filename, &file_status) != 0) /* cannot stat file */
2904 return getStringCopy(i_to_a(0));
2905
2906 return getStringCopy(i_to_a(file_status.st_mtime));
2907 #endif
2908 }
2909
modifiedFileTimestamp(char * filename,char * timestamp_string)2910 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2911 {
2912 struct stat file_status;
2913
2914 if (timestamp_string == NULL)
2915 return TRUE;
2916
2917 if (stat(filename, &file_status) != 0) /* cannot stat file */
2918 return TRUE;
2919
2920 return (file_status.st_mtime != atoi(timestamp_string));
2921 }
2922
getArtworkInfoCacheEntry(LevelDirTree * level_node,int type)2923 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2924 {
2925 char *identifier = level_node->subdir;
2926 char *type_string = ARTWORK_DIRECTORY(type);
2927 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2928 char *token_main = getCacheToken(token_prefix, "CACHED");
2929 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2930 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2931 TreeInfo *artwork_info = NULL;
2932
2933 if (!use_artworkinfo_cache)
2934 return NULL;
2935
2936 if (cached)
2937 {
2938 int i;
2939
2940 artwork_info = newTreeInfo();
2941 setTreeInfoToDefaults(artwork_info, type);
2942
2943 /* set all structure fields according to the token/value pairs */
2944 ldi = *artwork_info;
2945 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2946 {
2947 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2948 char *value = getHashEntry(artworkinfo_cache_old, token);
2949
2950 setSetupInfo(artworkinfo_tokens, i, value);
2951
2952 /* check if cache entry for this item is invalid or incomplete */
2953 if (value == NULL)
2954 {
2955 #if 1
2956 Error(ERR_WARN, "cache entry '%s' invalid", token);
2957 #endif
2958
2959 cached = FALSE;
2960 }
2961 }
2962
2963 *artwork_info = ldi;
2964 }
2965
2966 if (cached)
2967 {
2968 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2969 LEVELINFO_FILENAME);
2970 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2971 ARTWORKINFO_FILENAME(type));
2972
2973 /* check if corresponding "levelinfo.conf" file has changed */
2974 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2975 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2976
2977 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2978 cached = FALSE;
2979
2980 /* check if corresponding "<artworkinfo>.conf" file has changed */
2981 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2982 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2983
2984 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2985 cached = FALSE;
2986
2987 #if 0
2988 if (!cached)
2989 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2990 #endif
2991
2992 checked_free(filename_levelinfo);
2993 checked_free(filename_artworkinfo);
2994 }
2995
2996 if (!cached && artwork_info != NULL)
2997 {
2998 freeTreeInfo(artwork_info);
2999
3000 return NULL;
3001 }
3002
3003 return artwork_info;
3004 }
3005
setArtworkInfoCacheEntry(TreeInfo * artwork_info,LevelDirTree * level_node,int type)3006 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3007 LevelDirTree *level_node, int type)
3008 {
3009 char *identifier = level_node->subdir;
3010 char *type_string = ARTWORK_DIRECTORY(type);
3011 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3012 char *token_main = getCacheToken(token_prefix, "CACHED");
3013 boolean set_cache_timestamps = TRUE;
3014 int i;
3015
3016 setHashEntry(artworkinfo_cache_new, token_main, "true");
3017
3018 if (set_cache_timestamps)
3019 {
3020 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3021 LEVELINFO_FILENAME);
3022 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3023 ARTWORKINFO_FILENAME(type));
3024 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3025 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3026
3027 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3028 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3029
3030 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3031 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3032
3033 checked_free(filename_levelinfo);
3034 checked_free(filename_artworkinfo);
3035 checked_free(timestamp_levelinfo);
3036 checked_free(timestamp_artworkinfo);
3037 }
3038
3039 ldi = *artwork_info;
3040 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3041 {
3042 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3043 char *value = getSetupValue(artworkinfo_tokens[i].type,
3044 artworkinfo_tokens[i].value);
3045 if (value != NULL)
3046 setHashEntry(artworkinfo_cache_new, token, value);
3047 }
3048 }
3049
3050
3051 /* -------------------------------------------------------------------------- */
3052 /* functions for loading level info and custom artwork info */
3053 /* -------------------------------------------------------------------------- */
3054
3055 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3056 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3057
LoadLevelInfoFromLevelConf(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory,char * directory_name)3058 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3059 TreeInfo *node_parent,
3060 char *level_directory,
3061 char *directory_name)
3062 {
3063 #if 0
3064 static unsigned long progress_delay = 0;
3065 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3066 #endif
3067 char *directory_path = getPath2(level_directory, directory_name);
3068 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3069 SetupFileHash *setup_file_hash;
3070 LevelDirTree *leveldir_new = NULL;
3071 int i;
3072
3073 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3074 if (!options.debug && !fileExists(filename))
3075 {
3076 free(directory_path);
3077 free(filename);
3078
3079 return FALSE;
3080 }
3081
3082 setup_file_hash = loadSetupFileHash(filename);
3083
3084 if (setup_file_hash == NULL)
3085 {
3086 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3087
3088 free(directory_path);
3089 free(filename);
3090
3091 return FALSE;
3092 }
3093
3094 leveldir_new = newTreeInfo();
3095
3096 if (node_parent)
3097 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3098 else
3099 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3100
3101 leveldir_new->subdir = getStringCopy(directory_name);
3102
3103 checkSetupFileHashIdentifier(setup_file_hash, filename,
3104 getCookie("LEVELINFO"));
3105
3106 /* set all structure fields according to the token/value pairs */
3107 ldi = *leveldir_new;
3108 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3109 setSetupInfo(levelinfo_tokens, i,
3110 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3111 *leveldir_new = ldi;
3112
3113 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3114 setString(&leveldir_new->name, leveldir_new->subdir);
3115
3116 if (leveldir_new->identifier == NULL)
3117 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3118
3119 if (leveldir_new->name_sorting == NULL)
3120 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3121
3122 if (node_parent == NULL) /* top level group */
3123 {
3124 leveldir_new->basepath = getStringCopy(level_directory);
3125 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3126 }
3127 else /* sub level group */
3128 {
3129 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3130 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3131 }
3132
3133 #if 0
3134 if (leveldir_new->levels < 1)
3135 leveldir_new->levels = 1;
3136 #endif
3137
3138 leveldir_new->last_level =
3139 leveldir_new->first_level + leveldir_new->levels - 1;
3140
3141 leveldir_new->in_user_dir =
3142 (!strEqual(leveldir_new->basepath, options.level_directory));
3143
3144 #if 0
3145 printf("::: '%s' -> %d\n",
3146 leveldir_new->identifier,
3147 leveldir_new->in_user_dir);
3148 #endif
3149
3150 /* adjust some settings if user's private level directory was detected */
3151 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3152 leveldir_new->in_user_dir &&
3153 (strEqual(leveldir_new->subdir, getLoginName()) ||
3154 strEqual(leveldir_new->name, getLoginName()) ||
3155 strEqual(leveldir_new->author, getRealName())))
3156 {
3157 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3158 leveldir_new->readonly = FALSE;
3159 }
3160
3161 leveldir_new->user_defined =
3162 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3163
3164 leveldir_new->color = LEVELCOLOR(leveldir_new);
3165
3166 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3167
3168 leveldir_new->handicap_level = /* set handicap to default value */
3169 (leveldir_new->user_defined || !leveldir_new->handicap ?
3170 leveldir_new->last_level : leveldir_new->first_level);
3171
3172 #if 1
3173 #if 1
3174 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3175 leveldir_new->level_group);
3176 #else
3177 if (leveldir_new->level_group ||
3178 DelayReached(&progress_delay, progress_delay_value))
3179 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3180 #endif
3181 #else
3182 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3183 #endif
3184
3185 #if 0
3186 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3187 #if 1
3188 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3189 {
3190 /* skip level sets without levels (which are probably artwork base sets) */
3191
3192 freeSetupFileHash(setup_file_hash);
3193 free(directory_path);
3194 free(filename);
3195
3196 return FALSE;
3197 }
3198 #endif
3199 #endif
3200
3201 pushTreeInfo(node_first, leveldir_new);
3202
3203 freeSetupFileHash(setup_file_hash);
3204
3205 if (leveldir_new->level_group)
3206 {
3207 /* create node to link back to current level directory */
3208 createParentTreeInfoNode(leveldir_new);
3209
3210 /* recursively step into sub-directory and look for more level series */
3211 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3212 leveldir_new, directory_path);
3213 }
3214
3215 free(directory_path);
3216 free(filename);
3217
3218 return TRUE;
3219 }
3220
LoadLevelInfoFromLevelDir(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory)3221 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3222 TreeInfo *node_parent,
3223 char *level_directory)
3224 {
3225 DIR *dir;
3226 struct dirent *dir_entry;
3227 boolean valid_entry_found = FALSE;
3228
3229 if ((dir = opendir(level_directory)) == NULL)
3230 {
3231 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3232 return;
3233 }
3234
3235 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3236 {
3237 struct stat file_status;
3238 char *directory_name = dir_entry->d_name;
3239 char *directory_path = getPath2(level_directory, directory_name);
3240
3241 /* skip entries for current and parent directory */
3242 if (strEqual(directory_name, ".") ||
3243 strEqual(directory_name, ".."))
3244 {
3245 free(directory_path);
3246 continue;
3247 }
3248
3249 /* find out if directory entry is itself a directory */
3250 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3251 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3252 {
3253 free(directory_path);
3254 continue;
3255 }
3256
3257 free(directory_path);
3258
3259 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3260 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3261 strEqual(directory_name, MUSIC_DIRECTORY))
3262 continue;
3263
3264 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3265 level_directory,
3266 directory_name);
3267 }
3268
3269 closedir(dir);
3270
3271 /* special case: top level directory may directly contain "levelinfo.conf" */
3272 if (node_parent == NULL && !valid_entry_found)
3273 {
3274 /* check if this directory directly contains a file "levelinfo.conf" */
3275 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3276 level_directory, ".");
3277 }
3278
3279 if (!valid_entry_found)
3280 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3281 level_directory);
3282 }
3283
AdjustGraphicsForEMC()3284 boolean AdjustGraphicsForEMC()
3285 {
3286 boolean settings_changed = FALSE;
3287
3288 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3289 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3290
3291 return settings_changed;
3292 }
3293
LoadLevelInfo()3294 void LoadLevelInfo()
3295 {
3296 InitUserLevelDirectory(getLoginName());
3297
3298 DrawInitText("Loading level series", 120, FC_GREEN);
3299
3300 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3301 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3302
3303 /* after loading all level set information, clone the level directory tree
3304 and remove all level sets without levels (these may still contain artwork
3305 to be offered in the setup menu as "custom artwork", and are therefore
3306 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3307 leveldir_first_all = leveldir_first;
3308 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3309
3310 AdjustGraphicsForEMC();
3311
3312 /* before sorting, the first entries will be from the user directory */
3313 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3314
3315 if (leveldir_first == NULL)
3316 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3317
3318 sortTreeInfo(&leveldir_first);
3319
3320 #if 0
3321 dumpTreeInfo(leveldir_first, 0);
3322 #endif
3323 }
3324
LoadArtworkInfoFromArtworkConf(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,char * directory_name,int type)3325 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3326 TreeInfo *node_parent,
3327 char *base_directory,
3328 char *directory_name, int type)
3329 {
3330 char *directory_path = getPath2(base_directory, directory_name);
3331 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3332 SetupFileHash *setup_file_hash = NULL;
3333 TreeInfo *artwork_new = NULL;
3334 int i;
3335
3336 if (fileExists(filename))
3337 setup_file_hash = loadSetupFileHash(filename);
3338
3339 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3340 {
3341 DIR *dir;
3342 struct dirent *dir_entry;
3343 boolean valid_file_found = FALSE;
3344
3345 if ((dir = opendir(directory_path)) != NULL)
3346 {
3347 while ((dir_entry = readdir(dir)) != NULL)
3348 {
3349 char *entry_name = dir_entry->d_name;
3350
3351 if (FileIsArtworkType(entry_name, type))
3352 {
3353 valid_file_found = TRUE;
3354 break;
3355 }
3356 }
3357
3358 closedir(dir);
3359 }
3360
3361 if (!valid_file_found)
3362 {
3363 if (!strEqual(directory_name, "."))
3364 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3365
3366 free(directory_path);
3367 free(filename);
3368
3369 return FALSE;
3370 }
3371 }
3372
3373 artwork_new = newTreeInfo();
3374
3375 if (node_parent)
3376 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3377 else
3378 setTreeInfoToDefaults(artwork_new, type);
3379
3380 artwork_new->subdir = getStringCopy(directory_name);
3381
3382 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3383 {
3384 #if 0
3385 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3386 #endif
3387
3388 /* set all structure fields according to the token/value pairs */
3389 ldi = *artwork_new;
3390 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3391 setSetupInfo(levelinfo_tokens, i,
3392 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3393 *artwork_new = ldi;
3394
3395 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3396 setString(&artwork_new->name, artwork_new->subdir);
3397
3398 if (artwork_new->identifier == NULL)
3399 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3400
3401 if (artwork_new->name_sorting == NULL)
3402 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3403 }
3404
3405 if (node_parent == NULL) /* top level group */
3406 {
3407 artwork_new->basepath = getStringCopy(base_directory);
3408 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3409 }
3410 else /* sub level group */
3411 {
3412 artwork_new->basepath = getStringCopy(node_parent->basepath);
3413 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3414 }
3415
3416 artwork_new->in_user_dir =
3417 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3418
3419 /* (may use ".sort_priority" from "setup_file_hash" above) */
3420 artwork_new->color = ARTWORKCOLOR(artwork_new);
3421
3422 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3423
3424 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3425 {
3426 if (strEqual(artwork_new->subdir, "."))
3427 {
3428 if (artwork_new->user_defined)
3429 {
3430 setString(&artwork_new->identifier, "private");
3431 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3432 }
3433 else
3434 {
3435 setString(&artwork_new->identifier, "classic");
3436 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3437 }
3438
3439 /* set to new values after changing ".sort_priority" */
3440 artwork_new->color = ARTWORKCOLOR(artwork_new);
3441
3442 setString(&artwork_new->class_desc,
3443 getLevelClassDescription(artwork_new));
3444 }
3445 else
3446 {
3447 setString(&artwork_new->identifier, artwork_new->subdir);
3448 }
3449
3450 setString(&artwork_new->name, artwork_new->identifier);
3451 setString(&artwork_new->name_sorting, artwork_new->name);
3452 }
3453
3454 #if 0
3455 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3456 #endif
3457
3458 pushTreeInfo(node_first, artwork_new);
3459
3460 freeSetupFileHash(setup_file_hash);
3461
3462 free(directory_path);
3463 free(filename);
3464
3465 return TRUE;
3466 }
3467
LoadArtworkInfoFromArtworkDir(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,int type)3468 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3469 TreeInfo *node_parent,
3470 char *base_directory, int type)
3471 {
3472 DIR *dir;
3473 struct dirent *dir_entry;
3474 boolean valid_entry_found = FALSE;
3475
3476 if ((dir = opendir(base_directory)) == NULL)
3477 {
3478 /* display error if directory is main "options.graphics_directory" etc. */
3479 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3480 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3481
3482 return;
3483 }
3484
3485 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3486 {
3487 struct stat file_status;
3488 char *directory_name = dir_entry->d_name;
3489 char *directory_path = getPath2(base_directory, directory_name);
3490
3491 /* skip directory entries for current and parent directory */
3492 if (strEqual(directory_name, ".") ||
3493 strEqual(directory_name, ".."))
3494 {
3495 free(directory_path);
3496 continue;
3497 }
3498
3499 /* skip directory entries which are not a directory or are not accessible */
3500 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3501 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3502 {
3503 free(directory_path);
3504 continue;
3505 }
3506
3507 free(directory_path);
3508
3509 /* check if this directory contains artwork with or without config file */
3510 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3511 base_directory,
3512 directory_name, type);
3513 }
3514
3515 closedir(dir);
3516
3517 /* check if this directory directly contains artwork itself */
3518 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3519 base_directory, ".",
3520 type);
3521 if (!valid_entry_found)
3522 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3523 base_directory);
3524 }
3525
getDummyArtworkInfo(int type)3526 static TreeInfo *getDummyArtworkInfo(int type)
3527 {
3528 /* this is only needed when there is completely no artwork available */
3529 TreeInfo *artwork_new = newTreeInfo();
3530
3531 setTreeInfoToDefaults(artwork_new, type);
3532
3533 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3534 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3535 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3536
3537 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3538 setString(&artwork_new->name, UNDEFINED_FILENAME);
3539 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3540
3541 return artwork_new;
3542 }
3543
LoadArtworkInfo()3544 void LoadArtworkInfo()
3545 {
3546 LoadArtworkInfoCache();
3547
3548 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3549
3550 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3551 options.graphics_directory,
3552 TREE_TYPE_GRAPHICS_DIR);
3553 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3554 getUserGraphicsDir(),
3555 TREE_TYPE_GRAPHICS_DIR);
3556
3557 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3558 options.sounds_directory,
3559 TREE_TYPE_SOUNDS_DIR);
3560 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3561 getUserSoundsDir(),
3562 TREE_TYPE_SOUNDS_DIR);
3563
3564 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3565 options.music_directory,
3566 TREE_TYPE_MUSIC_DIR);
3567 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3568 getUserMusicDir(),
3569 TREE_TYPE_MUSIC_DIR);
3570
3571 if (artwork.gfx_first == NULL)
3572 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3573 if (artwork.snd_first == NULL)
3574 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3575 if (artwork.mus_first == NULL)
3576 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3577
3578 /* before sorting, the first entries will be from the user directory */
3579 artwork.gfx_current =
3580 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3581 if (artwork.gfx_current == NULL)
3582 artwork.gfx_current =
3583 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3584 if (artwork.gfx_current == NULL)
3585 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3586
3587 artwork.snd_current =
3588 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3589 if (artwork.snd_current == NULL)
3590 artwork.snd_current =
3591 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3592 if (artwork.snd_current == NULL)
3593 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3594
3595 artwork.mus_current =
3596 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3597 if (artwork.mus_current == NULL)
3598 artwork.mus_current =
3599 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3600 if (artwork.mus_current == NULL)
3601 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3602
3603 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3604 artwork.snd_current_identifier = artwork.snd_current->identifier;
3605 artwork.mus_current_identifier = artwork.mus_current->identifier;
3606
3607 #if 0
3608 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3609 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3610 printf("music set == %s\n\n", artwork.mus_current_identifier);
3611 #endif
3612
3613 sortTreeInfo(&artwork.gfx_first);
3614 sortTreeInfo(&artwork.snd_first);
3615 sortTreeInfo(&artwork.mus_first);
3616
3617 #if 0
3618 dumpTreeInfo(artwork.gfx_first, 0);
3619 dumpTreeInfo(artwork.snd_first, 0);
3620 dumpTreeInfo(artwork.mus_first, 0);
3621 #endif
3622 }
3623
LoadArtworkInfoFromLevelInfo(ArtworkDirTree ** artwork_node,LevelDirTree * level_node)3624 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3625 LevelDirTree *level_node)
3626 {
3627 #if 0
3628 static unsigned long progress_delay = 0;
3629 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3630 #endif
3631 int type = (*artwork_node)->type;
3632
3633 /* recursively check all level directories for artwork sub-directories */
3634
3635 while (level_node)
3636 {
3637 /* check all tree entries for artwork, but skip parent link entries */
3638 if (!level_node->parent_link)
3639 {
3640 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3641 boolean cached = (artwork_new != NULL);
3642
3643 if (cached)
3644 {
3645 pushTreeInfo(artwork_node, artwork_new);
3646 }
3647 else
3648 {
3649 TreeInfo *topnode_last = *artwork_node;
3650 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3651 ARTWORK_DIRECTORY(type));
3652
3653 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3654
3655 if (topnode_last != *artwork_node) /* check for newly added node */
3656 {
3657 artwork_new = *artwork_node;
3658
3659 setString(&artwork_new->identifier, level_node->subdir);
3660 setString(&artwork_new->name, level_node->name);
3661 setString(&artwork_new->name_sorting, level_node->name_sorting);
3662
3663 artwork_new->sort_priority = level_node->sort_priority;
3664 artwork_new->color = LEVELCOLOR(artwork_new);
3665 }
3666
3667 free(path);
3668 }
3669
3670 /* insert artwork info (from old cache or filesystem) into new cache */
3671 if (artwork_new != NULL)
3672 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3673 }
3674
3675 #if 1
3676 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3677 level_node->level_group);
3678 #else
3679 if (level_node->level_group ||
3680 DelayReached(&progress_delay, progress_delay_value))
3681 DrawInitText(level_node->name, 150, FC_YELLOW);
3682 #endif
3683
3684 if (level_node->node_group != NULL)
3685 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3686
3687 level_node = level_node->next;
3688 }
3689 }
3690
LoadLevelArtworkInfo()3691 void LoadLevelArtworkInfo()
3692 {
3693 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3694
3695 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3696 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3697 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3698
3699 SaveArtworkInfoCache();
3700
3701 /* needed for reloading level artwork not known at ealier stage */
3702
3703 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3704 {
3705 artwork.gfx_current =
3706 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3707 if (artwork.gfx_current == NULL)
3708 artwork.gfx_current =
3709 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3710 if (artwork.gfx_current == NULL)
3711 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3712 }
3713
3714 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3715 {
3716 artwork.snd_current =
3717 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3718 if (artwork.snd_current == NULL)
3719 artwork.snd_current =
3720 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3721 if (artwork.snd_current == NULL)
3722 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3723 }
3724
3725 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3726 {
3727 artwork.mus_current =
3728 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3729 if (artwork.mus_current == NULL)
3730 artwork.mus_current =
3731 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3732 if (artwork.mus_current == NULL)
3733 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3734 }
3735
3736 sortTreeInfo(&artwork.gfx_first);
3737 sortTreeInfo(&artwork.snd_first);
3738 sortTreeInfo(&artwork.mus_first);
3739
3740 #if 0
3741 dumpTreeInfo(artwork.gfx_first, 0);
3742 dumpTreeInfo(artwork.snd_first, 0);
3743 dumpTreeInfo(artwork.mus_first, 0);
3744 #endif
3745 }
3746
SaveUserLevelInfo()3747 static void SaveUserLevelInfo()
3748 {
3749 LevelDirTree *level_info;
3750 char *filename;
3751 FILE *file;
3752 int i;
3753
3754 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3755
3756 if (!(file = fopen(filename, MODE_WRITE)))
3757 {
3758 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3759 free(filename);
3760 return;
3761 }
3762
3763 level_info = newTreeInfo();
3764
3765 /* always start with reliable default values */
3766 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3767
3768 setString(&level_info->name, getLoginName());
3769 setString(&level_info->author, getRealName());
3770 level_info->levels = 100;
3771 level_info->first_level = 1;
3772
3773 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3774
3775 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3776 getCookie("LEVELINFO")));
3777
3778 ldi = *level_info;
3779 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3780 {
3781 if (i == LEVELINFO_TOKEN_NAME ||
3782 i == LEVELINFO_TOKEN_AUTHOR ||
3783 i == LEVELINFO_TOKEN_LEVELS ||
3784 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3785 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3786
3787 /* just to make things nicer :) */
3788 if (i == LEVELINFO_TOKEN_AUTHOR)
3789 fprintf(file, "\n");
3790 }
3791
3792 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3793
3794 fclose(file);
3795
3796 SetFilePermissions(filename, PERMS_PRIVATE);
3797
3798 freeTreeInfo(level_info);
3799 free(filename);
3800 }
3801
getSetupValue(int type,void * value)3802 char *getSetupValue(int type, void *value)
3803 {
3804 static char value_string[MAX_LINE_LEN];
3805
3806 if (value == NULL)
3807 return NULL;
3808
3809 switch (type)
3810 {
3811 case TYPE_BOOLEAN:
3812 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3813 break;
3814
3815 case TYPE_SWITCH:
3816 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3817 break;
3818
3819 case TYPE_SWITCH3:
3820 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3821 *(int *)value == FALSE ? "off" : "on"));
3822 break;
3823
3824 case TYPE_YES_NO:
3825 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3826 break;
3827
3828 case TYPE_YES_NO_AUTO:
3829 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3830 *(int *)value == FALSE ? "no" : "yes"));
3831 break;
3832
3833 case TYPE_ECS_AGA:
3834 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3835 break;
3836
3837 case TYPE_KEY:
3838 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3839 break;
3840
3841 case TYPE_KEY_X11:
3842 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3843 break;
3844
3845 case TYPE_INTEGER:
3846 sprintf(value_string, "%d", *(int *)value);
3847 break;
3848
3849 case TYPE_STRING:
3850 if (*(char **)value == NULL)
3851 return NULL;
3852
3853 strcpy(value_string, *(char **)value);
3854 break;
3855
3856 default:
3857 value_string[0] = '\0';
3858 break;
3859 }
3860
3861 if (type & TYPE_GHOSTED)
3862 strcpy(value_string, "n/a");
3863
3864 return value_string;
3865 }
3866
getSetupLine(struct TokenInfo * token_info,char * prefix,int token_nr)3867 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3868 {
3869 int i;
3870 char *line;
3871 static char token_string[MAX_LINE_LEN];
3872 int token_type = token_info[token_nr].type;
3873 void *setup_value = token_info[token_nr].value;
3874 char *token_text = token_info[token_nr].text;
3875 char *value_string = getSetupValue(token_type, setup_value);
3876
3877 /* build complete token string */
3878 sprintf(token_string, "%s%s", prefix, token_text);
3879
3880 /* build setup entry line */
3881 line = getFormattedSetupEntry(token_string, value_string);
3882
3883 if (token_type == TYPE_KEY_X11)
3884 {
3885 Key key = *(Key *)setup_value;
3886 char *keyname = getKeyNameFromKey(key);
3887
3888 /* add comment, if useful */
3889 if (!strEqual(keyname, "(undefined)") &&
3890 !strEqual(keyname, "(unknown)"))
3891 {
3892 /* add at least one whitespace */
3893 strcat(line, " ");
3894 for (i = strlen(line); i < token_comment_position; i++)
3895 strcat(line, " ");
3896
3897 strcat(line, "# ");
3898 strcat(line, keyname);
3899 }
3900 }
3901
3902 return line;
3903 }
3904
LoadLevelSetup_LastSeries()3905 void LoadLevelSetup_LastSeries()
3906 {
3907 /* ----------------------------------------------------------------------- */
3908 /* ~/.<program>/levelsetup.conf */
3909 /* ----------------------------------------------------------------------- */
3910
3911 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3912 SetupFileHash *level_setup_hash = NULL;
3913
3914 /* always start with reliable default values */
3915 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3916
3917 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3918 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3919 "jue_start");
3920 if (leveldir_current == NULL)
3921 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3922 #endif
3923
3924 if ((level_setup_hash = loadSetupFileHash(filename)))
3925 {
3926 char *last_level_series =
3927 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3928
3929 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3930 last_level_series);
3931 if (leveldir_current == NULL)
3932 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3933
3934 checkSetupFileHashIdentifier(level_setup_hash, filename,
3935 getCookie("LEVELSETUP"));
3936
3937 freeSetupFileHash(level_setup_hash);
3938 }
3939 else
3940 Error(ERR_WARN, "using default setup values");
3941
3942 free(filename);
3943 }
3944
SaveLevelSetup_LastSeries()3945 void SaveLevelSetup_LastSeries()
3946 {
3947 /* ----------------------------------------------------------------------- */
3948 /* ~/.<program>/levelsetup.conf */
3949 /* ----------------------------------------------------------------------- */
3950
3951 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3952 char *level_subdir = leveldir_current->subdir;
3953 FILE *file;
3954
3955 InitUserDataDirectory();
3956
3957 if (!(file = fopen(filename, MODE_WRITE)))
3958 {
3959 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3960 free(filename);
3961 return;
3962 }
3963
3964 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3965 getCookie("LEVELSETUP")));
3966 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3967 level_subdir));
3968
3969 fclose(file);
3970
3971 SetFilePermissions(filename, PERMS_PRIVATE);
3972
3973 free(filename);
3974 }
3975
checkSeriesInfo()3976 static void checkSeriesInfo()
3977 {
3978 static char *level_directory = NULL;
3979 DIR *dir;
3980 struct dirent *dir_entry;
3981
3982 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3983
3984 level_directory = getPath2((leveldir_current->in_user_dir ?
3985 getUserLevelDir(NULL) :
3986 options.level_directory),
3987 leveldir_current->fullpath);
3988
3989 if ((dir = opendir(level_directory)) == NULL)
3990 {
3991 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3992 return;
3993 }
3994
3995 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3996 {
3997 if (strlen(dir_entry->d_name) > 4 &&
3998 dir_entry->d_name[3] == '.' &&
3999 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4000 {
4001 char levelnum_str[4];
4002 int levelnum_value;
4003
4004 strncpy(levelnum_str, dir_entry->d_name, 3);
4005 levelnum_str[3] = '\0';
4006
4007 levelnum_value = atoi(levelnum_str);
4008
4009 #if 0
4010 if (levelnum_value < leveldir_current->first_level)
4011 {
4012 Error(ERR_WARN, "additional level %d found", levelnum_value);
4013 leveldir_current->first_level = levelnum_value;
4014 }
4015 else if (levelnum_value > leveldir_current->last_level)
4016 {
4017 Error(ERR_WARN, "additional level %d found", levelnum_value);
4018 leveldir_current->last_level = levelnum_value;
4019 }
4020 #endif
4021 }
4022 }
4023
4024 closedir(dir);
4025 }
4026
LoadLevelSetup_SeriesInfo()4027 void LoadLevelSetup_SeriesInfo()
4028 {
4029 char *filename;
4030 SetupFileHash *level_setup_hash = NULL;
4031 char *level_subdir = leveldir_current->subdir;
4032
4033 /* always start with reliable default values */
4034 level_nr = leveldir_current->first_level;
4035
4036 checkSeriesInfo(leveldir_current);
4037
4038 /* ----------------------------------------------------------------------- */
4039 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4040 /* ----------------------------------------------------------------------- */
4041
4042 level_subdir = leveldir_current->subdir;
4043
4044 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4045
4046 if ((level_setup_hash = loadSetupFileHash(filename)))
4047 {
4048 char *token_value;
4049
4050 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4051
4052 if (token_value)
4053 {
4054 level_nr = atoi(token_value);
4055
4056 if (level_nr < leveldir_current->first_level)
4057 level_nr = leveldir_current->first_level;
4058 if (level_nr > leveldir_current->last_level)
4059 level_nr = leveldir_current->last_level;
4060 }
4061
4062 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4063
4064 if (token_value)
4065 {
4066 int level_nr = atoi(token_value);
4067
4068 if (level_nr < leveldir_current->first_level)
4069 level_nr = leveldir_current->first_level;
4070 if (level_nr > leveldir_current->last_level + 1)
4071 level_nr = leveldir_current->last_level;
4072
4073 if (leveldir_current->user_defined || !leveldir_current->handicap)
4074 level_nr = leveldir_current->last_level;
4075
4076 leveldir_current->handicap_level = level_nr;
4077 }
4078
4079 checkSetupFileHashIdentifier(level_setup_hash, filename,
4080 getCookie("LEVELSETUP"));
4081
4082 freeSetupFileHash(level_setup_hash);
4083 }
4084 else
4085 Error(ERR_WARN, "using default setup values");
4086
4087 free(filename);
4088 }
4089
SaveLevelSetup_SeriesInfo()4090 void SaveLevelSetup_SeriesInfo()
4091 {
4092 char *filename;
4093 char *level_subdir = leveldir_current->subdir;
4094 char *level_nr_str = int2str(level_nr, 0);
4095 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4096 FILE *file;
4097
4098 /* ----------------------------------------------------------------------- */
4099 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4100 /* ----------------------------------------------------------------------- */
4101
4102 InitLevelSetupDirectory(level_subdir);
4103
4104 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4105
4106 if (!(file = fopen(filename, MODE_WRITE)))
4107 {
4108 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4109 free(filename);
4110 return;
4111 }
4112
4113 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4114 getCookie("LEVELSETUP")));
4115 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4116 level_nr_str));
4117 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4118 handicap_level_str));
4119
4120 fclose(file);
4121
4122 SetFilePermissions(filename, PERMS_PRIVATE);
4123
4124 free(filename);
4125 }
4126