# HG changeset patch # User Diggory Hardy # Date 1212682612 -3600 # Node ID f000d6cd0f7437410487fc14af6b7e75a847aae0 # Parent 387a80724c35ac8bbf7d613b71e7eaa7c50c03eb Changes to paths, command line arguments and font LCD rendering. Use "./" instead of "" as default install dir on windows. Implemented a command-line argument parser. Changes to LCD filter/render-mode option handling after testing what actually happens. Changed some FontTexture messages and internals. diff -r 387a80724c35 -r f000d6cd0f74 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Jun 05 17:16:52 2008 +0100 @@ -0,0 +1,4 @@ +syntax: glob +dsss.last +bin/* +dsss_objs/* diff -r 387a80724c35 -r f000d6cd0f74 codeDoc/jobs.txt --- a/codeDoc/jobs.txt Mon Jun 02 14:34:24 2008 +0100 +++ b/codeDoc/jobs.txt Thu Jun 05 17:16:52 2008 +0100 @@ -52,5 +52,3 @@ Done (for git log message): -Options: impl template to ease creating Options sub-classes. -New OptionsFont class (currently only controls render mode and hinting). \ No newline at end of file diff -r 387a80724c35 -r f000d6cd0f74 data/conf/options.mtt --- a/data/conf/options.mtt Mon Jun 02 14:34:24 2008 +0100 +++ b/data/conf/options.mtt Thu Jun 05 17:16:52 2008 +0100 @@ -4,11 +4,12 @@ + {font} - - + + {video} @@ -16,7 +17,7 @@ - + - + diff -r 387a80724c35 -r f000d6cd0f74 mde/Options.d --- a/mde/Options.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/Options.d Thu Jun 05 17:16:52 2008 +0100 @@ -475,7 +475,7 @@ /** A home for all miscellaneous options, at least for now. */ OptionsMisc miscOpts; class OptionsMisc : Options { - mixin (impl!("bool useThreads, exitImmediately; int logLevel; double pollInterval; char[] L10n;")); + mixin (impl!("bool useThreads, exitImmediately; int logOptions; double pollInterval; char[] L10n;")); static this() { miscOpts = new OptionsMisc; diff -r 387a80724c35 -r f000d6cd0f74 mde/mde.d --- a/mde/mde.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/mde.d Thu Jun 05 17:16:52 2008 +0100 @@ -36,11 +36,10 @@ import tango.time.Clock; // Clock.now() import tango.util.log.Log : Log, Logger; -int main() +int main(char[][] args) { //BEGIN Initialisation Logger logger = Log.getLogger ("mde.mde"); - logger.info ("Starting mde..."); // Create instances now, so they can be used during init (if necessary) input = new Input(); @@ -48,7 +47,7 @@ scope Init init; try { - init = new Init(); // initialisation + init = new Init(args); // initialisation } catch (InitException e) { logger.fatal ("Initialisation failed: " ~ e.msg); return 1; diff -r 387a80724c35 -r f000d6cd0f74 mde/resource/FontTexture.d --- a/mde/resource/FontTexture.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/resource/FontTexture.d Thu Jun 05 17:16:52 2008 +0100 @@ -32,7 +32,6 @@ import Utf = tango.text.convert.Utf; import tango.util.log.Log : Log, Logger; -import tango.io.Stdout; private Logger logger; static this () { @@ -74,7 +73,7 @@ void updateCache (FT_Face face, char[] str, ref TextBlock cache) { debug scope (failure) - logger.warn ("updateCache failed"); + logger.error ("updateCache failed"); if (cache.cacheVer == cacheVer) // Existing cache is up-to-date return; @@ -165,7 +164,7 @@ void drawTextCache (FT_Face face, char[] str, ref TextBlock cache, int x, int y) { updateCache (face, str, cache); // update if necessary debug scope (failure) - logger.warn ("drawTextCache failed"); + logger.error ("drawTextCache failed"); glEnable (GL_TEXTURE_2D); glEnable(GL_BLEND); @@ -198,7 +197,7 @@ void addGlyph (FT_Face face, dchar chr) { debug scope (failure) - logger.warn ("FontTexture.addGlyph failed!"); + logger.error ("FontTexture.addGlyph failed!"); auto gi = FT_Get_Char_Index (face, chr); auto g = face.glyph; @@ -208,11 +207,6 @@ throw new fontGlyphException ("Unable to render glyph"); auto b = g.bitmap; - if (b.pitch != b.width) { - char[128] tmp; - logger.warn (logger.format (tmp, "b.pitch is {}, b.width is {}", b.pitch, b.width)); - //throw new fontGlyphException ("Unsupported freetype bitmap: b.pitch != b.width"); - } glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //glPixelStorei (GL_UNPACK_ROW_LENGTH, b.pitch); @@ -234,7 +228,7 @@ } // if here, no existing texture had the room for the glyph so create a new texture // NOTE: check if using more than one texture impacts performance due to texture switching - logger.info ("Creating a font texture."); + logger.info ("Creating a new font texture."); tex ~= TexPacker.create(); assert (tex[$-1].addGlyph (ga), "Failed to fit glyph in a new texture but addGlyph didn't throw"); @@ -243,6 +237,7 @@ GLenum format; ubyte[] buffer; // use our own pointer, since for LCD modes we need to perform a conversion if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_GRAY && b.num_grays == 256) { + assert (b.pitch == b.width, "Have assumed b.pitch == b.width for gray glyphs."); buffer = b.buffer[0..b.pitch*b.rows]; format = GL_LUMINANCE; } else if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_LCD) { @@ -357,6 +352,9 @@ if (attr.w > dimW || attr.h > dimH) throw new fontGlyphException ("Glyph too large to fit texture!"); + attr.texID = texID; // Set now. Possibly reset if new texture is needed. + if (attr.w == 0) return true; // 0 sized glyph; x and y are unimportant. + bool cantFitExtraLine = nextYPos + attr.h >= dimH; foreach (ref line; lines) { if (line.length + attr.w <= dimW && // if sufficient length and @@ -380,7 +378,6 @@ Line line; line.yPos = nextYPos; line.height = attr.h * EXTRA_H; - Stdout.format ("Creating new line of height {}", line.height).newline; line.length = attr.w; size_t i = 0; while (i < lines.length && lines[i].height < line.height) ++i; @@ -389,7 +386,6 @@ attr.x = 0; // first glyph in the line attr.y = line.yPos; - attr.texID = texID; return true; } diff -r 387a80724c35 -r f000d6cd0f74 mde/resource/font.d --- a/mde/resource/font.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/resource/font.d Thu Jun 05 17:16:52 2008 +0100 @@ -71,18 +71,20 @@ logger.warn (logger.format (tmp, "Using an untested FreeType version: {}.{}.{}", maj, min, patch)); } - // Set LCD filtering method (note: FT_Library_SetLcdFilter can still complain with - // FT_LcdFilter.FT_LCD_FILTER_NONE). - if (fontOpts.lcdFilter && FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { - // If setting failed, leave at default (disabled status). Note: it is disabled by - // default because the code isn't compiled in by default, to avoid patents. - logger.warn ("Bad/unsupported LCD filter option; disabling LCD filtering."); + // Set LCD filtering method if LCD rendering is enabled. + const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; + if (fontOpts.renderMode & RMF && + FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { + /* An error occurred, presumably because LCD rendering support + * is not compiled into the library. */ + logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering."); logger.warn ("Your FreeType 2 library may compiled without support for LCD/sub-pixel rendering."); - Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_NONE); - } - const RMF = FT_LOAD_TARGET_MONO | FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; - if (fontOpts.renderMode & RMF && fontOpts.lcdFilter == 0) { - logger.warn ("Using LCD rendering when LCD filtering is disabled has no effect; disabling."); + + // Reset the default filter (in case an invalid value was set in config files). + Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT); + + /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3 + * times wider glyphs. So disable and save the extra work. */ Options.setInt ("font", "renderMode", FT_LOAD_TARGET_NORMAL); } diff -r 387a80724c35 -r f000d6cd0f74 mde/resource/paths.d --- a/mde/resource/paths.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/resource/paths.d Thu Jun 05 17:16:52 2008 +0100 @@ -39,8 +39,7 @@ import tango.io.Console; import tango.io.FilePath; -import tango.stdc.stdlib; -import tango.stdc.stringz; +import tango.sys.Environment; //import tango.scrapple.sys.win32.Registry; // Trouble getting this to work /** Order to read files in. @@ -115,6 +114,15 @@ return false; } + /// Print all paths found. + static void printPaths () { + Cout ("Data paths found:"); + dataDir.coutPaths; + Cout ("\nConf paths found:"); + confDir.coutPaths; + Cout ("\nLog file directory:\n\t")(logDir).newline; + } + private: PathView[] getFiles (char[] filename, PRIORITY readOrder) in { @@ -165,6 +173,14 @@ return false; } + void coutPaths () { + if (pathsLen) { + for (size_t i = 0; i < pathsLen; ++i) + Cout ("\n\t" ~ paths[i]); + } else + Cout ("[none]"); + } + // Use a static array to store all possible paths with separate length counters. // Lowest priority paths are first. char[][MAX_PATHS] paths; @@ -185,49 +201,56 @@ * Note: the logger cannot be used yet, so only output is exception messages. */ // FIXME: use tango/sys/Environment.d version (linux) { - void resolvePaths () { + // base-path not used on posix + void resolvePaths (char[] = null) { // Home directory: - char[] HOME = fromStringz (getenv (toStringz ("HOME"))); + char[] HOME = Environment.get("HOME", "."); // Base paths: // Static data (must exist): - PathView staticPath = findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); + PathView staticPath = + findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); // Config (can just use defaults if necessary, so long as we can save afterwards): PathView userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); // Static data paths: dataDir.addPath (staticPath.toString); // we know this is valid anyway dataDir.tryPath (userPath.toString ~ DATA); + if (extraDataPath) dataDir.tryPath (extraDataPath); if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); // Configuration paths: confDir.tryPath (staticPath.toString ~ CONF); confDir.tryPath ("/etc/mde"); confDir.tryPath (userPath.toString ~ CONF, true); + if (extraConfPath) confDir.tryPath (extraConfPath); if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); // Logging path: logDir = userPath.toString; } } else version (Windows) { - void resolvePaths () { + void resolvePaths (char[] base = "./") { //FIXME: Get path from registry //FIXME: Get user path (Docs&Settings/USER/Local Settings/Application data/mde) + //http://www.dsource.org/projects/tango/forums/topic/187 // Base paths: - PathView installPath = findPath (false, "");; - PathView userPath = findPath (true, "user"); // FIXME: see above - PathView staticPath = findPath (false, "data"); + PathView installPath = findPath (false, base); + PathView staticPath = findPath (false, installPath.append("data").toString); + PathView userPath = findPath (true, installPath.append("user").toString); // FIXME: see above // Static data paths: dataDir.addPath (staticPath.toString); // we know this is valid anyway dataDir.tryPath (userPath.toString ~ DATA); + if (extraDataPath) dataDir.tryPath (extraDataPath); if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); // Configuration paths: confDir.tryPath (staticPath.toString ~ CONF); - confDir.tryPath ("conf"); + confDir.tryPath (installPath.append("user").toString); confDir.tryPath (userPath.toString ~ CONF, true); + if (extraConfPath) confDir.tryPath (extraConfPath); if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); // Logging path: @@ -237,86 +260,98 @@ static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!"); } -private: -// The maximum number of paths for any one "directory". -// There are NO CHECKS that this is not exceeded. -const MAX_PATHS = 3; +/// For command line args: these paths are added if non-null, with highest priority. +char[] extraDataPath, extraConfPath; -/* Try each path in succession, returning the first to exist and be a folder. -* If none are valid and create is true, will try creating each in turn. -* If still none are valid, throws. */ -PathView findPath (bool create, char[][] paths ...) { - foreach (path; paths) { - PathView pv = new FilePath (path); - if (pv.exists && pv.isFolder) return pv; // got a valid path - } - if (create) { // try to create a folder, using each path in turn until succesful - foreach (path; paths) { - FilePath fp = new FilePath (path); - try { - return fp.create; - } - catch (Exception e) {} - } - } - // no valid path... - char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; - foreach (path; paths) msg ~= " \"" ~ path ~ '\"'; - throw new mdeException (msg); -} -//END Path resolution - -/** A special adapter for reading from multiple mergetag files with the same relative path to an -* mdeDirectory simultaneously. -*/ -class mdeReader : IReader -{ - private this (PathView[] files, DataSet ds, bool rdHeader) - in { - assert (files !is null, "mdeReader.this: files is null"); - } body { - if (ds is null) ds = new DataSet; - - foreach (file; files) { - IReader r = makeReader (file, ds, rdHeader); - - readers[readersLen++] = r; +private { + class PathException : mdeException { + this(char[] msg) { + super (msg); } } - DataSet dataset () { /// Get the DataSet - return readers[0].dataset; // all readers share the same dataset +// The maximum number of paths for any one "directory". +// There are NO CHECKS that this is not exceeded. + const MAX_PATHS = 4; + + /* Try each path in succession, returning the first to exist and be a folder. + * If none are valid and create is true, will try creating each in turn. + * If still none are valid, throws. */ + PathView findPath (bool create, char[][] paths ...) { + FilePath[] fps; + fps.length = paths.length; + foreach (i,path; paths) { + FilePath pv = new FilePath (path); + if (pv.exists && pv.isFolder) return pv; // got a valid path + fps[i] = pv; + } + if (create) { // try to create a folder, using each path in turn until succesful + foreach (fp; fps) { + try { + return fp.create; + } + catch (Exception e) {} + } + } + // no valid path... + char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; + foreach (path; paths) msg ~= " \"" ~ path ~ '\"'; + throw new PathException (msg); } - void dataset (DataSet ds) { /// Set the DataSet - for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds); - } +//END Path resolution + + /** A special adapter for reading from multiple mergetag files with the same relative path to an + * mdeDirectory simultaneously. + */ + class mdeReader : IReader + { + private this (PathView[] files, DataSet ds, bool rdHeader) + in { + assert (files !is null, "mdeReader.this: files is null"); + } body { + if (ds is null) ds = new DataSet; + + foreach (file; files) { + IReader r = makeReader (file, ds, rdHeader); + + readers[readersLen++] = r; + } + } - void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator - for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; - } + DataSet dataset () { /// Get the DataSet + return readers[0].dataset; // all readers share the same dataset + } + void dataset (DataSet ds) { /// Set the DataSet + for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds); + } + + void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator + for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; + } /** Get identifiers for all sections. - * - * Note: the identifiers from all sections in all files are just strung together, starting with - * the highest-priority file. */ + * + * Note: the identifiers from all sections in all files are just strung together, starting with + * the highest-priority file. */ ID[] getSectionNames () { ID[] names; for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames; return names; - } - void read () { /// Commence reading - for (uint i = 0; i < readersLen; ++i) readers[i].read(); - } - void read (ID[] secSet) { /// ditto - for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + } + void read () { /// Commence reading + for (uint i = 0; i < readersLen; ++i) readers[i].read(); + } + void read (ID[] secSet) { /// ditto + for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + } + void read (View!(ID) secSet) { /// ditto + for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + } + + private: + IReader[MAX_PATHS] readers; + ubyte readersLen = 0; + + PRIORITY rdOrder; } - void read (View!(ID) secSet) { /// ditto - for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); - } - - private: - IReader[MAX_PATHS] readers; - ubyte readersLen = 0; - - PRIORITY rdOrder; -} +} \ No newline at end of file diff -r 387a80724c35 -r f000d6cd0f74 mde/scheduler/Init.d --- a/mde/scheduler/Init.d Mon Jun 02 14:34:24 2008 +0100 +++ b/mde/scheduler/Init.d Thu Jun 05 17:16:52 2008 +0100 @@ -34,6 +34,9 @@ import tango.core.Thread; import tango.core.Exception; import tango.stdc.stringz : fromStringz; +import tango.io.Console; // for printing command-line usage +import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock; // output date in log file +import tango.util.Arguments; import tango.util.log.Log : Log, Logger; import tango.util.log.ConsoleAppender : ConsoleAppender; @@ -58,37 +61,12 @@ */ static this() { - // Find/create paths: - try { - paths.resolvePaths(); - } catch (Exception e) { - // NOTE: an exception thrown here cannot be caught by main()! - throw new InitException ("Resolving paths failed: " ~ e.msg); - } - - // Set up the logger: - { - // Where logging is done to is determined at compile-time, currently just via static ifs. - Logger root = Log.getRootLogger(); - - static if (true ) { // Log to files (first appender so root seperator messages don't show on console) - version (SwitchAppender) { - root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); - } else { - // Use 2 log files with a maximum size of 1 MB: - root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); - root.info (""); // some kind of separation between runs - root.info (""); - } - } - static if (true ) { // Log to the console - root.addAppender(new ConsoleAppender); - } - - // Set the level here, but set it again once options have been loaded: - debug root.setLevel(root.Level.Trace); - else root.setLevel(root.Level.Info); - } + Logger root = Log.getRootLogger(); + // Set the level here, but set it again once options have been loaded: + debug root.setLevel(root.Level.Trace); + else root.setLevel(root.Level.Info); + // Temporarily log to the console (until we've found paths and loaded options): + root.addAppender(new ConsoleAppender); } static ~this() { @@ -124,28 +102,105 @@ * must not run where the initialisation functions have failed. * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct. */ - this() + this(char[][] cmdArgs) { debug logger.trace ("Init: starting"); //BEGIN Pre-init (stage init0) + //FIXME: warn on invalid arguments, including base-path on non-Windows + // But Arguments doesn't support this (in tango 0.99.6 and in r3563). + Arguments args; + try { + args = new Arguments(); + args.define("base-path").parameters(1); + args.define("data-path").parameters(1,-1); + args.define("conf-path").parameters(1,-1); + args.define("paths"); + args.define("q").aliases(["quick-exit"]); + args.define("help").aliases(["h"]); + args.parse(cmdArgs); + if (args.contains("help")) // lazy way to print help + throw new InitException ("Help requested"); // and stop + } catch (Exception e) { + printUsage(cmdArgs[0]); + throw new InitException ("Parsing arguments failed: "~e.msg); + } + + // Find/create paths: + try { + if (args.contains("data-path")) + paths.extraDataPath = args["data-path"]; + if (args.contains("conf-path")) + paths.extraConfPath = args["conf-path"]; + if (args.contains("base-path")) + paths.resolvePaths (args["base-path"]); + else + paths.resolvePaths(); + } catch (Exception e) { + throw new InitException ("Resolving paths failed: " ~ e.msg); + } + if (args.contains("paths")) { + paths.mdeDirectory.printPaths; + throw new InitException ("Paths requested"); // lazy way to stop + } + debug logger.trace ("Init: resolved paths successfully"); + /* Load options now. Don't load in a thread since: - * Loading should be fast - * Most work is probably disk access - * It's a really good idea to let the options apply to all other loading. */ + * Loading should be fast & most work is probably disk access + * It enables logging to be controlled by options + * It's a really good idea to let the options apply to all other loading */ try { Options.load(); } catch (optionsLoadException e) { throw new InitException ("Loading options failed: " ~ e.msg); } + debug logger.trace ("Init: loaded options successfully"); - // Now re-set the logging level, using the value from the config file: - Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); - // And set this (debug option): - imde.run = !miscOpts.exitImmediately; - //END Pre-init + // Set up the logger: + Logger root; + try { + enum LOG { + LEVEL = 0x10, // mask for log level + CONSOLE = 0x1001, // log to console? + ROLLFILE= 0x1002 // use Rolling/Switching File Appender? + } + + // Where logging is done to is determined at compile-time, currently just via static ifs. + root = Log.getRootLogger(); + root.clearAppenders; // we may no longer want to log to the console + + // Now re-set the logging level, using the value from the config file: + Log.getRootLogger.setLevel (cast(Log.Level) (miscOpts.logOptions & LOG.LEVEL), true); + + // Log to a file (first appender so root seperator messages don't show on console): + if (miscOpts.logOptions & LOG.ROLLFILE) { + version (SwitchAppender) { + root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); + } else { + // Use 2 log files with a maximum size of 1 MB: + root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); + root.info (""); // some kind of separation between runs + root.info (""); + } + } else if (!(miscOpts.logOptions & LOG.CONSOLE)) { + // make sure at least one logger is enabled + Options.setInt ("misc", "logOptions", miscOpts.logOptions | LOG.CONSOLE); + } + if (miscOpts.logOptions & LOG.CONSOLE) { // Log to the console + root.addAppender(new ConsoleAppender); + } + logger.info ("Starting mde [no version] on " ~ TimeStamp.toString(WallClock.now)); + } catch (Exception e) { + // Presumably it was only adding a file appender which failed; set up a new console + // logger and if that fails let the exception kill the program. + root.clearAppenders; + root.addAppender (new ConsoleAppender); + logger.warn ("Exception while setting up the logger; logging to the console instead."); + } - debug logger.trace ("Init: pre-init done"); + // a debugging option: + imde.run = !args.contains("q") && !miscOpts.exitImmediately; + debug logger.trace ("Init: applied pre-init options"); //BEGIN Load dynamic libraries /* Can be done by init functions but much neater to do here. @@ -160,9 +215,10 @@ throw new InitException ("Loading dynamic libraries failed (see above)."); } + debug logger.trace ("Init: dynamic libraries loaded"); //END Load dynamic libraries + //END Pre-init - debug logger.trace ("Init: dynamic libraries loaded"); //BEGIN Init (stages init2, init4) /* Call init functions. @@ -293,8 +349,28 @@ if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. return false; // Done successfully } + //END runStage... + + void printUsage (char[] progName) { + Cout ("mde [no version]").newline; + Cout ("Usage:").newline; + Cout (progName ~ ` [options]`).newline; + version(Windows) + Cout ( +` --base-path path Use path as the base (install) path (Windows only). It + should contain the "data" directory.`).newline; + Cout ( +` --data-path path(s) Add path(s) as a potential location for data files. + First path argument becomes the preffered location to + load data files from. + --conf-path path(s) Add path(s) as a potential location for config files. + Configuration in the first path given take highest + priority. + --paths Print all paths found and exit. + --quick-exit, -q Exit immediately, without entering main loop. + --help, -h Print this message.`).newline; + } } - //END runStage... debug (mdeUnitTest) unittest { /* Fake init and cleanup. Use unittest-specific init and cleanup InitStages to avoid