8e6a72bdf761cee3e3ff78b75fd40c95c638765c
[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 //                  http://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 /* ========================================================================= */
34 /* level file functions                                                      */
35 /* ========================================================================= */
36
37 static void ReadChunk_MM_VERS(File *file, int *file_version, int *game_version)
38 {
39   int file_version_major, file_version_minor, file_version_patch;
40   int game_version_major, game_version_minor, game_version_patch;
41
42   file_version_major = getFile8Bit(file);
43   file_version_minor = getFile8Bit(file);
44   file_version_patch = getFile8Bit(file);
45   getFile8Bit(file);            /* not used */
46
47   game_version_major = getFile8Bit(file);
48   game_version_minor = getFile8Bit(file);
49   game_version_patch = getFile8Bit(file);
50   getFile8Bit(file);            /* not used */
51
52   *file_version = MM_VERSION_IDENT(file_version_major,
53                                    file_version_minor,
54                                    file_version_patch);
55
56   *game_version = MM_VERSION_IDENT(game_version_major,
57                                    game_version_minor,
58                                    game_version_patch);
59 }
60
61 static void WriteChunk_MM_VERS(FILE *file, int file_version, int game_version)
62 {
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);
69
70   fputc(file_version_major, file);
71   fputc(file_version_minor, file);
72   fputc(file_version_patch, file);
73   fputc(0, file);       /* not used */
74
75   fputc(game_version_major, file);
76   fputc(game_version_minor, file);
77   fputc(game_version_patch, file);
78   fputc(0, file);       /* not used */
79 }
80
81 void setLevelInfoToDefaults_MM()
82 {
83   int i, x, y;
84
85   native_mm_level.file_version = MM_FILE_VERSION_ACTUAL;
86   native_mm_level.game_version = MM_GAME_VERSION_ACTUAL;
87
88   native_mm_level.encoding_16bit_field = FALSE; /* default: only 8-bit elements */
89
90   lev_fieldx = native_mm_level.fieldx = STD_LEV_FIELDX;
91   lev_fieldy = native_mm_level.fieldy = STD_LEV_FIELDY;
92
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;
96
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;
105
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';
110
111   strcpy(native_mm_level.name, NAMELESS_LEVEL_NAME);
112   strcpy(native_mm_level.author, ANONYMOUS_NAME);
113
114   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
115     native_mm_level.score[i] = 10;
116
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;
121 }
122
123 static int checkLevelElement(int element)
124 {
125   if (element >= EL_FIRST_RUNTIME_EL)
126   {
127     Error(ERR_WARN, "invalid level element %d", element);
128     element = EL_CHAR_FRAGE;
129   }
130
131   return element;
132 }
133
134 static int LoadLevel_MM_VERS(File *file, int chunk_size,
135                              struct LevelInfo_MM *level)
136 {
137   ReadChunk_MM_VERS(file, &level->file_version, &level->game_version);
138
139   return chunk_size;
140 }
141
142 static int LoadLevel_MM_HEAD(File *file, int chunk_size,
143                              struct LevelInfo_MM *level)
144 {
145   int i;
146   int laser_color;
147
148   lev_fieldx = level->fieldx = getFile8Bit(file);
149   lev_fieldy = level->fieldy = getFile8Bit(file);
150
151   level->time           = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
152   level->kettles_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
153
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)
156     level->time *= 4;
157
158   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
159     level->name[i] = getFile8Bit(file);
160   level->name[MAX_LEVEL_NAME_LEN] = 0;
161
162   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
163     level->score[i] = getFile8Bit(file);
164
165   level->auto_count_kettles     = (getFile8Bit(file) == 1 ? TRUE : FALSE);
166   level->amoeba_speed           = getFile8Bit(file);
167   level->time_fuse              = getFile8Bit(file);
168
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;
173
174   level->encoding_16bit_field   = (getFile8Bit(file) == 1 ? TRUE : FALSE);
175
176   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
177
178   return chunk_size;
179 }
180
181 static int LoadLevel_MM_AUTH(File *file, int chunk_size,
182                              struct LevelInfo_MM *level)
183 {
184   int i;
185
186   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
187     level->author[i] = getFile8Bit(file);
188   level->author[MAX_LEVEL_NAME_LEN] = 0;
189
190   return chunk_size;
191 }
192
193 static int LoadLevel_MM_BODY(File *file, int chunk_size,
194                              struct LevelInfo_MM *level)
195 {
196   int x, y;
197   int chunk_size_expected = level->fieldx * level->fieldy;
198
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. */
203
204   if (level->encoding_16bit_field && level->file_version >= MM_FILE_VERSION_2_0)
205     chunk_size_expected *= 2;
206
207   if (chunk_size_expected != chunk_size)
208   {
209     ReadUnusedBytesFromFile(file, chunk_size);
210
211     return chunk_size_expected;
212   }
213
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) :
219                           getFile8Bit(file));
220   return chunk_size;
221 }
222
223 boolean LoadNativeLevel_MM(char *filename, boolean level_info_only)
224 {
225   char cookie[MAX_LINE_LEN];
226   char chunk_name[CHUNK_ID_LEN + 1];
227   int chunk_size;
228   File *file;
229
230   static struct
231   {
232     char *name;
233     int size;
234     int (*loader)(File *, int, struct LevelInfo_MM *);
235   }
236   chunk_info[] =
237   {
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 },
242     {  NULL,  0,                        NULL }
243   };
244
245   /* always start with reliable default values */
246   setLevelInfoToDefaults_MM();
247
248   if (!(file = openFile(filename, MODE_READ)))
249   {
250     if (!level_info_only)
251       Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
252
253     return FALSE;
254   }
255
256   getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
257   if (strcmp(chunk_name, "MMII") == 0)
258   {
259     getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);   /* not used */
260
261     getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN);
262     if (strcmp(chunk_name, "CAVE") != 0)
263     {
264       Error(ERR_WARN, "unknown format of level file '%s'", filename);
265
266       closeFile(file);
267
268       return FALSE;
269     }
270   }
271   else  /* check for pre-2.0 file format with cookie string */
272   {
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';
277
278     if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
279     {
280       Error(ERR_WARN, "unknown format of level file '%s'", filename);
281
282       closeFile(file);
283
284       return FALSE;
285     }
286
287     if ((native_mm_level.file_version = getFileVersionFromCookieString(cookie))
288         == -1)
289     {
290       Error(ERR_WARN, "unsupported version of level file '%s'", filename);
291
292       closeFile(file);
293
294       return FALSE;
295     }
296   }
297
298   while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN))
299   {
300     int i = 0;
301
302     while (chunk_info[i].name != NULL &&
303            strcmp(chunk_name, chunk_info[i].name) != 0)
304       i++;
305
306     if (chunk_info[i].name == NULL)
307     {
308       Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
309             chunk_name, filename);
310
311       ReadUnusedBytesFromFile(file, chunk_size);
312     }
313     else if (chunk_info[i].size != -1 &&
314              chunk_info[i].size != chunk_size)
315     {
316       Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
317             chunk_size, chunk_name, filename);
318
319       ReadUnusedBytesFromFile(file, chunk_size);
320     }
321     else
322     {
323       /* call function to load this level chunk */
324       int chunk_size_expected =
325         (chunk_info[i].loader)(file, chunk_size, &native_mm_level);
326
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);
333     }
334   }
335
336   closeFile(file);
337
338   return TRUE;
339 }
340
341 static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level)
342 {
343   int i;
344   int laser_color;
345
346   fputc(level->fieldx, file);
347   fputc(level->fieldy, file);
348
349   putFile16BitInteger(file, level->time,           BYTE_ORDER_BIG_ENDIAN);
350   putFile16BitInteger(file, level->kettles_needed, BYTE_ORDER_BIG_ENDIAN);
351
352   for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
353     fputc(level->name[i], file);
354
355   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
356     fputc(level->score[i], file);
357
358   fputc((level->auto_count_kettles ? 1 : 0), file);
359   fputc(level->amoeba_speed, file);
360   fputc(level->time_fuse, file);
361
362   laser_color = ((level->laser_red   << 2) |
363                  (level->laser_green << 1) |
364                  (level->laser_blue  << 0));
365   fputc(laser_color, file);
366
367   fputc((level->encoding_16bit_field ? 1 : 0), file);
368
369   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
370 }
371
372 static void SaveLevel_MM_AUTH(FILE *file, struct LevelInfo_MM *level)
373 {
374   int i;
375
376   for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
377     fputc(level->author[i], file);
378 }
379
380 static void SaveLevel_MM_BODY(FILE *file, struct LevelInfo_MM *level)
381 {
382   int x, y;
383
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);
388       else
389         fputc(Ur[x][y], file);
390 }
391
392 void SaveNativeLevel_MM(char *filename)
393 {
394   int x, y;
395   int body_chunk_size;
396   FILE *file;
397
398   if (!(file = fopen(filename, MODE_WRITE)))
399   {
400     Error(ERR_WARN, "cannot save level file '%s'", filename);
401
402     return;
403   }
404
405   /* check level field for 16-bit elements */
406   native_mm_level.encoding_16bit_field = FALSE;
407
408   for (y = 0; y < native_mm_level.fieldy; y++)
409     for (x = 0; x < native_mm_level.fieldx; x++)
410       if (Ur[x][y] > 255)
411         native_mm_level.encoding_16bit_field = TRUE;
412
413   body_chunk_size =
414     native_mm_level.fieldx * native_mm_level.fieldy *
415     (native_mm_level.encoding_16bit_field ? 2 : 1);
416
417   putFileChunk(file, "MMII", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN);
418   putFileChunk(file, "CAVE", CHUNK_SIZE_NONE,      BYTE_ORDER_BIG_ENDIAN);
419
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);
422
423   putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
424   SaveLevel_MM_HEAD(file, &native_mm_level);
425
426   putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
427   SaveLevel_MM_AUTH(file, &native_mm_level);
428
429   putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN);
430   SaveLevel_MM_BODY(file, &native_mm_level);
431
432   fclose(file);
433
434   SetFilePermissions(filename, PERMS_PRIVATE);
435 }