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 
24 #include "common/debug.h"
25 #include "common/textconsole.h"
26 
27 #include "sword1/memman.h"
28 #include "sword1/resman.h"
29 #include "sword1/swordres.h"
30 
31 #include "gui/message.h"
32 
33 namespace Sword1 {
guiFatalError(char * msg)34 void guiFatalError(char *msg) {
35 	// Displays a dialog on-screen before terminating the engine.
36 	// TODO: We really need to setup a special palette for cases when
37 	// the engine is erroring before setting one... otherwise invisible cursor :)
38 
39 	GUI::MessageDialog dialog(msg);
40 	dialog.runModal();
41 	error("%s", msg);
42 }
43 
44 #define MAX_PATH_LEN 260
45 
ResMan(const char * fileName,bool isMacFile)46 ResMan::ResMan(const char *fileName, bool isMacFile) {
47 	_openCluStart = _openCluEnd = NULL;
48 	_openClus = 0;
49 	_isBigEndian = isMacFile;
50 	_memMan = new MemMan();
51 	loadCluDescript(fileName);
52 }
53 
~ResMan()54 ResMan::~ResMan() {
55 #if 0
56 	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
57 		Clu *cluster = _prj.clu[clusCnt];
58 		if (cluster) {
59 			for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
60 				Grp *group = cluster->grp[grpCnt];
61 				if (group) {
62 					for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++) {
63 						if (group->resHandle[resCnt].cond == MEM_DONT_FREE) {
64 							warning("ResMan::~ResMan: Resource %02X.%04X.%02X is still open",
65 							        clusCnt + 1, grpCnt, resCnt);
66 						}
67 					}
68 				}
69 			}
70 		}
71 	}
72 	debug(0, "ResMan closed\n");
73 #endif
74 	flush();
75 	freeCluDescript();
76 	delete _memMan;
77 }
78 
loadCluDescript(const char * fileName)79 void ResMan::loadCluDescript(const char *fileName) {
80 	// The cluster description file is always little endian (even on the mac version, whose cluster files are big endian)
81 	Common::File file;
82 	file.open(fileName);
83 
84 	if (!file.isOpen()) {
85 		char msg[512];
86 		sprintf(msg, "Couldn't open CLU description '%s'\n\nIf you are running from CD, please ensure you have read the ScummVM documentation regarding multi-cd games.", fileName);
87 		guiFatalError(msg);
88 	}
89 
90 
91 	_prj.noClu = file.readUint32LE();
92 	_prj.clu = new Clu[_prj.noClu];
93 	memset(_prj.clu, 0, _prj.noClu * sizeof(Clu));
94 
95 	uint32 *cluIndex = (uint32 *)malloc(_prj.noClu * 4);
96 	file.read(cluIndex, _prj.noClu * 4);
97 
98 	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++)
99 		if (cluIndex[clusCnt]) {
100 			Clu *cluster = _prj.clu + clusCnt;
101 			file.read(cluster->label, MAX_LABEL_SIZE);
102 
103 			cluster->file = NULL;
104 			cluster->noGrp = file.readUint32LE();
105 			cluster->grp = new Grp[cluster->noGrp];
106 			cluster->nextOpen = NULL;
107 			memset(cluster->grp, 0, cluster->noGrp * sizeof(Grp));
108 			cluster->refCount = 0;
109 
110 			uint32 *grpIndex = (uint32 *)malloc(cluster->noGrp * 4);
111 			file.read(grpIndex, cluster->noGrp * 4);
112 
113 			for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++)
114 				if (grpIndex[grpCnt]) {
115 					Grp *group = cluster->grp + grpCnt;
116 					group->noRes = file.readUint32LE();
117 					group->resHandle = new MemHandle[group->noRes];
118 					group->offset = new uint32[group->noRes];
119 					group->length = new uint32[group->noRes];
120 					uint32 *resIdIdx = (uint32 *)malloc(group->noRes * 4);
121 					file.read(resIdIdx, group->noRes * 4);
122 
123 					for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++) {
124 						if (resIdIdx[resCnt]) {
125 							group->offset[resCnt] = file.readUint32LE();
126 							group->length[resCnt] = file.readUint32LE();
127 							_memMan->initHandle(group->resHandle + resCnt);
128 						} else {
129 							group->offset[resCnt] = 0xFFFFFFFF;
130 							group->length[resCnt] = 0;
131 							_memMan->initHandle(group->resHandle + resCnt);
132 						}
133 					}
134 					free(resIdIdx);
135 				}
136 			free(grpIndex);
137 		}
138 	free(cluIndex);
139 
140 	if (_prj.clu[3].grp[5].noRes == 29)
141 		for (uint8 cnt = 0; cnt < 29; cnt++)
142 			_srIdList[cnt] = 0x04050000 | cnt;
143 }
144 
freeCluDescript()145 void ResMan::freeCluDescript() {
146 
147 	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
148 		Clu *cluster = _prj.clu + clusCnt;
149 		for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
150 			Grp *group = cluster->grp + grpCnt;
151 			if (group->resHandle != NULL) {
152 				for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++)
153 					_memMan->freeNow(group->resHandle + resCnt);
154 
155 				delete[] group->resHandle;
156 				delete[] group->offset;
157 				delete[] group->length;
158 			}
159 		}
160 		delete[] cluster->grp;
161 		delete cluster->file;
162 	}
163 	delete[] _prj.clu;
164 }
165 
flush()166 void ResMan::flush() {
167 	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
168 		Clu *cluster = _prj.clu + clusCnt;
169 		for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
170 			Grp *group = cluster->grp + grpCnt;
171 			for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++)
172 				if (group->resHandle[resCnt].cond != MEM_FREED) {
173 					_memMan->setCondition(group->resHandle + resCnt, MEM_CAN_FREE);
174 					group->resHandle[resCnt].refCount = 0;
175 				}
176 		}
177 		if (cluster->file) {
178 			cluster->file->close();
179 			delete cluster->file;
180 			cluster->file = NULL;
181 			cluster->refCount = 0;
182 		}
183 	}
184 	_openClus = 0;
185 	_openCluStart = _openCluEnd = NULL;
186 	// the memory manager cached the blocks we asked it to free, so explicitly make it free them
187 	_memMan->flush();
188 }
189 
fetchRes(uint32 id)190 void *ResMan::fetchRes(uint32 id) {
191 	MemHandle *memHandle = resHandle(id);
192 	if (!memHandle) {
193 		warning("fetchRes:: resource %d out of bounds", id);
194 		return NULL;
195 	}
196 	if (!memHandle->data)
197 		error("fetchRes:: resource %d is not open", id);
198 	return memHandle->data;
199 }
200 
openFetchRes(uint32 id)201 void *ResMan::openFetchRes(uint32 id) {
202 	resOpen(id);
203 	return fetchRes(id);
204 }
205 
dumpRes(uint32 id)206 void ResMan::dumpRes(uint32 id) {
207 	char outn[30];
208 	sprintf(outn, "DUMP%08X.BIN", id);
209 	Common::DumpFile outf;
210 	if (outf.open(outn)) {
211 		resOpen(id);
212 		MemHandle *memHandle = resHandle(id);
213 		if (memHandle) {
214 			outf.write(memHandle->data, memHandle->size);
215 			outf.close();
216 		}
217 		resClose(id);
218 	}
219 }
220 
lockScript(uint32 scrID)221 Header *ResMan::lockScript(uint32 scrID) {
222 	if (!_scriptList[scrID / ITM_PER_SEC])
223 		error("Script id %d not found", scrID);
224 	scrID = _scriptList[scrID / ITM_PER_SEC];
225 #ifdef SCUMM_BIG_ENDIAN
226 	openScriptResourceBigEndian(scrID);
227 #else
228 	openScriptResourceLittleEndian(scrID);
229 #endif
230 	MemHandle *handle = resHandle(scrID);
231 	if (!handle)
232 		error("Script resource handle %d not found", scrID);
233 	return (Header *)handle->data;
234 }
235 
unlockScript(uint32 scrID)236 void ResMan::unlockScript(uint32 scrID) {
237 	resClose(_scriptList[scrID / ITM_PER_SEC]);
238 }
239 
cptResOpen(uint32 id)240 void *ResMan::cptResOpen(uint32 id) {
241 #ifdef SCUMM_BIG_ENDIAN
242 	openCptResourceBigEndian(id);
243 #else
244 	openCptResourceLittleEndian(id);
245 #endif
246 	MemHandle *handle = resHandle(id);
247 	return handle != NULL ? handle->data : NULL;
248 }
249 
resOpen(uint32 id)250 void ResMan::resOpen(uint32 id) {  // load resource ID into memory
251 	MemHandle *memHandle = resHandle(id);
252 	if (!memHandle)
253 		return;
254 	if (memHandle->cond == MEM_FREED) { // memory has been freed
255 		uint32 size = resLength(id);
256 		_memMan->alloc(memHandle, size);
257 		Common::File *clusFile = resFile(id);
258 		assert(clusFile);
259 		clusFile->seek(resOffset(id));
260 		clusFile->read(memHandle->data, size);
261 		if (clusFile->err() || clusFile->eos()) {
262 			error("Can't read %d bytes from offset %d from cluster file %s\nResource ID: %d (%08X)", size, resOffset(id), _prj.clu[(id >> 24) - 1].label, id, id);
263 		}
264 	} else
265 		_memMan->setCondition(memHandle, MEM_DONT_FREE);
266 
267 	memHandle->refCount++;
268 	if (memHandle->refCount > 20) {
269 		debug(1, "%d references to id %d. Guess there's something wrong.", memHandle->refCount, id);
270 	}
271 }
272 
resClose(uint32 id)273 void ResMan::resClose(uint32 id) {
274 	MemHandle *handle = resHandle(id);
275 	if (!handle)
276 		return;
277 	if (!handle->refCount) {
278 		warning("Resource Manager fail: unlocking object with refCount 0. Id: %d", id);
279 	} else {
280 		handle->refCount--;
281 		if (!handle->refCount)
282 			_memMan->setCondition(handle, MEM_CAN_FREE);
283 	}
284 }
285 
fetchFrame(void * resourceData,uint32 frameNo)286 FrameHeader *ResMan::fetchFrame(void *resourceData, uint32 frameNo) {
287 	uint8 *frameFile = (uint8 *)resourceData;
288 	uint8 *idxData = frameFile + sizeof(Header);
289 	if (_isBigEndian) {
290 		if (frameNo >= READ_BE_UINT32(idxData))
291 			error("fetchFrame:: frame %d doesn't exist in resource.", frameNo);
292 		frameFile += READ_BE_UINT32(idxData + (frameNo + 1) * 4);
293 	} else {
294 		if (frameNo >= READ_LE_UINT32(idxData))
295 			error("fetchFrame:: frame %d doesn't exist in resource.", frameNo);
296 		frameFile += READ_LE_UINT32(idxData + (frameNo + 1) * 4);
297 	}
298 	return (FrameHeader *)frameFile;
299 }
300 
resFile(uint32 id)301 Common::File *ResMan::resFile(uint32 id) {
302 	Clu *cluster = _prj.clu + ((id >> 24) - 1);
303 	if (cluster->file == NULL) {
304 		_openClus++;
305 		if (_openCluEnd == NULL) {
306 			_openCluStart = _openCluEnd = cluster;
307 		} else {
308 			_openCluEnd->nextOpen = cluster;
309 			_openCluEnd = cluster;
310 		}
311 		cluster->file = new Common::File();
312 		char fileName[36];
313 		// Supposes that big endian means mac cluster file and little endian means PC cluster file.
314 		// This works, but we may want to separate the file name from the endianess or try .CLM extension if opening.clu file fail.
315 		if (_isBigEndian)
316 			sprintf(fileName, "%s.CLM", _prj.clu[(id >> 24) - 1].label);
317 		else
318 			sprintf(fileName, "%s.CLU", _prj.clu[(id >> 24) - 1].label);
319 		cluster->file->open(fileName);
320 		if (!cluster->file->isOpen()) {
321 			char msg[512];
322 			sprintf(msg, "Couldn't open game cluster file '%s'\n\nIf you are running from CD, please ensure you have read the ScummVM documentation regarding multi-cd games.", fileName);
323 			guiFatalError(msg);
324 		}
325 		while (_openClus > MAX_OPEN_CLUS) {
326 			assert(_openCluStart);
327 			Clu *closeClu = _openCluStart;
328 			_openCluStart = _openCluStart->nextOpen;
329 
330 			if (closeClu->file)
331 				closeClu->file->close();
332 			delete closeClu->file;
333 			closeClu->file = NULL;
334 			closeClu->nextOpen = NULL;
335 
336 			_openClus--;
337 		}
338 	}
339 	return cluster->file;
340 }
341 
resHandle(uint32 id)342 MemHandle *ResMan::resHandle(uint32 id) {
343 	if ((id >> 16) == 0x0405)
344 		id = _srIdList[id & 0xFFFF];
345 	uint8 cluster = (uint8)((id >> 24) - 1);
346 	uint8 group = (uint8)(id >> 16);
347 
348 	// There is a known case of reading beyond array boundaries when trying to use
349 	// portuguese subtitles (cluster file 2, group 6) with a version that does not
350 	// contain subtitles for this languages (i.e. has only 6 languages and not 7).
351 	if (cluster >= _prj.noClu || group >= _prj.clu[cluster].noGrp)
352 		return NULL;
353 
354 	return &(_prj.clu[cluster].grp[group].resHandle[id & 0xFFFF]);
355 }
356 
resLength(uint32 id)357 uint32 ResMan::resLength(uint32 id) {
358 	if ((id >> 16) == 0x0405)
359 		id = _srIdList[id & 0xFFFF];
360 	uint8 cluster = (uint8)((id >> 24) - 1);
361 	uint8 group = (uint8)(id >> 16);
362 
363 	if (cluster >= _prj.noClu || group >= _prj.clu[cluster].noGrp)
364 		return 0;
365 
366 	return _prj.clu[cluster].grp[group].length[id & 0xFFFF];
367 }
368 
resOffset(uint32 id)369 uint32 ResMan::resOffset(uint32 id) {
370 	if ((id >> 16) == 0x0405)
371 		id = _srIdList[id & 0xFFFF];
372 	uint8 cluster = (uint8)((id >> 24) - 1);
373 	uint8 group = (uint8)(id >> 16);
374 
375 	if (cluster >= _prj.noClu || group >= _prj.clu[cluster].noGrp)
376 		return 0;
377 
378 	return _prj.clu[cluster].grp[group].offset[id & 0xFFFF];
379 }
380 
openCptResourceBigEndian(uint32 id)381 void ResMan::openCptResourceBigEndian(uint32 id) {
382 	bool needByteSwap = false;
383 	if (!_isBigEndian) {
384 		// Cluster files are in little endian fomat.
385 		// If the resource are not in memory anymore, and therefore will be read
386 		// from disk, they will need to be byte swaped.
387 		MemHandle *memHandle = resHandle(id);
388 		if (memHandle)
389 			needByteSwap = (memHandle->cond == MEM_FREED);
390 	}
391 	resOpen(id);
392 	if (needByteSwap) {
393 		MemHandle *handle = resHandle(id);
394 		if (!handle)
395 			return;
396 		uint32 totSize = handle->size;
397 		uint32 *data = (uint32 *)((uint8 *)handle->data + sizeof(Header));
398 		totSize -= sizeof(Header);
399 		if (totSize & 3)
400 			error("Illegal compact size for id %d: %d", id, totSize);
401 		totSize /= 4;
402 		for (uint32 cnt = 0; cnt < totSize; cnt++) {
403 			*data = READ_LE_UINT32(data);
404 			data++;
405 		}
406 	}
407 }
408 
openCptResourceLittleEndian(uint32 id)409 void ResMan::openCptResourceLittleEndian(uint32 id) {
410 	bool needByteSwap = false;
411 	if (_isBigEndian) {
412 		// Cluster files are in big endian fomat.
413 		// If the resource are not in memory anymore, and therefore will be read
414 		// from disk, they will need to be byte swaped.
415 		MemHandle *memHandle = resHandle(id);
416 		if (memHandle)
417 			needByteSwap = (memHandle->cond == MEM_FREED);
418 	}
419 	resOpen(id);
420 	if (needByteSwap) {
421 		MemHandle *handle = resHandle(id);
422 		if (!handle)
423 			return;
424 		uint32 totSize = handle->size;
425 		uint32 *data = (uint32 *)((uint8 *)handle->data + sizeof(Header));
426 		totSize -= sizeof(Header);
427 		if (totSize & 3)
428 			error("Illegal compact size for id %d: %d", id, totSize);
429 		totSize /= 4;
430 		for (uint32 cnt = 0; cnt < totSize; cnt++) {
431 			*data = READ_BE_UINT32(data);
432 			data++;
433 		}
434 	}
435 }
436 
openScriptResourceBigEndian(uint32 id)437 void ResMan::openScriptResourceBigEndian(uint32 id) {
438 	bool needByteSwap = false;
439 	if (!_isBigEndian) {
440 		// Cluster files are in little endian fomat.
441 		// If the resource are not in memory anymore, and therefore will be read
442 		// from disk, they will need to be byte swaped.
443 		MemHandle *memHandle = resHandle(id);
444 		if (memHandle)
445 			needByteSwap = (memHandle->cond == MEM_FREED);
446 	}
447 	resOpen(id);
448 	if (needByteSwap) {
449 		MemHandle *handle = resHandle(id);
450 		if (!handle)
451 			return;
452 		// uint32 totSize = handle->size;
453 		Header *head = (Header *)handle->data;
454 		head->comp_length = FROM_LE_32(head->comp_length);
455 		head->decomp_length = FROM_LE_32(head->decomp_length);
456 		head->version = FROM_LE_16(head->version);
457 		uint32 *data = (uint32 *)((uint8 *)handle->data + sizeof(Header));
458 		uint32 size = handle->size - sizeof(Header);
459 		if (size & 3)
460 			error("Odd size during script endian conversion. Resource ID =%d, size = %d", id, size);
461 		size >>= 2;
462 		for (uint32 cnt = 0; cnt < size; cnt++) {
463 			*data = READ_LE_UINT32(data);
464 			data++;
465 		}
466 	}
467 }
468 
openScriptResourceLittleEndian(uint32 id)469 void ResMan::openScriptResourceLittleEndian(uint32 id) {
470 	bool needByteSwap = false;
471 	if (_isBigEndian) {
472 		// Cluster files are in big endian fomat.
473 		// If the resource are not in memory anymore, and therefore will be read
474 		// from disk, they will need to be byte swaped.
475 		MemHandle *memHandle = resHandle(id);
476 		if (memHandle)
477 			needByteSwap = (memHandle->cond == MEM_FREED);
478 	}
479 	resOpen(id);
480 	if (needByteSwap) {
481 		MemHandle *handle = resHandle(id);
482 		if (!handle)
483 			return;
484 		// uint32 totSize = handle->size;
485 		Header *head = (Header *)handle->data;
486 		head->comp_length = FROM_BE_32(head->comp_length);
487 		head->decomp_length = FROM_BE_32(head->decomp_length);
488 		head->version = FROM_BE_16(head->version);
489 		uint32 *data = (uint32 *)((uint8 *)handle->data + sizeof(Header));
490 		uint32 size = handle->size - sizeof(Header);
491 		if (size & 3)
492 			error("Odd size during script endian conversion. Resource ID =%d, size = %d", id, size);
493 		size >>= 2;
494 		for (uint32 cnt = 0; cnt < size; cnt++) {
495 			*data = READ_BE_UINT32(data);
496 			data++;
497 		}
498 	}
499 }
500 
501 
502 uint32 ResMan::_srIdList[29] = { // the file numbers differ for the control panel file IDs, so we need this array
503 	OTHER_SR_FONT,      // SR_FONT
504 	0x04050000,         // SR_BUTTON
505 	OTHER_SR_REDFONT,   // SR_REDFONT
506 	0x04050001,         // SR_PALETTE
507 	0x04050002,         // SR_PANEL_ENGLISH
508 	0x04050003,         // SR_PANEL_FRENCH
509 	0x04050004,         // SR_PANEL_GERMAN
510 	0x04050005,         // SR_PANEL_ITALIAN
511 	0x04050006,         // SR_PANEL_SPANISH
512 	0x04050007,         // SR_PANEL_AMERICAN
513 	0x04050008,         // SR_TEXT_BUTTON
514 	0x04050009,         // SR_SPEED
515 	0x0405000A,         // SR_SCROLL1
516 	0x0405000B,         // SR_SCROLL2
517 	0x0405000C,         // SR_CONFIRM
518 	0x0405000D,         // SR_VOLUME
519 	0x0405000E,         // SR_VLIGHT
520 	0x0405000F,         // SR_VKNOB
521 	0x04050010,         // SR_WINDOW
522 	0x04050011,         // SR_SLAB1
523 	0x04050012,         // SR_SLAB2
524 	0x04050013,         // SR_SLAB3
525 	0x04050014,         // SR_SLAB4
526 	0x04050015,         // SR_BUTUF
527 	0x04050016,         // SR_BUTUS
528 	0x04050017,         // SR_BUTDS
529 	0x04050018,         // SR_BUTDF
530 	0x04050019,         // SR_DEATHPANEL
531 	0,
532 };
533 
534 } // End of namespace Sword1
535