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 #if 0
1634 #ifdef DEBUG
1635 static void printSetupFileList(SetupFileList *list)
1636 {
1637 if (!list)
1638 return;
1639
1640 printf("token: '%s'\n", list->token);
1641 printf("value: '%s'\n", list->value);
1642
1643 printSetupFileList(list->next);
1644 }
1645 #endif
1646 #endif
1647
1648 #ifdef DEBUG
1649 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1650 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1651 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1652 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1653 #else
1654 #define insert_hash_entry hashtable_insert
1655 #define search_hash_entry hashtable_search
1656 #define change_hash_entry hashtable_change
1657 #define remove_hash_entry hashtable_remove
1658 #endif
1659
get_hash_from_key(void * key)1660 unsigned int get_hash_from_key(void *key)
1661 {
1662 /*
1663 djb2
1664
1665 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1666 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1667 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1668 it works better than many other constants, prime or not) has never been
1669 adequately explained.
1670
1671 If you just want to have a good hash function, and cannot wait, djb2
1672 is one of the best string hash functions i know. It has excellent
1673 distribution and speed on many different sets of keys and table sizes.
1674 You are not likely to do better with one of the "well known" functions
1675 such as PJW, K&R, etc.
1676
1677 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1678 */
1679
1680 char *str = (char *)key;
1681 unsigned int hash = 5381;
1682 int c;
1683
1684 while ((c = *str++))
1685 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1686
1687 return hash;
1688 }
1689
keys_are_equal(void * key1,void * key2)1690 static int keys_are_equal(void *key1, void *key2)
1691 {
1692 return (strEqual((char *)key1, (char *)key2));
1693 }
1694
newSetupFileHash()1695 SetupFileHash *newSetupFileHash()
1696 {
1697 SetupFileHash *new_hash =
1698 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1699
1700 if (new_hash == NULL)
1701 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1702
1703 return new_hash;
1704 }
1705
freeSetupFileHash(SetupFileHash * hash)1706 void freeSetupFileHash(SetupFileHash *hash)
1707 {
1708 if (hash == NULL)
1709 return;
1710
1711 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1712 }
1713
getHashEntry(SetupFileHash * hash,char * token)1714 char *getHashEntry(SetupFileHash *hash, char *token)
1715 {
1716 if (hash == NULL)
1717 return NULL;
1718
1719 return search_hash_entry(hash, token);
1720 }
1721
setHashEntry(SetupFileHash * hash,char * token,char * value)1722 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1723 {
1724 char *value_copy;
1725
1726 if (hash == NULL)
1727 return;
1728
1729 value_copy = getStringCopy(value);
1730
1731 /* change value; if it does not exist, insert it as new */
1732 if (!change_hash_entry(hash, token, value_copy))
1733 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1734 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1735 }
1736
removeHashEntry(SetupFileHash * hash,char * token)1737 char *removeHashEntry(SetupFileHash *hash, char *token)
1738 {
1739 if (hash == NULL)
1740 return NULL;
1741
1742 return remove_hash_entry(hash, token);
1743 }
1744
1745 #if 0
1746 static void printSetupFileHash(SetupFileHash *hash)
1747 {
1748 BEGIN_HASH_ITERATION(hash, itr)
1749 {
1750 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1751 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1752 }
1753 END_HASH_ITERATION(hash, itr)
1754 }
1755 #endif
1756
1757 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1758 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1759 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1760
1761 static boolean token_value_separator_found = FALSE;
1762 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1763 static boolean token_value_separator_warning = FALSE;
1764 #endif
1765 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1766 static boolean token_already_exists_warning = FALSE;
1767 #endif
1768
getTokenValueFromSetupLineExt(char * line,char ** token_ptr,char ** value_ptr,char * filename,char * line_raw,int line_nr,boolean separator_required)1769 static boolean getTokenValueFromSetupLineExt(char *line,
1770 char **token_ptr, char **value_ptr,
1771 char *filename, char *line_raw,
1772 int line_nr,
1773 boolean separator_required)
1774 {
1775 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1776 char *token, *value, *line_ptr;
1777
1778 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1779 if (line_raw == NULL)
1780 {
1781 strncpy(line_copy, line, MAX_LINE_LEN);
1782 line_copy[MAX_LINE_LEN] = '\0';
1783 line = line_copy;
1784
1785 strcpy(line_raw_copy, line_copy);
1786 line_raw = line_raw_copy;
1787 }
1788
1789 /* cut trailing comment from input line */
1790 for (line_ptr = line; *line_ptr; line_ptr++)
1791 {
1792 if (*line_ptr == '#')
1793 {
1794 *line_ptr = '\0';
1795 break;
1796 }
1797 }
1798
1799 /* cut trailing whitespaces from input line */
1800 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1801 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1802 *line_ptr = '\0';
1803
1804 /* ignore empty lines */
1805 if (*line == '\0')
1806 return FALSE;
1807
1808 /* cut leading whitespaces from token */
1809 for (token = line; *token; token++)
1810 if (*token != ' ' && *token != '\t')
1811 break;
1812
1813 /* start with empty value as reliable default */
1814 value = "";
1815
1816 token_value_separator_found = FALSE;
1817
1818 /* find end of token to determine start of value */
1819 for (line_ptr = token; *line_ptr; line_ptr++)
1820 {
1821 #if 1
1822 /* first look for an explicit token/value separator, like ':' or '=' */
1823 if (*line_ptr == ':' || *line_ptr == '=')
1824 #else
1825 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1826 #endif
1827 {
1828 *line_ptr = '\0'; /* terminate token string */
1829 value = line_ptr + 1; /* set beginning of value */
1830
1831 token_value_separator_found = TRUE;
1832
1833 break;
1834 }
1835 }
1836
1837 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1838 /* fallback: if no token/value separator found, also allow whitespaces */
1839 if (!token_value_separator_found && !separator_required)
1840 {
1841 for (line_ptr = token; *line_ptr; line_ptr++)
1842 {
1843 if (*line_ptr == ' ' || *line_ptr == '\t')
1844 {
1845 *line_ptr = '\0'; /* terminate token string */
1846 value = line_ptr + 1; /* set beginning of value */
1847
1848 token_value_separator_found = TRUE;
1849
1850 break;
1851 }
1852 }
1853
1854 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1855 if (token_value_separator_found)
1856 {
1857 if (!token_value_separator_warning)
1858 {
1859 Error(ERR_INFO_LINE, "-");
1860
1861 if (filename != NULL)
1862 {
1863 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1864 Error(ERR_INFO, "- config file: '%s'", filename);
1865 }
1866 else
1867 {
1868 Error(ERR_WARN, "missing token/value separator(s):");
1869 }
1870
1871 token_value_separator_warning = TRUE;
1872 }
1873
1874 if (filename != NULL)
1875 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1876 else
1877 Error(ERR_INFO, "- line: '%s'", line_raw);
1878 }
1879 #endif
1880 }
1881 #endif
1882
1883 /* cut trailing whitespaces from token */
1884 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1885 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1886 *line_ptr = '\0';
1887
1888 /* cut leading whitespaces from value */
1889 for (; *value; value++)
1890 if (*value != ' ' && *value != '\t')
1891 break;
1892
1893 #if 0
1894 if (*value == '\0')
1895 value = "true"; /* treat tokens without value as "true" */
1896 #endif
1897
1898 *token_ptr = token;
1899 *value_ptr = value;
1900
1901 return TRUE;
1902 }
1903
getTokenValueFromSetupLine(char * line,char ** token,char ** value)1904 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1905 {
1906 /* while the internal (old) interface does not require a token/value
1907 separator (for downwards compatibility with existing files which
1908 don't use them), it is mandatory for the external (new) interface */
1909
1910 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1911 }
1912
1913 #if 1
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)1914 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1915 boolean top_recursion_level, boolean is_hash)
1916 {
1917 static SetupFileHash *include_filename_hash = NULL;
1918 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1919 char *token, *value, *line_ptr;
1920 void *insert_ptr = NULL;
1921 boolean read_continued_line = FALSE;
1922 FILE *file;
1923 int line_nr = 0, token_count = 0, include_count = 0;
1924
1925 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1926 token_value_separator_warning = FALSE;
1927 #endif
1928
1929 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1930 token_already_exists_warning = FALSE;
1931 #endif
1932
1933 if (!(file = fopen(filename, MODE_READ)))
1934 {
1935 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1936
1937 return FALSE;
1938 }
1939
1940 /* use "insert pointer" to store list end for constant insertion complexity */
1941 if (!is_hash)
1942 insert_ptr = setup_file_data;
1943
1944 /* on top invocation, create hash to mark included files (to prevent loops) */
1945 if (top_recursion_level)
1946 include_filename_hash = newSetupFileHash();
1947
1948 /* mark this file as already included (to prevent including it again) */
1949 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1950
1951 while (!feof(file))
1952 {
1953 /* read next line of input file */
1954 if (!fgets(line, MAX_LINE_LEN, file))
1955 break;
1956
1957 /* check if line was completely read and is terminated by line break */
1958 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1959 line_nr++;
1960
1961 /* cut trailing line break (this can be newline and/or carriage return) */
1962 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1963 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1964 *line_ptr = '\0';
1965
1966 /* copy raw input line for later use (mainly debugging output) */
1967 strcpy(line_raw, line);
1968
1969 if (read_continued_line)
1970 {
1971 #if 0
1972 /* !!! ??? WHY ??? !!! */
1973 /* cut leading whitespaces from input line */
1974 for (line_ptr = line; *line_ptr; line_ptr++)
1975 if (*line_ptr != ' ' && *line_ptr != '\t')
1976 break;
1977 #endif
1978
1979 /* append new line to existing line, if there is enough space */
1980 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1981 strcat(previous_line, line_ptr);
1982
1983 strcpy(line, previous_line); /* copy storage buffer to line */
1984
1985 read_continued_line = FALSE;
1986 }
1987
1988 /* if the last character is '\', continue at next line */
1989 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1990 {
1991 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1992 strcpy(previous_line, line); /* copy line to storage buffer */
1993
1994 read_continued_line = TRUE;
1995
1996 continue;
1997 }
1998
1999 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2000 line_raw, line_nr, FALSE))
2001 continue;
2002
2003 if (*token)
2004 {
2005 if (strEqual(token, "include"))
2006 {
2007 if (getHashEntry(include_filename_hash, value) == NULL)
2008 {
2009 char *basepath = getBasePath(filename);
2010 char *basename = getBaseName(value);
2011 char *filename_include = getPath2(basepath, basename);
2012
2013 #if 0
2014 Error(ERR_INFO, "[including file '%s']", filename_include);
2015 #endif
2016
2017 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2018
2019 free(basepath);
2020 free(basename);
2021 free(filename_include);
2022
2023 include_count++;
2024 }
2025 else
2026 {
2027 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2028 }
2029 }
2030 else
2031 {
2032 if (is_hash)
2033 {
2034 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2035 char *old_value =
2036 getHashEntry((SetupFileHash *)setup_file_data, token);
2037
2038 if (old_value != NULL)
2039 {
2040 if (!token_already_exists_warning)
2041 {
2042 Error(ERR_INFO_LINE, "-");
2043 Error(ERR_WARN, "duplicate token(s) found in config file:");
2044 Error(ERR_INFO, "- config file: '%s'", filename);
2045
2046 token_already_exists_warning = TRUE;
2047 }
2048
2049 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2050 Error(ERR_INFO, " old value: '%s'", old_value);
2051 Error(ERR_INFO, " new value: '%s'", value);
2052 }
2053 #endif
2054
2055 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2056 }
2057 else
2058 {
2059 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2060 }
2061
2062 token_count++;
2063 }
2064 }
2065 }
2066
2067 fclose(file);
2068
2069 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2070 if (token_value_separator_warning)
2071 Error(ERR_INFO_LINE, "-");
2072 #endif
2073
2074 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2075 if (token_already_exists_warning)
2076 Error(ERR_INFO_LINE, "-");
2077 #endif
2078
2079 if (token_count == 0 && include_count == 0)
2080 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2081
2082 if (top_recursion_level)
2083 freeSetupFileHash(include_filename_hash);
2084
2085 return TRUE;
2086 }
2087
2088 #else
2089
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)2090 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2091 boolean top_recursion_level, boolean is_hash)
2092 {
2093 static SetupFileHash *include_filename_hash = NULL;
2094 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2095 char *token, *value, *line_ptr;
2096 void *insert_ptr = NULL;
2097 boolean read_continued_line = FALSE;
2098 FILE *file;
2099 int line_nr = 0;
2100 int token_count = 0;
2101
2102 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2103 token_value_separator_warning = FALSE;
2104 #endif
2105
2106 if (!(file = fopen(filename, MODE_READ)))
2107 {
2108 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2109
2110 return FALSE;
2111 }
2112
2113 /* use "insert pointer" to store list end for constant insertion complexity */
2114 if (!is_hash)
2115 insert_ptr = setup_file_data;
2116
2117 /* on top invocation, create hash to mark included files (to prevent loops) */
2118 if (top_recursion_level)
2119 include_filename_hash = newSetupFileHash();
2120
2121 /* mark this file as already included (to prevent including it again) */
2122 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2123
2124 while (!feof(file))
2125 {
2126 /* read next line of input file */
2127 if (!fgets(line, MAX_LINE_LEN, file))
2128 break;
2129
2130 /* check if line was completely read and is terminated by line break */
2131 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2132 line_nr++;
2133
2134 /* cut trailing line break (this can be newline and/or carriage return) */
2135 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2136 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2137 *line_ptr = '\0';
2138
2139 /* copy raw input line for later use (mainly debugging output) */
2140 strcpy(line_raw, line);
2141
2142 if (read_continued_line)
2143 {
2144 /* cut leading whitespaces from input line */
2145 for (line_ptr = line; *line_ptr; line_ptr++)
2146 if (*line_ptr != ' ' && *line_ptr != '\t')
2147 break;
2148
2149 /* append new line to existing line, if there is enough space */
2150 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2151 strcat(previous_line, line_ptr);
2152
2153 strcpy(line, previous_line); /* copy storage buffer to line */
2154
2155 read_continued_line = FALSE;
2156 }
2157
2158 /* if the last character is '\', continue at next line */
2159 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2160 {
2161 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2162 strcpy(previous_line, line); /* copy line to storage buffer */
2163
2164 read_continued_line = TRUE;
2165
2166 continue;
2167 }
2168
2169 /* cut trailing comment from input line */
2170 for (line_ptr = line; *line_ptr; line_ptr++)
2171 {
2172 if (*line_ptr == '#')
2173 {
2174 *line_ptr = '\0';
2175 break;
2176 }
2177 }
2178
2179 /* cut trailing whitespaces from input line */
2180 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2181 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2182 *line_ptr = '\0';
2183
2184 /* ignore empty lines */
2185 if (*line == '\0')
2186 continue;
2187
2188 /* cut leading whitespaces from token */
2189 for (token = line; *token; token++)
2190 if (*token != ' ' && *token != '\t')
2191 break;
2192
2193 /* start with empty value as reliable default */
2194 value = "";
2195
2196 token_value_separator_found = FALSE;
2197
2198 /* find end of token to determine start of value */
2199 for (line_ptr = token; *line_ptr; line_ptr++)
2200 {
2201 #if 1
2202 /* first look for an explicit token/value separator, like ':' or '=' */
2203 if (*line_ptr == ':' || *line_ptr == '=')
2204 #else
2205 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2206 #endif
2207 {
2208 *line_ptr = '\0'; /* terminate token string */
2209 value = line_ptr + 1; /* set beginning of value */
2210
2211 token_value_separator_found = TRUE;
2212
2213 break;
2214 }
2215 }
2216
2217 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2218 /* fallback: if no token/value separator found, also allow whitespaces */
2219 if (!token_value_separator_found)
2220 {
2221 for (line_ptr = token; *line_ptr; line_ptr++)
2222 {
2223 if (*line_ptr == ' ' || *line_ptr == '\t')
2224 {
2225 *line_ptr = '\0'; /* terminate token string */
2226 value = line_ptr + 1; /* set beginning of value */
2227
2228 token_value_separator_found = TRUE;
2229
2230 break;
2231 }
2232 }
2233
2234 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2235 if (token_value_separator_found)
2236 {
2237 if (!token_value_separator_warning)
2238 {
2239 Error(ERR_INFO_LINE, "-");
2240 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2241 Error(ERR_INFO, "- config file: '%s'", filename);
2242
2243 token_value_separator_warning = TRUE;
2244 }
2245
2246 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2247 }
2248 #endif
2249 }
2250 #endif
2251
2252 /* cut trailing whitespaces from token */
2253 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2254 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2255 *line_ptr = '\0';
2256
2257 /* cut leading whitespaces from value */
2258 for (; *value; value++)
2259 if (*value != ' ' && *value != '\t')
2260 break;
2261
2262 #if 0
2263 if (*value == '\0')
2264 value = "true"; /* treat tokens without value as "true" */
2265 #endif
2266
2267 if (*token)
2268 {
2269 if (strEqual(token, "include"))
2270 {
2271 if (getHashEntry(include_filename_hash, value) == NULL)
2272 {
2273 char *basepath = getBasePath(filename);
2274 char *basename = getBaseName(value);
2275 char *filename_include = getPath2(basepath, basename);
2276
2277 #if 0
2278 Error(ERR_INFO, "[including file '%s']", filename_include);
2279 #endif
2280
2281 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2282
2283 free(basepath);
2284 free(basename);
2285 free(filename_include);
2286 }
2287 else
2288 {
2289 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2290 }
2291 }
2292 else
2293 {
2294 if (is_hash)
2295 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2296 else
2297 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2298
2299 token_count++;
2300 }
2301 }
2302 }
2303
2304 fclose(file);
2305
2306 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2307 if (token_value_separator_warning)
2308 Error(ERR_INFO_LINE, "-");
2309 #endif
2310
2311 if (token_count == 0)
2312 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2313
2314 if (top_recursion_level)
2315 freeSetupFileHash(include_filename_hash);
2316
2317 return TRUE;
2318 }
2319 #endif
2320
saveSetupFileHash(SetupFileHash * hash,char * filename)2321 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2322 {
2323 FILE *file;
2324
2325 if (!(file = fopen(filename, MODE_WRITE)))
2326 {
2327 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2328
2329 return;
2330 }
2331
2332 BEGIN_HASH_ITERATION(hash, itr)
2333 {
2334 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2335 HASH_ITERATION_VALUE(itr)));
2336 }
2337 END_HASH_ITERATION(hash, itr)
2338
2339 fclose(file);
2340 }
2341
loadSetupFileList(char * filename)2342 SetupFileList *loadSetupFileList(char *filename)
2343 {
2344 SetupFileList *setup_file_list = newSetupFileList("", "");
2345 SetupFileList *first_valid_list_entry;
2346
2347 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2348 {
2349 freeSetupFileList(setup_file_list);
2350
2351 return NULL;
2352 }
2353
2354 first_valid_list_entry = setup_file_list->next;
2355
2356 /* free empty list header */
2357 setup_file_list->next = NULL;
2358 freeSetupFileList(setup_file_list);
2359
2360 return first_valid_list_entry;
2361 }
2362
loadSetupFileHash(char * filename)2363 SetupFileHash *loadSetupFileHash(char *filename)
2364 {
2365 SetupFileHash *setup_file_hash = newSetupFileHash();
2366
2367 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2368 {
2369 freeSetupFileHash(setup_file_hash);
2370
2371 return NULL;
2372 }
2373
2374 return setup_file_hash;
2375 }
2376
checkSetupFileHashIdentifier(SetupFileHash * setup_file_hash,char * filename,char * identifier)2377 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2378 char *filename, char *identifier)
2379 {
2380 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2381
2382 if (value == NULL)
2383 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2384 else if (!checkCookieString(value, identifier))
2385 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2386 }
2387
2388
2389 /* ========================================================================= */
2390 /* setup file stuff */
2391 /* ========================================================================= */
2392
2393 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2394 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2395 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2396
2397 /* level directory info */
2398 #define LEVELINFO_TOKEN_IDENTIFIER 0
2399 #define LEVELINFO_TOKEN_NAME 1
2400 #define LEVELINFO_TOKEN_NAME_SORTING 2
2401 #define LEVELINFO_TOKEN_AUTHOR 3
2402 #define LEVELINFO_TOKEN_YEAR 4
2403 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2404 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2405 #define LEVELINFO_TOKEN_TESTED_BY 7
2406 #define LEVELINFO_TOKEN_LEVELS 8
2407 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2408 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2409 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2410 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2411 #define LEVELINFO_TOKEN_READONLY 13
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2413 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2414 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2415 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2416 #define LEVELINFO_TOKEN_MUSIC_SET 18
2417 #define LEVELINFO_TOKEN_FILENAME 19
2418 #define LEVELINFO_TOKEN_FILETYPE 20
2419 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2420 #define LEVELINFO_TOKEN_HANDICAP 22
2421 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2422
2423 #define NUM_LEVELINFO_TOKENS 24
2424
2425 static LevelDirTree ldi;
2426
2427 static struct TokenInfo levelinfo_tokens[] =
2428 {
2429 /* level directory info */
2430 { TYPE_STRING, &ldi.identifier, "identifier" },
2431 { TYPE_STRING, &ldi.name, "name" },
2432 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2433 { TYPE_STRING, &ldi.author, "author" },
2434 { TYPE_STRING, &ldi.year, "year" },
2435 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2436 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2437 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2438 { TYPE_INTEGER, &ldi.levels, "levels" },
2439 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2440 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2441 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2442 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2443 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2444 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2445 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2446 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2447 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2448 { TYPE_STRING, &ldi.music_set, "music_set" },
2449 { TYPE_STRING, &ldi.level_filename, "filename" },
2450 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2451 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2452 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2453 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2454 };
2455
2456 static struct TokenInfo artworkinfo_tokens[] =
2457 {
2458 /* artwork directory info */
2459 { TYPE_STRING, &ldi.identifier, "identifier" },
2460 { TYPE_STRING, &ldi.subdir, "subdir" },
2461 { TYPE_STRING, &ldi.name, "name" },
2462 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2463 { TYPE_STRING, &ldi.author, "author" },
2464 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2465 { TYPE_STRING, &ldi.basepath, "basepath" },
2466 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2467 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2468 { TYPE_INTEGER, &ldi.color, "color" },
2469 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2470
2471 { -1, NULL, NULL },
2472 };
2473
setTreeInfoToDefaults(TreeInfo * ti,int type)2474 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2475 {
2476 ti->type = type;
2477
2478 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2479 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2480 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2481 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2482 NULL);
2483
2484 ti->node_parent = NULL;
2485 ti->node_group = NULL;
2486 ti->next = NULL;
2487
2488 ti->cl_first = -1;
2489 ti->cl_cursor = -1;
2490
2491 ti->subdir = NULL;
2492 ti->fullpath = NULL;
2493 ti->basepath = NULL;
2494 ti->identifier = NULL;
2495 ti->name = getStringCopy(ANONYMOUS_NAME);
2496 ti->name_sorting = NULL;
2497 ti->author = getStringCopy(ANONYMOUS_NAME);
2498 ti->year = NULL;
2499
2500 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2501 ti->latest_engine = FALSE; /* default: get from level */
2502 ti->parent_link = FALSE;
2503 ti->in_user_dir = FALSE;
2504 ti->user_defined = FALSE;
2505 ti->color = 0;
2506 ti->class_desc = NULL;
2507
2508 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2509
2510 if (ti->type == TREE_TYPE_LEVEL_DIR)
2511 {
2512 ti->imported_from = NULL;
2513 ti->imported_by = NULL;
2514 ti->tested_by = NULL;
2515
2516 ti->graphics_set_ecs = NULL;
2517 ti->graphics_set_aga = NULL;
2518 ti->graphics_set = NULL;
2519 ti->sounds_set = NULL;
2520 ti->music_set = NULL;
2521 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2522 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2523 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2524
2525 ti->level_filename = NULL;
2526 ti->level_filetype = NULL;
2527
2528 ti->special_flags = NULL;
2529
2530 ti->levels = 0;
2531 ti->first_level = 0;
2532 ti->last_level = 0;
2533 ti->level_group = FALSE;
2534 ti->handicap_level = 0;
2535 ti->readonly = TRUE;
2536 ti->handicap = TRUE;
2537 ti->skip_levels = FALSE;
2538 }
2539 }
2540
setTreeInfoToDefaultsFromParent(TreeInfo * ti,TreeInfo * parent)2541 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2542 {
2543 if (parent == NULL)
2544 {
2545 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2546
2547 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2548
2549 return;
2550 }
2551
2552 /* copy all values from the parent structure */
2553
2554 ti->type = parent->type;
2555
2556 ti->node_top = parent->node_top;
2557 ti->node_parent = parent;
2558 ti->node_group = NULL;
2559 ti->next = NULL;
2560
2561 ti->cl_first = -1;
2562 ti->cl_cursor = -1;
2563
2564 ti->subdir = NULL;
2565 ti->fullpath = NULL;
2566 ti->basepath = NULL;
2567 ti->identifier = NULL;
2568 ti->name = getStringCopy(ANONYMOUS_NAME);
2569 ti->name_sorting = NULL;
2570 ti->author = getStringCopy(parent->author);
2571 ti->year = getStringCopy(parent->year);
2572
2573 ti->sort_priority = parent->sort_priority;
2574 ti->latest_engine = parent->latest_engine;
2575 ti->parent_link = FALSE;
2576 ti->in_user_dir = parent->in_user_dir;
2577 ti->user_defined = parent->user_defined;
2578 ti->color = parent->color;
2579 ti->class_desc = getStringCopy(parent->class_desc);
2580
2581 ti->infotext = getStringCopy(parent->infotext);
2582
2583 if (ti->type == TREE_TYPE_LEVEL_DIR)
2584 {
2585 ti->imported_from = getStringCopy(parent->imported_from);
2586 ti->imported_by = getStringCopy(parent->imported_by);
2587 ti->tested_by = getStringCopy(parent->tested_by);
2588
2589 ti->graphics_set_ecs = NULL;
2590 ti->graphics_set_aga = NULL;
2591 ti->graphics_set = NULL;
2592 ti->sounds_set = NULL;
2593 ti->music_set = NULL;
2594 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2595 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2596 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2597
2598 ti->level_filename = NULL;
2599 ti->level_filetype = NULL;
2600
2601 ti->special_flags = getStringCopy(parent->special_flags);
2602
2603 ti->levels = 0;
2604 ti->first_level = 0;
2605 ti->last_level = 0;
2606 ti->level_group = FALSE;
2607 ti->handicap_level = 0;
2608 #if 1
2609 ti->readonly = parent->readonly;
2610 #else
2611 ti->readonly = TRUE;
2612 #endif
2613 ti->handicap = TRUE;
2614 ti->skip_levels = FALSE;
2615 }
2616 }
2617
getTreeInfoCopy(TreeInfo * ti)2618 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2619 {
2620 TreeInfo *ti_copy = newTreeInfo();
2621
2622 /* copy all values from the original structure */
2623
2624 ti_copy->type = ti->type;
2625
2626 ti_copy->node_top = ti->node_top;
2627 ti_copy->node_parent = ti->node_parent;
2628 ti_copy->node_group = ti->node_group;
2629 ti_copy->next = ti->next;
2630
2631 ti_copy->cl_first = ti->cl_first;
2632 ti_copy->cl_cursor = ti->cl_cursor;
2633
2634 ti_copy->subdir = getStringCopy(ti->subdir);
2635 ti_copy->fullpath = getStringCopy(ti->fullpath);
2636 ti_copy->basepath = getStringCopy(ti->basepath);
2637 ti_copy->identifier = getStringCopy(ti->identifier);
2638 ti_copy->name = getStringCopy(ti->name);
2639 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2640 ti_copy->author = getStringCopy(ti->author);
2641 ti_copy->year = getStringCopy(ti->year);
2642 ti_copy->imported_from = getStringCopy(ti->imported_from);
2643 ti_copy->imported_by = getStringCopy(ti->imported_by);
2644 ti_copy->tested_by = getStringCopy(ti->tested_by);
2645
2646 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2647 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2648 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2649 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2650 ti_copy->music_set = getStringCopy(ti->music_set);
2651 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2652 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2653 ti_copy->music_path = getStringCopy(ti->music_path);
2654
2655 ti_copy->level_filename = getStringCopy(ti->level_filename);
2656 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2657
2658 ti_copy->special_flags = getStringCopy(ti->special_flags);
2659
2660 ti_copy->levels = ti->levels;
2661 ti_copy->first_level = ti->first_level;
2662 ti_copy->last_level = ti->last_level;
2663 ti_copy->sort_priority = ti->sort_priority;
2664
2665 ti_copy->latest_engine = ti->latest_engine;
2666
2667 ti_copy->level_group = ti->level_group;
2668 ti_copy->parent_link = ti->parent_link;
2669 ti_copy->in_user_dir = ti->in_user_dir;
2670 ti_copy->user_defined = ti->user_defined;
2671 ti_copy->readonly = ti->readonly;
2672 ti_copy->handicap = ti->handicap;
2673 ti_copy->skip_levels = ti->skip_levels;
2674
2675 ti_copy->color = ti->color;
2676 ti_copy->class_desc = getStringCopy(ti->class_desc);
2677 ti_copy->handicap_level = ti->handicap_level;
2678
2679 ti_copy->infotext = getStringCopy(ti->infotext);
2680
2681 return ti_copy;
2682 }
2683
freeTreeInfo(TreeInfo * ti)2684 void freeTreeInfo(TreeInfo *ti)
2685 {
2686 if (ti == NULL)
2687 return;
2688
2689 checked_free(ti->subdir);
2690 checked_free(ti->fullpath);
2691 checked_free(ti->basepath);
2692 checked_free(ti->identifier);
2693
2694 checked_free(ti->name);
2695 checked_free(ti->name_sorting);
2696 checked_free(ti->author);
2697 checked_free(ti->year);
2698
2699 checked_free(ti->class_desc);
2700
2701 checked_free(ti->infotext);
2702
2703 if (ti->type == TREE_TYPE_LEVEL_DIR)
2704 {
2705 checked_free(ti->imported_from);
2706 checked_free(ti->imported_by);
2707 checked_free(ti->tested_by);
2708
2709 checked_free(ti->graphics_set_ecs);
2710 checked_free(ti->graphics_set_aga);
2711 checked_free(ti->graphics_set);
2712 checked_free(ti->sounds_set);
2713 checked_free(ti->music_set);
2714
2715 checked_free(ti->graphics_path);
2716 checked_free(ti->sounds_path);
2717 checked_free(ti->music_path);
2718
2719 checked_free(ti->level_filename);
2720 checked_free(ti->level_filetype);
2721
2722 checked_free(ti->special_flags);
2723 }
2724
2725 checked_free(ti);
2726 }
2727
setSetupInfo(struct TokenInfo * token_info,int token_nr,char * token_value)2728 void setSetupInfo(struct TokenInfo *token_info,
2729 int token_nr, char *token_value)
2730 {
2731 int token_type = token_info[token_nr].type;
2732 void *setup_value = token_info[token_nr].value;
2733
2734 if (token_value == NULL)
2735 return;
2736
2737 /* set setup field to corresponding token value */
2738 switch (token_type)
2739 {
2740 case TYPE_BOOLEAN:
2741 case TYPE_SWITCH:
2742 *(boolean *)setup_value = get_boolean_from_string(token_value);
2743 break;
2744
2745 case TYPE_SWITCH3:
2746 *(int *)setup_value = get_switch3_from_string(token_value);
2747 break;
2748
2749 case TYPE_KEY:
2750 *(Key *)setup_value = getKeyFromKeyName(token_value);
2751 break;
2752
2753 case TYPE_KEY_X11:
2754 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2755 break;
2756
2757 case TYPE_INTEGER:
2758 *(int *)setup_value = get_integer_from_string(token_value);
2759 break;
2760
2761 case TYPE_STRING:
2762 checked_free(*(char **)setup_value);
2763 *(char **)setup_value = getStringCopy(token_value);
2764 break;
2765
2766 default:
2767 break;
2768 }
2769 }
2770
compareTreeInfoEntries(const void * object1,const void * object2)2771 static int compareTreeInfoEntries(const void *object1, const void *object2)
2772 {
2773 const TreeInfo *entry1 = *((TreeInfo **)object1);
2774 const TreeInfo *entry2 = *((TreeInfo **)object2);
2775 int class_sorting1, class_sorting2;
2776 int compare_result;
2777
2778 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2779 {
2780 class_sorting1 = LEVELSORTING(entry1);
2781 class_sorting2 = LEVELSORTING(entry2);
2782 }
2783 else
2784 {
2785 class_sorting1 = ARTWORKSORTING(entry1);
2786 class_sorting2 = ARTWORKSORTING(entry2);
2787 }
2788
2789 if (entry1->parent_link || entry2->parent_link)
2790 compare_result = (entry1->parent_link ? -1 : +1);
2791 else if (entry1->sort_priority == entry2->sort_priority)
2792 {
2793 char *name1 = getStringToLower(entry1->name_sorting);
2794 char *name2 = getStringToLower(entry2->name_sorting);
2795
2796 compare_result = strcmp(name1, name2);
2797
2798 free(name1);
2799 free(name2);
2800 }
2801 else if (class_sorting1 == class_sorting2)
2802 compare_result = entry1->sort_priority - entry2->sort_priority;
2803 else
2804 compare_result = class_sorting1 - class_sorting2;
2805
2806 return compare_result;
2807 }
2808
createParentTreeInfoNode(TreeInfo * node_parent)2809 static void createParentTreeInfoNode(TreeInfo *node_parent)
2810 {
2811 TreeInfo *ti_new;
2812
2813 if (node_parent == NULL)
2814 return;
2815
2816 ti_new = newTreeInfo();
2817 setTreeInfoToDefaults(ti_new, node_parent->type);
2818
2819 ti_new->node_parent = node_parent;
2820 ti_new->parent_link = TRUE;
2821
2822 setString(&ti_new->identifier, node_parent->identifier);
2823 setString(&ti_new->name, ".. (parent directory)");
2824 setString(&ti_new->name_sorting, ti_new->name);
2825
2826 setString(&ti_new->subdir, "..");
2827 setString(&ti_new->fullpath, node_parent->fullpath);
2828
2829 ti_new->sort_priority = node_parent->sort_priority;
2830 ti_new->latest_engine = node_parent->latest_engine;
2831
2832 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2833
2834 pushTreeInfo(&node_parent->node_group, ti_new);
2835 }
2836
2837
2838 /* -------------------------------------------------------------------------- */
2839 /* functions for handling level and custom artwork info cache */
2840 /* -------------------------------------------------------------------------- */
2841
LoadArtworkInfoCache()2842 static void LoadArtworkInfoCache()
2843 {
2844 InitCacheDirectory();
2845
2846 if (artworkinfo_cache_old == NULL)
2847 {
2848 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2849
2850 /* try to load artwork info hash from already existing cache file */
2851 artworkinfo_cache_old = loadSetupFileHash(filename);
2852
2853 /* if no artwork info cache file was found, start with empty hash */
2854 if (artworkinfo_cache_old == NULL)
2855 artworkinfo_cache_old = newSetupFileHash();
2856
2857 free(filename);
2858 }
2859
2860 if (artworkinfo_cache_new == NULL)
2861 artworkinfo_cache_new = newSetupFileHash();
2862 }
2863
SaveArtworkInfoCache()2864 static void SaveArtworkInfoCache()
2865 {
2866 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2867
2868 InitCacheDirectory();
2869
2870 saveSetupFileHash(artworkinfo_cache_new, filename);
2871
2872 free(filename);
2873 }
2874
getCacheTokenPrefix(char * prefix1,char * prefix2)2875 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2876 {
2877 static char *prefix = NULL;
2878
2879 checked_free(prefix);
2880
2881 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2882
2883 return prefix;
2884 }
2885
2886 /* (identical to above function, but separate string buffer needed -- nasty) */
getCacheToken(char * prefix,char * suffix)2887 static char *getCacheToken(char *prefix, char *suffix)
2888 {
2889 static char *token = NULL;
2890
2891 checked_free(token);
2892
2893 token = getStringCat2WithSeparator(prefix, suffix, ".");
2894
2895 return token;
2896 }
2897
getFileTimestampString(char * filename)2898 static char *getFileTimestampString(char *filename)
2899 {
2900 #if 1
2901 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2902 #else
2903 struct stat file_status;
2904
2905 if (stat(filename, &file_status) != 0) /* cannot stat file */
2906 return getStringCopy(i_to_a(0));
2907
2908 return getStringCopy(i_to_a(file_status.st_mtime));
2909 #endif
2910 }
2911
modifiedFileTimestamp(char * filename,char * timestamp_string)2912 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2913 {
2914 struct stat file_status;
2915
2916 if (timestamp_string == NULL)
2917 return TRUE;
2918
2919 if (stat(filename, &file_status) != 0) /* cannot stat file */
2920 return TRUE;
2921
2922 return (file_status.st_mtime != atoi(timestamp_string));
2923 }
2924
getArtworkInfoCacheEntry(LevelDirTree * level_node,int type)2925 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2926 {
2927 char *identifier = level_node->subdir;
2928 char *type_string = ARTWORK_DIRECTORY(type);
2929 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2930 char *token_main = getCacheToken(token_prefix, "CACHED");
2931 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2932 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2933 TreeInfo *artwork_info = NULL;
2934
2935 if (!use_artworkinfo_cache)
2936 return NULL;
2937
2938 if (cached)
2939 {
2940 int i;
2941
2942 artwork_info = newTreeInfo();
2943 setTreeInfoToDefaults(artwork_info, type);
2944
2945 /* set all structure fields according to the token/value pairs */
2946 ldi = *artwork_info;
2947 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2948 {
2949 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2950 char *value = getHashEntry(artworkinfo_cache_old, token);
2951
2952 setSetupInfo(artworkinfo_tokens, i, value);
2953
2954 /* check if cache entry for this item is invalid or incomplete */
2955 if (value == NULL)
2956 {
2957 #if 1
2958 Error(ERR_WARN, "cache entry '%s' invalid", token);
2959 #endif
2960
2961 cached = FALSE;
2962 }
2963 }
2964
2965 *artwork_info = ldi;
2966 }
2967
2968 if (cached)
2969 {
2970 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2971 LEVELINFO_FILENAME);
2972 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2973 ARTWORKINFO_FILENAME(type));
2974
2975 /* check if corresponding "levelinfo.conf" file has changed */
2976 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2977 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2978
2979 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2980 cached = FALSE;
2981
2982 /* check if corresponding "<artworkinfo>.conf" file has changed */
2983 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2984 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2985
2986 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2987 cached = FALSE;
2988
2989 #if 0
2990 if (!cached)
2991 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2992 #endif
2993
2994 checked_free(filename_levelinfo);
2995 checked_free(filename_artworkinfo);
2996 }
2997
2998 if (!cached && artwork_info != NULL)
2999 {
3000 freeTreeInfo(artwork_info);
3001
3002 return NULL;
3003 }
3004
3005 return artwork_info;
3006 }
3007
setArtworkInfoCacheEntry(TreeInfo * artwork_info,LevelDirTree * level_node,int type)3008 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3009 LevelDirTree *level_node, int type)
3010 {
3011 char *identifier = level_node->subdir;
3012 char *type_string = ARTWORK_DIRECTORY(type);
3013 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3014 char *token_main = getCacheToken(token_prefix, "CACHED");
3015 boolean set_cache_timestamps = TRUE;
3016 int i;
3017
3018 setHashEntry(artworkinfo_cache_new, token_main, "true");
3019
3020 if (set_cache_timestamps)
3021 {
3022 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3023 LEVELINFO_FILENAME);
3024 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3025 ARTWORKINFO_FILENAME(type));
3026 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3027 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3028
3029 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3030 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3031
3032 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3033 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3034
3035 checked_free(filename_levelinfo);
3036 checked_free(filename_artworkinfo);
3037 checked_free(timestamp_levelinfo);
3038 checked_free(timestamp_artworkinfo);
3039 }
3040
3041 ldi = *artwork_info;
3042 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3043 {
3044 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3045 char *value = getSetupValue(artworkinfo_tokens[i].type,
3046 artworkinfo_tokens[i].value);
3047 if (value != NULL)
3048 setHashEntry(artworkinfo_cache_new, token, value);
3049 }
3050 }
3051
3052
3053 /* -------------------------------------------------------------------------- */
3054 /* functions for loading level info and custom artwork info */
3055 /* -------------------------------------------------------------------------- */
3056
3057 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3058 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3059
LoadLevelInfoFromLevelConf(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory,char * directory_name)3060 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3061 TreeInfo *node_parent,
3062 char *level_directory,
3063 char *directory_name)
3064 {
3065 #if 0
3066 static unsigned int progress_delay = 0;
3067 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3068 #endif
3069 char *directory_path = getPath2(level_directory, directory_name);
3070 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3071 SetupFileHash *setup_file_hash;
3072 LevelDirTree *leveldir_new = NULL;
3073 int i;
3074
3075 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3076 if (!options.debug && !fileExists(filename))
3077 {
3078 free(directory_path);
3079 free(filename);
3080
3081 return FALSE;
3082 }
3083
3084 setup_file_hash = loadSetupFileHash(filename);
3085
3086 if (setup_file_hash == NULL)
3087 {
3088 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3089
3090 free(directory_path);
3091 free(filename);
3092
3093 return FALSE;
3094 }
3095
3096 leveldir_new = newTreeInfo();
3097
3098 if (node_parent)
3099 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3100 else
3101 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3102
3103 leveldir_new->subdir = getStringCopy(directory_name);
3104
3105 checkSetupFileHashIdentifier(setup_file_hash, filename,
3106 getCookie("LEVELINFO"));
3107
3108 /* set all structure fields according to the token/value pairs */
3109 ldi = *leveldir_new;
3110 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3111 setSetupInfo(levelinfo_tokens, i,
3112 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3113 *leveldir_new = ldi;
3114
3115 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3116 setString(&leveldir_new->name, leveldir_new->subdir);
3117
3118 if (leveldir_new->identifier == NULL)
3119 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3120
3121 if (leveldir_new->name_sorting == NULL)
3122 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3123
3124 if (node_parent == NULL) /* top level group */
3125 {
3126 leveldir_new->basepath = getStringCopy(level_directory);
3127 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3128 }
3129 else /* sub level group */
3130 {
3131 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3132 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3133 }
3134
3135 #if 0
3136 if (leveldir_new->levels < 1)
3137 leveldir_new->levels = 1;
3138 #endif
3139
3140 leveldir_new->last_level =
3141 leveldir_new->first_level + leveldir_new->levels - 1;
3142
3143 leveldir_new->in_user_dir =
3144 (!strEqual(leveldir_new->basepath, options.level_directory));
3145
3146 #if 0
3147 printf("::: '%s' -> %d\n",
3148 leveldir_new->identifier,
3149 leveldir_new->in_user_dir);
3150 #endif
3151
3152 /* adjust some settings if user's private level directory was detected */
3153 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3154 leveldir_new->in_user_dir &&
3155 (strEqual(leveldir_new->subdir, getLoginName()) ||
3156 strEqual(leveldir_new->name, getLoginName()) ||
3157 strEqual(leveldir_new->author, getRealName())))
3158 {
3159 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3160 leveldir_new->readonly = FALSE;
3161 }
3162
3163 leveldir_new->user_defined =
3164 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3165
3166 leveldir_new->color = LEVELCOLOR(leveldir_new);
3167
3168 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3169
3170 leveldir_new->handicap_level = /* set handicap to default value */
3171 (leveldir_new->user_defined || !leveldir_new->handicap ?
3172 leveldir_new->last_level : leveldir_new->first_level);
3173
3174 #if 1
3175 #if 1
3176 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3177 leveldir_new->level_group);
3178 #else
3179 if (leveldir_new->level_group ||
3180 DelayReached(&progress_delay, progress_delay_value))
3181 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3182 #endif
3183 #else
3184 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3185 #endif
3186
3187 #if 0
3188 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3189 #if 1
3190 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3191 {
3192 /* skip level sets without levels (which are probably artwork base sets) */
3193
3194 freeSetupFileHash(setup_file_hash);
3195 free(directory_path);
3196 free(filename);
3197
3198 return FALSE;
3199 }
3200 #endif
3201 #endif
3202
3203 pushTreeInfo(node_first, leveldir_new);
3204
3205 freeSetupFileHash(setup_file_hash);
3206
3207 if (leveldir_new->level_group)
3208 {
3209 /* create node to link back to current level directory */
3210 createParentTreeInfoNode(leveldir_new);
3211
3212 /* recursively step into sub-directory and look for more level series */
3213 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3214 leveldir_new, directory_path);
3215 }
3216
3217 free(directory_path);
3218 free(filename);
3219
3220 return TRUE;
3221 }
3222
LoadLevelInfoFromLevelDir(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory)3223 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3224 TreeInfo *node_parent,
3225 char *level_directory)
3226 {
3227 DIR *dir;
3228 struct dirent *dir_entry;
3229 boolean valid_entry_found = FALSE;
3230
3231 if ((dir = opendir(level_directory)) == NULL)
3232 {
3233 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3234 return;
3235 }
3236
3237 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3238 {
3239 struct stat file_status;
3240 char *directory_name = dir_entry->d_name;
3241 char *directory_path = getPath2(level_directory, directory_name);
3242
3243 /* skip entries for current and parent directory */
3244 if (strEqual(directory_name, ".") ||
3245 strEqual(directory_name, ".."))
3246 {
3247 free(directory_path);
3248 continue;
3249 }
3250
3251 /* find out if directory entry is itself a directory */
3252 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3253 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3254 {
3255 free(directory_path);
3256 continue;
3257 }
3258
3259 free(directory_path);
3260
3261 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3262 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3263 strEqual(directory_name, MUSIC_DIRECTORY))
3264 continue;
3265
3266 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3267 level_directory,
3268 directory_name);
3269 }
3270
3271 closedir(dir);
3272
3273 /* special case: top level directory may directly contain "levelinfo.conf" */
3274 if (node_parent == NULL && !valid_entry_found)
3275 {
3276 /* check if this directory directly contains a file "levelinfo.conf" */
3277 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3278 level_directory, ".");
3279 }
3280
3281 if (!valid_entry_found)
3282 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3283 level_directory);
3284 }
3285
AdjustGraphicsForEMC()3286 boolean AdjustGraphicsForEMC()
3287 {
3288 boolean settings_changed = FALSE;
3289
3290 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3291 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3292
3293 return settings_changed;
3294 }
3295
LoadLevelInfo()3296 void LoadLevelInfo()
3297 {
3298 InitUserLevelDirectory(getLoginName());
3299
3300 DrawInitText("Loading level series", 120, FC_GREEN);
3301
3302 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3303 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3304
3305 /* after loading all level set information, clone the level directory tree
3306 and remove all level sets without levels (these may still contain artwork
3307 to be offered in the setup menu as "custom artwork", and are therefore
3308 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3309 leveldir_first_all = leveldir_first;
3310 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3311
3312 AdjustGraphicsForEMC();
3313
3314 /* before sorting, the first entries will be from the user directory */
3315 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3316
3317 if (leveldir_first == NULL)
3318 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3319
3320 sortTreeInfo(&leveldir_first);
3321
3322 #if 0
3323 dumpTreeInfo(leveldir_first, 0);
3324 #endif
3325 }
3326
LoadArtworkInfoFromArtworkConf(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,char * directory_name,int type)3327 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3328 TreeInfo *node_parent,
3329 char *base_directory,
3330 char *directory_name, int type)
3331 {
3332 char *directory_path = getPath2(base_directory, directory_name);
3333 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3334 SetupFileHash *setup_file_hash = NULL;
3335 TreeInfo *artwork_new = NULL;
3336 int i;
3337
3338 if (fileExists(filename))
3339 setup_file_hash = loadSetupFileHash(filename);
3340
3341 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3342 {
3343 DIR *dir;
3344 struct dirent *dir_entry;
3345 boolean valid_file_found = FALSE;
3346
3347 if ((dir = opendir(directory_path)) != NULL)
3348 {
3349 while ((dir_entry = readdir(dir)) != NULL)
3350 {
3351 char *entry_name = dir_entry->d_name;
3352
3353 if (FileIsArtworkType(entry_name, type))
3354 {
3355 valid_file_found = TRUE;
3356 break;
3357 }
3358 }
3359
3360 closedir(dir);
3361 }
3362
3363 if (!valid_file_found)
3364 {
3365 if (!strEqual(directory_name, "."))
3366 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3367
3368 free(directory_path);
3369 free(filename);
3370
3371 return FALSE;
3372 }
3373 }
3374
3375 artwork_new = newTreeInfo();
3376
3377 if (node_parent)
3378 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3379 else
3380 setTreeInfoToDefaults(artwork_new, type);
3381
3382 artwork_new->subdir = getStringCopy(directory_name);
3383
3384 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3385 {
3386 #if 0
3387 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3388 #endif
3389
3390 /* set all structure fields according to the token/value pairs */
3391 ldi = *artwork_new;
3392 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3393 setSetupInfo(levelinfo_tokens, i,
3394 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3395 *artwork_new = ldi;
3396
3397 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3398 setString(&artwork_new->name, artwork_new->subdir);
3399
3400 if (artwork_new->identifier == NULL)
3401 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3402
3403 if (artwork_new->name_sorting == NULL)
3404 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3405 }
3406
3407 if (node_parent == NULL) /* top level group */
3408 {
3409 artwork_new->basepath = getStringCopy(base_directory);
3410 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3411 }
3412 else /* sub level group */
3413 {
3414 artwork_new->basepath = getStringCopy(node_parent->basepath);
3415 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3416 }
3417
3418 artwork_new->in_user_dir =
3419 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3420
3421 /* (may use ".sort_priority" from "setup_file_hash" above) */
3422 artwork_new->color = ARTWORKCOLOR(artwork_new);
3423
3424 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3425
3426 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3427 {
3428 if (strEqual(artwork_new->subdir, "."))
3429 {
3430 if (artwork_new->user_defined)
3431 {
3432 setString(&artwork_new->identifier, "private");
3433 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3434 }
3435 else
3436 {
3437 setString(&artwork_new->identifier, "classic");
3438 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3439 }
3440
3441 /* set to new values after changing ".sort_priority" */
3442 artwork_new->color = ARTWORKCOLOR(artwork_new);
3443
3444 setString(&artwork_new->class_desc,
3445 getLevelClassDescription(artwork_new));
3446 }
3447 else
3448 {
3449 setString(&artwork_new->identifier, artwork_new->subdir);
3450 }
3451
3452 setString(&artwork_new->name, artwork_new->identifier);
3453 setString(&artwork_new->name_sorting, artwork_new->name);
3454 }
3455
3456 #if 0
3457 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3458 #endif
3459
3460 pushTreeInfo(node_first, artwork_new);
3461
3462 freeSetupFileHash(setup_file_hash);
3463
3464 free(directory_path);
3465 free(filename);
3466
3467 return TRUE;
3468 }
3469
LoadArtworkInfoFromArtworkDir(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,int type)3470 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3471 TreeInfo *node_parent,
3472 char *base_directory, int type)
3473 {
3474 DIR *dir;
3475 struct dirent *dir_entry;
3476 boolean valid_entry_found = FALSE;
3477
3478 if ((dir = opendir(base_directory)) == NULL)
3479 {
3480 /* display error if directory is main "options.graphics_directory" etc. */
3481 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3482 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3483
3484 return;
3485 }
3486
3487 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3488 {
3489 struct stat file_status;
3490 char *directory_name = dir_entry->d_name;
3491 char *directory_path = getPath2(base_directory, directory_name);
3492
3493 /* skip directory entries for current and parent directory */
3494 if (strEqual(directory_name, ".") ||
3495 strEqual(directory_name, ".."))
3496 {
3497 free(directory_path);
3498 continue;
3499 }
3500
3501 /* skip directory entries which are not a directory or are not accessible */
3502 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3503 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3504 {
3505 free(directory_path);
3506 continue;
3507 }
3508
3509 free(directory_path);
3510
3511 /* check if this directory contains artwork with or without config file */
3512 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3513 base_directory,
3514 directory_name, type);
3515 }
3516
3517 closedir(dir);
3518
3519 /* check if this directory directly contains artwork itself */
3520 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3521 base_directory, ".",
3522 type);
3523 if (!valid_entry_found)
3524 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3525 base_directory);
3526 }
3527
getDummyArtworkInfo(int type)3528 static TreeInfo *getDummyArtworkInfo(int type)
3529 {
3530 /* this is only needed when there is completely no artwork available */
3531 TreeInfo *artwork_new = newTreeInfo();
3532
3533 setTreeInfoToDefaults(artwork_new, type);
3534
3535 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3536 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3537 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3538
3539 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3540 setString(&artwork_new->name, UNDEFINED_FILENAME);
3541 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3542
3543 return artwork_new;
3544 }
3545
LoadArtworkInfo()3546 void LoadArtworkInfo()
3547 {
3548 LoadArtworkInfoCache();
3549
3550 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3551
3552 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3553 options.graphics_directory,
3554 TREE_TYPE_GRAPHICS_DIR);
3555 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3556 getUserGraphicsDir(),
3557 TREE_TYPE_GRAPHICS_DIR);
3558
3559 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3560 options.sounds_directory,
3561 TREE_TYPE_SOUNDS_DIR);
3562 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3563 getUserSoundsDir(),
3564 TREE_TYPE_SOUNDS_DIR);
3565
3566 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3567 options.music_directory,
3568 TREE_TYPE_MUSIC_DIR);
3569 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3570 getUserMusicDir(),
3571 TREE_TYPE_MUSIC_DIR);
3572
3573 if (artwork.gfx_first == NULL)
3574 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3575 if (artwork.snd_first == NULL)
3576 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3577 if (artwork.mus_first == NULL)
3578 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3579
3580 /* before sorting, the first entries will be from the user directory */
3581 artwork.gfx_current =
3582 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3583 if (artwork.gfx_current == NULL)
3584 artwork.gfx_current =
3585 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3586 if (artwork.gfx_current == NULL)
3587 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3588
3589 artwork.snd_current =
3590 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3591 if (artwork.snd_current == NULL)
3592 artwork.snd_current =
3593 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3594 if (artwork.snd_current == NULL)
3595 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3596
3597 artwork.mus_current =
3598 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3599 if (artwork.mus_current == NULL)
3600 artwork.mus_current =
3601 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3602 if (artwork.mus_current == NULL)
3603 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3604
3605 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3606 artwork.snd_current_identifier = artwork.snd_current->identifier;
3607 artwork.mus_current_identifier = artwork.mus_current->identifier;
3608
3609 #if 0
3610 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3611 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3612 printf("music set == %s\n\n", artwork.mus_current_identifier);
3613 #endif
3614
3615 sortTreeInfo(&artwork.gfx_first);
3616 sortTreeInfo(&artwork.snd_first);
3617 sortTreeInfo(&artwork.mus_first);
3618
3619 #if 0
3620 dumpTreeInfo(artwork.gfx_first, 0);
3621 dumpTreeInfo(artwork.snd_first, 0);
3622 dumpTreeInfo(artwork.mus_first, 0);
3623 #endif
3624 }
3625
LoadArtworkInfoFromLevelInfo(ArtworkDirTree ** artwork_node,LevelDirTree * level_node)3626 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3627 LevelDirTree *level_node)
3628 {
3629 #if 0
3630 static unsigned int progress_delay = 0;
3631 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3632 #endif
3633 int type = (*artwork_node)->type;
3634
3635 /* recursively check all level directories for artwork sub-directories */
3636
3637 while (level_node)
3638 {
3639 /* check all tree entries for artwork, but skip parent link entries */
3640 if (!level_node->parent_link)
3641 {
3642 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3643 boolean cached = (artwork_new != NULL);
3644
3645 if (cached)
3646 {
3647 pushTreeInfo(artwork_node, artwork_new);
3648 }
3649 else
3650 {
3651 TreeInfo *topnode_last = *artwork_node;
3652 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3653 ARTWORK_DIRECTORY(type));
3654
3655 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3656
3657 if (topnode_last != *artwork_node) /* check for newly added node */
3658 {
3659 artwork_new = *artwork_node;
3660
3661 setString(&artwork_new->identifier, level_node->subdir);
3662 setString(&artwork_new->name, level_node->name);
3663 setString(&artwork_new->name_sorting, level_node->name_sorting);
3664
3665 artwork_new->sort_priority = level_node->sort_priority;
3666 artwork_new->color = LEVELCOLOR(artwork_new);
3667 }
3668
3669 free(path);
3670 }
3671
3672 /* insert artwork info (from old cache or filesystem) into new cache */
3673 if (artwork_new != NULL)
3674 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3675 }
3676
3677 #if 1
3678 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3679 level_node->level_group);
3680 #else
3681 if (level_node->level_group ||
3682 DelayReached(&progress_delay, progress_delay_value))
3683 DrawInitText(level_node->name, 150, FC_YELLOW);
3684 #endif
3685
3686 if (level_node->node_group != NULL)
3687 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3688
3689 level_node = level_node->next;
3690 }
3691 }
3692
LoadLevelArtworkInfo()3693 void LoadLevelArtworkInfo()
3694 {
3695 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3696
3697 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3698 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3699 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3700
3701 SaveArtworkInfoCache();
3702
3703 /* needed for reloading level artwork not known at ealier stage */
3704
3705 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3706 {
3707 artwork.gfx_current =
3708 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3709 if (artwork.gfx_current == NULL)
3710 artwork.gfx_current =
3711 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3712 if (artwork.gfx_current == NULL)
3713 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3714 }
3715
3716 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3717 {
3718 artwork.snd_current =
3719 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3720 if (artwork.snd_current == NULL)
3721 artwork.snd_current =
3722 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3723 if (artwork.snd_current == NULL)
3724 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3725 }
3726
3727 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3728 {
3729 artwork.mus_current =
3730 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3731 if (artwork.mus_current == NULL)
3732 artwork.mus_current =
3733 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3734 if (artwork.mus_current == NULL)
3735 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3736 }
3737
3738 sortTreeInfo(&artwork.gfx_first);
3739 sortTreeInfo(&artwork.snd_first);
3740 sortTreeInfo(&artwork.mus_first);
3741
3742 #if 0
3743 dumpTreeInfo(artwork.gfx_first, 0);
3744 dumpTreeInfo(artwork.snd_first, 0);
3745 dumpTreeInfo(artwork.mus_first, 0);
3746 #endif
3747 }
3748
SaveUserLevelInfo()3749 static void SaveUserLevelInfo()
3750 {
3751 LevelDirTree *level_info;
3752 char *filename;
3753 FILE *file;
3754 int i;
3755
3756 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3757
3758 if (!(file = fopen(filename, MODE_WRITE)))
3759 {
3760 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3761 free(filename);
3762 return;
3763 }
3764
3765 level_info = newTreeInfo();
3766
3767 /* always start with reliable default values */
3768 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3769
3770 setString(&level_info->name, getLoginName());
3771 setString(&level_info->author, getRealName());
3772 level_info->levels = 100;
3773 level_info->first_level = 1;
3774
3775 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3776
3777 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3778 getCookie("LEVELINFO")));
3779
3780 ldi = *level_info;
3781 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3782 {
3783 if (i == LEVELINFO_TOKEN_NAME ||
3784 i == LEVELINFO_TOKEN_AUTHOR ||
3785 i == LEVELINFO_TOKEN_LEVELS ||
3786 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3787 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3788
3789 /* just to make things nicer :) */
3790 if (i == LEVELINFO_TOKEN_AUTHOR)
3791 fprintf(file, "\n");
3792 }
3793
3794 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3795
3796 fclose(file);
3797
3798 SetFilePermissions(filename, PERMS_PRIVATE);
3799
3800 freeTreeInfo(level_info);
3801 free(filename);
3802 }
3803
getSetupValue(int type,void * value)3804 char *getSetupValue(int type, void *value)
3805 {
3806 static char value_string[MAX_LINE_LEN];
3807
3808 if (value == NULL)
3809 return NULL;
3810
3811 switch (type)
3812 {
3813 case TYPE_BOOLEAN:
3814 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3815 break;
3816
3817 case TYPE_SWITCH:
3818 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3819 break;
3820
3821 case TYPE_SWITCH3:
3822 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3823 *(int *)value == FALSE ? "off" : "on"));
3824 break;
3825
3826 case TYPE_YES_NO:
3827 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3828 break;
3829
3830 case TYPE_YES_NO_AUTO:
3831 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3832 *(int *)value == FALSE ? "no" : "yes"));
3833 break;
3834
3835 case TYPE_ECS_AGA:
3836 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3837 break;
3838
3839 case TYPE_KEY:
3840 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3841 break;
3842
3843 case TYPE_KEY_X11:
3844 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3845 break;
3846
3847 case TYPE_INTEGER:
3848 sprintf(value_string, "%d", *(int *)value);
3849 break;
3850
3851 case TYPE_STRING:
3852 if (*(char **)value == NULL)
3853 return NULL;
3854
3855 strcpy(value_string, *(char **)value);
3856 break;
3857
3858 default:
3859 value_string[0] = '\0';
3860 break;
3861 }
3862
3863 if (type & TYPE_GHOSTED)
3864 strcpy(value_string, "n/a");
3865
3866 return value_string;
3867 }
3868
getSetupLine(struct TokenInfo * token_info,char * prefix,int token_nr)3869 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3870 {
3871 int i;
3872 char *line;
3873 static char token_string[MAX_LINE_LEN];
3874 int token_type = token_info[token_nr].type;
3875 void *setup_value = token_info[token_nr].value;
3876 char *token_text = token_info[token_nr].text;
3877 char *value_string = getSetupValue(token_type, setup_value);
3878
3879 /* build complete token string */
3880 sprintf(token_string, "%s%s", prefix, token_text);
3881
3882 /* build setup entry line */
3883 line = getFormattedSetupEntry(token_string, value_string);
3884
3885 if (token_type == TYPE_KEY_X11)
3886 {
3887 Key key = *(Key *)setup_value;
3888 char *keyname = getKeyNameFromKey(key);
3889
3890 /* add comment, if useful */
3891 if (!strEqual(keyname, "(undefined)") &&
3892 !strEqual(keyname, "(unknown)"))
3893 {
3894 /* add at least one whitespace */
3895 strcat(line, " ");
3896 for (i = strlen(line); i < token_comment_position; i++)
3897 strcat(line, " ");
3898
3899 strcat(line, "# ");
3900 strcat(line, keyname);
3901 }
3902 }
3903
3904 return line;
3905 }
3906
LoadLevelSetup_LastSeries()3907 void LoadLevelSetup_LastSeries()
3908 {
3909 /* ----------------------------------------------------------------------- */
3910 /* ~/.<program>/levelsetup.conf */
3911 /* ----------------------------------------------------------------------- */
3912
3913 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3914 SetupFileHash *level_setup_hash = NULL;
3915
3916 /* always start with reliable default values */
3917 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3918
3919 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3920 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3921 "jue_start");
3922 if (leveldir_current == NULL)
3923 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3924 #endif
3925
3926 if ((level_setup_hash = loadSetupFileHash(filename)))
3927 {
3928 char *last_level_series =
3929 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3930
3931 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3932 last_level_series);
3933 if (leveldir_current == NULL)
3934 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3935
3936 checkSetupFileHashIdentifier(level_setup_hash, filename,
3937 getCookie("LEVELSETUP"));
3938
3939 freeSetupFileHash(level_setup_hash);
3940 }
3941 else
3942 Error(ERR_WARN, "using default setup values");
3943
3944 free(filename);
3945 }
3946
SaveLevelSetup_LastSeries()3947 void SaveLevelSetup_LastSeries()
3948 {
3949 /* ----------------------------------------------------------------------- */
3950 /* ~/.<program>/levelsetup.conf */
3951 /* ----------------------------------------------------------------------- */
3952
3953 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3954 char *level_subdir = leveldir_current->subdir;
3955 FILE *file;
3956
3957 InitUserDataDirectory();
3958
3959 if (!(file = fopen(filename, MODE_WRITE)))
3960 {
3961 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3962 free(filename);
3963 return;
3964 }
3965
3966 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3967 getCookie("LEVELSETUP")));
3968 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3969 level_subdir));
3970
3971 fclose(file);
3972
3973 SetFilePermissions(filename, PERMS_PRIVATE);
3974
3975 free(filename);
3976 }
3977
checkSeriesInfo()3978 static void checkSeriesInfo()
3979 {
3980 static char *level_directory = NULL;
3981 DIR *dir;
3982 #if 0
3983 struct dirent *dir_entry;
3984 #endif
3985
3986 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3987
3988 level_directory = getPath2((leveldir_current->in_user_dir ?
3989 getUserLevelDir(NULL) :
3990 options.level_directory),
3991 leveldir_current->fullpath);
3992
3993 if ((dir = opendir(level_directory)) == NULL)
3994 {
3995 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3996
3997 return;
3998 }
3999
4000 #if 0
4001 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4002 {
4003 if (strlen(dir_entry->d_name) > 4 &&
4004 dir_entry->d_name[3] == '.' &&
4005 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4006 {
4007 char levelnum_str[4];
4008 int levelnum_value;
4009
4010 strncpy(levelnum_str, dir_entry->d_name, 3);
4011 levelnum_str[3] = '\0';
4012
4013 levelnum_value = atoi(levelnum_str);
4014
4015 if (levelnum_value < leveldir_current->first_level)
4016 {
4017 Error(ERR_WARN, "additional level %d found", levelnum_value);
4018 leveldir_current->first_level = levelnum_value;
4019 }
4020 else if (levelnum_value > leveldir_current->last_level)
4021 {
4022 Error(ERR_WARN, "additional level %d found", levelnum_value);
4023 leveldir_current->last_level = levelnum_value;
4024 }
4025 }
4026 }
4027 #endif
4028
4029 closedir(dir);
4030 }
4031
LoadLevelSetup_SeriesInfo()4032 void LoadLevelSetup_SeriesInfo()
4033 {
4034 char *filename;
4035 SetupFileHash *level_setup_hash = NULL;
4036 char *level_subdir = leveldir_current->subdir;
4037 int i;
4038
4039 /* always start with reliable default values */
4040 level_nr = leveldir_current->first_level;
4041
4042 for (i = 0; i < MAX_LEVELS; i++)
4043 {
4044 LevelStats_setPlayed(i, 0);
4045 LevelStats_setSolved(i, 0);
4046 }
4047
4048 checkSeriesInfo(leveldir_current);
4049
4050 /* ----------------------------------------------------------------------- */
4051 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4052 /* ----------------------------------------------------------------------- */
4053
4054 level_subdir = leveldir_current->subdir;
4055
4056 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4057
4058 if ((level_setup_hash = loadSetupFileHash(filename)))
4059 {
4060 char *token_value;
4061
4062 /* get last played level in this level set */
4063
4064 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4065
4066 if (token_value)
4067 {
4068 level_nr = atoi(token_value);
4069
4070 if (level_nr < leveldir_current->first_level)
4071 level_nr = leveldir_current->first_level;
4072 if (level_nr > leveldir_current->last_level)
4073 level_nr = leveldir_current->last_level;
4074 }
4075
4076 /* get handicap level in this level set */
4077
4078 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4079
4080 if (token_value)
4081 {
4082 int level_nr = atoi(token_value);
4083
4084 if (level_nr < leveldir_current->first_level)
4085 level_nr = leveldir_current->first_level;
4086 if (level_nr > leveldir_current->last_level + 1)
4087 level_nr = leveldir_current->last_level;
4088
4089 if (leveldir_current->user_defined || !leveldir_current->handicap)
4090 level_nr = leveldir_current->last_level;
4091
4092 leveldir_current->handicap_level = level_nr;
4093 }
4094
4095 /* get number of played and solved levels in this level set */
4096
4097 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4098 {
4099 char *token = HASH_ITERATION_TOKEN(itr);
4100 char *value = HASH_ITERATION_VALUE(itr);
4101
4102 if (strlen(token) == 3 &&
4103 token[0] >= '0' && token[0] <= '9' &&
4104 token[1] >= '0' && token[1] <= '9' &&
4105 token[2] >= '0' && token[2] <= '9')
4106 {
4107 int level_nr = atoi(token);
4108
4109 if (value != NULL)
4110 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4111
4112 value = strchr(value, ' ');
4113
4114 if (value != NULL)
4115 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4116 }
4117 }
4118 END_HASH_ITERATION(hash, itr)
4119
4120 checkSetupFileHashIdentifier(level_setup_hash, filename,
4121 getCookie("LEVELSETUP"));
4122
4123 freeSetupFileHash(level_setup_hash);
4124 }
4125 else
4126 Error(ERR_WARN, "using default setup values");
4127
4128 free(filename);
4129 }
4130
SaveLevelSetup_SeriesInfo()4131 void SaveLevelSetup_SeriesInfo()
4132 {
4133 char *filename;
4134 char *level_subdir = leveldir_current->subdir;
4135 char *level_nr_str = int2str(level_nr, 0);
4136 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4137 FILE *file;
4138 int i;
4139
4140 /* ----------------------------------------------------------------------- */
4141 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4142 /* ----------------------------------------------------------------------- */
4143
4144 InitLevelSetupDirectory(level_subdir);
4145
4146 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4147
4148 if (!(file = fopen(filename, MODE_WRITE)))
4149 {
4150 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4151 free(filename);
4152 return;
4153 }
4154
4155 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4156 getCookie("LEVELSETUP")));
4157 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4158 level_nr_str));
4159 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4160 handicap_level_str));
4161
4162 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4163 i++)
4164 {
4165 if (LevelStats_getPlayed(i) > 0 ||
4166 LevelStats_getSolved(i) > 0)
4167 {
4168 char token[16];
4169 char value[16];
4170
4171 sprintf(token, "%03d", i);
4172 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4173
4174 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4175 }
4176 }
4177
4178 fclose(file);
4179
4180 SetFilePermissions(filename, PERMS_PRIVATE);
4181
4182 free(filename);
4183 }
4184
LevelStats_getPlayed(int nr)4185 int LevelStats_getPlayed(int nr)
4186 {
4187 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4188 }
4189
LevelStats_getSolved(int nr)4190 int LevelStats_getSolved(int nr)
4191 {
4192 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4193 }
4194
LevelStats_setPlayed(int nr,int value)4195 void LevelStats_setPlayed(int nr, int value)
4196 {
4197 if (nr >= 0 && nr < MAX_LEVELS)
4198 level_stats[nr].played = value;
4199 }
4200
LevelStats_setSolved(int nr,int value)4201 void LevelStats_setSolved(int nr, int value)
4202 {
4203 if (nr >= 0 && nr < MAX_LEVELS)
4204 level_stats[nr].solved = value;
4205 }
4206
LevelStats_incPlayed(int nr)4207 void LevelStats_incPlayed(int nr)
4208 {
4209 if (nr >= 0 && nr < MAX_LEVELS)
4210 level_stats[nr].played++;
4211 }
4212
LevelStats_incSolved(int nr)4213 void LevelStats_incSolved(int nr)
4214 {
4215 if (nr >= 0 && nr < MAX_LEVELS)
4216 level_stats[nr].solved++;
4217 }
4218