1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://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 int default_score[LEVEL_SCORE_ELEMENTS] =
35 [SC_COLLECTIBLE] = 10,
43 // ============================================================================
44 // level file functions
45 // ============================================================================
47 static void ReadChunk_MM_VERS(File *file, int *file_version, int *game_version)
49 int file_version_major, file_version_minor, file_version_patch;
50 int game_version_major, game_version_minor, game_version_patch;
52 file_version_major = getFile8Bit(file);
53 file_version_minor = getFile8Bit(file);
54 file_version_patch = getFile8Bit(file);
55 getFile8Bit(file); // not used
57 game_version_major = getFile8Bit(file);
58 game_version_minor = getFile8Bit(file);
59 game_version_patch = getFile8Bit(file);
60 getFile8Bit(file); // not used
62 *file_version = MM_VERSION_IDENT(file_version_major,
66 *game_version = MM_VERSION_IDENT(game_version_major,
71 static void WriteChunk_MM_VERS(FILE *file, int file_version, int game_version)
73 int file_version_major = MM_VERSION_MAJOR(file_version);
74 int file_version_minor = MM_VERSION_MINOR(file_version);
75 int file_version_patch = MM_VERSION_PATCH(file_version);
76 int game_version_major = MM_VERSION_MAJOR(game_version);
77 int game_version_minor = MM_VERSION_MINOR(game_version);
78 int game_version_patch = MM_VERSION_PATCH(game_version);
80 fputc(file_version_major, file);
81 fputc(file_version_minor, file);
82 fputc(file_version_patch, file);
83 fputc(0, file); // not used
85 fputc(game_version_major, file);
86 fputc(game_version_minor, file);
87 fputc(game_version_patch, file);
88 fputc(0, file); // not used
91 void setLevelInfoToDefaults_MM(void)
95 native_mm_level.file_version = MM_FILE_VERSION_ACTUAL;
96 native_mm_level.game_version = MM_GAME_VERSION_ACTUAL;
98 native_mm_level.encoding_16bit_field = FALSE; // default: only 8-bit elements
100 native_mm_level.fieldx = STD_LEV_FIELDX;
101 native_mm_level.fieldy = STD_LEV_FIELDY;
103 for (x = 0; x < MAX_LEV_FIELDX; x++)
104 for (y = 0; y < MAX_LEV_FIELDY; y++)
105 native_mm_level.field[x][y] = Ur[x][y] = EL_EMPTY;
107 native_mm_level.time = 100;
108 native_mm_level.kettles_needed = 0;
109 native_mm_level.auto_count_kettles = TRUE;
110 native_mm_level.amoeba_speed = 0;
111 native_mm_level.time_fuse = 25;
112 native_mm_level.time_bomb = 75;
113 native_mm_level.time_ball = 75;
114 native_mm_level.time_block = 75;
115 native_mm_level.mm_laser_red = FALSE;
116 native_mm_level.mm_laser_green = FALSE;
117 native_mm_level.mm_laser_blue = TRUE;
118 native_mm_level.df_laser_red = TRUE;
119 native_mm_level.df_laser_green = TRUE;
120 native_mm_level.df_laser_blue = FALSE;
122 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
123 native_mm_level.name[i] = '\0';
124 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
125 native_mm_level.author[i] = '\0';
127 strcpy(native_mm_level.name, NAMELESS_LEVEL_NAME);
128 strcpy(native_mm_level.author, ANONYMOUS_NAME);
130 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
131 native_mm_level.score[i] = 10;
136 EL_MIRROR_FIXED_START,
138 EL_POLAR_CROSS_START,
144 int num_ball_contents = sizeof(ball_content) / sizeof(int);
146 native_mm_level.num_ball_contents = num_ball_contents;
147 native_mm_level.ball_choice_mode = ANIM_RANDOM;
149 for (i = 0; i < num_ball_contents; i++)
150 native_mm_level.ball_content[i] = ball_content[i];
152 native_mm_level.field[0][0] = Ur[0][0] = EL_MCDUFFIN_RIGHT;
153 native_mm_level.field[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
154 Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED;
157 static int checkLevelElement(int element)
159 if (element >= EL_FIRST_RUNTIME_EL)
161 Warn("invalid level element %d", element);
163 element = EL_CHAR_FRAGE;
169 static int LoadLevel_MM_VERS(File *file, int chunk_size,
170 struct LevelInfo_MM *level)
172 ReadChunk_MM_VERS(file, &level->file_version, &level->game_version);
177 static int LoadLevel_MM_HEAD(File *file, int chunk_size,
178 struct LevelInfo_MM *level)
183 level->fieldx = getFile8Bit(file);
184 level->fieldy = getFile8Bit(file);
186 level->time = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
187 level->kettles_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
189 // one time unit was equivalent to four seconds in level files up to 2.0.x
190 if (level->file_version <= MM_FILE_VERSION_2_0)
193 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
194 level->name[i] = getFile8Bit(file);
195 level->name[MAX_LEVEL_NAME_LEN] = 0;
197 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
198 level->score[i] = getFile8Bit(file);
200 // scores were 0 and hardcoded in game engine in level files up to 2.0.x
201 if (level->file_version <= MM_FILE_VERSION_2_0)
202 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
203 if (level->score[i] == 0)
204 level->score[i] = default_score[i];
206 level->auto_count_kettles = (getFile8Bit(file) == 1 ? TRUE : FALSE);
207 level->amoeba_speed = getFile8Bit(file);
208 level->time_fuse = getFile8Bit(file);
210 // fuse time was 0 and hardcoded in game engine in level files up to 2.0.x
211 if (level->file_version <= MM_FILE_VERSION_2_0)
212 level->time_fuse = 25;
214 laser_color = getFile8Bit(file);
215 level->mm_laser_red = (laser_color >> 2) & 0x01;
216 level->mm_laser_green = (laser_color >> 1) & 0x01;
217 level->mm_laser_blue = (laser_color >> 0) & 0x01;
219 level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE);
221 ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
226 static int LoadLevel_MM_AUTH(File *file, int chunk_size,
227 struct LevelInfo_MM *level)
231 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
232 level->author[i] = getFile8Bit(file);
233 level->author[MAX_LEVEL_NAME_LEN] = 0;
238 static int LoadLevel_MM_BODY(File *file, int chunk_size,
239 struct LevelInfo_MM *level)
242 int chunk_size_expected = level->fieldx * level->fieldy;
244 /* Note: "chunk_size" was wrong before version 2.0 when elements are
245 stored with 16-bit encoding (and should be twice as big then).
246 Even worse, playfield data was stored 16-bit when only yamyam content
247 contained 16-bit elements and vice versa. */
249 if (level->encoding_16bit_field && level->file_version >= MM_FILE_VERSION_2_0)
250 chunk_size_expected *= 2;
252 if (chunk_size_expected != chunk_size)
254 ReadUnusedBytesFromFile(file, chunk_size);
256 return chunk_size_expected;
259 for (y = 0; y < level->fieldy; y++)
260 for (x = 0; x < level->fieldx; x++)
261 native_mm_level.field[x][y] = Ur[x][y] =
262 checkLevelElement(level->encoding_16bit_field ?
263 getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) :
268 boolean LoadNativeLevel_MM(char *filename, boolean level_info_only)
270 char cookie[MAX_LINE_LEN];
271 char chunk_name[CHUNK_ID_LEN + 1];
279 int (*loader)(File *, int, struct LevelInfo_MM *);
283 { "VERS", FILE_VERS_CHUNK_SIZE, LoadLevel_MM_VERS },
284 { "HEAD", LEVEL_HEADER_SIZE, LoadLevel_MM_HEAD },
285 { "AUTH", MAX_LEVEL_AUTHOR_LEN, LoadLevel_MM_AUTH },
286 { "BODY", -1, LoadLevel_MM_BODY },
290 // always start with reliable default values
291 setLevelInfoToDefaults_MM();
293 if (!(file = openFile(filename, MODE_READ)))
295 if (!level_info_only)
296 Warn("cannot read level '%s' - creating new level", filename);
301 getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
302 if (strcmp(chunk_name, "MMII") == 0)
304 getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); // not used
306 getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
307 if (strcmp(chunk_name, "CAVE") != 0)
309 Warn("unknown format of level file '%s'", filename);
316 else // check for pre-2.0 file format with cookie string
318 strcpy(cookie, chunk_name);
319 getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4);
320 if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
321 cookie[strlen(cookie) - 1] = '\0';
323 if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
325 Warn("unknown format of level file '%s'", filename);
332 if ((native_mm_level.file_version = getFileVersionFromCookieString(cookie))
335 Warn("unsupported version of level file '%s'", filename);
343 while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN))
347 while (chunk_info[i].name != NULL &&
348 strcmp(chunk_name, chunk_info[i].name) != 0)
351 if (chunk_info[i].name == NULL)
353 Warn("unknown chunk '%s' in level file '%s'",
354 chunk_name, filename);
356 ReadUnusedBytesFromFile(file, chunk_size);
358 else if (chunk_info[i].size != -1 &&
359 chunk_info[i].size != chunk_size)
361 Warn("wrong size (%d) of chunk '%s' in level file '%s'",
362 chunk_size, chunk_name, filename);
364 ReadUnusedBytesFromFile(file, chunk_size);
368 // call function to load this level chunk
369 int chunk_size_expected =
370 (chunk_info[i].loader)(file, chunk_size, &native_mm_level);
372 // the size of some chunks cannot be checked before reading other
373 // chunks first (like "HEAD" and "BODY") that contain some header
374 // information, so check them here
375 if (chunk_size_expected != chunk_size)
376 Warn("wrong size (%d) of chunk '%s' in level file '%s'",
377 chunk_size, chunk_name, filename);
386 static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level)
391 fputc(level->fieldx, file);
392 fputc(level->fieldy, file);
394 putFile16BitInteger(file, level->time, BYTE_ORDER_BIG_ENDIAN);
395 putFile16BitInteger(file, level->kettles_needed, BYTE_ORDER_BIG_ENDIAN);
397 for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
398 fputc(level->name[i], file);
400 for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
401 fputc(level->score[i], file);
403 fputc((level->auto_count_kettles ? 1 : 0), file);
404 fputc(level->amoeba_speed, file);
405 fputc(level->time_fuse, file);
407 laser_color = ((level->mm_laser_red << 2) |
408 (level->mm_laser_green << 1) |
409 (level->mm_laser_blue << 0));
410 fputc(laser_color, file);
412 fputc((level->encoding_16bit_field ? 1 : 0), file);
414 WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
417 static void SaveLevel_MM_AUTH(FILE *file, struct LevelInfo_MM *level)
421 for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
422 fputc(level->author[i], file);
425 static void SaveLevel_MM_BODY(FILE *file, struct LevelInfo_MM *level)
429 for (y = 0; y < level->fieldy; y++)
430 for (x = 0; x < level->fieldx; x++)
431 if (level->encoding_16bit_field)
432 putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN);
434 fputc(Ur[x][y], file);
437 void SaveNativeLevel_MM(char *filename)
443 if (!(file = fopen(filename, MODE_WRITE)))
445 Warn("cannot save level file '%s'", filename);
450 // check level field for 16-bit elements
451 native_mm_level.encoding_16bit_field = FALSE;
453 for (y = 0; y < native_mm_level.fieldy; y++)
454 for (x = 0; x < native_mm_level.fieldx; x++)
456 native_mm_level.encoding_16bit_field = TRUE;
459 native_mm_level.fieldx * native_mm_level.fieldy *
460 (native_mm_level.encoding_16bit_field ? 2 : 1);
462 putFileChunk(file, "MMII", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN);
463 putFileChunk(file, "CAVE", CHUNK_SIZE_NONE, BYTE_ORDER_BIG_ENDIAN);
465 putFileChunk(file, "VERS", FILE_VERS_CHUNK_SIZE, BYTE_ORDER_BIG_ENDIAN);
466 WriteChunk_MM_VERS(file, MM_FILE_VERSION_ACTUAL, MM_GAME_VERSION_ACTUAL);
468 putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
469 SaveLevel_MM_HEAD(file, &native_mm_level);
471 putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
472 SaveLevel_MM_AUTH(file, &native_mm_level);
474 putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN);
475 SaveLevel_MM_BODY(file, &native_mm_level);
479 SetFilePermissions(filename, PERMS_PRIVATE);