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