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 "common/file.h"
24 #include "gui/debugger.h"
25 #include "startrek/console.h"
26 #include "startrek/resource.h"
27 #include "startrek/room.h"
28 #include "startrek/startrek.h"
29
30 namespace StarTrek {
31
Console(StarTrekEngine * vm)32 Console::Console(StarTrekEngine *vm) : GUI::Debugger(), _vm(vm) {
33 registerCmd("room", WRAP_METHOD(Console, Cmd_Room));
34 registerCmd("actions", WRAP_METHOD(Console, Cmd_Actions));
35 registerCmd("text", WRAP_METHOD(Console, Cmd_Text));
36 registerCmd("bg", WRAP_METHOD(Console, Cmd_Bg));
37 registerCmd("filedump", WRAP_METHOD(Console, Cmd_DumpFile));
38 registerCmd("filesearch", WRAP_METHOD(Console, Cmd_SearchFile));
39 registerCmd("score", WRAP_METHOD(Console, Cmd_Score));
40 registerCmd("bridgeseq", WRAP_METHOD(Console, Cmd_BridgeSequence));
41 registerCmd("dumptext", WRAP_METHOD(Console, Cmd_DumpText));
42 }
43
~Console()44 Console::~Console() {
45 }
46
Cmd_Room(int argc,const char ** argv)47 bool Console::Cmd_Room(int argc, const char **argv) {
48 if (argc < 3) {
49 debugPrintf("Current room: %s\n", _vm->getScreenName().c_str());
50 debugPrintf("Use room <mission> <room> to teleport\n");
51 debugPrintf("Valid missions are: DEMON, TUG, LOVE, MUDD, FEATHER, TRIAL, SINS, VENG\n");
52 return true;
53 }
54
55 _vm->_missionToLoad = argv[1];
56 _vm->_missionToLoad.toUppercase();
57 _vm->_roomIndexToLoad = atoi(argv[2]);
58 _vm->runAwayMission();
59
60 return false;
61 }
62
Cmd_Actions(int argc,const char ** argv)63 bool Console::Cmd_Actions(int argc, const char **argv) {
64 Common::String screenName = _vm->getScreenName();
65
66 if (argc == 3) {
67 Common::String missionName = argv[1];
68 missionName.toUppercase();
69 int roomIndex = atoi(argv[2]);
70
71 screenName = missionName + (char)(roomIndex + '0');
72 }
73
74 Common::MemoryReadStreamEndian *rdfFile = _vm->_resource->loadFile(screenName + ".RDF");
75 rdfFile->seek(14);
76
77 uint16 startOffset = rdfFile->readUint16LE();
78 uint16 endOffset = rdfFile->readUint16LE();
79 uint16 offset = startOffset;
80
81 while (offset < endOffset) {
82 rdfFile->seek(offset);
83
84 uint32 action = rdfFile->readUint32LE();
85 uint16 nextOffset = rdfFile->readUint16LE();
86
87 debugPrintf("Offset %d: %s\n", offset, EventToString(action).c_str());
88 offset = nextOffset;
89 }
90
91 delete rdfFile;
92
93 return true;
94 }
95
Cmd_Text(int argc,const char ** argv)96 bool Console::Cmd_Text(int argc, const char **argv) {
97 const RoomTextOffsets *textList = _vm->_room->_roomTextList;
98 Common::String screenName = _vm->getScreenName();
99 byte *rdfData = _vm->_room->loadRoomRDF(screenName);
100 int index = 0;
101
102 do {
103 uint16 offset = textList[index].offsetEnglishCD;
104 debugPrintf("%i - %i: %s\n", textList[index].id, offset, rdfData+offset);
105 index++;
106 } while (textList[index].id != -1);
107
108 delete[] rdfData;
109
110 return true;
111 }
112
Cmd_Bg(int argc,const char ** argv)113 bool Console::Cmd_Bg(int argc, const char **argv) {
114 if (argc < 2) {
115 debugPrintf("Usage: %s <background image name>\n", argv[0]);
116 return true;
117 }
118
119 _vm->_gfx->setBackgroundImage(argv[1]);
120 _vm->_gfx->copyBackgroundScreen();
121 _vm->_system->updateScreen();
122
123 return false;
124 }
125
dumpFile(Common::String fileName)126 void Console::dumpFile(Common::String fileName) {
127 debugPrintf("Dumping %s...\n", fileName.c_str());
128
129 Common::MemoryReadStreamEndian *stream = _vm->_resource->loadFile(fileName, 0, false);
130 if (!stream) {
131 debugPrintf("File not found\n");
132 return;
133 }
134
135 uint32 size = stream->size();
136 byte *data = new byte[size];
137 stream->read(data, size);
138 delete stream;
139
140 Common::DumpFile out;
141 out.open(fileName);
142 out.write(data, size);
143 out.flush();
144 out.close();
145 delete[] data;
146 }
147
Cmd_DumpFile(int argc,const char ** argv)148 bool Console::Cmd_DumpFile(int argc, const char **argv) {
149 if (argc < 2) {
150 debugPrintf("Usage: %s <file name>\n", argv[0]);
151 return true;
152 }
153
154 Common::String fileName = argv[1];
155
156 if (fileName != "*") {
157 dumpFile(fileName);
158 } else {
159 for (Common::List<ResourceIndex>::const_iterator i = _vm->_resource->_resources.begin(), end = _vm->_resource->_resources.end(); i != end; ++i) {
160 if (i->fileName == "S5ROOM3.BMP" || i->fileName == "Z_LIST.TXT")
161 continue;
162 dumpFile(i->fileName);
163 }
164 }
165
166 return true;
167 }
168
Cmd_SearchFile(int argc,const char ** argv)169 bool Console::Cmd_SearchFile(int argc, const char **argv) {
170 if (argc < 2) {
171 debugPrintf("Usage: %s <file name>\n", argv[0]);
172 return true;
173 }
174
175 Common::String filename = argv[1];
176 filename.toUppercase();
177
178 Common::List<ResourceIndex> records = _vm->_resource->searchIndex(filename);
179 debugPrintf("Found:\n");
180 for (Common::List<ResourceIndex>::const_iterator i = records.begin(), end = records.end(); i != end; ++i) {
181 debugPrintf("%s, offset: %d\n", i->fileName.c_str(), i->indexOffset);
182 }
183
184 return true;
185 }
186
Cmd_Score(int argc,const char ** argv)187 bool Console::Cmd_Score(int argc, const char **argv) {
188 debugPrintf("Chapter 1: Demon world (demon): %d\n", _vm->_awayMission.demon.missionScore);
189 debugPrintf("Chapter 2: Hijacked (tug): %d\n", _vm->_awayMission.tug.missionScore);
190 debugPrintf("Chapter 3: Love's Labor Jeopardized (love): %d\n", _vm->_awayMission.love.missionScore);
191 debugPrintf("Chapter 4: Another Fine Mess (mudd): %d\n", _vm->_awayMission.mudd.missionScore);
192 debugPrintf("Chapter 5A: The Feathered Serpent (feather): %d\n", _vm->_awayMission.feather.missionScore);
193 debugPrintf("Chapter 5B: The Feathered Serpent (trial): %d\n", _vm->_awayMission.trial.missionScore);
194 debugPrintf("Chapter 6: The Old Devil Moon (sins): %d\n", _vm->_awayMission.sins.missionScore);
195 debugPrintf("Chapter 7: Vengeance (veng): %d\n", _vm->_awayMission.veng.missionScore);
196 return true;
197 }
198
Cmd_BridgeSequence(int argc,const char ** argv)199 bool Console::Cmd_BridgeSequence(int argc, const char **argv) {
200 if (argc < 2) {
201 debugPrintf("Usage: %s <sequence ID> to start a bridge sequence\n", argv[0]);
202 return true;
203 } else {
204 _vm->_bridgeSequenceToLoad = atoi(argv[1]);
205 return false;
206 }
207 }
208
209 struct MessageInfo {
210 Common::String key;
211 Common::String value;
212 uint16 pos;
213 };
214
215 struct MessageInfoComparator {
operator ()StarTrek::MessageInfoComparator216 bool operator()(const MessageInfo &x, const MessageInfo &y) const {
217 return x.key < y.key;
218 }
219 };
220
Cmd_DumpText(int argc,const char ** argv)221 bool Console::Cmd_DumpText(int argc, const char **argv) {
222 if (argc < 2) {
223 debugPrintf("Dumps room text messages from CD-ROM versions of ST25\n");
224 debugPrintf("Usage: %s <room RDF file name> <table format>\n", argv[0]);
225 } else {
226 Common::String fileName = argv[1];
227 bool tableFormat = false;
228 Common::List<MessageInfo> keys;
229
230 if (argc > 2)
231 tableFormat = !scumm_stricmp(argv[2], "true") || !strcmp(argv[2], "1");
232
233 Common::MemoryReadStreamEndian *rdfFile = _vm->_resource->loadFile(fileName + ".RDF");
234 rdfFile->seek(32, SEEK_SET);
235 uint16 messageOffset = rdfFile->readUint16LE();
236 rdfFile->seek(messageOffset, SEEK_SET);
237
238 while (!rdfFile->eos() && !rdfFile->err()) {
239 Common::String message;
240 uint16 pos = rdfFile->pos();
241 byte c = rdfFile->readByte();
242 if (!Common::isPrint(c))
243 break;
244
245 while (c != '\0') {
246 message += c;
247 c = rdfFile->readByte();
248 }
249
250 if (!message.empty()) {
251 if (!tableFormat) {
252 debug("%s, %d", message.c_str(), pos);
253 } else {
254 MessageInfo m;
255 m.key = message.size() >= 14 ? message.substr(6, 8) : message;
256 m.value = message;
257 m.pos = pos;
258 keys.push_back(m);
259 }
260 }
261 }
262
263 int size = rdfFile->size();
264 rdfFile->seek(14, SEEK_SET);
265 uint16 startOffset = rdfFile->readUint16LE();
266 uint16 offset = startOffset;
267 rdfFile->seek(startOffset, SEEK_SET);
268 const char *validPrefixes[] = {
269 "BRI", "COM", "DEM", "FEA", "GEN", "LOV", "MUD", "SIN", "TRI", "TUG", "VEN"};
270
271 while (!rdfFile->eos() && !rdfFile->err()) {
272 rdfFile->skip(4);
273 uint16 nextOffset = rdfFile->readUint16LE();
274 if (nextOffset >= size || offset >= nextOffset)
275 break;
276
277 while (offset < nextOffset) {
278 int pos = rdfFile->pos();
279 byte c = rdfFile->readByte();
280 bool found = false;
281
282 if (c == '#') {
283 rdfFile->skip(4);
284 c = rdfFile->readByte();
285
286 if (c == '\\') {
287 found = true;
288 rdfFile->seek(pos, SEEK_SET);
289 Common::String message;
290 c = rdfFile->readByte();
291 while (c != '\0') {
292 message += c;
293 c = rdfFile->readByte();
294 }
295
296 Common::String prefix = message.substr(1, 3);
297
298 for (uint i = 0; i < ARRAYSIZE(validPrefixes); i++) {
299 if (prefix == validPrefixes[i]) {
300 MessageInfo m;
301 m.key = message.size() >= 14 ? message.substr(6, 8) : message;
302 m.value = message;
303 m.pos = pos;
304 keys.push_back(m);
305
306 break;
307 }
308 }
309 }
310 }
311
312 if (!found)
313 rdfFile->seek(pos + 1, SEEK_SET);
314
315 offset = rdfFile->pos();
316 }
317 }
318
319 if (tableFormat) {
320 int index = 0;
321 Common::String line;
322
323 Common::sort(keys.begin(), keys.end(), MessageInfoComparator());
324
325 for (Common::List<MessageInfo>::const_iterator i = keys.begin(), end = keys.end(); i != end; ++i) {
326 line += "TX_" + (*i).key + ", ";
327 index++;
328 if (index % 5 == 0) {
329 debug("%s", line.c_str());
330 line = "";
331 }
332 }
333
334 debug("%s", line.c_str());
335
336 for (Common::List<MessageInfo>::const_iterator i = keys.begin(), end = keys.end(); i != end; ++i) {
337 debug("{ TX_%s, %d, 0 },", (*i).key.c_str(), (*i).pos);
338 }
339 }
340
341 delete rdfFile;
342 }
343
344 return true;
345 }
346
EventToString(uint32 action)347 Common::String Console::EventToString(uint32 action) {
348 const char *actions[] = {
349 "Tick",
350 "Walk",
351 "Use",
352 "Get",
353 "Look",
354 "Talk"
355 };
356
357 byte verb = action & 0xff;
358 byte subject = (action >> 8) & 0xff;
359 byte b2 = (action >> 16) & 0xff;
360 byte b3 = (action >> 24) & 0xff;
361
362 String retString;
363 switch (verb) {
364 case 0: // Tick
365 retString = Common::String::format("Tick %d", (subject | (b2 << 8)));
366 break;
367 case 2: // Use
368 retString = Common::String(actions[verb]) + " " + ItemToString(subject) + ", " + ItemToString(b2);
369 break;
370 case 1: // Walk
371 case 3: // Get
372 case 4: // Look
373 case 5: // Talk
374 retString = Common::String(actions[verb]) + " " + ItemToString(subject);
375 break;
376 case 6: // Warp touched
377 retString = Common::String::format("Touched warp %d", subject);
378 break;
379 case 7: // Hotspot touched
380 retString = Common::String::format("Touched hotspot %d", subject);
381 break;
382 case 8: // Timer expired
383 retString = Common::String::format("Timer %d expired", subject);
384 break;
385 case 10: // Animation finished
386 retString = Common::String::format("Finished animation (%d)", subject);
387 break;
388 case 12: // Walking finished
389 retString = Common::String::format("Finished walking (%d)", subject);
390 break;
391 default:
392 retString = Common::String::format("%x%x%x%x", verb, subject, b2, b3);
393 break;
394 }
395
396 // Check for actions using bytes they're not expected to use
397 if (b3 != 0)
398 debugPrintf("WARNING: b3 nonzero in action: %s\n", retString.c_str());
399 if (b2 != 0 && verb != 0 && verb != 2)
400 debugPrintf("WARNING: b2 nonzero in action: %s\n", retString.c_str());
401
402 return retString;
403 }
404
405 const char *itemNames[] = {
406 "IPHASERS",
407 "IPHASERK",
408 "IHAND",
409 "IROCK",
410 "ISTRICOR",
411 "IMTRICOR",
412 "IDEADGUY",
413 "ICOMM",
414 "IPBC",
415 "IRLG",
416 "IWRENCH",
417 "IINSULAT",
418 "ISAMPLE",
419 "ICURE",
420 "IDISHES",
421 "IRT",
422 "IRTWB",
423 "ICOMBBIT",
424 "IJNKMETL",
425 "IWIRING",
426 "IWIRSCRP",
427 "IPWF",
428 "IPWE",
429 "IDEADPH",
430 "IBOMB",
431 "IMETAL",
432 "ISKULL",
433 "IMINERAL",
434 "IMETEOR",
435 "ISHELLS",
436 "IDEGRIME",
437 "ILENSES",
438 "IDISKS",
439 "IANTIGRA",
440 "IN2GAS",
441 "IO2GAS",
442 "IH2GAS",
443 "IN2O",
444 "INH3",
445 "IH2O",
446 "IWROD",
447 "IIROD",
448 "IREDGEM_A",
449 "IREDGEM_B",
450 "IREDGEM_C",
451 "IGRNGEM_A",
452 "IGRNGEM_B",
453 "IGRNGEM_C",
454 "IBLUGEM_A",
455 "IBLUGEM_B",
456 "IBLUGEM_C",
457 "ICONECT",
458 "IS8ROCKS",
459 "IIDCARD",
460 "ISNAKE",
461 "IFERN",
462 "ICRYSTAL",
463 "IKNIFE",
464 "IDETOXIN",
465 "IBERRY",
466 "IDOOVER",
467 "IALIENDV",
468 "ICAPSULE",
469 "IMEDKIT",
470 "IBEAM",
471 "IDRILL",
472 "IHYPO",
473 "IFUSION",
474 "ICABLE1",
475 "ICABLE2",
476 "ILMD",
477 "IDECK",
478 "ITECH"
479 };
480
ItemToString(byte index)481 Common::String Console::ItemToString(byte index) {
482 if (index == 0)
483 return "KIRK";
484 else if (index == 1)
485 return "SPOCK";
486 else if (index == 2)
487 return "MCCOY";
488 else if (index == 3)
489 return "REDSHIRT";
490 else if (index >= 0x40 && (index - 0x40) < ARRAYSIZE(itemNames))
491 return itemNames[index - 0x40];
492 return Common::String(Common::String::format("0x%02x:", index)); // TODO
493 }
494
495 } // End of namespace StarTrek
496