1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "cruise/cruise_main.h"
24 #include "common/endian.h"
25 #include "common/memstream.h"
26 #include "common/textconsole.h"
27 
28 namespace Cruise {
29 
30 enum fileTypeEnum {
31 	type_UNK,
32 	type_SPL,
33 	type_SET,
34 	type_FNT
35 };
36 
37 int loadSingleFile;
38 
39 /**
40  * Takes care of decoding a compressed graphic
41  */
decodeGfxUnified(dataFileEntry * pCurrentFileEntry,int16 format)42 void decodeGfxUnified(dataFileEntry *pCurrentFileEntry, int16 format) {
43 	uint8 *dataPtr = pCurrentFileEntry->subData.ptr;
44 	int spriteSize;
45 
46 	// Unified how to get spriteSize
47 	switch (format) {
48 	case 1:
49 	case 4:
50 		spriteSize = pCurrentFileEntry->height * pCurrentFileEntry->width;
51 		break;
52 	case 5:
53 		spriteSize = pCurrentFileEntry->height * pCurrentFileEntry->widthInColumn;
54 		break;
55 
56 	default:
57 		error("Unknown gfx format %d", format);
58 	}
59 
60 	uint8 *buffer = (uint8 *)MemAlloc(spriteSize);
61 
62 	// Perform format specific decoding
63 	switch (format) {
64 	case 1:
65 	case 4: {
66 		int x = 0;
67 		while (x < spriteSize) {
68 			uint8 c;
69 			uint16 p0;
70 			// Format 4
71 			uint16 p1 = 0, p2 = 0, p3 = 0;
72 
73 			p0 = (dataPtr[0] << 8) | dataPtr[1];
74 
75 			// Format 4
76 			if (format == 4) {
77 				p1 = (dataPtr[2] << 8) | dataPtr[3];
78 				p2 = (dataPtr[4] << 8) | dataPtr[5];
79 				p3 = (dataPtr[6] << 8) | dataPtr[7];
80 			}
81 
82 			/* decode planes */
83 			for (c = 0; c < 16; c++) {
84 				// Format 4
85 				if (format == 4) {
86 					buffer[x + c] = ((p0 >> 15) & 1) | ((p1 >> 14) & 2) | ((p2 >> 13) & 4) | ((p3 >> 12) & 8);
87 				} else {
88 					buffer[x + c] = ((p0 >> 15) & 1);
89 				}
90 
91 				p0 <<= 1;
92 
93 				// Format 4
94 				if (format == 4) {
95 					p1 <<= 1;
96 					p2 <<= 1;
97 					p3 <<= 1;
98 				}
99 			}
100 
101 			x += 16;
102 
103 			dataPtr += (2 * format);
104 		}
105 
106 		break;
107 	}
108 	case 5: {
109 		uint8 *destP = buffer;
110 		int range = pCurrentFileEntry->height * pCurrentFileEntry->width;
111 
112 		for (int line = 0; line < pCurrentFileEntry->height; line++) {
113 			uint8 p0, p1, p2, p3, p4;
114 
115 			for (int x = 0; x < pCurrentFileEntry->widthInColumn; x++) {
116 				int bit = 7 - (x % 8);
117 				int col = x / 8;
118 
119 				p0 = (dataPtr[line*pCurrentFileEntry->width + col + range * 0] >> bit) & 1;
120 				p1 = (dataPtr[line*pCurrentFileEntry->width + col + range * 1] >> bit) & 1;
121 				p2 = (dataPtr[line*pCurrentFileEntry->width + col + range * 2] >> bit) & 1;
122 				p3 = (dataPtr[line*pCurrentFileEntry->width + col + range * 3] >> bit) & 1;
123 				p4 = (dataPtr[line*pCurrentFileEntry->width + col + range * 4] >> bit) & 1;
124 
125 				*destP++ = p0 | (p1 << 1) | (p2 << 2) | (p3 << 3) | (p4 << 4);
126 			}
127 		}
128 
129 		break;
130 	}
131 	default:
132 		break;
133 	}
134 
135 	MemFree(pCurrentFileEntry->subData.ptr);
136 	pCurrentFileEntry->subData.ptr = buffer;
137 }
138 
updateResFileEntry(int height,int width,int size,int entryNumber,int resType)139 int updateResFileEntry(int height, int width, int size, int entryNumber, int resType) {
140 	int div = 0;
141 
142 	resetFileEntry(entryNumber);
143 
144 	filesDatabase[entryNumber].subData.compression = 0;
145 
146 	int maskSize = size;
147 
148 	if (resType == 4) {
149 		div = maskSize / 4;
150 	} else if (resType == 5) {
151 		width = (width * 8) / 5;
152 		maskSize = MAX(size, height * width);
153 	}
154 
155 	filesDatabase[entryNumber].subData.ptr = (uint8 *)mallocAndZero(maskSize + div);
156 
157 	if (!filesDatabase[entryNumber].subData.ptr)
158 		return (-2);
159 
160 	filesDatabase[entryNumber].widthInColumn = width;
161 	filesDatabase[entryNumber].subData.ptrMask = (uint8 *) mallocAndZero(maskSize);
162 	filesDatabase[entryNumber].width = width / 8;
163 	filesDatabase[entryNumber].resType = resType;
164 	filesDatabase[entryNumber].height = height;
165 	filesDatabase[entryNumber].subData.index = -1;
166 
167 	return entryNumber;
168 }
169 
createResFileEntry(int width,int height,int size,int resType)170 int createResFileEntry(int width, int height, int size, int resType) {
171 	error("Executing untested createResFileEntry");
172 	return 0;	// for compilers that don't support NORETURN
173 
174 #if 0
175 	int entryNumber;
176 	int div = 0;
177 
178 	int i = 0;
179 	for (; i < NUM_FILE_ENTRIES; i++) {
180 		if (!filesDatabase[i].subData.ptr)
181 			break;
182 	}
183 
184 	if (i >= NUM_FILE_ENTRIES) {
185 		return (-19);
186 	}
187 
188 	entryNumber = i;
189 
190 	filesDatabase[entryNumber].subData.compression = 0;
191 
192 	if (resType == 4) {
193 		div = size / 4;
194 	} else if (resType == 5) {
195 		width = (width * 8) / 5;
196 	}
197 
198 	filesDatabase[entryNumber].subData.ptr = (uint8 *) mallocAndZero(size + div);
199 
200 	if (!filesDatabase[entryNumber].subData.ptr) {
201 		return (-2);
202 	}
203 
204 	filesDatabase[entryNumber].widthInColumn = width;
205 	filesDatabase[entryNumber].subData.ptrMask = filesDatabase[entryNumber].subData.ptr + size;
206 	filesDatabase[entryNumber].width = width / 8;
207 	filesDatabase[entryNumber].resType = resType;
208 	filesDatabase[entryNumber].height = height;
209 	filesDatabase[entryNumber].subData.index = -1;
210 
211 	return entryNumber;
212 #endif
213 }
214 
getFileType(const char * name)215 fileTypeEnum getFileType(const char *name) {
216 	char extentionBuffer[16];
217 
218 	fileTypeEnum newFileType = type_UNK;
219 
220 	getFileExtention(name, extentionBuffer);
221 
222 	if (!strcmp(extentionBuffer, ".SPL")) {
223 		newFileType = type_SPL;
224 	} else if (!strcmp(extentionBuffer, ".SET")) {
225 		newFileType = type_SET;
226 	} else if (!strcmp(extentionBuffer, ".FNT")) {
227 		newFileType = type_FNT;
228 	}
229 
230 	assert(newFileType != type_UNK);
231 
232 	return newFileType;
233 }
234 
getNumMaxEntiresInSet(uint8 * ptr)235 int getNumMaxEntiresInSet(uint8 *ptr) {
236 	uint16 numEntries = READ_BE_UINT16(ptr + 4);
237 	return numEntries;
238 }
239 
loadFile(const char * name,int idx,int destIdx)240 int loadFile(const char* name, int idx, int destIdx) {
241 	uint8 *ptr = NULL;
242 	fileTypeEnum fileType;
243 
244 	fileType = getFileType(name);
245 
246 	loadFileSub1(&ptr, name, NULL);
247 
248 	switch (fileType) {
249 	case type_SET: {
250 
251 		int numMaxEntriesInSet = getNumMaxEntiresInSet(ptr);
252 
253 		if (destIdx > numMaxEntriesInSet) {
254 			MemFree(ptr);
255 			return 0;	// exit if limit is reached
256 		}
257 		int res = loadSetEntry(name, ptr, destIdx, idx);
258 		MemFree(ptr);
259 
260 		return res;
261 	}
262 	case type_FNT: {
263 		int res = loadFNTSub(ptr, idx);
264 		MemFree(ptr);
265 
266 		return res;
267 	}
268 	case type_SPL: {
269 		// Sound file
270 		loadSPLSub(ptr, idx);
271 		break;
272 	}
273 	default:
274 		error("Unknown fileType in loadFile");
275 	}
276 
277 	MemFree(ptr);
278 
279 	return -1;
280 }
281 
loadFileRange(const char * name,int startIdx,int currentEntryIdx,int numIdx)282 int loadFileRange(const char *name, int startIdx, int currentEntryIdx, int numIdx) {
283 	uint8 *ptr = NULL;
284 	fileTypeEnum fileType;
285 
286 	fileType = getFileType(name);
287 
288 	loadFileSub1(&ptr, name, NULL);
289 
290 	switch (fileType) {
291 	case type_SET: {
292 		int numMaxEntriesInSet = getNumMaxEntiresInSet(ptr);
293 
294 		for (int i = 0; i < numIdx; i++) {
295 			if ((startIdx + i) > numMaxEntriesInSet) {
296 				MemFree(ptr);
297 				return 0;	// exit if limit is reached
298 			}
299 			loadSetEntry(name, ptr, startIdx + i, currentEntryIdx + i);
300 		}
301 
302 		break;
303 	}
304 	case type_FNT: {
305 		loadFNTSub(ptr, startIdx);
306 		break;
307 	}
308 	case type_SPL: {
309 		// Sound file
310 		loadSPLSub(ptr, startIdx);
311 		break;
312 	}
313 	default:
314 		error("Unknown fileType in loadFileRange");
315 	}
316 
317 	MemFree(ptr);
318 
319 	return 0;
320 }
321 
loadFullBundle(const char * name,int startIdx)322 int loadFullBundle(const char *name, int startIdx) {
323 	uint8 *ptr = NULL;
324 	fileTypeEnum fileType;
325 
326 	fileType = getFileType(name);
327 
328 	loadFileSub1(&ptr, name, NULL);
329 
330 	if (ptr == NULL)
331 		return 0;
332 
333 	switch (fileType) {
334 	case type_SET: {
335 		// Sprite set
336 		int numMaxEntriesInSet = getNumMaxEntiresInSet(ptr);	// get maximum number of sprites/animations in SET file
337 
338 		for (int i = 0; i < numMaxEntriesInSet; i++) {
339 			loadSetEntry(name, ptr, i, startIdx + i);
340 		}
341 
342 		break;
343 	}
344 	case type_FNT: {
345 		// Font file
346 		loadFNTSub(ptr, startIdx);
347 		break;
348 	}
349 	case type_SPL: {
350 		// Sound file
351 		loadSPLSub(ptr, startIdx);
352 		break;
353 	}
354 	default:
355 		error("Unknown fileType in loadFullBundle");
356 	}
357 
358 	MemFree(ptr);
359 
360 	return 0;
361 }
362 
loadFNTSub(uint8 * ptr,int destIdx)363 int loadFNTSub(uint8 *ptr, int destIdx) {
364 	uint8 *ptr2 = ptr + 4;
365 	loadFileVar1 = READ_BE_UINT32(ptr2);
366 
367 	int fileIndex;
368 	if (destIdx == -1)
369 		fileIndex = createResFileEntry(loadFileVar1, 1, loadFileVar1, 1);
370 	else
371 		fileIndex = updateResFileEntry(loadFileVar1, 1, loadFileVar1, destIdx, 1);
372 
373 	if (fileIndex < 0)
374 		error("Unable to load FNT resource");
375 
376 	uint8 *destPtr = filesDatabase[fileIndex].subData.ptr;
377 
378 	if (destPtr != NULL) {
379 		memcpy(destPtr, ptr2, loadFileVar1);
380 
381 		destPtr = filesDatabase[fileIndex].subData.ptr;
382 
383 		bigEndianLongToNative((int32 *) destPtr);
384 		bigEndianLongToNative((int32 *)(destPtr + 4));
385 		flipGen(destPtr + 8, 6);
386 
387 		uint8 *currentPtr = destPtr + 14;
388 
389 		for (int i = 0; i < (int16)READ_UINT16(destPtr + 8); i++) {
390 			bigEndianLongToNative((int32 *) currentPtr);
391 			currentPtr += 4;
392 
393 			flipGen(currentPtr, 8);
394 			currentPtr += 8;
395 		}
396 	}
397 
398 	return 1;
399 }
400 
loadSPLSub(uint8 * ptr,int destIdx)401 int loadSPLSub(uint8 *ptr, int destIdx) {
402 	int fileIndex;
403 
404 	if (destIdx == -1)
405 		fileIndex = createResFileEntry(loadFileVar1, 1, loadFileVar1, 1);
406 	else
407 		fileIndex = updateResFileEntry(loadFileVar1, 1, loadFileVar1, destIdx, 1);
408 
409 	if (fileIndex < 0)
410 		error("Unable to load SPL resource");
411 
412 	uint8* destPtr = filesDatabase[fileIndex].subData.ptr;
413 	memcpy(destPtr, ptr, loadFileVar1);
414 
415 	return 1;
416 }
417 
418 
loadSetEntry(const char * name,uint8 * ptr,int currentEntryIdx,int currentDestEntry)419 int loadSetEntry(const char *name, uint8 *ptr, int currentEntryIdx, int currentDestEntry) {
420 	uint8 *ptr3;
421 	int offset;
422 	int sec = 0;
423 	uint16 numIdx;
424 
425 	if (!strcmp((char *)ptr, "SEC"))
426 		sec = 1;
427 
428 	numIdx = READ_BE_UINT16(ptr + 4);
429 	ptr3 = ptr + 6;
430 	offset = currentEntryIdx * 16;
431 
432 	int resourceSize;
433 	int fileIndex;
434 	setHeaderEntry localBuffer;
435 
436 	Common::MemoryReadStream s4(ptr + offset + 6, 16);
437 
438 	localBuffer.offset = s4.readUint32BE();
439 	localBuffer.width = s4.readUint16BE();
440 	localBuffer.height = s4.readUint16BE();
441 	localBuffer.type = s4.readUint16BE();
442 	localBuffer.transparency = s4.readUint16BE() & 0x1F;
443 	localBuffer.hotspotY = s4.readUint16BE();
444 	localBuffer.hotspotX = s4.readUint16BE();
445 
446 	if (sec == 1)
447 		// Type 1: Width - (1*2) , Type 5: Width - (5*2)
448 		localBuffer.width -= localBuffer.type * 2;
449 
450 	resourceSize = localBuffer.width * localBuffer.height;
451 
452 	if (!sec && (localBuffer.type == 5))
453 		// Type 5: Width - (2*5)
454 		localBuffer.width -= 10;
455 
456 	if (currentDestEntry == -1)
457 		fileIndex = createResFileEntry(localBuffer.width, localBuffer.height, resourceSize, localBuffer.type);
458 	else
459 		fileIndex = updateResFileEntry(localBuffer.height, localBuffer.width, resourceSize, currentDestEntry, localBuffer.type);
460 
461 	if (fileIndex < 0)
462 		return -1;	// TODO: buffer is not freed
463 
464 	if (!sec && (localBuffer.type == 5)) {
465 		// There are sometimes sprites with a reduced width than what their pixels provide.
466 		// The original handled this here by copy parts of each line - for ScummVM, we're
467 		// simply setting the width in bytes and letting the decoder do the rest
468 		filesDatabase[fileIndex].width += 2;
469 	}
470 
471 	uint8 *ptr5 = ptr3 + localBuffer.offset + numIdx * 16;
472 	memcpy(filesDatabase[fileIndex].subData.ptr, ptr5, resourceSize);
473 
474 	switch (localBuffer.type) {
475 		case 0:   // polygon
476 			filesDatabase[fileIndex].subData.resourceType = OBJ_TYPE_POLY;
477 			filesDatabase[fileIndex].subData.index = currentEntryIdx;
478 			break;
479 
480 		case 1:
481 			filesDatabase[fileIndex].width = filesDatabase[fileIndex].widthInColumn * 8;
482 			filesDatabase[fileIndex].subData.resourceType = OBJ_TYPE_BGMASK;
483 			decodeGfxUnified(&filesDatabase[fileIndex], localBuffer.type);
484 			filesDatabase[fileIndex].subData.index = currentEntryIdx;
485 			filesDatabase[fileIndex].subData.transparency = 0;
486 			break;
487 
488 		case 4:
489 			filesDatabase[fileIndex].width = filesDatabase[fileIndex].widthInColumn * 2;
490 			filesDatabase[fileIndex].subData.resourceType = OBJ_TYPE_SPRITE;
491 			decodeGfxUnified(&filesDatabase[fileIndex], localBuffer.type);
492 			filesDatabase[fileIndex].subData.index = currentEntryIdx;
493 			filesDatabase[fileIndex].subData.transparency = localBuffer.transparency % 0x10;
494 			break;
495 
496 		case 5:
497 			filesDatabase[fileIndex].subData.resourceType = OBJ_TYPE_SPRITE;
498 			decodeGfxUnified(&filesDatabase[fileIndex], localBuffer.type);
499 			filesDatabase[fileIndex].width = filesDatabase[fileIndex].widthInColumn;
500 			filesDatabase[fileIndex].subData.index = currentEntryIdx;
501 			filesDatabase[fileIndex].subData.transparency = localBuffer.transparency;
502 			break;
503 
504 		case 8:
505 			filesDatabase[fileIndex].subData.resourceType = OBJ_TYPE_SPRITE;
506 			filesDatabase[fileIndex].width = filesDatabase[fileIndex].widthInColumn;
507 			filesDatabase[fileIndex].subData.index = currentEntryIdx;
508 			filesDatabase[fileIndex].subData.transparency = localBuffer.transparency;
509 			break;
510 
511 		default:
512 			warning("Unsupported gfx loading type: %d", localBuffer.type);
513 			break;
514 	}
515 
516 	if (name != filesDatabase[fileIndex].subData.name)
517 		Common::strlcpy(filesDatabase[fileIndex].subData.name, name, sizeof(filesDatabase[fileIndex].subData.name));
518 
519 	// create the mask
520 	switch (localBuffer.type) {
521 		case 1:
522 		case 4:
523 		case 5:
524 		case 8:
525 			memset(filesDatabase[fileIndex].subData.ptrMask, 0, filesDatabase[fileIndex].width / 8 * filesDatabase[fileIndex].height);
526 
527 			for (int maskY = 0; maskY < filesDatabase[fileIndex].height; maskY++) {
528 				for (int maskX = 0; maskX < filesDatabase[fileIndex].width; maskX++) {
529 					if (*(filesDatabase[fileIndex].subData.ptr + filesDatabase[fileIndex].width * maskY + maskX) != filesDatabase[fileIndex].subData.transparency) {
530 						*(filesDatabase[fileIndex].subData.ptrMask + filesDatabase[fileIndex].width / 8 * maskY + maskX / 8) |= 0x80 >> (maskX & 7);
531 					}
532 				}
533 			}
534 			break;
535 
536 		default:
537 			break;
538 	}
539 
540 	// TODO: free
541 
542 	return 1;
543 }
544 
545 } // End of namespace Cruise
546