1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #define CHUNK_ID_LEN 4 /* IFF style chunk id length */
22 #define CHUNK_SIZE_UNDEFINED 0 /* undefined chunk size == 0 */
23 #define CHUNK_SIZE_NONE -1 /* do not write chunk size */
24 #define FILE_VERS_CHUNK_SIZE 8 /* size of file version chunk */
25 #define LEVEL_HEADER_SIZE 80 /* size of level file header */
26 #define LEVEL_HEADER_UNUSED 19 /* unused level header bytes */
28 /* file identifier strings */
29 #define LEVEL_COOKIE_TMPL "MIRRORMAGIC_LEVEL_FILE_VERSION_x.x"
30 #define SCORE_COOKIE "MIRRORMAGIC_SCORE_FILE_VERSION_1.4"
33 /* ========================================================================= */
34 /* level file functions */
35 /* ========================================================================= */
37 static void ReadChunk_MM_VERS(File *file, int *file_version, int *game_version)
39 int file_version_major, file_version_minor, file_version_patch;
40 int game_version_major, game_version_minor, game_version_patch;
42 file_version_major = getFile8Bit(file);
43 file_version_minor = getFile8Bit(file);
44 file_version_patch = getFile8Bit(file);
45 getFile8Bit(file); /* not used */
47 game_version_major = getFile8Bit(file);
48 game_version_minor = getFile8Bit(file);
49 game_version_patch = getFile8Bit(file);
50 getFile8Bit(file); /* not used */
52 *file_version = MM_VERSION_IDENT(file_version_major,
56 *game_version = MM_VERSION_IDENT(game_version_major,
61 static void WriteChunk_MM_VERS(FILE *file, int file_version, int game_version)
63 int file_version_major = MM_VERSION_MAJOR(file_version);
64 int file_version_minor = MM_VERSION_MINOR(file_version);
65 int file_version_patch = MM_VERSION_PATCH(file_version);
66 int game_version_major = MM_VERSION_MAJOR(game_version);
67 int game_version_minor = MM_VERSION_MINOR(game_version);
68 int game_version_patch = MM_VERSION_PATCH(game_version);
70 fputc(file_version_major, file);
71 fputc(file_version_minor, file);
72 fputc(file_version_patch, file);
73 fputc(0, file); /* not used */
75 fputc(game_version_major, file);
76 fputc(game_version_minor, file);
77 fputc(game_version_patch, file);
78 fputc(0, file); /* not used */
81 void setLevelInfoToDefaults_MM()
85 native_mm_level.file_version = MM_FILE_VERSION_ACTUAL;
86 native_mm_level.game_version = MM_GAME_VERSION_ACTUAL;
88 native_mm_level.encoding_16bit_field = FALSE; /* default: only 8-bit elements */
90 lev_fieldx = native_mm_level.fieldx = STD_LEV_FIELDX;
91 lev_fieldy = native_mm_level.fieldy = STD_LEV_FIELDY;
93 for (x = 0; x < MAX_LEV_FIELDX; x++)
94 for (y = 0; y < MAX_LEV_FIELDY; y++)
95 native_mm_level.field[x][y] = Feld[x][y] = Ur[x][y] = EL_EMPTY;
97 native_mm_level.time = 100;
98 native_mm_level.kettles_needed = 0;
99 native_mm_level.auto_count_kettles = TRUE;
100 native_mm_level.amoeba_speed = 0;
101 native_mm_level.time_fuse = 0;
102 native_mm_level.laser_red = FALSE;
103 native_mm_level.laser_green = FALSE;
104 native_mm_level.laser_blue = TRUE;
106 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
107 native_mm_level.name[i] = '\0';
108 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
109 native_mm_level.author[i] = '\0';
111 strcpy(native_mm_level.name, NAMELESS_LEVEL_NAME);
112 strcpy(native_mm_level.author, ANONYMOUS_NAME);
114 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
115 native_mm_level.score[i] = 10;
117 native_mm_level.field[0][0] = Feld[0][0] = Ur[0][0] = EL_MCDUFFIN_RIGHT;
118 native_mm_level.field[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
119 Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
120 Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED;
123 static int checkLevelElement(int element)
125 if (element >= EL_FIRST_RUNTIME_EL)
127 Error(ERR_WARN, "invalid level element %d", element);
128 element = EL_CHAR_FRAGE;
134 static int LoadLevel_MM_VERS(File *file, int chunk_size,
135 struct LevelInfo_MM *level)
137 ReadChunk_MM_VERS(file, &level->file_version, &level->game_version);
142 static int LoadLevel_MM_HEAD(File *file, int chunk_size,
143 struct LevelInfo_MM *level)
148 lev_fieldx = level->fieldx = getFile8Bit(file);
149 lev_fieldy = level->fieldy = getFile8Bit(file);
151 level->time = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
152 level->kettles_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
154 // one time unit was equivalent to four seconds in level files up to 2.0.x
155 if (level->file_version <= MM_FILE_VERSION_2_0)
158 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
159 level->name[i] = getFile8Bit(file);
160 level->name[MAX_LEVEL_NAME_LEN] = 0;
162 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
163 level->score[i] = getFile8Bit(file);
165 level->auto_count_kettles = (getFile8Bit(file) == 1 ? TRUE : FALSE);
166 level->amoeba_speed = getFile8Bit(file);
167 level->time_fuse = getFile8Bit(file);
169 laser_color = getFile8Bit(file);
170 level->laser_red = (laser_color >> 2) & 0x01;
171 level->laser_green = (laser_color >> 1) & 0x01;
172 level->laser_blue = (laser_color >> 0) & 0x01;
174 level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE);
176 ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
181 static int LoadLevel_MM_AUTH(File *file, int chunk_size,
182 struct LevelInfo_MM *level)
186 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
187 level->author[i] = getFile8Bit(file);
188 level->author[MAX_LEVEL_NAME_LEN] = 0;
193 static int LoadLevel_MM_BODY(File *file, int chunk_size,
194 struct LevelInfo_MM *level)
197 int chunk_size_expected = level->fieldx * level->fieldy;
199 /* Note: "chunk_size" was wrong before version 2.0 when elements are
200 stored with 16-bit encoding (and should be twice as big then).
201 Even worse, playfield data was stored 16-bit when only yamyam content
202 contained 16-bit elements and vice versa. */
204 if (level->encoding_16bit_field && level->file_version >= MM_FILE_VERSION_2_0)
205 chunk_size_expected *= 2;
207 if (chunk_size_expected != chunk_size)
209 ReadUnusedBytesFromFile(file, chunk_size);
211 return chunk_size_expected;
214 for (y = 0; y < level->fieldy; y++)
215 for (x = 0; x < level->fieldx; x++)
216 native_mm_level.field[x][y] = Feld[x][y] = Ur[x][y] =
217 checkLevelElement(level->encoding_16bit_field ?
218 getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) :
223 boolean LoadNativeLevel_MM(char *filename, boolean level_info_only)
225 char cookie[MAX_LINE_LEN];
226 char chunk_name[CHUNK_ID_LEN + 1];
234 int (*loader)(File *, int, struct LevelInfo_MM *);
238 { "VERS", FILE_VERS_CHUNK_SIZE, LoadLevel_MM_VERS },
239 { "HEAD", LEVEL_HEADER_SIZE, LoadLevel_MM_HEAD },
240 { "AUTH", MAX_LEVEL_AUTHOR_LEN, LoadLevel_MM_AUTH },
241 { "BODY", -1, LoadLevel_MM_BODY },
245 /* always start with reliable default values */
246 setLevelInfoToDefaults_MM();
248 if (!(file = openFile(filename, MODE_READ)))
250 if (!level_info_only)
251 Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
256 getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
257 if (strcmp(chunk_name, "MMII") == 0)
259 getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); /* not used */
261 getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
262 if (strcmp(chunk_name, "CAVE") != 0)
264 Error(ERR_WARN, "unknown format of level file '%s'", filename);
271 else /* check for pre-2.0 file format with cookie string */
273 strcpy(cookie, chunk_name);
274 getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4);
275 if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
276 cookie[strlen(cookie) - 1] = '\0';
278 if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
280 Error(ERR_WARN, "unknown format of level file '%s'", filename);
287 if ((native_mm_level.file_version = getFileVersionFromCookieString(cookie))
290 Error(ERR_WARN, "unsupported version of level file '%s'", filename);
298 while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN))
302 while (chunk_info[i].name != NULL &&
303 strcmp(chunk_name, chunk_info[i].name) != 0)
306 if (chunk_info[i].name == NULL)
308 Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
309 chunk_name, filename);
311 ReadUnusedBytesFromFile(file, chunk_size);
313 else if (chunk_info[i].size != -1 &&
314 chunk_info[i].size != chunk_size)
316 Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
317 chunk_size, chunk_name, filename);
319 ReadUnusedBytesFromFile(file, chunk_size);
323 /* call function to load this level chunk */
324 int chunk_size_expected =
325 (chunk_info[i].loader)(file, chunk_size, &native_mm_level);
327 /* the size of some chunks cannot be checked before reading other
328 chunks first (like "HEAD" and "BODY") that contain some header
329 information, so check them here */
330 if (chunk_size_expected != chunk_size)
331 Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
332 chunk_size, chunk_name, filename);
341 static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level)
346 fputc(level->fieldx, file);
347 fputc(level->fieldy, file);
349 putFile16BitInteger(file, level->time, BYTE_ORDER_BIG_ENDIAN);
350 putFile16BitInteger(file, level->kettles_needed, BYTE_ORDER_BIG_ENDIAN);
352 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
353 fputc(level->name[i], file);
355 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
356 fputc(level->score[i], file);
358 fputc((level->auto_count_kettles ? 1 : 0), file);
359 fputc(level->amoeba_speed, file);
360 fputc(level->time_fuse, file);
362 laser_color = ((level->laser_red << 2) |
363 (level->laser_green << 1) |
364 (level->laser_blue << 0));
365 fputc(laser_color, file);
367 fputc((level->encoding_16bit_field ? 1 : 0), file);
369 WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
372 static void SaveLevel_MM_AUTH(FILE *file, struct LevelInfo_MM *level)
376 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
377 fputc(level->author[i], file);
380 static void SaveLevel_MM_BODY(FILE *file, struct LevelInfo_MM *level)
384 for (y = 0; y < level->fieldy; y++)
385 for (x = 0; x < level->fieldx; x++)
386 if (level->encoding_16bit_field)
387 putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN);
389 fputc(Ur[x][y], file);
392 void SaveNativeLevel_MM(char *filename)
398 if (!(file = fopen(filename, MODE_WRITE)))
400 Error(ERR_WARN, "cannot save level file '%s'", filename);
405 /* check level field for 16-bit elements */
406 native_mm_level.encoding_16bit_field = FALSE;
408 for (y = 0; y < native_mm_level.fieldy; y++)
409 for (x = 0; x < native_mm_level.fieldx; x++)
411 native_mm_level.encoding_16bit_field = TRUE;
414 native_mm_level.fieldx * native_mm_level.fieldy *
415 (native_mm_level.encoding_16bit_field ? 2 : 1);
417 putFileChunk(file, "MMII", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN);
418 putFileChunk(file, "CAVE", CHUNK_SIZE_NONE, BYTE_ORDER_BIG_ENDIAN);
420 putFileChunk(file, "VERS", FILE_VERS_CHUNK_SIZE, BYTE_ORDER_BIG_ENDIAN);
421 WriteChunk_MM_VERS(file, MM_FILE_VERSION_ACTUAL, MM_GAME_VERSION_ACTUAL);
423 putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
424 SaveLevel_MM_HEAD(file, &native_mm_level);
426 putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
427 SaveLevel_MM_AUTH(file, &native_mm_level);
429 putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN);
430 SaveLevel_MM_BODY(file, &native_mm_level);
434 SetFilePermissions(filename, PERMS_PRIVATE);