1 /*
2  *  This file is part of Dune Legacy.
3  *
4  *  Dune Legacy is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Dune Legacy is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Dune Legacy.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <FileClasses/Icnfile.h>
19 #include <FileClasses/Palette.h>
20 
21 #include <misc/exceptions.h>
22 
23 #include <SDL_endian.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #define SIZE_X  16
29 #define SIZE_Y  16
30 
31 extern Palette palette;
32 
33 /// Constructor
34 /**
35     The constructor reads from icnRWop and mapRWop all data and saves them internal. The SDL_RWops can be readonly but
36     must support seeking. Immediately after the Icnfile-Object is constructed both RWops can be closed. All data is saved
37     in the class.
38     \param  icnRWop SDL_RWops to the icn-File. (can be readonly)
39     \param  mapRWop SDL_RWops to the map-File. (can be readonly)
40     \param  freesrc A non-zero value means it will automatically close/free the icnRWop and mapRWop for you.
41 */
Icnfile(SDL_RWops * icnRWop,SDL_RWops * mapRWop,int freesrc)42 Icnfile::Icnfile(SDL_RWops* icnRWop, SDL_RWops* mapRWop, int freesrc)
43 {
44     pIcnFiledata = nullptr;
45 
46     if(icnRWop == nullptr) {
47         if(freesrc && mapRWop != nullptr) SDL_RWclose(mapRWop);
48         THROW(std::invalid_argument, "Icnfile::Icnfile(): icnRWop == nullptr!");
49     } else if(mapRWop == nullptr) {
50         if(freesrc) SDL_RWclose(icnRWop);
51         THROW(std::invalid_argument, "Icnfile::Icnfile(): mapRWop == nullptr!");
52     }
53 
54     uint8_t* pMapFiledata = nullptr;
55 
56     try {
57         Sint64 icnEndOffset = SDL_RWsize(icnRWop);
58         if(icnEndOffset <= 0) {
59             THROW(std::runtime_error, "Icnfile::Icnfile(): Cannot determine size of this *.icn-File!");
60         }
61 
62         size_t icnFilesize = static_cast<size_t>(icnEndOffset);
63         pIcnFiledata = new uint8_t[icnFilesize];
64 
65         if(SDL_RWread(icnRWop, &pIcnFiledata[0], icnFilesize, 1) != 1) {
66             THROW(std::runtime_error, "Icnfile::Icnfile(): Reading this *.icn-File failed!");
67         }
68 
69         Sint64 mapEndOffset = SDL_RWsize(mapRWop);
70         if(mapEndOffset <= 0) {
71             THROW(std::runtime_error, "Icnfile::Icnfile(): Cannot determine size of this *.map-File!");
72         }
73 
74         size_t mapFilesize = static_cast<size_t>(mapEndOffset);
75         pMapFiledata = new uint8_t[mapFilesize];
76 
77         if(SDL_RWread(mapRWop, &pMapFiledata[0], mapFilesize, 1) != 1) {
78             THROW(std::runtime_error, "Icnfile::Icnfile(): Reading this *.map-File failed!");
79         }
80 
81         // now we can start creating the Tilesetindex
82         if(mapFilesize < 2) {
83             THROW(std::runtime_error, "Icnfile::Icnfile(): This *.map-File is too short!");
84         }
85 
86         Uint16 numTilesets = SDL_SwapLE16( *((Uint16 *) pMapFiledata));
87 
88         if(mapFilesize < static_cast<size_t>(numTilesets * 2)) {
89             THROW(std::runtime_error, "Icnfile::Icnfile(): This *.map-File is too short!");
90         }
91 
92         // calculate size for all entries
93         Uint16 index = SDL_SwapLE16( ((Uint16*) pMapFiledata)[0]);
94         for(int i = 1; i < numTilesets; i++) {
95             Uint16 tmp = SDL_SwapLE16( ((Uint16*) pMapFiledata)[i]);
96             MapfileEntry newMapfileEntry;
97             newMapfileEntry.numTiles = tmp - index;
98             tilesets.push_back(newMapfileEntry);
99             index = tmp;
100         }
101         MapfileEntry newMapfileEntry;
102         newMapfileEntry.numTiles = (mapFilesize/2) - index;
103         tilesets.push_back(newMapfileEntry);
104 
105         for(int i = 0; i < numTilesets; i++) {
106             index = SDL_SwapLE16( ((Uint16*) pMapFiledata)[i]);
107 
108             if((unsigned int) mapFilesize < (index+tilesets[i].numTiles)*2 ) {
109                 THROW(std::runtime_error, "Icnfile::Icnfile(): This *.map-File is too short!");
110             }
111 
112             // now we can read in
113             for(unsigned int j = 0; j < tilesets[i].numTiles; j++) {
114                 tilesets[i].tileIndices.push_back(SDL_SwapLE16( ((Uint16*) pMapFiledata)[index+j]));
115             }
116         }
117 
118         delete [] pMapFiledata;
119         // reading MAP-File is now finished
120 
121         // check if we can access first section in ICN-File
122         if(icnFilesize < 0x20) {
123             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: No SSET-Section found!\n");
124         }
125 
126         SSET = pIcnFiledata+0x18;
127 
128         // check SSET-Section
129         if(     (SSET[0] != 'S')
130             ||  (SSET[1] != 'S')
131             ||  (SSET[2] != 'E')
132             ||  (SSET[3] != 'T')) {
133             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: No SSET-Section found!\n");
134         }
135 
136         SSET_Length = SDL_SwapBE32( *((Uint32*) (SSET + 4))) - 8;
137         SSET += 16;
138 
139         if(pIcnFiledata + icnFilesize < SSET + SSET_Length) {
140             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: SSET-Section is bigger than ICN-File!\n");
141         }
142 
143         RPAL = SSET + SSET_Length;
144 
145         // check RPAL-Section
146         if(     (RPAL[0] != 'R')
147             ||  (RPAL[1] != 'P')
148             ||  (RPAL[2] != 'A')
149             ||  (RPAL[3] != 'L')) {
150             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: No RPAL-Section found!\n");
151         }
152 
153         RPAL_Length = SDL_SwapBE32( *((Uint32*) (RPAL + 4)));
154         RPAL += 8;
155 
156         if(pIcnFiledata + icnFilesize < RPAL + RPAL_Length) {
157             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: RPAL-Section is bigger than ICN-File!\n");
158         }
159 
160         RTBL = RPAL + RPAL_Length;
161 
162         // check RTBL-Section
163         if(     (RTBL[0] != 'R')
164             ||  (RTBL[1] != 'T')
165             ||  (RTBL[2] != 'B')
166             ||  (RTBL[3] != 'L')) {
167             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: No RTBL-Section found!\n");
168         }
169 
170         RTBL_Length = SDL_SwapBE32( *((Uint32*) (RTBL + 4)));
171         RTBL += 8;
172 
173         if(pIcnFiledata + icnFilesize < RTBL + RTBL_Length) {
174             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: RTBL-Section is bigger than ICN-File!\n");
175         }
176 
177         numFiles = SSET_Length / ((SIZE_X * SIZE_Y) / 2);
178 
179         if(RTBL_Length < numFiles) {
180             THROW(std::runtime_error, "Icnfile::Icnfile(): Invalid ICN-File: RTBL-Section is too small!\n");
181         }
182 
183         if(freesrc) SDL_RWclose(icnRWop);
184         if(freesrc) SDL_RWclose(mapRWop);
185     } catch (std::exception&) {
186         delete [] pMapFiledata;
187         delete [] pIcnFiledata;
188 
189         if(freesrc) SDL_RWclose(icnRWop);
190         if(freesrc) SDL_RWclose(mapRWop);
191         throw;
192     }
193 }
194 
195 /// Destructor
196 /**
197     Frees all memory.
198 */
~Icnfile()199 Icnfile::~Icnfile()
200 {
201     delete [] pIcnFiledata;
202 }
203 
204 /// Returns one tile in the icn-File
205 /**
206     This method returns a SDL_Surface containing the nth tile/picture in the icn-File.
207     The returned SDL_Surface should be freed with SDL_FreeSurface() if no longer needed.
208     \param  indexOfFile specifies which tile/picture to return (zero based)
209     \return nth tile/picture in this icn-File
210 */
getPicture(Uint32 indexOfFile)211 SDL_Surface* Icnfile::getPicture(Uint32 indexOfFile) {
212     SDL_Surface * pic;
213 
214     if(indexOfFile >= numFiles) {
215         return nullptr;
216     }
217 
218     // check if palette is in range
219     if(RTBL[indexOfFile] >= RPAL_Length / 16) {
220         return nullptr;
221     }
222 
223     unsigned char* palettestart = RPAL + (16 * RTBL[indexOfFile]);
224 
225     unsigned char * filestart = SSET + (indexOfFile * ((SIZE_X * SIZE_Y)/2));
226 
227     // create new picture surface
228     if((pic = SDL_CreateRGBSurface(0,SIZE_X,SIZE_Y,8,0,0,0,0))== nullptr) {
229         return nullptr;
230     }
231 
232     palette.applyToSurface(pic);
233     SDL_LockSurface(pic);
234 
235     //Now we can copy to surface
236     unsigned char *dest = (unsigned char*) (pic->pixels);
237     unsigned char pixel;
238     for(int y = 0; y < SIZE_Y;y++) {
239         for(int x = 0; x < SIZE_X; x+=2) {
240             pixel = filestart[ (y*SIZE_X + x) / 2];
241             pixel = pixel >> 4;
242             dest[x] = palettestart[pixel];
243 
244             pixel = filestart[ (y*SIZE_X + x) / 2];
245             pixel = pixel & 0x0F;
246             dest[x+1] = palettestart[pixel];
247         }
248         dest += pic->pitch;
249     }
250 
251     SDL_UnlockSurface(pic);
252 
253     return pic;
254 }
255 
256 /// Returns an array of pictures in the icn-File
257 /**
258     This method returns a SDL_Surface containing multiple tiles/pictures. Which tiles to include is specified by MapfileIndex. The
259     MapfileIndex specifies the tileset. One tileset constists of multiple tiles of the icn-File.
260     The last 3 parameters specify how to arrange the tiles:
261      - If all 3 parameters are 0 then a "random" layout is choosen, which should look good.
262      - If tilesX and tilesY is set to non-zero values then the result surface contains tilesX*tilesY tiles and this tilesN-times side by side.
263      - If all there parameters are non-zero then the result surface is exactly in this arrangement.
264 
265     tilesX*tilesY*tilesN must always the number of tiles in this tileset. Otherwise nullptr is returned.<br><br>
266     Example:
267     \code
268     Tileset = 10,11,12,13,14,15,16,17,18,19,20,21
269     tilesX = 2; tilesY = 2; tilesN = 3
270 
271     returned picture:
272      10 11 14 15 18 19
273      12 13 16 17 20 21
274     \endcode
275     <br>
276     The returned SDL_Surface should be freed with SDL_FreeSurface() if no longer needed.
277     \param  mapfileIndex    specifies which tileset to use (zero based)
278     \param  tilesX          how many tiles in x direction
279     \param  tilesY          how many tiles in y direction
280     \param  tilesN          how many tilesX*tilesY blocks in a row
281     \return the result surface with tilesX*tilesY*tilesN tiles
282 */
getPictureArray(Uint32 mapfileIndex,int tilesX,int tilesY,int tilesN)283 SDL_Surface* Icnfile::getPictureArray(Uint32 mapfileIndex, int tilesX, int tilesY, int tilesN) {
284     SDL_Surface * pic;
285 
286     if(mapfileIndex >= tilesets.size()) {
287         return nullptr;
288     }
289 
290     if((tilesX == 0) && (tilesY == 0) && (tilesN == 0)) {
291         // guest what is best
292         int tmp = tilesets[mapfileIndex].numTiles;
293         if(tmp == 24) {
294             // special case (radar station and light factory)
295             tilesX = 2;
296             tilesY = 2;
297             tilesN = 6;
298         } else if((tmp % 9) == 0) {
299             tilesX = 3;
300             tilesY = 3;
301             tilesN = tmp / 9;
302         } else if((tmp % 6) == 0) {
303             tilesX = 3;
304             tilesY = 2;
305             tilesN = tmp / 6;
306         } else if((tmp % 4) == 0) {
307             tilesX = 2;
308             tilesY = 2;
309             tilesN = tmp / 4;
310         } else if((tmp>=40) && ((tmp % 5) == 0)) {
311             tilesX = tmp/5;
312             tilesY = 5;
313             tilesN = 1;
314         } else {
315             tilesX = 1;
316             tilesY = 1;
317             tilesN = tmp;
318         }
319 
320     } else if( ((tilesX == 0) || (tilesY == 0)) && (tilesN == 0)) {
321         // not possible
322         return nullptr;
323     } else if((tilesX == 0) && (tilesY == 0) && (tilesN != 0)) {
324         if(tilesets[mapfileIndex].numTiles % tilesN == 0) {
325             // guest what is best
326             int tmp = tilesets[mapfileIndex].numTiles / tilesN;
327             if((tmp % 3) == 0) {
328                 tilesX = tmp/3;
329                 tilesY = 3;
330             } else if((tmp % 2) == 0) {
331                 tilesX = tmp/2;
332                 tilesY = 2;
333             } else {
334                 tilesX = tmp;
335                 tilesY = 1;
336             }
337         } else {
338             // not possible
339             return nullptr;
340         }
341     } else {
342         if((unsigned int)tilesX*tilesY*tilesN != tilesets[mapfileIndex].numTiles) {
343             return nullptr;
344         }
345     }
346 
347     // create new picture surface
348     if((pic = SDL_CreateRGBSurface(0,SIZE_X*tilesX*tilesN,SIZE_Y*tilesY,8,0,0,0,0))== nullptr) {
349         return nullptr;
350     }
351 
352     palette.applyToSurface(pic);
353     SDL_LockSurface(pic);
354 
355     int tileidx=0;
356     for(int n = 0; n < tilesN; n++) {
357         for(int y = 0; y < tilesY; y++) {
358             for(int x = 0; x < tilesX; x++) {
359                 int IndexOfFile = tilesets[mapfileIndex].tileIndices[tileidx];
360 
361                 // check if palette is in range
362                 if(RTBL[IndexOfFile] >= RPAL_Length / 16) {
363                     SDL_UnlockSurface(pic);
364                     SDL_FreeSurface(pic);
365                     return nullptr;
366                 }
367 
368                 unsigned char* palettestart = RPAL + (16 * RTBL[IndexOfFile]);
369                 unsigned char * filestart = SSET + (IndexOfFile * ((SIZE_X * SIZE_Y)/2));
370 
371                 //Now we can copy to surface
372                 unsigned char *dest = (unsigned char*) (pic->pixels) + (pic->pitch)*y*SIZE_Y + (x+n*tilesX) * SIZE_X;
373                 unsigned char pixel;
374                 for(int y = 0; y < SIZE_Y;y++) {
375                     for(int x = 0; x < SIZE_X; x+=2) {
376                         pixel = filestart[ (y*SIZE_X + x) / 2];
377                         pixel = pixel >> 4;
378                         dest[x] = palettestart[pixel];
379 
380                         pixel = filestart[ (y*SIZE_X + x) / 2];
381                         pixel = pixel & 0x0F;
382                         dest[x+1] = palettestart[pixel];
383                     }
384                     dest += pic->pitch;
385                 }
386 
387                 tileidx++;
388             }
389         }
390     }
391 
392     SDL_UnlockSurface(pic);
393 
394     return pic;
395 }
396 
397 /// Returns a row of pictures in the icn-File
398 /**
399     This method returns a SDL_Surface containing multiple tiles/pictures. The returned surface contains all
400     tiles from startIndex to endIndex.
401     The returned SDL_Surface should be freed with SDL_FreeSurface() if no longer needed.
402     \param  startIndex      The first tile to use
403     \param  endIndex        The last tile to use
404     \param  maxRowLength    Used to limit the number of tiles per row and put the remaining tiles on the following rows (0 equals no row limitation)
405     \return the result surface with (endIndex-startIndex+1) tiles. nullptr on errors.
406 */
getPictureRow(Uint32 startIndex,Uint32 endIndex,Uint32 maxRowLength)407 SDL_Surface* Icnfile::getPictureRow(Uint32 startIndex, Uint32 endIndex, Uint32 maxRowLength) {
408     SDL_Surface * pic;
409 
410     if((startIndex >= numFiles)||(endIndex >= numFiles)||(startIndex > endIndex)) {
411         return nullptr;
412     }
413 
414     Uint32 numTiles = endIndex - startIndex + 1;
415     Uint32 numCols = (maxRowLength == 0) ? numTiles : maxRowLength;
416     Uint32 numRows = (numTiles+numCols-1) / numCols;
417 
418     // create new picture surface
419     if((pic = SDL_CreateRGBSurface(0,SIZE_X*numCols,SIZE_Y*numRows,8,0,0,0,0))== nullptr) {
420         return nullptr;
421     }
422     palette.applyToSurface(pic);
423 
424     SDL_LockSurface(pic);
425 
426     Uint32 tileCount = 0;
427     for(Uint32 row = 0; (row < numRows) && (tileCount < numTiles); row++) {
428         for(Uint32 col = 0; (col < numCols) && (tileCount < numTiles); col++) {
429             Uint32 indexOfFile = startIndex + tileCount;
430 
431             // check if palette is in range
432             if(RTBL[indexOfFile] >= RPAL_Length / 16) {
433                 SDL_UnlockSurface(pic);
434                 SDL_FreeSurface(pic);
435                 return nullptr;
436             }
437 
438             unsigned char* palettestart = RPAL + (16 * RTBL[indexOfFile]);
439             unsigned char * filestart = SSET + (indexOfFile * ((SIZE_X * SIZE_Y)/2));
440 
441             //Now we can copy to surface
442             unsigned char *dest = (unsigned char*) (pic->pixels) + (row*SIZE_Y*pic->pitch) + (col*SIZE_X);
443             unsigned char pixel;
444             for(int y = 0; y < SIZE_Y;y++) {
445                 for(int x = 0; x < SIZE_X; x+=2) {
446                     pixel = filestart[ (y*SIZE_X + x) / 2];
447                     pixel = pixel >> 4;
448                     dest[x] = palettestart[pixel];
449 
450                     pixel = filestart[ (y*SIZE_X + x) / 2];
451                     pixel = pixel & 0x0F;
452                     dest[x+1] = palettestart[pixel];
453                 }
454                 dest += pic->pitch;
455             }
456 
457             tileCount++;
458         }
459     }
460 
461     SDL_UnlockSurface(pic);
462     return pic;
463 }
464 
465 /// Returns a row of pictures in the icn-File
466 /**
467     This method returns a SDL_Surface containing multiple tiles/pictures. The returned surface contains all
468     tiles specified be the parameters
469     The returned SDL_Surface should be freed with SDL_FreeSurface() if no longer needed.
470     \param  numTiles    the number of tiles that should be extrated.
471     \return the result surface with all specified tiles in a row. nullptr on errors.
472 */
getPictureRow2(unsigned int numTiles,...)473 SDL_Surface* Icnfile::getPictureRow2(unsigned int numTiles, ...) {
474     SDL_Surface * pic;
475 
476     // create new picture surface
477     if((pic = SDL_CreateRGBSurface(0,SIZE_X*numTiles,SIZE_Y,8,0,0,0,0))== nullptr) {
478         return nullptr;
479     }
480 
481     palette.applyToSurface(pic);
482     SDL_LockSurface(pic);
483 
484     va_list arg_ptr;
485     va_start(arg_ptr, numTiles);
486 
487     for(unsigned int i = 0; i < numTiles; i++) {
488         unsigned int indexOfFile = va_arg( arg_ptr, unsigned int);
489 
490         if(indexOfFile >= numFiles) {
491             SDL_UnlockSurface(pic);
492             SDL_FreeSurface(pic);
493             va_end(arg_ptr);
494             return nullptr;
495         }
496 
497         // check if palette is in range
498         if(RTBL[indexOfFile] >= RPAL_Length / 16) {
499             SDL_UnlockSurface(pic);
500             SDL_FreeSurface(pic);
501             va_end(arg_ptr);
502             return nullptr;
503         }
504 
505         unsigned char* palettestart = RPAL + (16 * RTBL[indexOfFile]);
506         unsigned char * filestart = SSET + (indexOfFile * ((SIZE_X * SIZE_Y)/2));
507 
508         //Now we can copy to surface
509         unsigned char *dest = (unsigned char*) (pic->pixels) + i*SIZE_X;
510         unsigned char pixel;
511         for(int y = 0; y < SIZE_Y;y++) {
512             for(int x = 0; x < SIZE_X; x+=2) {
513                 pixel = filestart[ (y*SIZE_X + x) / 2];
514                 pixel = pixel >> 4;
515                 dest[x] = palettestart[pixel];
516 
517                 pixel = filestart[ (y*SIZE_X + x) / 2];
518                 pixel = pixel & 0x0F;
519                 dest[x+1] = palettestart[pixel];
520             }
521             dest += pic->pitch;
522         }
523     }
524 
525     va_end(arg_ptr);
526 
527     SDL_UnlockSurface(pic);
528     return pic;
529 }
530