added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / game_mm / mm_files.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_files.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <ctype.h>
15 #include <dirent.h>
16
17 #include "main_mm.h"
18
19 #include "mm_main.h"
20
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
27
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"
31
32
33 int default_score[LEVEL_SCORE_ELEMENTS] =
34 {
35   [SC_COLLECTIBLE]      = 10,
36   [SC_PACMAN]           = 50,
37   [SC_KEY]              = 10,
38   [SC_TIME_BONUS]       = 1,
39   [SC_LIGHTBALL]        = 10,
40 };
41
42
43 // ============================================================================
44 // level file functions
45 // ============================================================================
46
47 static void ReadChunk_MM_VERS(File *file, int *file_version, int *game_version)
48 {
49   int file_version_major, file_version_minor, file_version_patch;
50   int game_version_major, game_version_minor, game_version_patch;
51
52   file_version_major = getFile8Bit(file);
53   file_version_minor = getFile8Bit(file);
54   file_version_patch = getFile8Bit(file);
55   getFile8Bit(file);            // not used
56
57   game_version_major = getFile8Bit(file);
58   game_version_minor = getFile8Bit(file);
59   game_version_patch = getFile8Bit(file);
60   getFile8Bit(file);            // not used
61
62   *file_version = MM_VERSION_IDENT(file_version_major,
63                                    file_version_minor,
64                                    file_version_patch);
65
66   *game_version = MM_VERSION_IDENT(game_version_major,
67                                    game_version_minor,
68                                    game_version_patch);
69 }
70
71 static void WriteChunk_MM_VERS(FILE *file, int file_version, int game_version)
72 {
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);
79
80   fputc(file_version_major, file);
81   fputc(file_version_minor, file);
82   fputc(file_version_patch, file);
83   fputc(0, file);       // not used
84
85   fputc(game_version_major, file);
86   fputc(game_version_minor, file);
87   fputc(game_version_patch, file);
88   fputc(0, file);       // not used
89 }
90
91 void setLevelInfoToDefaults_MM(void)
92 {
93   int i, x, y;
94
95   native_mm_level.file_version = MM_FILE_VERSION_ACTUAL;
96   native_mm_level.game_version = MM_GAME_VERSION_ACTUAL;
97
98   native_mm_level.encoding_16bit_field = FALSE; // default: only 8-bit elements
99
100   native_mm_level.fieldx = STD_LEV_FIELDX;
101   native_mm_level.fieldy = STD_LEV_FIELDY;
102
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;
106
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;
121
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';
126
127   strcpy(native_mm_level.name, NAMELESS_LEVEL_NAME);
128   strcpy(native_mm_level.author, ANONYMOUS_NAME);
129
130   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
131     native_mm_level.score[i] = 10;
132
133   int ball_content[] =
134   {
135     EL_MIRROR_START,
136     EL_MIRROR_FIXED_START,
137     EL_POLAR_START,
138     EL_POLAR_CROSS_START,
139     EL_PACMAN_START,
140     EL_KETTLE,
141     EL_BOMB,
142     EL_PRISM
143   };
144   int num_ball_contents = sizeof(ball_content) / sizeof(int);
145
146   native_mm_level.num_ball_contents = num_ball_contents;
147   native_mm_level.ball_choice_mode = ANIM_RANDOM;
148
149   for (i = 0; i < num_ball_contents; i++)
150     native_mm_level.ball_content[i] = ball_content[i];
151
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;
155 }
156
157 static int checkLevelElement(int element)
158 {
159   if (element >= EL_FIRST_RUNTIME_EL)
160   {
161     Warn("invalid level element %d", element);
162
163     element = EL_CHAR_FRAGE;
164   }
165
166   return element;
167 }
168
169 static int LoadLevel_MM_VERS(File *file, int chunk_size,
170                              struct LevelInfo_MM *level)
171 {
172   ReadChunk_MM_VERS(file, &level->file_version, &level->game_version);
173
174   return chunk_size;
175 }
176
177 static int LoadLevel_MM_HEAD(File *file, int chunk_size,
178                              struct LevelInfo_MM *level)
179 {
180   int i;
181   int laser_color;
182
183   level->fieldx = getFile8Bit(file);
184   level->fieldy = getFile8Bit(file);
185
186   level->time           = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
187   level->kettles_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
188
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)
191     level->time *= 4;
192
193   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
194     level->name[i] = getFile8Bit(file);
195   level->name[MAX_LEVEL_NAME_LEN] = 0;
196
197   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
198     level->score[i] = getFile8Bit(file);
199
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];
205
206   level->auto_count_kettles     = (getFile8Bit(file) == 1 ? TRUE : FALSE);
207   level->amoeba_speed           = getFile8Bit(file);
208   level->time_fuse              = getFile8Bit(file);
209
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;
213
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;
218
219   level->encoding_16bit_field   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
220
221   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
222
223   return chunk_size;
224 }
225
226 static int LoadLevel_MM_AUTH(File *file, int chunk_size,
227                              struct LevelInfo_MM *level)
228 {
229   int i;
230
231   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
232     level->author[i] = getFile8Bit(file);
233   level->author[MAX_LEVEL_NAME_LEN] = 0;
234
235   return chunk_size;
236 }
237
238 static int LoadLevel_MM_BODY(File *file, int chunk_size,
239                              struct LevelInfo_MM *level)
240 {
241   int x, y;
242   int chunk_size_expected = level->fieldx * level->fieldy;
243
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. */
248
249   if (level->encoding_16bit_field && level->file_version >= MM_FILE_VERSION_2_0)
250     chunk_size_expected *= 2;
251
252   if (chunk_size_expected != chunk_size)
253   {
254     ReadUnusedBytesFromFile(file, chunk_size);
255
256     return chunk_size_expected;
257   }
258
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) :
264                           getFile8Bit(file));
265   return chunk_size;
266 }
267
268 boolean LoadNativeLevel_MM(char *filename, boolean level_info_only)
269 {
270   char cookie[MAX_LINE_LEN];
271   char chunk_name[CHUNK_ID_LEN + 1];
272   int chunk_size;
273   File *file;
274
275   static struct
276   {
277     char *name;
278     int size;
279     int (*loader)(File *, int, struct LevelInfo_MM *);
280   }
281   chunk_info[] =
282   {
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 },
287     {  NULL,  0,                        NULL }
288   };
289
290   // always start with reliable default values
291   setLevelInfoToDefaults_MM();
292
293   if (!(file = openFile(filename, MODE_READ)))
294   {
295     if (!level_info_only)
296       Warn("cannot read level '%s' - creating new level", filename);
297
298     return FALSE;
299   }
300
301   getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
302   if (strcmp(chunk_name, "MMII") == 0)
303   {
304     getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);   // not used
305
306     getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
307     if (strcmp(chunk_name, "CAVE") != 0)
308     {
309       Warn("unknown format of level file '%s'", filename);
310
311       closeFile(file);
312
313       return FALSE;
314     }
315   }
316   else  // check for pre-2.0 file format with cookie string
317   {
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';
322
323     if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
324     {
325       Warn("unknown format of level file '%s'", filename);
326
327       closeFile(file);
328
329       return FALSE;
330     }
331
332     if ((native_mm_level.file_version = getFileVersionFromCookieString(cookie))
333         == -1)
334     {
335       Warn("unsupported version of level file '%s'", filename);
336
337       closeFile(file);
338
339       return FALSE;
340     }
341   }
342
343   while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN))
344   {
345     int i = 0;
346
347     while (chunk_info[i].name != NULL &&
348            strcmp(chunk_name, chunk_info[i].name) != 0)
349       i++;
350
351     if (chunk_info[i].name == NULL)
352     {
353       Warn("unknown chunk '%s' in level file '%s'",
354             chunk_name, filename);
355
356       ReadUnusedBytesFromFile(file, chunk_size);
357     }
358     else if (chunk_info[i].size != -1 &&
359              chunk_info[i].size != chunk_size)
360     {
361       Warn("wrong size (%d) of chunk '%s' in level file '%s'",
362             chunk_size, chunk_name, filename);
363
364       ReadUnusedBytesFromFile(file, chunk_size);
365     }
366     else
367     {
368       // call function to load this level chunk
369       int chunk_size_expected =
370         (chunk_info[i].loader)(file, chunk_size, &native_mm_level);
371
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);
378     }
379   }
380
381   closeFile(file);
382
383   return TRUE;
384 }
385
386 static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level)
387 {
388   int i;
389   int laser_color;
390
391   fputc(level->fieldx, file);
392   fputc(level->fieldy, file);
393
394   putFile16BitInteger(file, level->time,           BYTE_ORDER_BIG_ENDIAN);
395   putFile16BitInteger(file, level->kettles_needed, BYTE_ORDER_BIG_ENDIAN);
396
397   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
398     fputc(level->name[i], file);
399
400   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
401     fputc(level->score[i], file);
402
403   fputc((level->auto_count_kettles ? 1 : 0), file);
404   fputc(level->amoeba_speed, file);
405   fputc(level->time_fuse, file);
406
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);
411
412   fputc((level->encoding_16bit_field ? 1 : 0), file);
413
414   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
415 }
416
417 static void SaveLevel_MM_AUTH(FILE *file, struct LevelInfo_MM *level)
418 {
419   int i;
420
421   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
422     fputc(level->author[i], file);
423 }
424
425 static void SaveLevel_MM_BODY(FILE *file, struct LevelInfo_MM *level)
426 {
427   int x, y;
428
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);
433       else
434         fputc(Ur[x][y], file);
435 }
436
437 void SaveNativeLevel_MM(char *filename)
438 {
439   int x, y;
440   int body_chunk_size;
441   FILE *file;
442
443   if (!(file = fopen(filename, MODE_WRITE)))
444   {
445     Warn("cannot save level file '%s'", filename);
446
447     return;
448   }
449
450   // check level field for 16-bit elements
451   native_mm_level.encoding_16bit_field = FALSE;
452
453   for (y = 0; y < native_mm_level.fieldy; y++)
454     for (x = 0; x < native_mm_level.fieldx; x++)
455       if (Ur[x][y] > 255)
456         native_mm_level.encoding_16bit_field = TRUE;
457
458   body_chunk_size =
459     native_mm_level.fieldx * native_mm_level.fieldy *
460     (native_mm_level.encoding_16bit_field ? 2 : 1);
461
462   putFileChunk(file, "MMII", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN);
463   putFileChunk(file, "CAVE", CHUNK_SIZE_NONE,      BYTE_ORDER_BIG_ENDIAN);
464
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);
467
468   putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
469   SaveLevel_MM_HEAD(file, &native_mm_level);
470
471   putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
472   SaveLevel_MM_AUTH(file, &native_mm_level);
473
474   putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN);
475   SaveLevel_MM_BODY(file, &native_mm_level);
476
477   fclose(file);
478
479   SetFilePermissions(filename, PERMS_PRIVATE);
480 }