cfad47cfa3/src/main.cc

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
/* FrobTADS main entrypoint.
2
 *
3
 * We don't use the default command line parsing that TADS provides
4
 * because a) it's clumsy, b) we need our own set of options (colors,
5
 * scrolling, etc) and c) Unix users expect certain standards
6
 * (--version, --help, bug-report email address, long vs. short options,
7
 * etc).
8
 */
9
#include "common.h"
10
11
#include <stdio.h>
12
#include <string.h>
13
#ifdef HAVE_LOCALE_H
14
#include <locale.h>
15
#endif
16
17
#include "frobtadsappcurses.h"
18
#include "frobtadsappplain.h"
19
#include "colors.h"
20
#include "options.h"
21
22
#include "os.h"
23
#include "trd.h"
24
#include "t3std.h"
25
#include "vmmain.h"
26
#include "vmvsn.h"
27
#include "vmmaincn.h"
28
#include "vmhostsi.h"
29
extern "C"
30
{
31
#include "osgen.h"
32
}
33
34
35
const char versionOutput[] =
36
"FrobTADS " PACKAGE_VERSION "\n"
37
"TADS 2 virtual machive v" TADS_RUNTIME_VERSION "\n"
38
"TADS 3 virtual machine v" T3VM_VSN_STRING " (" T3VM_IDENTIFICATION ")\n"
39
"Copyright (C) 2005 Nikos Chantziaras.\n"
40
"TADS copyright (C) 2005 Michael J. Roberts.\n"
41
"FrobTADS comes with NO WARRANTY, to the extent permitted by law.\n"
42
"You may redistribute copies of FrobTADS under certain terms and conditions.\n"
43
"See the file named COPYING for more information.";
44
45
46
const char helpOutput[] =
47
"Options:\n"
48
"  -h, --help           This help text\n"
49
"  -v, --version        Version information\n"
50
"  -n, --no-colors      Disable colors\n"
51
"  -f, --force-colors   Try to enable colors even if the system claims that\n"
52
"                       colors aren't available\n"
53
"  -o, --no-defcolors   Don't use the terminal's default colors\n"
54
"  -t, --tcolor         Text color (default white)\n"
55
"  -b, --bcolor         Background color (default black)\n"
56
"  -l, --stat-tcolor    Statusline text color (default is -b)\n"
57
"  -g, --stat-bcolor    Statusline background color (default is -t)\n"
58
"  -c, --no-scrolling   Disable soft (line by line) scrolling\n"
59
"  -p, --no-pause       Don't pause prior to quiting\n"
60
"  -d, --no-chdir       Don't change the current directory\n"
61
"  -s, --safety-level   File I/O safety level (default is 2)\n"
62
"  -e, --scroll-buffer  Size of the scroll-back buffer. Must be between 8 and\n"
63
"                       8192kB (default is 512kB)\n"
64
"  -r, --restore        Load a saved game position\n"
65
"  -u, --t3undo         Multiply the availabe T3VM undo buffer by n. Must be\n"
66
"                       between 1 and 64 (default is 16; about 100 UNDOs)\n"
67
"  -k, --character-set  Use given charset as the keyboard and display\n"
68
"                       character set.\n"
69
"  -i, --interface      Use given screen interface (curses or plain). Default\n"
70
"                       is curses. (Advanced features like statusline, banners\n"
71
"                       and colors are not available when using the plain\n"
72
"                       interface.)\n"
73
"Color codes:\n"
74
"   0:black 1:red 2:green 3:yellow 4:blue 5:magenta 6:cyan 7:white\n"
75
"(Note that yellow is actually brown on some hardware, mostly PCs.)\n"
76
"\n"
77
"File I/O safety levels:\n"
78
"   0: Safety mechanism disabled (unlimited read/write access)\n"
79
"   1: Read everywhere, write in current directory only\n"
80
"   2: Read/write in current directory only\n"
81
"   3: Read in current directory only, no writing allowed\n"
82
"   4: File I/O disabled (no reading or writing allowed)\n"
83
"\n"
84
"Short options are case-sensitive, long options are not. Options may be given\n"
85
"in any order and may be specified both before and after the filename. If an\n"
86
"option that takes an argument is given multiple times the last value is used.\n"
87
"Options may be abbreviated: --no-scr will be recognized as --no-scrolling.\n"
88
"\n"
89
"Report bugs to <" PACKAGE_BUGREPORT ">.\n"
90
"Please include the output of the --version option with the report.";
91
92
93
int main( int argc, char** argv )
94
{
95
	// These are the command-line options we recognize.  See
96
	// options.h for details on the format.  We keep this list
97
	// sorted alphabetically, in order to easily detect duplicate
98
	// short options.
99
	const char* optv[] = {
100
		"b:bcolor <0..7>",
101
		"c|no-scrolling",
102
		"d|no-chdir",
103
		"e:scroll-buffer <8..8192>",
104
		"f|force-colors",
105
		"g:stat-bcolor <0..7>",
106
		"h|help",
107
		"i:interface <curses|plain>",
108
		"k:character-set <charset>",
109
		"l:stat-tcolor <0..7>",
110
		"n|no-colors",
111
		"o|no-defcolors",
112
		"p|no-pause",
113
		"r:restore <filename>",
114
		"s:safety-level <0..4>",
115
		"t:tcolor <0..7>",
116
		"u:undo-size <1..64>",
117
		"v|version",
118
		0
119
	};
120
121
#ifdef HAVE_SETLOCALE
122
	// First, initialise locale, if available. We need only
123
	// LC_CTYPE.  Don't try to mess with numeric formats!
124
	setlocale(LC_CTYPE, "");
125
#endif
126
127
	// These are used for actual command-line parsing.
128
	int optChar;
129
	const char* optArg;
130
	Options opts(argv[0], optv);
131
	OptArgvIter iter(argc - 1, &argv[1]);
132
133
	// Allow the filename to be specified between options.
134
	// TODO: Maybe we shouldn't do this.  We could ignore any
135
	// options that come after the filename and just pass them to
136
	// the main() function of the game (TADS 3).  But this would
137
	// make no sense for TADS 2.  Another solution is to pass only
138
	// the explicitly ignored options to TADS 3 (that means, any
139
	// options specified after a "-- " in the command-line.
140
	opts.ctrls(Options::PARSE_POS);
141
142
	// We set this to true if an invalid option was given (unknown
143
	// or ambigous), so that we print an error message only the
144
	// first time an error occurs.
145
	bool optionError = false;
146
147
	// A semi-functional, portable ostream as defined in options.h.
148
	ostream cerr(stderr);
149
	ostream cout(stdout);
150
151
	// Available screen interfaces.
152
	enum screenInterface {cursesInterface, plainInterface};
153
	// We will use curses by default.
154
	screenInterface interface = cursesInterface;
155
156
	// Increase the T3VM undo-size 16 times by default.
157
	frobVmUndoMaxRecords = defaultVmUndoMaxRecords * 16;
158
	FrobTadsApplication::FrobOptions frobOpts = {
159
		// We assume some defaults.  They might change while
160
		// parsing the command line.
161
		true,       // Use colors.
162
		false,      // Don't force colors.
163
		true,       // Use terminal's defaults for color pair 0.
164
		true,       // Enable soft-scrolling.
165
		true,       // Pause prior to exit.
166
		true,       // Change to the game's directory.
167
		FROB_WHITE, // Text.
168
		FROB_BLACK, // Background.
169
		-1,         // Statusline text; none yet.
170
		-1,         // Statusline background; none yet.
171
		512*1024,   // Scroll-back buffer size.
172
		// Default file I/O safety level is read/write access
173
		// in current directory only.
174
		VM_IO_SAFETY_READWRITE_CUR,
175
  		// TODO: Revert the default back to "\0" when Unicode output
176
  		// is finally implemented.
177
		"us-ascii"  // Character set.
178
	};
179
180
	// Name of the game to run.
181
	const char* filename = 0;
182
183
	// Saved game position to restore (optional).
184
	const char* savedPosFilename = 0;
185
186
	// Start parsing.  Stop when there are no more options to parse.
187
	while ((optChar = opts(iter, optArg)) != Options::ENDOPTS) switch (optChar) {
188
	  // --version
189
	  case 'v':
190
		cout << versionOutput << "\n\n";
191
		return 0;
192
193
	  // --help
194
	  case 'h':
195
		cout << "Usage: " << opts.name() << " [options] file\n";
196
		cout << helpOutput << "\n\n";
197
		return 0;
198
		break;
199
200
	  // --no-colors
201
	  case 'n':
202
		frobOpts.useColors = false;
203
		break;
204
205
	  // --force-colors
206
	  case 'f':
207
		frobOpts.forceColors = true;
208
		break;
209
210
	  // --no-defcolors
211
	  case 'o':
212
		frobOpts.defColors = false;
213
		break;
214
215
	  // --no-scrolling
216
	  case 'c':
217
		frobOpts.softScroll = false;
218
		break;
219
220
	  // --no-pause
221
	  case 'p':
222
		frobOpts.exitPause = false;
223
		break;
224
225
	  // --no-chdir
226
	  case 'd':
227
		frobOpts.changeDir = false;
228
		break;
229
230
	  case 't': // --tcolor
231
	  case 'b': // --bcolor
232
	  case 'l': // --stat-tcolor
233
	  case 'g': // --stat-bcolor
234
	  {
235
		if (optionError) break;
236
		// We'll convert the string argument to a number.
237
		int tmp;
238
		if (optArg == 0) {
239
			// Argument is missing.
240
			optionError = true;
241
			break;
242
		}
243
		// Convert string to number.
244
		if (sscanf(optArg, "%d", &tmp) == 0) {
245
			// Conversion failed; it was not a number.
246
			cerr << opts.name() << ": colors must be numerical.\n";
247
			optionError = true;
248
			break;
249
		}
250
		if (tmp < 0 or tmp > 7) {
251
			// Invalid color.
252
			cerr << opts.name() << ": a color must be between 0 and 7.\n";
253
			optionError = true;
254
			break;
255
		}
256
		switch (optChar) {
257
		  case 't': frobOpts.textColor = tmp; break;
258
		  case 'b': frobOpts.bgColor = tmp; break;
259
		  case 'l': frobOpts.statTextColor = tmp; break;
260
		  case 'g': frobOpts.statBgColor = tmp; break;
261
		}
262
		break;
263
	  }
264
265
	  // --safety-level
266
	  case 's': {
267
		if (optionError) break;
268
		int tmp;
269
		if (optArg == 0) {
270
			// Argument is missing.
271
			optionError = true;
272
			break;
273
		}
274
		if (sscanf(optArg, "%d", &tmp) == 0) {
275
			// The argument was not a number.
276
			cerr << opts.name() << ": safety level must be numerical.\n";
277
			optionError = true;
278
			break;
279
		}
280
		if (tmp < 0 or tmp > 4) {
281
			// Out of range.
282
			cerr << opts.name() << ": safety level must be between 0-4.\n";
283
			optionError = true;
284
			break;
285
		}
286
		frobOpts.safetyLevel = tmp;
287
		break;
288
	  }
289
290
	  // --scroll-buffer
291
	  case 'e': {
292
		if (optionError) break;
293
		unsigned int tmp;
294
		if (optArg == 0) {
295
			// Argument is missing.
296
			optionError = true;
297
			break;
298
		}
299
		if (sscanf(optArg, "%u", &tmp) == 0) {
300
			// The argument was not a number.
301
			cerr << opts.name() << ": buffer size must be numerical.\n";
302
			optionError = true;
303
			break;
304
		}
305
		if (tmp < 8 or tmp > 8192) {
306
			// Buffer is out of range.
307
			cerr << opts.name() << ": buffer size must be between 8-8192 kB.\n";
308
			optionError = true;
309
			break;
310
		}
311
		// Adjust from kB to bytes.
312
		tmp *= 1024;
313
		frobOpts.scrollBufSize = tmp;
314
		break;
315
	  }
316
317
	  // --restore
318
	  case 'r': {
319
		if (optionError) break;
320
		if (optArg == 0) {
321
			// Argument is missing.
322
			optionError = true;
323
			break;
324
		}
325
		savedPosFilename = optArg;
326
		break;
327
	  }
328
329
	  // --undo-size
330
	  case 'u': {
331
		if (optionError) break;
332
		int tmp;
333
		if (optArg == 0) {
334
			// Argument is missing.
335
			optionError = true;
336
			break;
337
		}
338
		if (sscanf(optArg, "%d", &tmp) == 0) {
339
			// The argument was not a number.
340
			cerr << opts.name() << ": undo multiplicator must be numerical.\n";
341
			optionError = true;
342
			break;
343
		}
344
		if (tmp < 1 or tmp > 64) {
345
			// Out of range.
346
			cerr << opts.name() << ": undo multiplicator must be between 1-64.\n";
347
			optionError = true;
348
			break;
349
		}
350
		frobVmUndoMaxRecords = defaultVmUndoMaxRecords * tmp;
351
		break;
352
	  }
353
354
	  // --character-set
355
	  case 'k':
356
		if (optionError) break;
357
		if (optArg == 0) {
358
			// Argument is missing.
359
			optionError = true;
360
			break;
361
		}
362
		strncpy(frobOpts.characterSet, optArg, 16);
363
		frobOpts.characterSet[15] = '\0';
364
		break;
365
366
	  // --interface
367
	  case 'i':
368
		if (optionError) break;
369
		if (optArg == 0) {
370
			optionError = true;
371
			break;
372
		}
373
		if (strcmp(optArg, "curses") == 0) {
374
			interface = cursesInterface;
375
		} else if (strcmp(optArg, "plain") == 0) {
376
			interface = plainInterface;
377
		} else {
378
			cerr << opts.name() << ": available interfaces: curses, plain.\n";
379
			optionError = true;
380
		}
381
		break;
382
383
	  // This occurs when the argument is something other than an
384
	  // option.  We treat it as the filename of the game to run.
385
	  case Options::POSITIONAL:
386
		if (optionError) break;
387
		if (filename != 0) {
388
			// User already specified a filename.
389
			cerr << opts.name() << ": more than one filename given.\n";
390
			optionError = true;
391
			break;
392
		}
393
		filename = optArg;
394
		break;
395
396
	  // Common errors below.
397
	  case Options::BADCHAR:
398
	  case Options::BADKWD:
399
		optionError = true;
400
		break;
401
402
	  case Options::AMBIGUOUS:
403
		optionError = true;
404
		break;
405
	}
406
407
	if (filename == 0 and not optionError) {
408
		if (savedPosFilename != 0) {
409
			// No filename given, but a saved game position
410
			// was specified.  Ask TADS to find out the
411
			// filename of the game that the savefile is
412
			// associated with.  We make the buffer static
413
			// since we need to store a pointer to it even
414
			// after exiting this if-block.
415
			static char detectedFilename[OSFNMAX + 1];
416
			const char* const argvDummy[] = {"frob", "-r", savedPosFilename};
417
			if (vm_get_game_arg(3, argvDummy, detectedFilename, OSFNMAX + 1)) {
418
				// Success.  Store the filename.
419
				filename = detectedFilename;
420
			} else {
421
				// Failed.
422
				optionError = true;
423
			}
424
		} else {
425
			optionError = true;
426
		}
427
		if (optionError) cerr << opts.name() << ": no filename given.\n";
428
	}
429
430
	if (optionError) {
431
		opts.usage(cerr, "filename[.gam|.t3]");
432
		return 1;
433
	}
434
435
	// Set up the statusline colors (if the user didn't change the
436
	// defaults).  We'll simply use the normal colors but with
437
	// foreground/background reversed.
438
	if (frobOpts.statTextColor == -1) frobOpts.statTextColor = frobOpts.bgColor;
439
	if (frobOpts.statBgColor == -1) frobOpts.statBgColor = frobOpts.textColor;
440
441
	// If the filename the user specified lacks an extension, try
442
	// these.
443
	const char* defExts[] = {"gam", "t3"};
444
	// The filename TADS detects (probably after trying the above
445
	// extensions).
446
	char actualFilename[OSFNMAX + 1];
447
448
	// Ask TADS to find out what the specified file is supposed to
449
	// be.
450
	switch (vm_get_game_type(filename, actualFilename, OSFNMAX, defExts, 2)) {
451
	  case VM_GGT_TADS2:
452
		// It's a Tads 2 game.  Fire-up the T2VM.
453
		switch (interface) {
454
		  case cursesInterface: return FrobTadsApplicationCurses(frobOpts).runTads(actualFilename, 0);
455
		  case plainInterface: return FrobTadsApplicationPlain(frobOpts).runTads(actualFilename, 0);
456
		}
457
458
	  case VM_GGT_TADS3: {
459
		// It's Tads 3.
460
		int t3vmRet;
461
		switch (interface) {
462
		  case cursesInterface:
463
			t3vmRet = FrobTadsApplicationCurses(frobOpts).runTads(actualFilename, 1);
464
			break;
465
		  case plainInterface:
466
			t3vmRet = FrobTadsApplicationPlain(frobOpts).runTads(actualFilename, 1);
467
			break;
468
		}
469
		// Show any unfreed memory blocks.  This does nothing if
470
		// Tads 3 has not been compiled with debugging support.
471
		t3_list_memory_blocks(0);
472
		return t3vmRet;
473
	  }
474
475
	  case VM_GGT_INVALID:
476
		// It's not a Tads game.
477
		cerr << opts.name() << ": " << filename << ": not a Tads game.\n";
478
		break;
479
480
	  case VM_GGT_NOT_FOUND:
481
		// No such file.
482
		cerr << opts.name() << ": " << filename << ": file not found.\n";
483
		break;
484
485
	  case VM_GGT_AMBIG:
486
		// Filename is ambiguous.
487
		cerr << opts.name() << ": " << filename
488
		     << ": ambiguous filename; please include the file suffix.\n";
489
		break;
490
	}
491
	// If we reached this point, an error occured.
492
	return 1;
493
}