1 /***************************************************************************
2                           xpmladen.cpp  -  description
3                              -------------------
4     begin                : Fri Apr 20 2001
5     copyright            : (C) 2001 by Immi
6     email                : cuyo@karimmi.de
7 
8 Modified 2002,2003,2006,2008-2011,2014 by the cuyo developers
9 
10  ***************************************************************************/
11 
12 /***************************************************************************
13  *                                                                         *
14  *   This program is free software; you can redistribute it and/or modify  *
15  *   it under the terms of the GNU General Public License as published by  *
16  *   the Free Software Foundation; either version 2 of the License, or     *
17  *   (at your option) any later version.                                   *
18  *                                                                         *
19  ***************************************************************************/
20 
21 #include <cstdlib>
22 #include <cstdio>
23 
24 #include "sdltools.h"
25 
26 #include "cuyointl.h"
27 #include "fehler.h"
28 #include "xpmladen.h"
29 #include "leveldaten.h"
30 #include "global.h"
31 #include "inkompatibel.h"
32 
33 #if HAVE_LIBZ
34 #include <zlib.h>
35 #endif
36 
37 
38 /* Die nachfolgenden Variablen existieren nur innerhalb von xpmladen.cpp */
39 static char * gDatAnfang;
40 static char * gDatBei;
41 static char * gDatEnde;
42 
43 /* True, wenn eine .xpm.gz-Datei gefunden wurde. Diese Var. ist nur
44    n�tig, damit wenn eine gezippte Datei gefunden wurde, die aber nicht
45    von meinem Algorithmus lesbar ist, nicht ein "Datei nicht gefunden"
46    ausgegeben wird. */
47 static bool gDateiGezippt;
48 
49 
50 /* Damit in ladeDateiLow nicht ein Haufen #ifdefs stehen m�ssen, hier
51    ein paar Dummy-Definitionen f�r wenn's die zlib nicht gibt. */
52 
53 
54 #if !HAVE_LIBZ
55 typedef int gzFile;
gzopen(char *,char *)56 int gzopen(char *, char *) {return 0;}
gzeof(int)57 bool gzeof(int) {return 0;}
gzread(int,char *,int)58 int gzread(int, char *, int) {return 0;}
gzclose(int)59 void gzclose(int) {}
60 #endif
61 
62 
63 
64 /** Wird von ladeDatei benutzt, um entweder eine normale oder eine
65     gz-Datei zu laden... */
ladeDateiLow(Str na,bool gz)66 bool ladeDateiLow(Str na, bool gz) {
67   FILE * f = 0; // Das "= 0" ist nur um keine Warnungen zu bekommen
68   gzFile gzf = 0;
69 
70   /* Datei �ffnen */
71   if (gz) {
72     /* Das "b" bedeutet binary-Datei. */
73     gzf = gzopen(na.data(), "rb");
74     if (!gzf) return false;
75   } else {
76     f = fopen(na.data(), "r");
77     if (!f) return false;
78   }
79 
80   int bei = 0;
81 
82   while ( gz  ?  !gzeof(gzf)  :  !feof(f) ) {
83     /* So viel wollen wir diesmal auf einmal lesen: So viel wie wir
84        schon haben, plus 4096 */
85     int neu = bei + 4096;
86 
87     /* Speicher f�r neu zu ladendes alloziieren. (Wenn noch nix
88        geladen war, ist gDatAnfang = 0 und realloc �quivalent zu malloc) */
89     gDatAnfang = (char *) realloc(gDatAnfang, bei + neu);
90 
91     if (gz) {
92       bei += neu = gzread(gzf, gDatAnfang + bei, neu);
93 
94       if (neu < 0) {
95         gzclose(gzf);
96         /* gzerror() could improve the error message... */
97         throw Fehler("%s","Read error");
98       }
99     } else {
100       bei += fread(gDatAnfang + bei, 1, neu, f);
101       if (ferror(f)) {
102         fclose(f);
103         throw Fehler("%s","Read error");
104       }
105     }
106   }
107 
108   /* OK, jetzt ist wahrscheinlich zu viel Speicher alloziiert.
109      Korrigieren wir das mal noch ein bisschen. Das "+1" ist, weil
110      wir am Ende noch eine "0" anh�ngen wollen. */
111   gDatAnfang = (char *) realloc(gDatAnfang, bei + 1);
112 
113   gDatEnde = gDatAnfang + bei;
114   *gDatEnde = 0;
115 
116   if (gz)
117     gzclose(gzf);
118   else
119     fclose(f);
120 
121   return true;
122 }
123 
124 
125 
126 /** L�d die angegebene Datei komplett. Danach zeigt gDatAnfang auf den
127     Anfang und gDatEnde auf das Ende. F�gt au�erdem noch eine 0 ans Ende
128     an.
129     Liefert false, wenn die Datei nicht gefunden wird.
130     Sucht auch nach der Datei na.gz. */
ladeDatei(Str na)131 bool ladeDatei(Str na) {
132 
133   /* Erst mal versuchen, eine ungezippte Datei zu laden. */
134   if (ladeDateiLow(na, false)) return true;
135 
136   /* Und dann eine gezippte. Aber nur mit zlib */
137   #if HAVE_LIBZ
138   if (ladeDateiLow(na + ".gz", true)) {
139     gDateiGezippt = true;
140     return true;
141   }
142   #endif
143 
144   return false;
145 }
146 
147 
148 
leerWeg()149 void leerWeg() {
150   while (*gDatBei == ' ' || *gDatBei == '\t' || *gDatBei == '\n' || *gDatBei == '\r')
151     gDatBei++;
152 }
153 
leerUndKommentarWeg()154 void leerUndKommentarWeg() {
155   while (1) {
156     leerWeg();
157     /* Kein Kommentar-Anfang? Dann fertig */
158     if (gDatBei[0] != '/' || gDatBei[1] != '*')
159       return;
160     /* Kommentar weglesen */
161     gDatBei += 2;
162     while (gDatBei[0] != '*' || gDatBei[1] != '/') {
163       if (!gDatBei[0])
164         throw Fehler("%s","Endless comment.");
165       gDatBei++;
166     }
167     gDatBei += 2;
168   }
169 }
170 
171 /** Erwartet, dass ein s kommt (davor ist whitespace erlaubt) */
erwarte(const char * s)172 void erwarte(const char * s) {
173   const char *t = s;
174   while (*s) {
175     if (*gDatBei != *s)
176       throw Fehler("\"%s\" expected", t);
177     gDatBei++;
178     s++;
179   }
180 }
181 
182 
183 /** Das gleiche mit char */
erwarte(char c)184 void erwarte(char c) {
185   if (*gDatBei != c)
186     throw Fehler("'%c' expected; found: '%c' (filepos: %d)", c, *gDatBei,
187               (int) (gDatBei - gDatAnfang));
188   gDatBei++;
189 }
190 
191 
192 
193 /** Liest so lange, bis ein c auftaucht. */
liesBis(char c)194 void liesBis(char c) {
195   while (*gDatBei != c) {
196     if (*gDatBei == 0)
197       throw Fehler("'%c' expected", c);
198     gDatBei++;
199   }
200   gDatBei++;
201 }
202 
203 
204 /** Pr�ft, ob der String s jetzt kommt. Wenn ja, wird
205     er weggelesen */
kommtString(const char * s)206 bool kommtString(const char * s) {
207   char * merk = gDatBei;
208   while (*s) {
209     if (*gDatBei != *s) {
210       gDatBei = merk;
211       return false;
212     }
213     gDatBei++;
214     s++;
215   }
216   return true;
217 }
218 
219 
220 
getInt()221 int getInt() {
222   int ret = 0;
223   bool geht = false;
224   leerWeg();
225   while (*gDatBei >= '0' && *gDatBei <= '9') {
226     ret = ret * 10 + *gDatBei - '0';
227     gDatBei++;
228     geht = true;
229   }
230   if (!geht)
231     throw Fehler("%s","Number expected");
232   return ret;
233 }
234 
235 
236 
decodeHex1(char a)237 int decodeHex1(char a) {
238   if (a >= '0' && a <= '9')
239     return a - '0';
240   if (a >= 'A' && a <= 'F')
241     return a - 'A' + 10;
242   if (a >= 'a' && a <= 'f')
243     return a - 'a' + 10;
244   throw Fehler("%s","Hex number expected");
245 }
246 
getHex()247 int getHex() {
248   int ret = decodeHex1(*gDatBei++) * 16;
249   return ret + decodeHex1(*gDatBei++);
250 }
251 
252 
253 
254 
255 struct info {
256   Uint32 farbcode;
257   bool hintergrund;
258 };
259 
260 union suchbaum {
261   info einziges;
262   info blatt[256];
263   suchbaum * weiter[256];
264 
suchbaum(int tiefe)265   suchbaum(int tiefe) {
266     if (tiefe>1)
267       for (int i=0; i<256; i++) weiter[i]=NULL;
268   }
269 
rein(int tiefe)270   inline info* rein(int tiefe) {
271     if (tiefe==0)
272       return &einziges;
273     suchbaum* such = this;
274     for (;tiefe-->1;) {
275       if (!such->weiter[(unsigned int)*gDatBei])
276         such->weiter[(unsigned int)*gDatBei] = new suchbaum(tiefe);
277       such = such->weiter[(unsigned int)*gDatBei++];
278     }
279     return (such->blatt) + (*gDatBei++);
280   }
281 
raus(int tiefe)282   inline info raus(int tiefe) {
283     if (tiefe==0)
284       return einziges;
285     suchbaum* such = this;
286     for (;tiefe-->1;) {
287       if (!such->weiter[(unsigned int)*gDatBei])
288         throw Fehler("%s","Undefined pixel name");
289       such = such->weiter[(unsigned int)*gDatBei++];
290     }
291     return such->blatt[(unsigned int)*gDatBei++];
292   }
293 
loesch(int tiefe)294   void loesch(int tiefe) {
295     if (tiefe-->1)
296       for (int i=0; i<256; i++)
297         if (weiter[i]) {
298 	  weiter[i]->loesch(tiefe);
299           delete weiter[i];
300         }
301   }
302 };
303 
304 
305 
306 
307 
308 
309 /* Versucht die Datei na zu laden.
310    Versucht au�erdem, die Datei na.gz zu laden.
311    Liefert 0, wenn keine der Dateien existiert.
312    Throwt, wenn's beim Laden einen Fehler gibt.
313    (Falls die SDL-Lad-Routine verwendet wird, kann nicht versucht werden,
314    die .gz-Datei zu laden.) */
ladXPM(Str na,RohMaske & maske)315 SDL_Surface * ladXPM(Str na, RohMaske & maske) {
316   SDL_Surface * s;
317 
318   gDatAnfang = 0;
319 
320   gDateiGezippt = false;
321 
322   /* Das nachfolgende try-catch ist 1. um evtl Speicher freizugeben
323      und zweitens, weil wir es dann nochmal mit der SDL-Laderoutine
324      versuchen wollen. */
325   try {
326 
327     /* Datei laden. Dabei werden gDatAnfang und gDatEnde gesetzt. */
328     if (!ladeDatei(na)) return 0;
329 
330 
331     gDatBei = gDatAnfang;
332 
333     /* XPM-Kommentar-Zeile lesen */
334     leerWeg();
335     erwarte("/*");
336     leerWeg();
337     erwarte("XPM");
338     leerWeg();
339     erwarte("*/");
340     /* Alles bis zur ersten { entfernen */
341     liesBis('{');
342 
343     /* OK, jetzt sind wir im interessanten Bereich. */
344     /* "groesse_x groesse_y farbzahl charpp" parsen. */
345     leerUndKommentarWeg();
346     erwarte('"');
347 
348     int groesse_x, groesse_y, farb, tiefe;
349     groesse_x = getInt();
350     groesse_y = getInt();
351     farb = getInt();
352     tiefe = getInt();
353     bool monochrom = tiefe==0;
354     leerWeg();
355     erwarte('"');
356 
357     s = SDLTools::createSurface32(groesse_x, groesse_y);
358     SDL_PixelFormat * pf = s->format;
359 
360     maske.init(groesse_x,groesse_y);
361 
362     /* Farben parsen */
363 
364     suchbaum farben(tiefe);
365 
366     for (int i = 0; i < farb; i++) {
367       leerUndKommentarWeg();
368       erwarte(',');
369       leerUndKommentarWeg();
370       erwarte('"');
371 
372       info* index = farben.rein(tiefe);
373       leerWeg();
374       char typ = *gDatBei++;
375       CASSERT(typ == 'c');
376       leerWeg();
377 
378       index->hintergrund=false;
379       if (kommtString("None")) {
380         // durchsichtig
381 	index->farbcode = SDL_MapRGBA(pf, 0, 0, 0, 0);
382       } else if (kommtString("black")) {
383         index->farbcode = SDL_MapRGBA(pf, 0, 0, 0, 255);
384       } else if (kommtString("white")) {
385         index->farbcode = SDL_MapRGBA(pf, 255, 255, 255, 255);
386       } else if (kommtString("Background")) {
387         // Farbe vom Level-Hintergrund (f�r Explosion)
388         index->farbcode = ld->mHintergrundFarbe.getPixel(pf);
389         index->hintergrund = true;
390 //       } else if (kommtString("FontDark")) {
391 //         // Farbe der Schrift in dem Level (f�r Punkt-Ziffern)
392 //         *index = ld->mSchriftFarbe[schrift_dunkel].getPixel(pf);
393 //       } else if (kommtString("Font")) {
394 //         // Farbe der Schrift in dem Level (f�r Punkt-Ziffern)
395 //         *index = ld->mSchriftFarbe[schrift_normal].getPixel(pf);
396 //       } else if (kommtString("FontLight")) {
397 //         // Farbe der Schrift in dem Level (f�r Punkt-Ziffern)
398 //         *index = ld->mSchriftFarbe[schrift_hell].getPixel(pf);
399       } else {
400         erwarte('#');
401 	int f_r = getHex();
402 	int f_g = getHex();
403 	int f_b = getHex();
404 	index->farbcode = SDL_MapRGBA(pf, f_r, f_g, f_b, 255);
405       }
406       liesBis('"');
407     }
408 
409     SDL_LockSurface(s); /* Damit wir Pixel direkt bearbeiten duerfen */
410 
411     if (monochrom) {
412       if (farb) {
413         info farbe = farben.raus(tiefe);
414         for (int y=0; y<groesse_y; y++)
415           for (int x=0; x<groesse_x; x++)
416             SDLTools::getPixel32(s,x,y) = farbe.farbcode;
417         maske.fill(farbe.hintergrund);
418       };
419     } else
420       for (int y = 0; y < groesse_y; y++) {
421         leerUndKommentarWeg();
422 	erwarte(',');
423 	leerUndKommentarWeg();
424 	erwarte('"');
425 	for (int x = 0; x < groesse_x; x++) {
426           info farbe = farben.raus(tiefe);
427 	  SDLTools::getPixel32(s, x, y) = farbe.farbcode;
428           maske.set_pixel(x,y,farbe.hintergrund);
429         };
430 	erwarte('"');
431      }
432 
433     SDL_UnlockSurface(s);
434 
435     farben.loesch(tiefe);
436 
437     /* Angeblich d�rfen jetzt noch Extensionen von xpm kommen. Da k�mmern wir
438        uns nicht weiter drum. */
439 
440     free(gDatAnfang);
441 
442   } catch (Fehler f) {
443     if (gDatAnfang) free(gDatAnfang);
444 
445     if (gDebug)
446       print_to_stderr(_sprintf("Fast xpm loading did not work for \"%s\":\n%s\n",
447 			       na.data(), f.getText().data()));
448 
449     /* Ich habe im Nachfolgenden IMG_Load-Verwendung auskommentiert:
450        IMG_Load scheint nicht zu funkionieren (segmentation fault).
451        Oder vielleicht funktioniert es auch, aber es reagiert mit segfault
452        auf Dateien, die es nicht lesen kann. Deshalb: Wenn wir die Datei
453        nicht selbst lesen koennen, dann lieber den Fehler ausgeben als
454        IMG_Load aufrufen. */
455 //    if (gDateiGezippt) {
456       /* Bei einer gezippten Datei geht fallBackLaden nicht... */
457       // TRANSLATORS: This is to prepend an error message
458       throw Fehler("File \"%s.gz\": %s\n", na.data(), f.getText().data());
459 //    } else {
460       /* Die sdl-Laderoutine verwenden. Waere eh zu pruefen ob die nicht besser ist als
461          unsere eigene */
462 //      return IMG_Load(na.data());
463 //    }
464   }
465 
466 
467   return s;
468 }
469