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/endian.h"
26 #include "common/file.h"
27 #include "common/textconsole.h"
28 #include "common/translation.h"
29 #include "sky/compact.h"
30 #include "gui/message.h"
31 #include <stddef.h> // for ptrdiff_t
32
33 namespace Sky {
34
35 #define SKY_CPT_SIZE 419427
36
37 #define OFFS(type,item) ((uint32)(((ptrdiff_t)(&((type *)42)->item))-42))
38 #define MK32(type,item) OFFS(type, item),0,0,0
39 #define MK16(type,item) OFFS(type, item),0
40 #define MK32_A5(type, item) MK32(type, item[0]), MK32(type, item[1]), \
41 MK32(type, item[2]), MK32(type, item[3]), MK32(type, item[4])
42
43 static const uint32 compactOffsets[] = {
44 MK16(Compact, logic),
45 MK16(Compact, status),
46 MK16(Compact, sync),
47 MK16(Compact, screen),
48 MK16(Compact, place),
49 MK32(Compact, getToTableId),
50 MK16(Compact, xcood),
51 MK16(Compact, ycood),
52 MK16(Compact, frame),
53 MK16(Compact, cursorText),
54 MK16(Compact, mouseOn),
55 MK16(Compact, mouseOff),
56 MK16(Compact, mouseClick),
57 MK16(Compact, mouseRelX),
58 MK16(Compact, mouseRelY),
59 MK16(Compact, mouseSizeX),
60 MK16(Compact, mouseSizeY),
61 MK16(Compact, actionScript),
62 MK16(Compact, upFlag),
63 MK16(Compact, downFlag),
64 MK16(Compact, getToFlag),
65 MK16(Compact, flag),
66 MK16(Compact, mood),
67 MK32(Compact, grafixProgId),
68 MK16(Compact, offset),
69 MK16(Compact, mode),
70 MK16(Compact, baseSub),
71 MK16(Compact, baseSub_off),
72 MK16(Compact, actionSub),
73 MK16(Compact, actionSub_off),
74 MK16(Compact, getToSub),
75 MK16(Compact, getToSub_off),
76 MK16(Compact, extraSub),
77 MK16(Compact, extraSub_off),
78 MK16(Compact, dir),
79 MK16(Compact, stopScript),
80 MK16(Compact, miniBump),
81 MK16(Compact, leaving),
82 MK16(Compact, atWatch),
83 MK16(Compact, atWas),
84 MK16(Compact, alt),
85 MK16(Compact, request),
86 MK16(Compact, spWidth_xx),
87 MK16(Compact, spColor),
88 MK16(Compact, spTextId),
89 MK16(Compact, spTime),
90 MK16(Compact, arAnimIndex),
91 MK32(Compact, turnProgId),
92 MK16(Compact, waitingFor),
93 MK16(Compact, arTargetX),
94 MK16(Compact, arTargetY),
95 MK32(Compact, animScratchId),
96 MK16(Compact, megaSet),
97 };
98
99 static const uint32 megaSetOffsets[] = {
100 MK16(MegaSet, gridWidth),
101 MK16(MegaSet, colOffset),
102 MK16(MegaSet, colWidth),
103 MK16(MegaSet, lastChr),
104 MK32(MegaSet, animUpId),
105 MK32(MegaSet, animDownId),
106 MK32(MegaSet, animLeftId),
107 MK32(MegaSet, animRightId),
108 MK32(MegaSet, standUpId),
109 MK32(MegaSet, standDownId),
110 MK32(MegaSet, standLeftId),
111 MK32(MegaSet, standRightId),
112 MK32(MegaSet, standTalkId),
113 };
114
115 static const uint32 turnTableOffsets[] = {
116 MK32_A5(TurnTable, turnTableUp),
117 MK32_A5(TurnTable, turnTableDown),
118 MK32_A5(TurnTable, turnTableLeft),
119 MK32_A5(TurnTable, turnTableRight),
120 MK32_A5(TurnTable, turnTableTalk),
121 };
122
123 #define COMPACT_SIZE (sizeof(compactOffsets)/sizeof(uint32))
124 #define MEGASET_SIZE (sizeof(megaSetOffsets)/sizeof(uint32))
125 #define TURNTABLE_SIZE (sizeof(turnTableOffsets)/sizeof(uint32))
126
SkyCompact()127 SkyCompact::SkyCompact() {
128 _cptFile = new Common::File();
129 Common::String filename = "sky.cpt";
130 if (!_cptFile->open(filename.c_str())) {
131 Common::String msg = Common::String::format(_("Unable to locate the '%s' engine data file."), filename.c_str());
132 GUIErrorMessage(msg);
133 error("%s", msg.c_str());
134 }
135
136 uint16 fileVersion = _cptFile->readUint16LE();
137 if (fileVersion != 0)
138 error("unknown \"sky.cpt\" version");
139
140 if (SKY_CPT_SIZE != _cptFile->size()) {
141 GUI::MessageDialog dialog(_("The \"sky.cpt\" engine data file has an incorrect size."), _("OK"), NULL);
142 dialog.runModal();
143 error("Incorrect sky.cpt size (%d, expected: %d)", _cptFile->size(), SKY_CPT_SIZE);
144 }
145
146 // set the necessary data structs up...
147 _numDataLists = _cptFile->readUint16LE();
148 _cptNames = (char***)malloc(_numDataLists * sizeof(char**));
149 _dataListLen = (uint16 *)malloc(_numDataLists * sizeof(uint16));
150 _cptSizes = (uint16 **)malloc(_numDataLists * sizeof(uint16 *));
151 _cptTypes = (uint16 **)malloc(_numDataLists * sizeof(uint16 *));
152 _compacts = (Compact***)malloc(_numDataLists * sizeof(Compact**));
153
154 for (int i = 0; i < _numDataLists; i++) {
155 _dataListLen[i] = _cptFile->readUint16LE();
156 _cptNames[i] = (char**)malloc(_dataListLen[i] * sizeof(char *));
157 _cptSizes[i] = (uint16 *)malloc(_dataListLen[i] * sizeof(uint16));
158 _cptTypes[i] = (uint16 *)malloc(_dataListLen[i] * sizeof(uint16));
159 _compacts[i] = (Compact**)malloc(_dataListLen[i] * sizeof(Compact *));
160 }
161
162 uint32 rawSize = _cptFile->readUint32LE() * sizeof(uint16);
163 uint16 *rawPos = _rawBuf = (uint16 *)malloc(rawSize);
164
165 uint32 srcSize = _cptFile->readUint32LE() * sizeof(uint16);
166 uint16 *srcBuf = (uint16 *)malloc(srcSize);
167 uint16 *srcPos = srcBuf;
168 _cptFile->read(srcBuf, srcSize);
169
170 uint32 asciiSize = _cptFile->readUint32LE();
171 char *asciiPos = _asciiBuf = (char *)malloc(asciiSize);
172 _cptFile->read(_asciiBuf, asciiSize);
173
174 // and fill them with the compact data
175 for (uint32 lcnt = 0; lcnt < _numDataLists; lcnt++) {
176 for (uint32 ecnt = 0; ecnt < _dataListLen[lcnt]; ecnt++) {
177 _cptSizes[lcnt][ecnt] = READ_LE_UINT16(srcPos++);
178 if (_cptSizes[lcnt][ecnt]) {
179 _cptTypes[lcnt][ecnt] = READ_LE_UINT16(srcPos++);
180 _compacts[lcnt][ecnt] = (Compact *)rawPos;
181 _cptNames[lcnt][ecnt] = asciiPos;
182 asciiPos += strlen(asciiPos) + 1;
183
184 for (uint16 elemCnt = 0; elemCnt < _cptSizes[lcnt][ecnt]; elemCnt++)
185 *rawPos++ = READ_LE_UINT16(srcPos++);
186 } else {
187 _cptTypes[lcnt][ecnt] = 0;
188 _compacts[lcnt][ecnt] = NULL;
189 _cptNames[lcnt][ecnt] = NULL;
190 }
191 }
192 }
193 free(srcBuf);
194
195 uint16 numDlincs = _cptFile->readUint16LE();
196 uint16 *dlincBuf = (uint16 *)malloc(numDlincs * 2 * sizeof(uint16));
197 uint16 *dlincPos = dlincBuf;
198 _cptFile->read(dlincBuf, numDlincs * 2 * sizeof(uint16));
199 // these compacts don't actually exist but only point to other ones...
200 uint16 cnt;
201 for (cnt = 0; cnt < numDlincs; cnt++) {
202 uint16 dlincId = READ_LE_UINT16(dlincPos++);
203 uint16 destId = READ_LE_UINT16(dlincPos++);
204 assert(((dlincId >> 12) < _numDataLists) && ((dlincId & 0xFFF) < _dataListLen[dlincId >> 12]) && (_compacts[dlincId >> 12][dlincId & 0xFFF] == NULL));
205 _compacts[dlincId >> 12][dlincId & 0xFFF] = _compacts[destId >> 12][destId & 0xFFF];
206
207 assert(_cptNames[dlincId >> 12][dlincId & 0xFFF] == NULL);
208 _cptNames[dlincId >> 12][dlincId & 0xFFF] = asciiPos;
209 asciiPos += strlen(asciiPos) + 1;
210 }
211 free(dlincBuf);
212
213 // if this is v0.0288, parse this diff data
214 uint16 numDiffs = _cptFile->readUint16LE();
215 uint16 diffSize = _cptFile->readUint16LE();
216 uint16 *diffBuf = (uint16 *)malloc(diffSize * sizeof(uint16));
217 _cptFile->read(diffBuf, diffSize * sizeof(uint16));
218 if (SkyEngine::_systemVars.gameVersion == 288) {
219 uint16 *diffPos = diffBuf;
220 for (cnt = 0; cnt < numDiffs; cnt++) {
221 uint16 cptId = READ_LE_UINT16(diffPos++);
222 uint16 *rawCpt = (uint16 *)fetchCpt(cptId);
223 rawCpt += READ_LE_UINT16(diffPos++);
224 uint16 len = READ_LE_UINT16(diffPos++);
225 for (uint16 elemCnt = 0; elemCnt < len; elemCnt++)
226 rawCpt[elemCnt] = READ_LE_UINT16(diffPos++);
227 }
228 assert(diffPos == (diffBuf + diffSize));
229 }
230 free(diffBuf);
231
232 // these are the IDs that have to be saved into savegame files.
233 _numSaveIds = _cptFile->readUint16LE();
234 _saveIds = (uint16 *)malloc(_numSaveIds * sizeof(uint16));
235 _cptFile->read(_saveIds, _numSaveIds * sizeof(uint16));
236 for (cnt = 0; cnt < _numSaveIds; cnt++)
237 _saveIds[cnt] = FROM_LE_16(_saveIds[cnt]);
238 _resetDataPos = _cptFile->pos();
239
240 checkAndFixOfficerBluntError();
241 }
242
~SkyCompact()243 SkyCompact::~SkyCompact() {
244 free(_rawBuf);
245 free(_asciiBuf);
246 free(_saveIds);
247 for (int i = 0; i < _numDataLists; i++) {
248 free(_cptNames[i]);
249 free(_cptSizes[i]);
250 free(_cptTypes[i]);
251 free(_compacts[i]);
252 }
253 free(_cptNames);
254 free(_dataListLen);
255 free(_cptSizes);
256 free(_cptTypes);
257 free(_compacts);
258 _cptFile->close();
259 delete _cptFile;
260 }
261
262 /* WORKAROUND for bug #2687:
263 The first release of scummvm with externalized, binary compact data has one broken 16 bit reference.
264 When talking to Officer Blunt on ground level while in a crouched position, the game enters an
265 unfinishable state because Blunt jumps into the lake and can no longer be interacted with.
266 This fixes the problem when playing with a broken sky.cpt */
267 #define SCUMMVM_BROKEN_TALK_INDEX 158
checkAndFixOfficerBluntError()268 void SkyCompact::checkAndFixOfficerBluntError() {
269 // Retrieve the table with the animation ids to use for talking
270 uint16 *talkTable = (uint16*)fetchCpt(CPT_TALK_TABLE_LIST);
271 if (talkTable[SCUMMVM_BROKEN_TALK_INDEX] == ID_SC31_GUARD_TALK) {
272 debug(1, "SKY.CPT with Officer Blunt bug encountered, fixing talk gfx.");
273 talkTable[SCUMMVM_BROKEN_TALK_INDEX] = ID_SC31_GUARD_TALK2;
274 }
275 }
276
277 // needed for some workaround where the engine has to check if it's currently processing joey, for example
cptIsId(Compact * cpt,uint16 id)278 bool SkyCompact::cptIsId(Compact *cpt, uint16 id) {
279 return (cpt == fetchCpt(id));
280 }
281
fetchCpt(uint16 cptId)282 Compact *SkyCompact::fetchCpt(uint16 cptId) {
283 if (cptId == 0xFFFF) // is this really still necessary?
284 return NULL;
285 assert(((cptId >> 12) < _numDataLists) && ((cptId & 0xFFF) < _dataListLen[cptId >> 12]));
286
287 debug(8, "Loading Compact %s [%s] (%04X=%d,%d)", _cptNames[cptId >> 12][cptId & 0xFFF], nameForType(_cptTypes[cptId >> 12][cptId & 0xFFF]), cptId, cptId >> 12, cptId & 0xFFF);
288
289 return _compacts[cptId >> 12][cptId & 0xFFF];
290 }
291
fetchCptInfo(uint16 cptId,uint16 * elems,uint16 * type,char * name)292 Compact *SkyCompact::fetchCptInfo(uint16 cptId, uint16 *elems, uint16 *type, char *name) {
293 assert(((cptId >> 12) < _numDataLists) && ((cptId & 0xFFF) < _dataListLen[cptId >> 12]));
294 if (elems)
295 *elems = _cptSizes[cptId >> 12][cptId & 0xFFF];
296 if (type)
297 *type = _cptTypes[cptId >> 12][cptId & 0xFFF];
298 if (name) {
299 if (_cptNames[cptId >> 12][cptId & 0xFFF] != NULL)
300 strcpy(name, _cptNames[cptId >> 12][cptId & 0xFFF]);
301 else
302 strcpy(name, "(null)");
303 }
304 return fetchCpt(cptId);
305 }
306
nameForType(uint16 type)307 const char *SkyCompact::nameForType(uint16 type) {
308 if (type >= NUM_CPT_TYPES)
309 return "unknown";
310 else
311 return _typeNames[type];
312 }
313
getSub(Compact * cpt,uint16 mode)314 uint16 SkyCompact::getSub(Compact *cpt, uint16 mode) {
315 switch (mode) {
316 case 0:
317 return cpt->baseSub;
318 case 2:
319 return cpt->baseSub_off;
320 case 4:
321 return cpt->actionSub;
322 case 6:
323 return cpt->actionSub_off;
324 case 8:
325 return cpt->getToSub;
326 case 10:
327 return cpt->getToSub_off;
328 case 12:
329 return cpt->extraSub;
330 case 14:
331 return cpt->extraSub_off;
332 default:
333 error("Invalid Mode (%d)", mode);
334 }
335 }
336
setSub(Compact * cpt,uint16 mode,uint16 value)337 void SkyCompact::setSub(Compact *cpt, uint16 mode, uint16 value) {
338 switch (mode) {
339 case 0:
340 cpt->baseSub = value;
341 return;
342 case 2:
343 cpt->baseSub_off = value;
344 return;
345 case 4:
346 cpt->actionSub = value;
347 return;
348 case 6:
349 cpt->actionSub_off = value;
350 return;
351 case 8:
352 cpt->getToSub = value;
353 return;
354 case 10:
355 cpt->getToSub_off = value;
356 return;
357 case 12:
358 cpt->extraSub = value;
359 return;
360 case 14:
361 cpt->extraSub_off = value;
362 return;
363 default:
364 error("Invalid Mode (%d)", mode);
365 }
366 }
367
getGrafixPtr(Compact * cpt)368 uint16 *SkyCompact::getGrafixPtr(Compact *cpt) {
369 uint16 *gfxBase = (uint16 *)fetchCpt(cpt->grafixProgId);
370 if (gfxBase == NULL)
371 return NULL;
372
373 return gfxBase + cpt->grafixProgPos;
374 }
375
376 /**
377 * Returns the n'th mega set specified by \a megaSet from Compact \a cpt.
378 */
getMegaSet(Compact * cpt)379 MegaSet *SkyCompact::getMegaSet(Compact *cpt) {
380 switch (cpt->megaSet) {
381 case 0:
382 return &cpt->megaSet0;
383 case NEXT_MEGA_SET:
384 return &cpt->megaSet1;
385 case NEXT_MEGA_SET*2:
386 return &cpt->megaSet2;
387 case NEXT_MEGA_SET*3:
388 return &cpt->megaSet3;
389 default:
390 error("Invalid MegaSet (%d)", cpt->megaSet);
391 }
392 }
393
394 /**
395 \brief Returns the turn table for direction \a dir
396 from Compact \a cpt in \a megaSet.
397
398 Functionally equivalent to:
399 \verbatim
400 clear eax
401 mov al,20
402 mul (cpt[esi]).c_dir
403 add ax,(cpt[esi]).c_mega_set
404 lea eax,(cpt[esi+eax]).c_turn_table_up
405 \endverbatim
406 */
getTurnTable(Compact * cpt,uint16 dir)407 uint16 *SkyCompact::getTurnTable(Compact *cpt, uint16 dir) {
408 MegaSet *m = getMegaSet(cpt);
409 TurnTable *turnTable = (TurnTable *)fetchCpt(m->turnTableId);
410 switch (dir) {
411 case 0:
412 return turnTable->turnTableUp;
413 case 1:
414 return turnTable->turnTableDown;
415 case 2:
416 return turnTable->turnTableLeft;
417 case 3:
418 return turnTable->turnTableRight;
419 case 4:
420 return turnTable->turnTableTalk;
421 default:
422 error("No TurnTable (%d) in MegaSet (%d)", dir, cpt->megaSet);
423 }
424 }
425
getCompactElem(Compact * cpt,uint16 off)426 void *SkyCompact::getCompactElem(Compact *cpt, uint16 off) {
427 if (off < COMPACT_SIZE)
428 return((uint8 *)cpt + compactOffsets[off]);
429 off -= COMPACT_SIZE;
430
431 if (off < MEGASET_SIZE)
432 return((uint8 *)&(cpt->megaSet0) + megaSetOffsets[off]);
433
434 off -= MEGASET_SIZE;
435 if (off < TURNTABLE_SIZE)
436 return ((uint8 *)fetchCpt(cpt->megaSet0.turnTableId) + turnTableOffsets[off]);
437
438 off -= TURNTABLE_SIZE;
439 if (off < MEGASET_SIZE)
440 return((uint8 *)&(cpt->megaSet1) + megaSetOffsets[off]);
441
442 off -= MEGASET_SIZE;
443 if (off < TURNTABLE_SIZE)
444 return ((uint8 *)fetchCpt(cpt->megaSet1.turnTableId) + turnTableOffsets[off]);
445
446 off -= TURNTABLE_SIZE;
447 if (off < MEGASET_SIZE)
448 return((uint8 *)&(cpt->megaSet2) + megaSetOffsets[off]);
449
450 off -= MEGASET_SIZE;
451 if (off < TURNTABLE_SIZE)
452 return ((uint8 *)fetchCpt(cpt->megaSet2.turnTableId) + turnTableOffsets[off]);
453
454 off -= TURNTABLE_SIZE;
455 if (off < MEGASET_SIZE)
456 return((uint8 *)&(cpt->megaSet3) + megaSetOffsets[off]);
457
458 off -= MEGASET_SIZE;
459 if (off < TURNTABLE_SIZE)
460 return ((uint8 *)fetchCpt(cpt->megaSet3.turnTableId) + turnTableOffsets[off]);
461 off -= TURNTABLE_SIZE;
462
463 error("Offset %X out of bounds of compact", (int)(off + COMPACT_SIZE + 4 * MEGASET_SIZE + 4 * TURNTABLE_SIZE));
464 }
465
createResetData(uint16 gameVersion)466 uint8 *SkyCompact::createResetData(uint16 gameVersion) {
467 _cptFile->seek(_resetDataPos);
468 uint32 dataSize = _cptFile->readUint16LE() * sizeof(uint16);
469 uint16 *resetBuf = (uint16 *)malloc(dataSize);
470 _cptFile->read(resetBuf, dataSize);
471 uint16 numDiffs = _cptFile->readUint16LE();
472 for (uint16 cnt = 0; cnt < numDiffs; cnt++) {
473 uint16 version = _cptFile->readUint16LE();
474 uint16 diffFields = _cptFile->readUint16LE();
475 if (version == gameVersion) {
476 for (uint16 diffCnt = 0; diffCnt < diffFields; diffCnt++) {
477 uint16 pos = _cptFile->readUint16LE();
478 resetBuf[pos] = TO_LE_16(_cptFile->readUint16LE());
479 }
480 return (uint8 *)resetBuf;
481 } else
482 _cptFile->seek(diffFields * 2 * sizeof(uint16), SEEK_CUR);
483 }
484 free(resetBuf);
485 error("Unable to find reset data for Beneath a Steel Sky Version 0.0%03d", gameVersion);
486 }
487
488 // - debugging functions
489
findCptId(void * cpt)490 uint16 SkyCompact::findCptId(void *cpt) {
491 for (uint16 listCnt = 0; listCnt < _numDataLists; listCnt++)
492 for (uint16 elemCnt = 0; elemCnt < _dataListLen[listCnt]; elemCnt++)
493 if (_compacts[listCnt][elemCnt] == cpt)
494 return (listCnt << 12) | elemCnt;
495 // not found
496 debug(1, "Id for Compact %p wasn't found", cpt);
497 return 0;
498 }
499
findCptId(const char * cptName)500 uint16 SkyCompact::findCptId(const char *cptName) {
501 for (uint16 listCnt = 0; listCnt < _numDataLists; listCnt++)
502 for (uint16 elemCnt = 0; elemCnt < _dataListLen[listCnt]; elemCnt++)
503 if (_cptNames[listCnt][elemCnt] != 0)
504 if (scumm_stricmp(cptName, _cptNames[listCnt][elemCnt]) == 0)
505 return (listCnt << 12) | elemCnt;
506 // not found
507 debug(1, "Id for Compact %s wasn't found", cptName);
508 return 0;
509 }
510
giveNumDataLists()511 uint16 SkyCompact::giveNumDataLists() {
512 return _numDataLists;
513 }
514
giveDataListLen(uint16 listNum)515 uint16 SkyCompact::giveDataListLen(uint16 listNum) {
516 if (listNum >= _numDataLists) // list doesn't exist
517 return 0;
518 else
519 return _dataListLen[listNum];
520 }
521
522 const char *const SkyCompact::_typeNames[NUM_CPT_TYPES] = {
523 "null",
524 "COMPACT",
525 "TURNTABLE",
526 "ANIM SEQ",
527 "UNKNOWN",
528 "GETTOTABLE",
529 "AR BUFFER",
530 "MAIN LIST"
531 };
532
533 } // End of namespace Sky
534