15f7e312c6/src/main.cc
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: 15f7e312c6
File Size: 13.9 KB
(August 24, 2009 16:08 UTC) Almost 3 years ago
Change "--t3undo" to "--undo-size" in helpOutput The option was changed from "--t3undo" to "--undo-size" at some point, but the --help output was not updated.
/* FrobTADS main entrypoint.
*
* We don't use the default command line parsing that TADS provides
* because a) it's clumsy, b) we need our own set of options (colors,
* scrolling, etc) and c) Unix users expect certain standards
* (--version, --help, bug-report email address, long vs. short options,
* etc).
*/
#include "common.h"
#include <stdio.h>
#include <string.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "frobtadsappcurses.h"
#include "frobtadsappplain.h"
#include "colors.h"
#include "options.h"
#include "os.h"
#include "trd.h"
#include "t3std.h"
#include "vmmain.h"
#include "vmvsn.h"
#include "vmmaincn.h"
#include "vmhostsi.h"
extern "C"
{
#include "osgen.h"
}
const char versionOutput[] =
"FrobTADS " PACKAGE_VERSION "\n"
"TADS 2 virtual machive v" TADS_RUNTIME_VERSION "\n"
"TADS 3 virtual machine v" T3VM_VSN_STRING " (" T3VM_IDENTIFICATION ")\n"
"FrobTADS copyright (C) 2009 Nikos Chantziaras.\n"
"TADS copyright (C) 2009 Michael J. Roberts.\n"
"FrobTADS comes with NO WARRANTY, to the extent permitted by law.\n"
"You may redistribute copies of FrobTADS under certain terms and conditions.\n"
"See the file named COPYING for more information.";
const char helpOutput[] =
"Options:\n"
" -h, --help This help text\n"
" -v, --version Version information\n"
" -n, --no-colors Disable colors\n"
" -f, --force-colors Try to enable colors even if the system claims that\n"
" colors aren't available\n"
" -o, --no-defcolors Don't use the terminal's default colors\n"
" -t, --tcolor Text color (default white)\n"
" -b, --bcolor Background color (default black)\n"
" -l, --stat-tcolor Statusline text color (default is -b)\n"
" -g, --stat-bcolor Statusline background color (default is -t)\n"
" -c, --no-scrolling Disable soft (line by line) scrolling\n"
" -p, --no-pause Don't pause prior to quiting\n"
" -d, --no-chdir Don't change the current directory\n"
" -s, --safety-level File I/O safety level (default is 2)\n"
" -e, --scroll-buffer Size of the scroll-back buffer. Must be between 8 and\n"
" 8192kB (default is 512kB)\n"
" -r, --restore Load a saved game position\n"
" -u, --undo-size Multiply the availabe T3VM undo buffer by n. Must be\n"
" between 1 and 64 (default is 16; about 100 UNDOs)\n"
" -k, --character-set Use given charset as the keyboard and display\n"
" character set.\n"
" -i, --interface Use given screen interface (curses or plain). Default\n"
" is curses. (Advanced features like statusline, banners\n"
" and colors are not available when using the plain\n"
" interface.)\n"
"Color codes:\n"
" 0:black 1:red 2:green 3:yellow 4:blue 5:magenta 6:cyan 7:white\n"
"(Note that yellow is actually brown on some hardware, mostly PCs.)\n"
"\n"
"File I/O safety levels:\n"
" 0: Safety mechanism disabled (unlimited read/write access)\n"
" 1: Read everywhere, write in current directory only\n"
" 2: Read/write in current directory only\n"
" 3: Read in current directory only, no writing allowed\n"
" 4: File I/O disabled (no reading or writing allowed)\n"
"\n"
"Short options are case-sensitive, long options are not. Options may be given\n"
"in any order and may be specified both before and after the filename. If an\n"
"option that takes an argument is given multiple times the last value is used.\n"
"Options may be abbreviated: --no-scr will be recognized as --no-scrolling.\n"
"\n"
"Report bugs to <" PACKAGE_BUGREPORT ">.\n"
"Please include the output of the --version option with the report.";
int main( int argc, char** argv )
{
// These are the command-line options we recognize. See
// options.h for details on the format. We keep this list
// sorted alphabetically, in order to easily detect duplicate
// short options.
const char* optv[] = {
"b:bcolor <0..7>",
"c|no-scrolling",
"d|no-chdir",
"e:scroll-buffer <8..8192>",
"f|force-colors",
"g:stat-bcolor <0..7>",
"h|help",
"i:interface <curses|plain>",
"k:character-set <charset>",
"l:stat-tcolor <0..7>",
"n|no-colors",
"o|no-defcolors",
"p|no-pause",
"r:restore <filename>",
"s:safety-level <0..4>",
"t:tcolor <0..7>",
"u:undo-size <1..64>",
"v|version",
0
};
#ifdef HAVE_SETLOCALE
// First, initialise locale, if available. We need only
// LC_CTYPE. Don't try to mess with numeric formats!
setlocale(LC_CTYPE, "");
#endif
// These are used for actual command-line parsing.
int optChar;
const char* optArg;
Options opts(argv[0], optv);
OptArgvIter iter(argc - 1, &argv[1]);
// Allow the filename to be specified between options.
// TODO: Maybe we shouldn't do this. We could ignore any
// options that come after the filename and just pass them to
// the main() function of the game (TADS 3). But this would
// make no sense for TADS 2. Another solution is to pass only
// the explicitly ignored options to TADS 3 (that means, any
// options specified after a "-- " in the command-line.
opts.ctrls(Options::PARSE_POS);
// We set this to true if an invalid option was given (unknown
// or ambigous), so that we print an error message only the
// first time an error occurs.
bool optionError = false;
// A semi-functional, portable ostream as defined in options.h.
ostream cerr(stderr);
ostream cout(stdout);
// Available screen interfaces.
enum screenInterface {cursesInterface, plainInterface};
// We will use curses by default.
screenInterface interface = cursesInterface;
// Increase the T3VM undo-size 16 times by default.
frobVmUndoMaxRecords = defaultVmUndoMaxRecords * 16;
FrobTadsApplication::FrobOptions frobOpts = {
// We assume some defaults. They might change while
// parsing the command line.
true, // Use colors.
false, // Don't force colors.
true, // Use terminal's defaults for color pair 0.
true, // Enable soft-scrolling.
true, // Pause prior to exit.
true, // Change to the game's directory.
FROB_WHITE, // Text.
FROB_BLACK, // Background.
-1, // Statusline text; none yet.
-1, // Statusline background; none yet.
512*1024, // Scroll-back buffer size.
// Default file I/O safety level is read/write access
// in current directory only.
VM_IO_SAFETY_READWRITE_CUR,
// TODO: Revert the default back to "\0" when Unicode output
// is finally implemented.
"us-ascii" // Character set.
};
// Name of the game to run.
const char* filename = 0;
// Saved game position to restore (optional).
const char* savedPosFilename = 0;
// Start parsing. Stop when there are no more options to parse.
while ((optChar = opts(iter, optArg)) != Options::ENDOPTS) switch (optChar) {
// --version
case 'v':
cout << versionOutput << "\n\n";
return 0;
// --help
case 'h':
cout << "Usage: " << opts.name() << " [options] file\n";
cout << helpOutput << "\n\n";
return 0;
break;
// --no-colors
case 'n':
frobOpts.useColors = false;
break;
// --force-colors
case 'f':
frobOpts.forceColors = true;
break;
// --no-defcolors
case 'o':
frobOpts.defColors = false;
break;
// --no-scrolling
case 'c':
frobOpts.softScroll = false;
break;
// --no-pause
case 'p':
frobOpts.exitPause = false;
break;
// --no-chdir
case 'd':
frobOpts.changeDir = false;
break;
case 't': // --tcolor
case 'b': // --bcolor
case 'l': // --stat-tcolor
case 'g': // --stat-bcolor
{
if (optionError) break;
// We'll convert the string argument to a number.
int tmp;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
// Convert string to number.
if (sscanf(optArg, "%d", &tmp) == 0) {
// Conversion failed; it was not a number.
cerr << opts.name() << ": colors must be numerical.\n";
optionError = true;
break;
}
if (tmp < 0 or tmp > 7) {
// Invalid color.
cerr << opts.name() << ": a color must be between 0 and 7.\n";
optionError = true;
break;
}
switch (optChar) {
case 't': frobOpts.textColor = tmp; break;
case 'b': frobOpts.bgColor = tmp; break;
case 'l': frobOpts.statTextColor = tmp; break;
case 'g': frobOpts.statBgColor = tmp; break;
}
break;
}
// --safety-level
case 's': {
if (optionError) break;
int tmp;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
if (sscanf(optArg, "%d", &tmp) == 0) {
// The argument was not a number.
cerr << opts.name() << ": safety level must be numerical.\n";
optionError = true;
break;
}
if (tmp < 0 or tmp > 4) {
// Out of range.
cerr << opts.name() << ": safety level must be between 0-4.\n";
optionError = true;
break;
}
frobOpts.safetyLevel = tmp;
break;
}
// --scroll-buffer
case 'e': {
if (optionError) break;
unsigned int tmp;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
if (sscanf(optArg, "%u", &tmp) == 0) {
// The argument was not a number.
cerr << opts.name() << ": buffer size must be numerical.\n";
optionError = true;
break;
}
if (tmp < 8 or tmp > 8192) {
// Buffer is out of range.
cerr << opts.name() << ": buffer size must be between 8-8192 kB.\n";
optionError = true;
break;
}
// Adjust from kB to bytes.
tmp *= 1024;
frobOpts.scrollBufSize = tmp;
break;
}
// --restore
case 'r': {
if (optionError) break;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
savedPosFilename = optArg;
break;
}
// --undo-size
case 'u': {
if (optionError) break;
int tmp;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
if (sscanf(optArg, "%d", &tmp) == 0) {
// The argument was not a number.
cerr << opts.name() << ": undo multiplicator must be numerical.\n";
optionError = true;
break;
}
if (tmp < 1 or tmp > 64) {
// Out of range.
cerr << opts.name() << ": undo multiplicator must be between 1-64.\n";
optionError = true;
break;
}
frobVmUndoMaxRecords = defaultVmUndoMaxRecords * tmp;
break;
}
// --character-set
case 'k':
if (optionError) break;
if (optArg == 0) {
// Argument is missing.
optionError = true;
break;
}
strncpy(frobOpts.characterSet, optArg, 16);
frobOpts.characterSet[15] = '\0';
break;
// --interface
case 'i':
if (optionError) break;
if (optArg == 0) {
optionError = true;
break;
}
if (strcmp(optArg, "curses") == 0) {
interface = cursesInterface;
} else if (strcmp(optArg, "plain") == 0) {
interface = plainInterface;
} else {
cerr << opts.name() << ": available interfaces: curses, plain.\n";
optionError = true;
}
break;
// This occurs when the argument is something other than an
// option. We treat it as the filename of the game to run.
case Options::POSITIONAL:
if (optionError) break;
if (filename != 0) {
// User already specified a filename.
cerr << opts.name() << ": more than one filename given.\n";
optionError = true;
break;
}
filename = optArg;
break;
// Common errors below.
case Options::BADCHAR:
case Options::BADKWD:
optionError = true;
break;
case Options::AMBIGUOUS:
optionError = true;
break;
}
if (filename == 0 and not optionError) {
if (savedPosFilename != 0) {
// No filename given, but a saved game position
// was specified. Ask TADS to find out the
// filename of the game that the savefile is
// associated with. We make the buffer static
// since we need to store a pointer to it even
// after exiting this if-block.
static char detectedFilename[OSFNMAX + 1];
const char* const argvDummy[] = {"frob", "-r", savedPosFilename};
if (vm_get_game_arg(3, argvDummy, detectedFilename, OSFNMAX + 1)) {
// Success. Store the filename.
filename = detectedFilename;
} else {
// Failed.
optionError = true;
}
} else {
optionError = true;
}
if (optionError) cerr << opts.name() << ": no filename given.\n";
}
if (optionError) {
opts.usage(cerr, "filename[.gam|.t3]");
return 1;
}
// Set up the statusline colors (if the user didn't change the
// defaults). We'll simply use the normal colors but with
// foreground/background reversed.
if (frobOpts.statTextColor == -1) frobOpts.statTextColor = frobOpts.bgColor;
if (frobOpts.statBgColor == -1) frobOpts.statBgColor = frobOpts.textColor;
// If the filename the user specified lacks an extension, try
// these.
const char* defExts[] = {"gam", "t3"};
// The filename TADS detects (probably after trying the above
// extensions).
char actualFilename[OSFNMAX + 1];
// Ask TADS to find out what the specified file is supposed to
// be.
switch (vm_get_game_type(filename, actualFilename, OSFNMAX, defExts, 2)) {
case VM_GGT_TADS2:
// It's a Tads 2 game. Fire-up the T2VM.
switch (interface) {
case cursesInterface: return FrobTadsApplicationCurses(frobOpts).runTads(actualFilename, 0);
case plainInterface: return FrobTadsApplicationPlain(frobOpts).runTads(actualFilename, 0);
}
case VM_GGT_TADS3: {
// It's Tads 3.
int t3vmRet;
switch (interface) {
case cursesInterface:
t3vmRet = FrobTadsApplicationCurses(frobOpts).runTads(actualFilename, 1);
break;
case plainInterface:
t3vmRet = FrobTadsApplicationPlain(frobOpts).runTads(actualFilename, 1);
break;
}
// Show any unfreed memory blocks. This does nothing if
// Tads 3 has not been compiled with debugging support.
t3_list_memory_blocks(0);
return t3vmRet;
}
case VM_GGT_INVALID:
// It's not a Tads game.
cerr << opts.name() << ": " << filename << ": not a Tads game.\n";
break;
case VM_GGT_NOT_FOUND:
// No such file.
cerr << opts.name() << ": " << filename << ": file not found.\n";
break;
case VM_GGT_AMBIG:
// Filename is ambiguous.
cerr << opts.name() << ": " << filename
<< ": ambiguous filename; please include the file suffix.\n";
break;
}
// If we reached this point, an error occured.
return 1;
} |