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