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