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