1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back! *
3 *----------------------------------------------------------*
4 * (c) 1995-2001 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
18 #include "libgame/libgame.h"
25 #define MAX_FILENAME_LEN 256 /* maximal filename length */
26 #define MAX_LINE_LEN 1000 /* maximal input line length */
27 #define CHUNK_ID_LEN 4 /* IFF style chunk id length */
28 #define LEVEL_HEADER_SIZE 80 /* size of level file header */
29 #define LEVEL_HEADER_UNUSED 15 /* unused level header bytes */
30 #define TAPE_HEADER_SIZE 20 /* size of tape file header */
31 #define TAPE_HEADER_UNUSED 7 /* unused tape header bytes */
34 #define FILE_VERSION_1_0 10 /* 1.0 file version (old) */
35 #define FILE_VERSION_1_2 12 /* 1.2 file version (still in use) */
36 #define FILE_VERSION_1_4 14 /* 1.4 file version (new) */
39 /* file identifier strings */
40 #define LEVEL_COOKIE "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.4"
41 #define SCORE_COOKIE "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
42 #define TAPE_COOKIE "ROCKSNDIAMONDS_TAPE_FILE_VERSION_1.2"
43 #define SETUP_COOKIE "ROCKSNDIAMONDS_SETUP_FILE_VERSION_1.2"
44 #define LEVELSETUP_COOKIE "ROCKSNDIAMONDS_LEVELSETUP_FILE_VERSION_1.2"
45 #define LEVELINFO_COOKIE "ROCKSNDIAMONDS_LEVELINFO_FILE_VERSION_1.2"
46 /* old file identifiers for backward compatibility */
47 #define LEVEL_COOKIE_10 "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.0"
48 #define LEVEL_COOKIE_12 "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.2"
49 #define TAPE_COOKIE_10 "ROCKSNDIAMONDS_LEVELREC_FILE_VERSION_1.0"
51 /* file names and filename extensions */
52 #if !defined(PLATFORM_MSDOS)
53 #define LEVELSETUP_DIRECTORY "levelsetup"
54 #define SETUP_FILENAME "setup.conf"
55 #define LEVELSETUP_FILENAME "levelsetup.conf"
56 #define LEVELINFO_FILENAME "levelinfo.conf"
57 #define LEVELFILE_EXTENSION "level"
58 #define TAPEFILE_EXTENSION "tape"
59 #define SCOREFILE_EXTENSION "score"
61 #define LEVELSETUP_DIRECTORY "lvlsetup"
62 #define SETUP_FILENAME "setup.cnf"
63 #define LEVELSETUP_FILENAME "lvlsetup.cnf"
64 #define LEVELINFO_FILENAME "lvlinfo.cnf"
65 #define LEVELFILE_EXTENSION "lvl"
66 #define TAPEFILE_EXTENSION "tap"
67 #define SCOREFILE_EXTENSION "sco"
70 #if defined(PLATFORM_WIN32)
72 #define S_IRGRP S_IRUSR
75 #define S_IROTH S_IRUSR
78 #define S_IWGRP S_IWUSR
81 #define S_IWOTH S_IWUSR
84 #define S_IXGRP S_IXUSR
87 #define S_IXOTH S_IXUSR
89 #endif /* PLATFORM_WIN32 */
91 /* file permissions for newly written files */
92 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
93 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
94 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
95 #define LEVEL_PERMS (MODE_R_ALL | MODE_W_ALL)
96 #define SCORE_PERMS LEVEL_PERMS
97 #define TAPE_PERMS LEVEL_PERMS
98 #define SETUP_PERMS LEVEL_PERMS
100 /* sort priorities of level series (also used as level series classes) */
101 #define LEVELCLASS_TUTORIAL_START 10
102 #define LEVELCLASS_TUTORIAL_END 99
103 #define LEVELCLASS_CLASSICS_START 100
104 #define LEVELCLASS_CLASSICS_END 199
105 #define LEVELCLASS_CONTRIBUTION_START 200
106 #define LEVELCLASS_CONTRIBUTION_END 299
107 #define LEVELCLASS_USER_START 300
108 #define LEVELCLASS_USER_END 399
109 #define LEVELCLASS_BD_START 400
110 #define LEVELCLASS_BD_END 499
111 #define LEVELCLASS_EM_START 500
112 #define LEVELCLASS_EM_END 599
113 #define LEVELCLASS_SP_START 600
114 #define LEVELCLASS_SP_END 699
115 #define LEVELCLASS_DX_START 700
116 #define LEVELCLASS_DX_END 799
118 #define LEVELCLASS_TUTORIAL LEVELCLASS_TUTORIAL_START
119 #define LEVELCLASS_CLASSICS LEVELCLASS_CLASSICS_START
120 #define LEVELCLASS_CONTRIBUTION LEVELCLASS_CONTRIBUTION_START
121 #define LEVELCLASS_USER LEVELCLASS_USER_START
122 #define LEVELCLASS_BD LEVELCLASS_BD_START
123 #define LEVELCLASS_EM LEVELCLASS_EM_START
124 #define LEVELCLASS_SP LEVELCLASS_SP_START
125 #define LEVELCLASS_DX LEVELCLASS_DX_START
127 #define LEVELCLASS_UNDEFINED 999
129 #define NUM_LEVELCLASS_DESC 8
130 char *levelclass_desc[NUM_LEVELCLASS_DESC] =
142 #define IS_LEVELCLASS_TUTORIAL(p) \
143 ((p)->sort_priority >= LEVELCLASS_TUTORIAL_START && \
144 (p)->sort_priority <= LEVELCLASS_TUTORIAL_END)
145 #define IS_LEVELCLASS_CLASSICS(p) \
146 ((p)->sort_priority >= LEVELCLASS_CLASSICS_START && \
147 (p)->sort_priority <= LEVELCLASS_CLASSICS_END)
148 #define IS_LEVELCLASS_CONTRIBUTION(p) \
149 ((p)->sort_priority >= LEVELCLASS_CONTRIBUTION_START && \
150 (p)->sort_priority <= LEVELCLASS_CONTRIBUTION_END)
151 #define IS_LEVELCLASS_USER(p) \
152 ((p)->sort_priority >= LEVELCLASS_USER_START && \
153 (p)->sort_priority <= LEVELCLASS_USER_END)
154 #define IS_LEVELCLASS_BD(p) \
155 ((p)->sort_priority >= LEVELCLASS_BD_START && \
156 (p)->sort_priority <= LEVELCLASS_BD_END)
157 #define IS_LEVELCLASS_EM(p) \
158 ((p)->sort_priority >= LEVELCLASS_EM_START && \
159 (p)->sort_priority <= LEVELCLASS_EM_END)
160 #define IS_LEVELCLASS_SP(p) \
161 ((p)->sort_priority >= LEVELCLASS_SP_START && \
162 (p)->sort_priority <= LEVELCLASS_SP_END)
163 #define IS_LEVELCLASS_DX(p) \
164 ((p)->sort_priority >= LEVELCLASS_DX_START && \
165 (p)->sort_priority <= LEVELCLASS_DX_END)
167 #define LEVELCLASS(n) (IS_LEVELCLASS_TUTORIAL(n) ? LEVELCLASS_TUTORIAL : \
168 IS_LEVELCLASS_CLASSICS(n) ? LEVELCLASS_CLASSICS : \
169 IS_LEVELCLASS_CONTRIBUTION(n) ? LEVELCLASS_CONTRIBUTION : \
170 IS_LEVELCLASS_USER(n) ? LEVELCLASS_USER : \
171 IS_LEVELCLASS_BD(n) ? LEVELCLASS_BD : \
172 IS_LEVELCLASS_EM(n) ? LEVELCLASS_EM : \
173 IS_LEVELCLASS_SP(n) ? LEVELCLASS_SP : \
174 IS_LEVELCLASS_DX(n) ? LEVELCLASS_DX : \
175 LEVELCLASS_UNDEFINED)
177 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
178 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
179 IS_LEVELCLASS_BD(n) ? FC_GREEN : \
180 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
181 IS_LEVELCLASS_SP(n) ? FC_GREEN : \
182 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
183 IS_LEVELCLASS_CONTRIBUTION(n) ? FC_GREEN : \
184 IS_LEVELCLASS_USER(n) ? FC_RED : \
187 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
188 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
189 IS_LEVELCLASS_BD(n) ? 2 : \
190 IS_LEVELCLASS_EM(n) ? 3 : \
191 IS_LEVELCLASS_SP(n) ? 4 : \
192 IS_LEVELCLASS_DX(n) ? 5 : \
193 IS_LEVELCLASS_CONTRIBUTION(n) ? 6 : \
194 IS_LEVELCLASS_USER(n) ? 7 : \
197 static int getFileVersionFromCookieString(const char *cookie)
199 const char *ptr_cookie1, *ptr_cookie2;
200 const char *pattern1 = "_FILE_VERSION_";
201 const char *pattern2 = "?.?";
202 const int len_cookie = strlen(cookie);
203 const int len_pattern1 = strlen(pattern1);
204 const int len_pattern2 = strlen(pattern2);
205 const int len_pattern = len_pattern1 + len_pattern2;
206 int version_major, version_minor;
208 if (len_cookie <= len_pattern)
211 ptr_cookie1 = &cookie[len_cookie - len_pattern];
212 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
214 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
217 if (ptr_cookie2[0] <= '0' || ptr_cookie2[0] >= '9' ||
218 ptr_cookie2[1] != '.' ||
219 ptr_cookie2[2] <= '0' || ptr_cookie2[2] >= '9')
222 version_major = ptr_cookie2[0] - '0';
223 version_minor = ptr_cookie2[2] - '0';
225 return (version_major * 10 + version_minor);
228 boolean checkCookieString(const char *cookie, const char *template)
230 const char *pattern = "_FILE_VERSION_?.?";
231 const int len_cookie = strlen(cookie);
232 const int len_template = strlen(template);
233 const int len_pattern = strlen(pattern);
235 if (len_cookie != len_template)
238 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
244 char *getLevelClassDescription(struct LevelDirInfo *ldi)
246 int position = ldi->sort_priority / 100;
248 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
249 return levelclass_desc[position];
251 return "Unknown Level Class";
254 static void SaveUserLevelInfo(); /* for 'InitUserLevelDir()' */
255 static char *getSetupLine(char *, int); /* for 'SaveUserLevelInfo()' */
257 static char *getSetupDir()
259 return getUserDataDir();
262 static char *getUserLevelDir(char *level_subdir)
264 static char *userlevel_dir = NULL;
265 char *data_dir = getUserDataDir();
266 char *userlevel_subdir = LEVELS_DIRECTORY;
271 if (strlen(level_subdir) > 0)
272 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
274 userlevel_dir = getPath2(data_dir, userlevel_subdir);
276 return userlevel_dir;
279 static char *getTapeDir(char *level_subdir)
281 static char *tape_dir = NULL;
282 char *data_dir = getUserDataDir();
283 char *tape_subdir = TAPES_DIRECTORY;
288 if (strlen(level_subdir) > 0)
289 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
291 tape_dir = getPath2(data_dir, tape_subdir);
296 static char *getScoreDir(char *level_subdir)
298 static char *score_dir = NULL;
299 char *data_dir = options.rw_base_directory;
300 char *score_subdir = SCORES_DIRECTORY;
305 if (strlen(level_subdir) > 0)
306 score_dir = getPath3(data_dir, score_subdir, level_subdir);
308 score_dir = getPath2(data_dir, score_subdir);
313 static char *getLevelSetupDir(char *level_subdir)
315 static char *levelsetup_dir = NULL;
316 char *data_dir = getUserDataDir();
317 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
320 free(levelsetup_dir);
322 if (strlen(level_subdir) > 0)
323 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
325 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
327 return levelsetup_dir;
330 static char *getLevelFilename(int nr)
332 static char *filename = NULL;
333 char basename[MAX_FILENAME_LEN];
335 if (filename != NULL)
338 sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
339 filename = getPath3((leveldir_current->user_defined ?
340 getUserLevelDir("") :
341 options.level_directory),
342 leveldir_current->fullpath,
348 static char *getTapeFilename(int nr)
350 static char *filename = NULL;
351 char basename[MAX_FILENAME_LEN];
353 if (filename != NULL)
356 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
357 filename = getPath2(getTapeDir(leveldir_current->filename), basename);
362 static char *getScoreFilename(int nr)
364 static char *filename = NULL;
365 char basename[MAX_FILENAME_LEN];
367 if (filename != NULL)
370 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
371 filename = getPath2(getScoreDir(leveldir_current->filename), basename);
376 static void InitTapeDirectory(char *level_subdir)
378 createDirectory(getUserDataDir(), "user data");
379 createDirectory(getTapeDir(""), "main tape");
380 createDirectory(getTapeDir(level_subdir), "level tape");
383 static void InitScoreDirectory(char *level_subdir)
385 createDirectory(getScoreDir(""), "main score");
386 createDirectory(getScoreDir(level_subdir), "level score");
389 static void InitUserLevelDirectory(char *level_subdir)
391 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
393 createDirectory(getUserDataDir(), "user data");
394 createDirectory(getUserLevelDir(""), "main user level");
395 createDirectory(getUserLevelDir(level_subdir), "user level");
401 static void InitLevelSetupDirectory(char *level_subdir)
403 createDirectory(getUserDataDir(), "user data");
404 createDirectory(getLevelSetupDir(""), "main level setup");
405 createDirectory(getLevelSetupDir(level_subdir), "level setup");
408 static void setLevelInfoToDefaults()
412 level.file_version = FILE_VERSION_ACTUAL;
413 level.game_version = GAME_VERSION_ACTUAL;
415 lev_fieldx = level.fieldx = STD_LEV_FIELDX;
416 lev_fieldy = level.fieldy = STD_LEV_FIELDY;
418 for(x=0; x<MAX_LEV_FIELDX; x++)
419 for(y=0; y<MAX_LEV_FIELDY; y++)
420 Feld[x][y] = Ur[x][y] = EL_ERDREICH;
423 level.gems_needed = 0;
424 level.amoeba_speed = 10;
425 level.time_magic_wall = 10;
426 level.time_wheel = 10;
427 level.time_light = 10;
428 level.time_timegate = 10;
429 level.amoeba_content = EL_DIAMANT;
430 level.double_speed = FALSE;
431 level.gravity = FALSE;
433 for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
434 level.name[i] = '\0';
435 for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
436 level.author[i] = '\0';
438 strcpy(level.name, NAMELESS_LEVEL_NAME);
439 strcpy(level.author, ANONYMOUS_NAME);
441 for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
444 level.num_yam_contents = STD_ELEMENT_CONTENTS;
445 for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
448 level.yam_content[i][x][y] = EL_FELSBROCKEN;
450 Feld[0][0] = Ur[0][0] = EL_SPIELFIGUR;
451 Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
452 Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_AUSGANG_ZU;
454 BorderElement = EL_BETON;
456 /* try to determine better author name than 'anonymous' */
457 if (strcmp(leveldir_current->author, ANONYMOUS_NAME) != 0)
459 strncpy(level.author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
460 level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
464 switch (LEVELCLASS(leveldir_current))
466 case LEVELCLASS_TUTORIAL:
467 strcpy(level.author, PROGRAM_AUTHOR_STRING);
470 case LEVELCLASS_CONTRIBUTION:
471 strncpy(level.author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN);
472 level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
475 case LEVELCLASS_USER:
476 strncpy(level.author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
477 level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
481 /* keep default value */
487 static int checkLevelElement(int element)
489 if (element >= EL_FIRST_RUNTIME_EL)
491 Error(ERR_WARN, "invalid level element %d", element);
492 element = EL_CHAR_FRAGE;
498 void LoadLevel(int level_nr)
501 char *filename = getLevelFilename(level_nr);
502 char cookie[MAX_LINE_LEN];
503 char chunk[CHUNK_ID_LEN + 1];
504 boolean encoding_16bit = FALSE; /* default: maximal 256 elements */
505 int file_version = FILE_VERSION_ACTUAL;
509 /* always start with reliable default values */
510 setLevelInfoToDefaults();
512 if (!(file = fopen(filename, MODE_READ)))
514 Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
518 /* check file identifier */
519 fgets(cookie, MAX_LINE_LEN, file);
520 if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
521 cookie[strlen(cookie) - 1] = '\0';
524 if (strcmp(cookie, LEVEL_COOKIE_10) == 0) /* old 1.0 level format */
525 file_version = FILE_VERSION_1_0;
526 else if (strcmp(cookie, LEVEL_COOKIE_12) == 0)/* 1.2 (8 bit) level format */
527 file_version = FILE_VERSION_1_2;
528 else if (strcmp(cookie, LEVEL_COOKIE) != 0) /* unknown level format */
530 Error(ERR_WARN, "wrong file identifier of level file '%s'", filename);
535 if (!checkCookieString(cookie, LEVEL_COOKIE)) /* unknown file format */
537 Error(ERR_WARN, "unknown format of level file '%s'", filename);
542 file_version = getFileVersionFromCookieString(cookie);
545 level.file_version = file_version;
547 /* read chunk "HEAD" */
548 if (file_version >= FILE_VERSION_1_2)
550 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
551 if (strcmp(chunk, "HEAD") || chunk_length != LEVEL_HEADER_SIZE)
553 Error(ERR_WARN, "wrong 'HEAD' chunk of level file '%s'", filename);
559 lev_fieldx = level.fieldx = fgetc(file);
560 lev_fieldy = level.fieldy = fgetc(file);
562 level.time = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
563 level.gems_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
565 for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
566 level.name[i] = fgetc(file);
567 level.name[MAX_LEVEL_NAME_LEN] = 0;
569 for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
570 level.score[i] = fgetc(file);
572 level.num_yam_contents = STD_ELEMENT_CONTENTS;
573 for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
579 if (i < STD_ELEMENT_CONTENTS)
580 level.yam_content[i][x][y] = checkLevelElement(fgetc(file));
582 level.yam_content[i][x][y] = EL_LEERRAUM;
587 level.amoeba_speed = fgetc(file);
588 level.time_magic_wall = fgetc(file);
589 level.time_wheel = fgetc(file);
590 level.amoeba_content = checkLevelElement(fgetc(file));
591 level.double_speed = (fgetc(file) == 1 ? TRUE : FALSE);
592 level.gravity = (fgetc(file) == 1 ? TRUE : FALSE);
594 encoding_16bit = (fgetc(file) == 1 ? TRUE : FALSE);
596 for(i=0; i<LEVEL_HEADER_UNUSED; i++) /* skip unused header bytes */
599 if (file_version >= FILE_VERSION_1_2)
601 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
603 /* look for optional author chunk */
604 if (strcmp(chunk, "AUTH") == 0 && chunk_length == MAX_LEVEL_AUTHOR_LEN)
606 for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
607 level.author[i] = fgetc(file);
608 level.author[MAX_LEVEL_NAME_LEN] = 0;
610 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
613 /* look for optional content chunk */
614 if (strcmp(chunk, "CONT") == 0 &&
615 chunk_length == 4 + MAX_ELEMENT_CONTENTS * 3 * 3)
618 level.num_yam_contents = fgetc(file);
622 if (level.num_yam_contents < 1 ||
623 level.num_yam_contents > MAX_ELEMENT_CONTENTS)
626 printf("WARNING: num_yam_contents == %d (corrected)\n",
627 level.num_yam_contents);
629 level.num_yam_contents = STD_ELEMENT_CONTENTS;
632 for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
635 level.yam_content[i][x][y] =
636 checkLevelElement(encoding_16bit ?
637 getFile16BitInteger(file,
638 BYTE_ORDER_BIG_ENDIAN) :
641 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
644 /* next check body chunk identifier and chunk length */
645 if (strcmp(chunk, "BODY") || chunk_length != lev_fieldx * lev_fieldy)
647 Error(ERR_WARN, "wrong 'BODY' chunk of level file '%s'", filename);
653 /* clear all other level fields (needed if resized in level editor later) */
654 for(x=0; x<MAX_LEV_FIELDX; x++)
655 for(y=0; y<MAX_LEV_FIELDY; y++)
656 Feld[x][y] = Ur[x][y] = EL_LEERRAUM;
658 /* now read in the valid level fields from level file */
659 for(y=0; y<lev_fieldy; y++)
660 for(x=0; x<lev_fieldx; x++)
661 Feld[x][y] = Ur[x][y] =
662 checkLevelElement(encoding_16bit ?
663 getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) :
668 if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
669 IS_LEVELCLASS_USER(leveldir_current))
671 /* for user contributed and private levels, use the version of
672 the game engine the levels were created for */
673 level.game_version = file_version;
675 /* player was faster than monsters in pre-1.0 levels */
676 if (file_version == FILE_VERSION_1_0)
678 Error(ERR_WARN, "level file '%s' has version number 1.0", filename);
679 Error(ERR_WARN, "using high speed movement for player");
680 level.double_speed = TRUE;
685 /* always use the latest version of the game engine for all but
686 user contributed and private levels */
687 level.game_version = GAME_VERSION_ACTUAL;
690 /* determine border element for this level */
694 void SaveLevel(int level_nr)
697 char *filename = getLevelFilename(level_nr);
698 boolean encoding_16bit = FALSE; /* default: maximal 256 elements */
699 char *oldest_possible_cookie;
702 if (!(file = fopen(filename, MODE_WRITE)))
704 Error(ERR_WARN, "cannot save level file '%s'", filename);
708 /* check yam content for 16-bit elements */
709 for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
712 if (level.yam_content[i][x][y] > 255)
713 encoding_16bit = TRUE;
715 /* check level field for 16-bit elements */
716 for(y=0; y<lev_fieldy; y++)
717 for(x=0; x<lev_fieldx; x++)
719 encoding_16bit = TRUE;
721 oldest_possible_cookie = (encoding_16bit ? LEVEL_COOKIE : LEVEL_COOKIE_12);
723 fputs(oldest_possible_cookie, file); /* file identifier */
726 putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
728 fputc(level.fieldx, file);
729 fputc(level.fieldy, file);
731 putFile16BitInteger(file, level.time, BYTE_ORDER_BIG_ENDIAN);
732 putFile16BitInteger(file, level.gems_needed, BYTE_ORDER_BIG_ENDIAN);
734 for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
735 fputc(level.name[i], file);
736 for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
737 fputc(level.score[i], file);
738 for(i=0; i<STD_ELEMENT_CONTENTS; i++)
741 fputc(encoding_16bit ? EL_LEERRAUM : level.yam_content[i][x][y], file);
742 fputc(level.amoeba_speed, file);
743 fputc(level.time_magic_wall, file);
744 fputc(level.time_wheel, file);
745 fputc(level.amoeba_content, file);
746 fputc((level.double_speed ? 1 : 0), file);
747 fputc((level.gravity ? 1 : 0), file);
749 fputc((encoding_16bit ? 1 : 0), file);
751 for(i=0; i<LEVEL_HEADER_UNUSED; i++) /* set unused header bytes to zero */
754 putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
756 for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
757 fputc(level.author[i], file);
759 putFileChunk(file, "CONT", 4 + MAX_ELEMENT_CONTENTS * 3 * 3,
760 BYTE_ORDER_BIG_ENDIAN);
762 fputc(EL_MAMPFER, file);
763 fputc(level.num_yam_contents, file);
767 for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
771 putFile16BitInteger(file, level.yam_content[i][x][y],
772 BYTE_ORDER_BIG_ENDIAN);
774 fputc(level.yam_content[i][x][y], file);
776 putFileChunk(file, "BODY", lev_fieldx * lev_fieldy, BYTE_ORDER_BIG_ENDIAN);
778 for(y=0; y<lev_fieldy; y++)
779 for(x=0; x<lev_fieldx; x++)
781 putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN);
783 fputc(Ur[x][y], file);
787 chmod(filename, LEVEL_PERMS);
790 void LoadTape(int level_nr)
793 char *filename = getTapeFilename(level_nr);
794 char cookie[MAX_LINE_LEN];
795 char chunk[CHUNK_ID_LEN + 1];
797 int num_participating_players;
798 int file_version = FILE_VERSION_ACTUAL; /* last version of tape files */
801 /* always start with reliable default values (empty tape) */
802 tape.file_version = FILE_VERSION_ACTUAL;
803 tape.game_version = GAME_VERSION_ACTUAL;
806 /* default values (also for pre-1.2 tapes) with only the first player */
807 tape.player_participates[0] = TRUE;
808 for(i=1; i<MAX_PLAYERS; i++)
809 tape.player_participates[i] = FALSE;
811 /* at least one (default: the first) player participates in every tape */
812 num_participating_players = 1;
814 if (!(file = fopen(filename, MODE_READ)))
817 /* check file identifier */
818 fgets(cookie, MAX_LINE_LEN, file);
819 if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
820 cookie[strlen(cookie) - 1] = '\0';
823 if (strcmp(cookie, TAPE_COOKIE_10) == 0) /* old 1.0 tape format */
824 file_version = FILE_VERSION_1_0;
825 else if (strcmp(cookie, TAPE_COOKIE) != 0) /* unknown tape format */
827 Error(ERR_WARN, "wrong file identifier of tape file '%s'", filename);
832 if (!checkCookieString(cookie, TAPE_COOKIE)) /* unknown file format */
834 Error(ERR_WARN, "unknown format of tape file '%s'", filename);
839 file_version = getFileVersionFromCookieString(cookie);
842 tape.file_version = file_version;
843 tape.game_version = file_version;
845 /* read chunk "HEAD" */
846 if (file_version >= FILE_VERSION_1_2)
848 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
849 if (strcmp(chunk, "HEAD") || chunk_length != TAPE_HEADER_SIZE)
851 Error(ERR_WARN, "wrong 'HEAD' chunk of tape file '%s'", filename);
857 tape.random_seed = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
858 tape.date = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
859 tape.length = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
861 /* read header fields that are new since version 1.2 */
862 if (file_version >= FILE_VERSION_1_2)
864 byte store_participating_players = fgetc(file);
866 for(i=0; i<TAPE_HEADER_UNUSED; i++) /* skip unused header bytes */
869 /* since version 1.2, tapes store which players participate in the tape */
870 num_participating_players = 0;
871 for(i=0; i<MAX_PLAYERS; i++)
873 tape.player_participates[i] = FALSE;
875 if (store_participating_players & (1 << i))
877 tape.player_participates[i] = TRUE;
878 num_participating_players++;
883 tape.level_nr = level_nr;
885 tape.changed = FALSE;
887 tape.recording = FALSE;
888 tape.playing = FALSE;
889 tape.pausing = FALSE;
891 /* read chunk "BODY" */
892 if (file_version >= FILE_VERSION_1_2)
894 getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
895 if (strcmp(chunk, "BODY") ||
896 chunk_length != (num_participating_players + 1) * tape.length)
898 Error(ERR_WARN, "wrong 'BODY' chunk of tape file '%s'", filename);
904 for(i=0; i<tape.length; i++)
906 if (i >= MAX_TAPELEN)
909 for(j=0; j<MAX_PLAYERS; j++)
911 tape.pos[i].action[j] = MV_NO_MOVING;
913 if (tape.player_participates[j])
914 tape.pos[i].action[j] = fgetc(file);
917 tape.pos[i].delay = fgetc(file);
919 if (file_version == FILE_VERSION_1_0)
921 /* eliminate possible diagonal moves in old tapes */
922 /* this is only for backward compatibility */
924 byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN };
925 byte action = tape.pos[i].action[0];
926 int k, num_moves = 0;
930 if (action & joy_dir[k])
932 tape.pos[i + num_moves].action[0] = joy_dir[k];
934 tape.pos[i + num_moves].delay = 0;
943 tape.length += num_moves;
953 if (i != tape.length)
954 Error(ERR_WARN, "level recording file '%s' corrupted", filename);
956 tape.length_seconds = GetTapeLength();
959 void SaveTape(int level_nr)
962 char *filename = getTapeFilename(level_nr);
964 boolean new_tape = TRUE;
965 byte store_participating_players;
966 int num_participating_players;
968 InitTapeDirectory(leveldir_current->filename);
970 /* if a tape still exists, ask to overwrite it */
971 if (access(filename, F_OK) == 0)
974 if (!Request("Replace old tape ?", REQ_ASK))
978 /* count number of players and set corresponding bits for compact storage */
979 store_participating_players = 0;
980 num_participating_players = 0;
981 for(i=0; i<MAX_PLAYERS; i++)
983 if (tape.player_participates[i])
985 num_participating_players++;
986 store_participating_players |= (1 << i);
990 if (!(file = fopen(filename, MODE_WRITE)))
992 Error(ERR_WARN, "cannot save level recording file '%s'", filename);
996 fputs(TAPE_COOKIE, file); /* file identifier */
999 putFileChunk(file, "HEAD", TAPE_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
1001 putFile32BitInteger(file, tape.random_seed, BYTE_ORDER_BIG_ENDIAN);
1002 putFile32BitInteger(file, tape.date, BYTE_ORDER_BIG_ENDIAN);
1003 putFile32BitInteger(file, tape.length, BYTE_ORDER_BIG_ENDIAN);
1005 fputc(store_participating_players, file);
1007 for(i=0; i<TAPE_HEADER_UNUSED; i++) /* set unused header bytes to zero */
1010 putFileChunk(file, "BODY", (num_participating_players + 1) * tape.length,
1011 BYTE_ORDER_BIG_ENDIAN);
1013 for(i=0; i<tape.length; i++)
1017 for(j=0; j<MAX_PLAYERS; j++)
1018 if (tape.player_participates[j])
1019 fputc(tape.pos[i].action[j], file);
1021 fputc(tape.pos[i].delay, file);
1026 chmod(filename, TAPE_PERMS);
1028 tape.changed = FALSE;
1031 Request("tape saved !", REQ_CONFIRM);
1034 void LoadScore(int level_nr)
1037 char *filename = getScoreFilename(level_nr);
1038 char cookie[MAX_LINE_LEN];
1039 char line[MAX_LINE_LEN];
1043 /* always start with reliable default values */
1044 for(i=0; i<MAX_SCORE_ENTRIES; i++)
1046 strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
1047 highscore[i].Score = 0;
1050 if (!(file = fopen(filename, MODE_READ)))
1053 /* check file identifier */
1054 fgets(cookie, MAX_LINE_LEN, file);
1055 if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
1056 cookie[strlen(cookie) - 1] = '\0';
1059 if (strcmp(cookie, SCORE_COOKIE) != 0)
1061 Error(ERR_WARN, "wrong file identifier of score file '%s'", filename);
1066 if (!checkCookieString(cookie, SCORE_COOKIE)) /* unknown file format */
1068 Error(ERR_WARN, "unknown format of score file '%s'", filename);
1074 for(i=0; i<MAX_SCORE_ENTRIES; i++)
1076 fscanf(file, "%d", &highscore[i].Score);
1077 fgets(line, MAX_LINE_LEN, file);
1079 if (line[strlen(line) - 1] == '\n')
1080 line[strlen(line) - 1] = '\0';
1082 for (line_ptr = line; *line_ptr; line_ptr++)
1084 if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
1086 strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
1087 highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
1096 void SaveScore(int level_nr)
1099 char *filename = getScoreFilename(level_nr);
1102 InitScoreDirectory(leveldir_current->filename);
1104 if (!(file = fopen(filename, MODE_WRITE)))
1106 Error(ERR_WARN, "cannot save score for level %d", level_nr);
1110 fprintf(file, "%s\n\n", SCORE_COOKIE);
1112 for(i=0; i<MAX_SCORE_ENTRIES; i++)
1113 fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
1117 chmod(filename, SCORE_PERMS);
1120 #define TOKEN_STR_FILE_IDENTIFIER "file_identifier"
1121 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1122 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1123 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1124 #define TOKEN_STR_PLAYER_PREFIX "player_"
1126 #define TOKEN_VALUE_POSITION 30
1129 #define SETUP_TOKEN_PLAYER_NAME 0
1130 #define SETUP_TOKEN_SOUND 1
1131 #define SETUP_TOKEN_SOUND_LOOPS 2
1132 #define SETUP_TOKEN_SOUND_MUSIC 3
1133 #define SETUP_TOKEN_SOUND_SIMPLE 4
1136 #define SETUP_TOKEN_TOONS 5
1137 #define SETUP_TOKEN_DOUBLE_BUFFERING 6
1140 #define SETUP_TOKEN_SCROLL_DELAY 5
1141 #define SETUP_TOKEN_SOFT_SCROLLING 6
1142 #define SETUP_TOKEN_FADING 7
1143 #define SETUP_TOKEN_AUTORECORD 8
1144 #define SETUP_TOKEN_QUICK_DOORS 9
1145 #define SETUP_TOKEN_TEAM_MODE 10
1146 #define SETUP_TOKEN_HANDICAP 11
1147 #define SETUP_TOKEN_TIME_LIMIT 12
1148 #define SETUP_TOKEN_FULLSCREEN 13
1151 #define SETUP_TOKEN_USE_JOYSTICK 14
1152 #define SETUP_TOKEN_JOY_DEVICE_NAME 15
1153 #define SETUP_TOKEN_JOY_XLEFT 16
1154 #define SETUP_TOKEN_JOY_XMIDDLE 17
1155 #define SETUP_TOKEN_JOY_XRIGHT 18
1156 #define SETUP_TOKEN_JOY_YUPPER 19
1157 #define SETUP_TOKEN_JOY_YMIDDLE 20
1158 #define SETUP_TOKEN_JOY_YLOWER 21
1159 #define SETUP_TOKEN_JOY_SNAP 22
1160 #define SETUP_TOKEN_JOY_BOMB 23
1161 #define SETUP_TOKEN_KEY_LEFT 24
1162 #define SETUP_TOKEN_KEY_RIGHT 25
1163 #define SETUP_TOKEN_KEY_UP 26
1164 #define SETUP_TOKEN_KEY_DOWN 27
1165 #define SETUP_TOKEN_KEY_SNAP 28
1166 #define SETUP_TOKEN_KEY_BOMB 29
1168 /* level directory info */
1169 #define LEVELINFO_TOKEN_NAME 30
1170 #define LEVELINFO_TOKEN_NAME_SHORT 31
1171 #define LEVELINFO_TOKEN_NAME_SORTING 32
1172 #define LEVELINFO_TOKEN_AUTHOR 33
1173 #define LEVELINFO_TOKEN_IMPORTED_FROM 34
1174 #define LEVELINFO_TOKEN_LEVELS 35
1175 #define LEVELINFO_TOKEN_FIRST_LEVEL 36
1176 #define LEVELINFO_TOKEN_SORT_PRIORITY 37
1177 #define LEVELINFO_TOKEN_LEVEL_GROUP 38
1178 #define LEVELINFO_TOKEN_READONLY 39
1180 #define FIRST_GLOBAL_SETUP_TOKEN SETUP_TOKEN_PLAYER_NAME
1181 #define LAST_GLOBAL_SETUP_TOKEN SETUP_TOKEN_FULLSCREEN
1183 #define FIRST_PLAYER_SETUP_TOKEN SETUP_TOKEN_USE_JOYSTICK
1184 #define LAST_PLAYER_SETUP_TOKEN SETUP_TOKEN_KEY_BOMB
1186 #define FIRST_LEVELINFO_TOKEN LEVELINFO_TOKEN_NAME
1187 #define LAST_LEVELINFO_TOKEN LEVELINFO_TOKEN_READONLY
1189 #define TYPE_BOOLEAN 1
1190 #define TYPE_SWITCH 2
1192 #define TYPE_INTEGER 4
1193 #define TYPE_STRING 5
1195 static struct SetupInfo si;
1196 static struct SetupInputInfo sii;
1197 static struct LevelDirInfo ldi;
1206 { TYPE_STRING, &si.player_name, "player_name" },
1207 { TYPE_SWITCH, &si.sound, "sound" },
1208 { TYPE_SWITCH, &si.sound_loops, "repeating_sound_loops" },
1209 { TYPE_SWITCH, &si.sound_music, "background_music" },
1210 { TYPE_SWITCH, &si.sound_simple, "simple_sound_effects" },
1213 { TYPE_SWITCH, &si.toons, "toons" },
1214 { TYPE_SWITCH, &si.double_buffering, "double_buffering" },
1217 { TYPE_SWITCH, &si.scroll_delay, "scroll_delay" },
1218 { TYPE_SWITCH, &si.soft_scrolling, "soft_scrolling" },
1219 { TYPE_SWITCH, &si.fading, "screen_fading" },
1220 { TYPE_SWITCH, &si.autorecord, "automatic_tape_recording" },
1221 { TYPE_SWITCH, &si.quick_doors, "quick_doors" },
1222 { TYPE_SWITCH, &si.team_mode, "team_mode" },
1223 { TYPE_SWITCH, &si.handicap, "handicap" },
1224 { TYPE_SWITCH, &si.time_limit, "time_limit" },
1225 { TYPE_SWITCH, &si.fullscreen, "fullscreen" },
1228 { TYPE_BOOLEAN, &sii.use_joystick, ".use_joystick" },
1229 { TYPE_STRING, &sii.joy.device_name, ".joy.device_name" },
1230 { TYPE_INTEGER, &sii.joy.xleft, ".joy.xleft" },
1231 { TYPE_INTEGER, &sii.joy.xmiddle, ".joy.xmiddle" },
1232 { TYPE_INTEGER, &sii.joy.xright, ".joy.xright" },
1233 { TYPE_INTEGER, &sii.joy.yupper, ".joy.yupper" },
1234 { TYPE_INTEGER, &sii.joy.ymiddle, ".joy.ymiddle" },
1235 { TYPE_INTEGER, &sii.joy.ylower, ".joy.ylower" },
1236 { TYPE_INTEGER, &sii.joy.snap, ".joy.snap_field" },
1237 { TYPE_INTEGER, &sii.joy.bomb, ".joy.place_bomb" },
1238 { TYPE_KEY, &sii.key.left, ".key.move_left" },
1239 { TYPE_KEY, &sii.key.right, ".key.move_right" },
1240 { TYPE_KEY, &sii.key.up, ".key.move_up" },
1241 { TYPE_KEY, &sii.key.down, ".key.move_down" },
1242 { TYPE_KEY, &sii.key.snap, ".key.snap_field" },
1243 { TYPE_KEY, &sii.key.bomb, ".key.place_bomb" },
1245 /* level directory info */
1246 { TYPE_STRING, &ldi.name, "name" },
1247 { TYPE_STRING, &ldi.name_short, "name_short" },
1248 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1249 { TYPE_STRING, &ldi.author, "author" },
1250 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1251 { TYPE_INTEGER, &ldi.levels, "levels" },
1252 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1253 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1254 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1255 { TYPE_BOOLEAN, &ldi.readonly, "readonly" }
1258 static char *string_tolower(char *s)
1260 static char s_lower[100];
1263 if (strlen(s) >= 100)
1268 for (i=0; i<strlen(s_lower); i++)
1269 s_lower[i] = tolower(s_lower[i]);
1274 static int get_string_integer_value(char *s)
1276 static char *number_text[][3] =
1278 { "0", "zero", "null", },
1279 { "1", "one", "first" },
1280 { "2", "two", "second" },
1281 { "3", "three", "third" },
1282 { "4", "four", "fourth" },
1283 { "5", "five", "fifth" },
1284 { "6", "six", "sixth" },
1285 { "7", "seven", "seventh" },
1286 { "8", "eight", "eighth" },
1287 { "9", "nine", "ninth" },
1288 { "10", "ten", "tenth" },
1289 { "11", "eleven", "eleventh" },
1290 { "12", "twelve", "twelfth" },
1295 for (i=0; i<13; i++)
1297 if (strcmp(string_tolower(s), number_text[i][j]) == 0)
1303 static boolean get_string_boolean_value(char *s)
1305 if (strcmp(string_tolower(s), "true") == 0 ||
1306 strcmp(string_tolower(s), "yes") == 0 ||
1307 strcmp(string_tolower(s), "on") == 0 ||
1308 get_string_integer_value(s) == 1)
1314 static char *getFormattedSetupEntry(char *token, char *value)
1317 static char entry[MAX_LINE_LEN];
1319 sprintf(entry, "%s:", token);
1320 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1324 strcat(entry, value);
1329 static void freeSetupFileList(struct SetupFileList *setup_file_list)
1331 if (!setup_file_list)
1334 if (setup_file_list->token)
1335 free(setup_file_list->token);
1336 if (setup_file_list->value)
1337 free(setup_file_list->value);
1338 if (setup_file_list->next)
1339 freeSetupFileList(setup_file_list->next);
1340 free(setup_file_list);
1343 static struct SetupFileList *newSetupFileList(char *token, char *value)
1345 struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
1347 new->token = checked_malloc(strlen(token) + 1);
1348 strcpy(new->token, token);
1350 new->value = checked_malloc(strlen(value) + 1);
1351 strcpy(new->value, value);
1358 static char *getTokenValue(struct SetupFileList *setup_file_list,
1361 if (!setup_file_list)
1364 if (strcmp(setup_file_list->token, token) == 0)
1365 return setup_file_list->value;
1367 return getTokenValue(setup_file_list->next, token);
1370 static void setTokenValue(struct SetupFileList *setup_file_list,
1371 char *token, char *value)
1373 if (!setup_file_list)
1376 if (strcmp(setup_file_list->token, token) == 0)
1378 free(setup_file_list->value);
1379 setup_file_list->value = checked_malloc(strlen(value) + 1);
1380 strcpy(setup_file_list->value, value);
1382 else if (setup_file_list->next == NULL)
1383 setup_file_list->next = newSetupFileList(token, value);
1385 setTokenValue(setup_file_list->next, token, value);
1389 static void printSetupFileList(struct SetupFileList *setup_file_list)
1391 if (!setup_file_list)
1394 printf("token: '%s'\n", setup_file_list->token);
1395 printf("value: '%s'\n", setup_file_list->value);
1397 printSetupFileList(setup_file_list->next);
1401 static struct SetupFileList *loadSetupFileList(char *filename)
1404 char line[MAX_LINE_LEN];
1405 char *token, *value, *line_ptr;
1406 struct SetupFileList *setup_file_list = newSetupFileList("", "");
1407 struct SetupFileList *first_valid_list_entry;
1411 if (!(file = fopen(filename, MODE_READ)))
1413 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1419 /* read next line of input file */
1420 if (!fgets(line, MAX_LINE_LEN, file))
1423 /* cut trailing comment or whitespace from input line */
1424 for (line_ptr = line; *line_ptr; line_ptr++)
1426 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1433 /* cut trailing whitespaces from input line */
1434 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1435 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1438 /* ignore empty lines */
1442 line_len = strlen(line);
1444 /* cut leading whitespaces from token */
1445 for (token = line; *token; token++)
1446 if (*token != ' ' && *token != '\t')
1449 /* find end of token */
1450 for (line_ptr = token; *line_ptr; line_ptr++)
1452 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1459 if (line_ptr < line + line_len)
1460 value = line_ptr + 1;
1464 /* cut leading whitespaces from value */
1465 for (; *value; value++)
1466 if (*value != ' ' && *value != '\t')
1469 if (*token && *value)
1470 setTokenValue(setup_file_list, token, value);
1475 first_valid_list_entry = setup_file_list->next;
1477 /* free empty list header */
1478 setup_file_list->next = NULL;
1479 freeSetupFileList(setup_file_list);
1481 if (first_valid_list_entry == NULL)
1482 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1484 return first_valid_list_entry;
1487 static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1490 if (!setup_file_list)
1493 if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1495 if (strcmp(setup_file_list->value, identifier) != 0)
1497 Error(ERR_WARN, "configuration file has wrong version");
1504 if (setup_file_list->next)
1505 checkSetupFileListIdentifier(setup_file_list->next, identifier);
1508 Error(ERR_WARN, "configuration file has no version information");
1513 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1515 ldi->filename = NULL;
1516 ldi->fullpath = NULL;
1517 ldi->basepath = NULL;
1518 ldi->name = getStringCopy(ANONYMOUS_NAME);
1519 ldi->name_short = NULL;
1520 ldi->name_sorting = NULL;
1521 ldi->author = getStringCopy(ANONYMOUS_NAME);
1522 ldi->imported_from = NULL;
1524 ldi->first_level = 0;
1525 ldi->last_level = 0;
1526 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1527 ldi->level_group = FALSE;
1528 ldi->parent_link = FALSE;
1529 ldi->user_defined = FALSE;
1530 ldi->readonly = TRUE;
1532 ldi->class_desc = NULL;
1533 ldi->handicap_level = 0;
1535 ldi->cl_cursor = -1;
1537 ldi->node_parent = NULL;
1538 ldi->node_group = NULL;
1542 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1543 struct LevelDirInfo *parent)
1547 setLevelDirInfoToDefaults(ldi);
1551 /* first copy all values from the parent structure ... */
1554 /* ... then set all fields to default that cannot be inherited from parent.
1555 This is especially important for all those fields that can be set from
1556 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1557 calls 'free()' for all already set token values which requires that no
1558 other structure's pointer may point to them!
1561 ldi->filename = NULL;
1562 ldi->fullpath = NULL;
1563 ldi->basepath = NULL;
1564 ldi->name = getStringCopy(ANONYMOUS_NAME);
1565 ldi->name_short = NULL;
1566 ldi->name_sorting = NULL;
1567 ldi->author = getStringCopy(parent->author);
1568 ldi->imported_from = getStringCopy(parent->imported_from);
1570 ldi->level_group = FALSE;
1571 ldi->parent_link = FALSE;
1573 ldi->node_parent = parent;
1574 ldi->node_group = NULL;
1578 static void setSetupInfoToDefaults(struct SetupInfo *si)
1582 si->player_name = getStringCopy(getLoginName());
1585 si->sound_loops = TRUE;
1586 si->sound_music = TRUE;
1587 si->sound_simple = TRUE;
1589 si->double_buffering = TRUE;
1590 si->direct_draw = !si->double_buffering;
1591 si->scroll_delay = TRUE;
1592 si->soft_scrolling = TRUE;
1594 si->autorecord = TRUE;
1595 si->quick_doors = FALSE;
1596 si->team_mode = FALSE;
1597 si->handicap = TRUE;
1598 si->time_limit = TRUE;
1599 si->fullscreen = FALSE;
1601 for (i=0; i<MAX_PLAYERS; i++)
1603 si->input[i].use_joystick = FALSE;
1604 si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]);
1605 si->input[i].joy.xleft = JOYSTICK_XLEFT;
1606 si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
1607 si->input[i].joy.xright = JOYSTICK_XRIGHT;
1608 si->input[i].joy.yupper = JOYSTICK_YUPPER;
1609 si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
1610 si->input[i].joy.ylower = JOYSTICK_YLOWER;
1611 si->input[i].joy.snap = (i == 0 ? JOY_BUTTON_1 : 0);
1612 si->input[i].joy.bomb = (i == 0 ? JOY_BUTTON_2 : 0);
1613 si->input[i].key.left = (i == 0 ? DEFAULT_KEY_LEFT : KSYM_UNDEFINED);
1614 si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED);
1615 si->input[i].key.up = (i == 0 ? DEFAULT_KEY_UP : KSYM_UNDEFINED);
1616 si->input[i].key.down = (i == 0 ? DEFAULT_KEY_DOWN : KSYM_UNDEFINED);
1617 si->input[i].key.snap = (i == 0 ? DEFAULT_KEY_SNAP : KSYM_UNDEFINED);
1618 si->input[i].key.bomb = (i == 0 ? DEFAULT_KEY_BOMB : KSYM_UNDEFINED);
1622 static void setSetupInfo(int token_nr, char *token_value)
1624 int token_type = token_info[token_nr].type;
1625 void *setup_value = token_info[token_nr].value;
1627 if (token_value == NULL)
1630 /* set setup field to corresponding token value */
1635 *(boolean *)setup_value = get_string_boolean_value(token_value);
1639 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1643 *(int *)setup_value = get_string_integer_value(token_value);
1647 if (*(char **)setup_value != NULL)
1648 free(*(char **)setup_value);
1649 *(char **)setup_value = getStringCopy(token_value);
1657 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
1661 if (!setup_file_list)
1664 /* handle global setup values */
1666 for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1667 setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1670 /* handle player specific setup values */
1671 for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1675 sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1677 sii = setup.input[pnr];
1678 for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1680 char full_token[100];
1682 sprintf(full_token, "%s%s", prefix, token_info[i].text);
1683 setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1685 setup.input[pnr] = sii;
1689 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1691 const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1692 const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1695 if (entry1->parent_link || entry2->parent_link)
1696 compare_result = (entry1->parent_link ? -1 : +1);
1697 else if (entry1->sort_priority == entry2->sort_priority)
1699 char *name1 = getStringToLower(entry1->name_sorting);
1700 char *name2 = getStringToLower(entry2->name_sorting);
1702 compare_result = strcmp(name1, name2);
1707 else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1708 compare_result = entry1->sort_priority - entry2->sort_priority;
1710 compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1712 return compare_result;
1715 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1717 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1719 setLevelDirInfoToDefaults(leveldir_new);
1721 leveldir_new->node_parent = node_parent;
1722 leveldir_new->parent_link = TRUE;
1724 leveldir_new->name = ".. (parent directory)";
1725 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1726 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1728 leveldir_new->filename = "..";
1729 leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1731 leveldir_new->sort_priority = node_parent->sort_priority;
1732 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1734 pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1737 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1738 struct LevelDirInfo *node_parent,
1739 char *level_directory)
1742 struct dirent *dir_entry;
1743 boolean valid_entry_found = FALSE;
1745 if ((dir = opendir(level_directory)) == NULL)
1747 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1751 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1753 struct SetupFileList *setup_file_list = NULL;
1754 struct stat file_status;
1755 char *directory_name = dir_entry->d_name;
1756 char *directory_path = getPath2(level_directory, directory_name);
1757 char *filename = NULL;
1759 /* skip entries for current and parent directory */
1760 if (strcmp(directory_name, ".") == 0 ||
1761 strcmp(directory_name, "..") == 0)
1763 free(directory_path);
1767 /* find out if directory entry is itself a directory */
1768 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1769 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1771 free(directory_path);
1775 filename = getPath2(directory_path, LEVELINFO_FILENAME);
1776 setup_file_list = loadSetupFileList(filename);
1778 if (setup_file_list)
1780 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1783 checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
1784 setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1786 /* set all structure fields according to the token/value pairs */
1787 ldi = *leveldir_new;
1788 for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1789 setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1790 *leveldir_new = ldi;
1792 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1794 if (leveldir_new->name_short == NULL)
1795 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1797 if (leveldir_new->name_sorting == NULL)
1798 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1800 leveldir_new->filename = getStringCopy(directory_name);
1802 if (node_parent == NULL) /* top level group */
1804 leveldir_new->basepath = level_directory;
1805 leveldir_new->fullpath = leveldir_new->filename;
1807 else /* sub level group */
1809 leveldir_new->basepath = node_parent->basepath;
1810 leveldir_new->fullpath = getPath2(node_parent->fullpath,
1814 if (leveldir_new->levels < 1)
1815 leveldir_new->levels = 1;
1817 leveldir_new->last_level =
1818 leveldir_new->first_level + leveldir_new->levels - 1;
1820 leveldir_new->user_defined =
1821 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1823 leveldir_new->color = LEVELCOLOR(leveldir_new);
1824 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1826 leveldir_new->handicap_level = /* set handicap to default value */
1827 (leveldir_new->user_defined ?
1828 leveldir_new->last_level :
1829 leveldir_new->first_level);
1831 pushLevelDirInfo(node_first, leveldir_new);
1833 freeSetupFileList(setup_file_list);
1834 valid_entry_found = TRUE;
1836 if (leveldir_new->level_group)
1838 /* create node to link back to current level directory */
1839 createParentLevelDirNode(leveldir_new);
1841 /* step into sub-directory and look for more level series */
1842 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1843 leveldir_new, directory_path);
1847 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1849 free(directory_path);
1855 if (!valid_entry_found)
1856 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1860 void LoadLevelInfo()
1862 InitUserLevelDirectory(getLoginName());
1864 DrawInitText("Loading level series:", 120, FC_GREEN);
1866 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1867 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1869 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1871 if (leveldir_first == NULL)
1872 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1874 sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1877 dumpLevelDirInfo(leveldir_first, 0);
1881 static void SaveUserLevelInfo()
1887 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1889 if (!(file = fopen(filename, MODE_WRITE)))
1891 Error(ERR_WARN, "cannot write level info file '%s'", filename);
1896 /* always start with reliable default values */
1897 setLevelDirInfoToDefaults(&ldi);
1899 ldi.name = getLoginName();
1900 ldi.author = getRealName();
1902 ldi.first_level = 1;
1903 ldi.sort_priority = LEVELCLASS_USER_START;
1904 ldi.readonly = FALSE;
1906 fprintf(file, "%s\n\n",
1907 getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
1909 for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1910 if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1911 i != LEVELINFO_TOKEN_NAME_SORTING &&
1912 i != LEVELINFO_TOKEN_IMPORTED_FROM)
1913 fprintf(file, "%s\n", getSetupLine("", i));
1918 chmod(filename, SETUP_PERMS);
1924 struct SetupFileList *setup_file_list = NULL;
1926 /* always start with reliable default values */
1927 setSetupInfoToDefaults(&setup);
1929 filename = getPath2(getSetupDir(), SETUP_FILENAME);
1931 setup_file_list = loadSetupFileList(filename);
1933 if (setup_file_list)
1935 checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1936 decodeSetupFileList(setup_file_list);
1938 setup.direct_draw = !setup.double_buffering;
1940 freeSetupFileList(setup_file_list);
1942 /* needed to work around problems with fixed length strings */
1943 if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
1944 setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
1945 else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
1947 char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
1949 strcpy(new_name, setup.player_name);
1950 free(setup.player_name);
1951 setup.player_name = new_name;
1955 Error(ERR_WARN, "using default setup values");
1960 static char *getSetupLine(char *prefix, int token_nr)
1963 static char entry[MAX_LINE_LEN];
1964 int token_type = token_info[token_nr].type;
1965 void *setup_value = token_info[token_nr].value;
1966 char *token_text = token_info[token_nr].text;
1968 /* start with the prefix, token and some spaces to format output line */
1969 sprintf(entry, "%s%s:", prefix, token_text);
1970 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1973 /* continue with the token's value (which can have different types) */
1977 strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1981 strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1986 Key key = *(Key *)setup_value;
1987 char *keyname = getKeyNameFromKey(key);
1989 strcat(entry, getX11KeyNameFromKey(key));
1990 for (i=strlen(entry); i<50; i++)
1993 /* add comment, if useful */
1994 if (strcmp(keyname, "(undefined)") != 0 &&
1995 strcmp(keyname, "(unknown)") != 0)
1997 strcat(entry, "# ");
1998 strcat(entry, keyname);
2005 char buffer[MAX_LINE_LEN];
2007 sprintf(buffer, "%d", *(int *)setup_value);
2008 strcat(entry, buffer);
2013 strcat(entry, *(char **)setup_value);
2029 InitUserDataDirectory();
2031 filename = getPath2(getSetupDir(), SETUP_FILENAME);
2033 if (!(file = fopen(filename, MODE_WRITE)))
2035 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2040 fprintf(file, "%s\n",
2041 getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
2042 fprintf(file, "\n");
2044 /* handle global setup values */
2046 for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
2048 fprintf(file, "%s\n", getSetupLine("", i));
2050 /* just to make things nicer :) */
2051 if (i == SETUP_TOKEN_PLAYER_NAME)
2052 fprintf(file, "\n");
2055 /* handle player specific setup values */
2056 for (pnr=0; pnr<MAX_PLAYERS; pnr++)
2060 sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
2061 fprintf(file, "\n");
2063 sii = setup.input[pnr];
2064 for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
2065 fprintf(file, "%s\n", getSetupLine(prefix, i));
2071 chmod(filename, SETUP_PERMS);
2074 void LoadLevelSetup_LastSeries()
2077 struct SetupFileList *level_setup_list = NULL;
2079 /* always start with reliable default values */
2080 leveldir_current = leveldir_first;
2082 /* ----------------------------------------------------------------------- */
2083 /* ~/.rocksndiamonds/levelsetup.conf */
2084 /* ----------------------------------------------------------------------- */
2086 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2088 if ((level_setup_list = loadSetupFileList(filename)))
2090 char *last_level_series =
2091 getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
2093 leveldir_current = getLevelDirInfoFromFilename(last_level_series);
2094 if (leveldir_current == NULL)
2095 leveldir_current = leveldir_first;
2097 checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2099 freeSetupFileList(level_setup_list);
2102 Error(ERR_WARN, "using default setup values");
2107 void SaveLevelSetup_LastSeries()
2110 char *level_subdir = leveldir_current->filename;
2113 /* ----------------------------------------------------------------------- */
2114 /* ~/.rocksndiamonds/levelsetup.conf */
2115 /* ----------------------------------------------------------------------- */
2117 InitUserDataDirectory();
2119 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2121 if (!(file = fopen(filename, MODE_WRITE)))
2123 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2128 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2129 LEVELSETUP_COOKIE));
2130 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2136 chmod(filename, SETUP_PERMS);
2139 static void checkSeriesInfo()
2141 static char *level_directory = NULL;
2143 struct dirent *dir_entry;
2145 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2147 level_directory = getPath2((leveldir_current->user_defined ?
2148 getUserLevelDir("") :
2149 options.level_directory),
2150 leveldir_current->fullpath);
2152 if ((dir = opendir(level_directory)) == NULL)
2154 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2158 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
2160 if (strlen(dir_entry->d_name) > 4 &&
2161 dir_entry->d_name[3] == '.' &&
2162 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2164 char levelnum_str[4];
2167 strncpy(levelnum_str, dir_entry->d_name, 3);
2168 levelnum_str[3] = '\0';
2170 levelnum_value = atoi(levelnum_str);
2172 if (levelnum_value < leveldir_current->first_level)
2174 Error(ERR_WARN, "additional level %d found", levelnum_value);
2175 leveldir_current->first_level = levelnum_value;
2177 else if (levelnum_value > leveldir_current->last_level)
2179 Error(ERR_WARN, "additional level %d found", levelnum_value);
2180 leveldir_current->last_level = levelnum_value;
2188 void LoadLevelSetup_SeriesInfo()
2191 struct SetupFileList *level_setup_list = NULL;
2192 char *level_subdir = leveldir_current->filename;
2194 /* always start with reliable default values */
2195 level_nr = leveldir_current->first_level;
2197 checkSeriesInfo(leveldir_current);
2199 /* ----------------------------------------------------------------------- */
2200 /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf */
2201 /* ----------------------------------------------------------------------- */
2203 level_subdir = leveldir_current->filename;
2205 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2207 if ((level_setup_list = loadSetupFileList(filename)))
2211 token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2215 level_nr = atoi(token_value);
2217 if (level_nr < leveldir_current->first_level)
2218 level_nr = leveldir_current->first_level;
2219 if (level_nr > leveldir_current->last_level)
2220 level_nr = leveldir_current->last_level;
2223 token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2227 int level_nr = atoi(token_value);
2229 if (level_nr < leveldir_current->first_level)
2230 level_nr = leveldir_current->first_level;
2231 if (level_nr > leveldir_current->last_level + 1)
2232 level_nr = leveldir_current->last_level;
2234 if (leveldir_current->user_defined)
2235 level_nr = leveldir_current->last_level;
2237 leveldir_current->handicap_level = level_nr;
2240 checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2242 freeSetupFileList(level_setup_list);
2245 Error(ERR_WARN, "using default setup values");
2250 void SaveLevelSetup_SeriesInfo()
2253 char *level_subdir = leveldir_current->filename;
2254 char *level_nr_str = int2str(level_nr, 0);
2255 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2258 /* ----------------------------------------------------------------------- */
2259 /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf */
2260 /* ----------------------------------------------------------------------- */
2262 InitLevelSetupDirectory(level_subdir);
2264 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2266 if (!(file = fopen(filename, MODE_WRITE)))
2268 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2273 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2274 LEVELSETUP_COOKIE));
2275 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2277 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2278 handicap_level_str));
2283 chmod(filename, SETUP_PERMS);
2285 /* LocalWords: Rocks'n