changeset 15:4608be19ebe2

Use OS paths (linux only for now), merging multiple paths. Init changes regarding options. Reorganised policies.txt a little. Implemented mde.resource.paths to read config from appropriate paths (currently linux only). Changed Init to load options before all other delegates are run and set logging level from options. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 14 Mar 2008 11:39:45 +0000
parents 0047b364b6d9
children 9cb7b9310168
files codeDoc/input/inputIDs.txt codeDoc/jobs.txt codeDoc/mergetag/file-format-binary.txt codeDoc/mergetag/file-format-requirements.txt codeDoc/mergetag/file-format-text.txt codeDoc/mergetag/issues.txt codeDoc/policies.txt codeDoc/resource/paths.txt conf/L10n/i18nUnitTest.mtt conf/L10n/mde.mtt conf/input.mtt conf/options.mtt data/L10n/i18nUnitTest.mtt data/L10n/mde.mtt data/conf/input.mtt data/conf/options.mtt doc/Changelog.txt doc/Readme.txt doc/changelog doc/input_ID_assignments doc/jobs doc/policies.txt mde/Init.d mde/exception.d mde/i18n.d mde/i18n/I18nTranslation.d mde/init.d mde/input/config.d mde/input/input.d mde/mde.d mde/mergetag/Reader.d mde/mergetag/Writer.d mde/mergetag/doc/file-format-binary.txt mde/mergetag/doc/file-format-requirements.txt mde/mergetag/doc/file-format-text.txt mde/mergetag/doc/issues.txt mde/mergetag/exception.d mde/mergetag/iface/IReader.d mde/options.d mde/resource/exception.d mde/resource/paths.d test/mdeTest.d
diffstat 42 files changed, 1540 insertions(+), 986 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/input/inputIDs.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,65 @@
+Event input status and callbacks are differentiated via IDs of type Input.inputID (currently uint).
+
+Multi-user support requiers some way to make these specific to particular users; this is intended to be implemented via each user having their own Input class object; user-common callbacks are added by each user's class, thus enabling these to have extra user-specific functionality added later. Thus inputIDs don't need to contain a user ID.
+
+4 bytes are available; of this the lowest 4 bits specifies which part of the engine uses these IDs, the rest is available for the engine part to use as it wants. The reason the lowest 4 bits are used for the engine part is so that if the inputID type is changed to a different sized integer, these 4 bits remain at the end of the block (i.e. the IDs available for the subsystem are still in one block).
+
+Bit:	31		   4  3		0
+	[ engine subsystem ]  [subsystem]
+
+
+Subsystem values:
+0	Direct engine commands:
+		Main commands handled by mde.mde (may be moved), e.g. direct quit
+1	GUI/User interface:
+		Interface commands, including quit dialog box
+2	Physics system:
+		Direct control of thrusters, etc (control also possible via scripts)
+3	Scripting system:
+		Indirect control of thrusters, etc. from physics objects
+		Other game-world interaction & scripting uses
+4-F	Unassigned
+
+
+This is not enforced by the engine (except perhaps for scripts?), but simply followed.
+
+
+
+Subsystem 0:
+xx_xx_xx_00	Quitting:
+00_00_00_00		End main loop normally
+xx_xx_xx_E0	Debug dumps/commands
+xx_xx_xx_F0	Test events
+
+
+
+
+For stream functions, codes are used as follows. Only the last (lowest) two bytes are used; of this the highest 4 bits categorise the rough type of the stream function, and the next 4 bits subdivide this. The lowest byte has no meaning but is just a number.
+
+Categorisation:
+
+1*_**	Output as event
+2*_**	Adjustments, keeping same output type
+3*_**	Adjustments with a different output type
+F*_**	Debug types
+    *
+2X_**	Adjustments:
+	OR'd flags for X:
+	1	uses timers
+	2	uses other events from same button/axis/etc.
+	4	uses other events from different button/axis/etc.
+	8	no output, only sets values for use by other adjusters
+    *
+3X_**	Adjustments with different output types (e.g. axis -> button):
+	OR'd flags as for 2X_**
+
+F*_**	Debug outputs:
+	F0_00	No output, doesn't do anything
+	F1_**	Logs output, passing event on
+
+Actual codes, with config values used:
+
+1000	Standard output functions
+		Use 1 config value for output ID
+2000	Reverse value (axes only)
+		No config values
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/jobs.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,41 @@
+In progress:
+
+To do:
+*   Move init's delegates to appropriate external modules and provide addInitDlg and addCleanupDlg functions callable from other modules' static this() methods.
+*   Control logging level/output via options.
+*   Windows building/compatibility (currently partial)
+*   gdc building/compatibility (wait for tango 0.99.5 release?)
+*   Config loaded from correct places
+*	OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
+*	Sensitivity adjustments. From es_a_out:
+        /+ FIXME: revise.
+        + I can't see any point using HALF_RANGE here, since it should really be used dependant on
+        + the device attached, not the axis. Also what about adjusted range like X3's throttle?
+        +
+        + Sensitivity: is this the right place to adjust it? For things like throttle where the
+        + ends of the interval must remain fixed, multiplying cannot be used to adjust and adjusting
+        + the curve via a power function doesn't seem to be what we want. For things where the
+        + end points needn't remain fixed, multiplying seems the right thing to do, but cannot be
+        + done here since we don't know the end points can be changed.
+        
+        real y = x;
+        uint conf = s.pop();
+        enum : uint {
+            HALF_RANGE	= 0x8000_0000u,
+            SENSITIVITY	= 0x0080_0000u,
+        }
+        // Convert ranges into standard intervals (with or without reverse values)
+        if (conf & HALF_RANGE) y = (y + 32767.0) * 1.5259254737998596e-05;	// range  0.0 - 1.0
+        else y *= 3.0518509475997192e-05;					// range -1.0 - 1.0
+        real a;
+        if (conf & SENSITIVITY) a = s.pop();
+        /+ When a global sensitivity is available (possibly only use if it's enabled)...
+        else a = axis.sensitivity;
+        y = sign(y) * pow(abs(y), a);		// sensitivity adjustment by a +/
+        myThis.axis[cast(inputID) s.pop()] = y;
+        +/
+
+Done (for git log message):
+Reorganised policies.txt a little.
+Implemented mde.resource.paths to read config from appropriate paths (currently linux only).
+Changed Init to load options before all other delegates are run and set logging level from options.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/mergetag/file-format-binary.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,11 @@
+This is the file format for mergetag binary files. The unit size is a byte. Most numbers to do with the layout (i.e. not stored data) should be stored as a 32-bit uint.
+
+
+BOM  ---  a Byte Order Mark should be used to determin endianness (MT01 (or other version) in bytes, but converted to two ushorts to detect endianness?)
+
+
+File should then consist of sections:
+
+Header data including an address for the header section data if included.
+
+Sections list. Include a list of sections with identifiers and addresses, sorted by identifier and in a suitible format to easily be converted to a D hash-map. Addresses for each section should consist of both a start and an end address; the end address should be checked upon reading the section. In addition the start address must be checked against the end of file to avoid security vulnerabilities with reading other memory blocks.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/mergetag/file-format-requirements.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,29 @@
+Requirements:
+
+---	Config - named entries (map associated by strings; sections by strings)
+Merging: chosing one entry over another
+
+---	Data - list of entries, each of custom compound type (e.g. list of 3-vector over reals)
+As sub blocks within a standard item (mergable: combine the blocks of multiple items).
+
++++	Global type for handling all this:
+File consists of sections.
+Each section consists of items.
+Items are sorted by ID and not by type, i.e. if two items with the same ID but different types exist, merging rules are used to choose between them.
+Items have a custom type, which can be a compound of:
++	Basic types:
+++		bool
+++		int (int+uint)
+++		real (or float or double? no.)
+++		string (char)
+++		binary (ubyte[])
++	Strings (of char, wchar or dchar)
++	Fixed-length arrays (single type)
++	Variable-length arrays (single type)
++	Fixed format tuples (multiple types which are prespecified)
++	The top-most type may be a "data list", which is identical to a variable-length array accept that merging items with identical types will combine their lists instead of choosing one over the other.
+To access an item, it should be found by ID, its type should be checked, and then it may be accessed.
+Types are specific to items. As an optimisation, a binary format may have a list of types and index them.
+
++++	Basic types:
+All D base types, including void, with support for writing strings.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/mergetag/file-format-text.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,168 @@
+This is the file format for mergetag text files.
+Version: 0.1 unfinalised
+
+
+The encoding should be unicode UTF-8, UTF-16 or UTF-32, and for anything other than UTF-8 must include a BOM.
+
+
+Hierarchy:
++	Sections	(special section: see header)
+++	Data Tags
+
+
+IDs:
+IDs are used for several purposes; they are UTF-8 strings. They are stored in text files as unquoted strings; escape sequences are not supported and the strings should not contain the following characters, although this is not checked: <|=>{}
+All characters between the appropriate markers are consumed into the ID, hence whitespace is meaningful.
+Multiple section or data tags with the same ID are allowed; see the "Merging rules" section.
+
+
+Outside of tags only whitespace or valid tags is allowed. Whitespace is ignored.
+The following tags are valid (see below for details):
+tag		purpose
+{...}		section identifiers
+<...>		data items
+!{...}		simple comment block
+!<...>		comment block parsed the same as <...>
+Within tags, type specifications or data items whitespace is allowed between symbols.
+
+
+Section identifier tags:
+Format: {ID}
+The ID is the section identifier/name. The ID type is DefaultData unless overriden by the code using the reader.
+A section identifier marks the beginning of a new section, extending until the next section identifier or the end of the file.
+
+
+Data item tags:
+Format: <tp|ID=dt>
+A data item with type tp, identifier ID and data dt. If the data does not fit the given type it is an error and the tag is ignored. Once split into a type string, ID and data string, the contents are passed to an addTag() function within the DataSection class which will parse tags of a recognised format and either ignore or print a warning about other tags.
+
+
+Data item tags: Type format:
+Note:
+	The type is read as a single token terminated by any of these characters:	<>|=
+	There must not be spaces within the type, e.g. "char []".
+	Of course any character other than a | terminating the token is an error.
+Format:
+	tp		a basic type
+	tp[]		a dynamic list of sub-type tp
+	t1[t2]		an associative array with key-type t2
+Possible future additions:
+	tp()		a dynamic merging list of sub-type tp (only valid as the primary type, ie <subtype()|...>, not a sub-type of a tuple or another dynamic list)
+	{t1,t2,...,tn}	a tuple with sub-types t1, t2, ..., tn
+
+Basic types (only items with a + are currently supported, items with * are in DefaultData):
+	name
+	
+	void	--- less useful type
++*	bool	--- integer types
++*	byte
++*	ubyte
++*	short
++*	ushort
++*	int
++*	uint
++*	long
++*	ulong
+	cent
+	ucent
+	
++*	binary	--- alias for ubyte[]
+	
++*	float	--- floating point types
++*	double
++*	real
+	ifloat
+	idouble
+	ireal
+	cfloat
+	cdouble
+	creal
+	
++*	char	--- single character types (actually these CANNOT support UTF8 symbols with length > 1)
+	wchar
+	dchar
++*	string	--- alias for char[] --- (DOES support UTF8)
+	wstring	--- alias for wchar[]
+	dstring	--- alias for dchar[]
+
+
+Data item tags: Data format:
+Valid chars:	[](){},+-.0-9eEixXa-fA-F '.' ".*"
+Format:
+	[d1,d2,...,dn]	data all of type t corresponding to t[]
+	(d1,d2,...,dn)	data all of type t corresponding to t()
+	{d1,d2,...,dn}	data corresponding to a type declaration of {t1,t2,...,tn}
+	d		a single data element
+
+Single data elements:
+	z		an integer number (regexp: [+-]?[0-9]+)
+	z		a floating point number (rough regexp: [+-]?[0-9]*[.]?[0-9]*(e[+-]?[0-9]+)?)
+	zi		an imaginary floating point number (z is a floating point number)
+	y+zi, y-zi	a complex number (4+0i may be written as 4, etc) (y, z are f.p.s)
+	0xz, -0xz	a hexadecimal integer z (composed of chars 0-9,a-f,A-F)
+	'c'		a char/wchar/dchar character, depending on the type specified (c may be any single character except ' or an escape sequence)
+	"string"	equivalent to ['s','t','r','i','n','g'] --- may contain the following escape sequences as defined in D: \" \' \\ \a \b \f \n \r \t \v
+	XX...XX		Binary (ubyte[]); each pair of chars is read as a hex ubyte
+	<void>		void "data" has no symbols
+
+
+Data format: Escape sequences:
+To be created and written.
+
+
+Comment tags (there are no line comments):
+Simple comment blocks:
+Format: !{...}
+This is a simple comment block, and only curly braces ({,}) are treated specially. A {, whether or not it is preceded by a !, starts an embedded comment block, and a } ends either an embedded block or the actual comment block. Note: beware commenting out anything containing curly braces which aren't in matching pairs.
+Commented data tags:
+Format: !<tp|ID=dt>
+Basically a commented out data tag. Conformance to the above spec may not be checked as strictly as normal, but the dt section is checked for strings so that a > within a string won't end the tag.
+
+
+Merging rules:
+if, when a data item is read, a data item with the same identifier
+within the same section exists in the DataSet being read into:
++	if the types are identical:
+++		if the primary type is a tp() mergeable dynamic list:
++++			the entries from the item being read are concatenated to those in the item
++++			in the DataSet
+++		else:
+++-			the item already in the DataSet takes priority and is left untouched
++	else:
++-		a warning is issued, and the data item within the DataSet is left untouched
+This allows merging some config settings in a user config file with the remaining settings in a
+complete system config file and some support for modifications overriding or adding to some data.
+
+
+Header:
+The header is a standard section which is mandatory and must be the first section. Its section identifier must start at the beginning of the file with no whitespace, declared with:
+	{MTXY}		where XY is a two digit CAPITAL HEX version number representing the mergetag format version, e.g. {MT01} .
+If these are not the first 6 characters of the file the file will not be regarded as valid.
+This formatting is very strict to allow reliable low-level parsing.
+
+
+The data tags within the header have no special meaning; any may be used such as the following:
+	<string|"Author"="...">
+	<string|"Name"="...">
+	<string|"Description"="...">
+	<string|"Program"="...">	(which program created/uses this?)
+	<*|"Version"=...>		(use any supported type)
+	<string|"Date"="YYYYMMDD">	(reverse date format; optionally "YYYYMMDDhhmmss")
+	<{u16,u8,u8}|"Date"={YYYY,MM,DD}>	(actually this type probably won't be supported by a standard section)
+	<string|"Copyright"=...>
+
+
+Example:	!THIS IS NO LONGER VALID!
+{MT01}
+{example section}
+<u32|"num"=5>
+<{u32,UTF8[]}()|"DATA"=(
+	{1,['a']},
+	{59,['w','o','r','d']},
+	{2,"strings can be written like this"} )>
+<wchar[]|"name"="This string is stored in UTF16, regardless of the file's encoding.">
+<{u32,UTF8[]}()|"DATA"=(
+	{3,"this is appended to the previous 'DATA' item"} )>
+{"section: section identifiers and tuples are not confused since tuples only occur inside <...> items"}
+<void|Empty tag= >
+!{this is a comment {containing a comment}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/mergetag/issues.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,19 @@
+This is mostly just a list of potential minor issues noticed while coding but not seen worth throwing an error about.
+
+Overall:
+
+read.d:
+	Support partially loading a file.
+	parseSection(): as mentioned at end of function
+	formatting errors could be more informative; in particular say where the error is
+	No binary support.
+
+write.d:
+	Threading support?
+	There is currently no way to specify the base in which numbers are written (in text form).
+
+parse.d:
+	Doesn't support cent/ucent.
+
+format.d:
+	No support for cent/ucent or ulong where val > long.max.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/policies.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,124 @@
+--- Introduction ---
+This is a collection of all coding policies for the mde engine as a whole. Policies for individual packages should be put in the individual package directory or elsewhere.
+
+These are principles, not cast-iron rules, which I (Diggory Hardy) generally try to adhere to. If any other programmers have better principles to apply over these rules, they may do so for their own coding providing they have a good reason (i.e. not simply wanting to be a little different).
+
+A warning: I have several times changed my mind about items I've written here. So don't expect all existing code to conform, and if you feel that a principle listed is not a good idea don't think you have to conform to it.
+
+
+
++++ CONTENTS +++
+
+0   Introduction
+
+1   Coding conventions
+
+2   Documentation
+
+3   Package design principle
+
+4   Initialisation / cleanup
+
+5   Testing
+
+6   Logging
+
+7   Exceptions
+
+8   Translating strings
+
+
+
+--- Coding conventions ---
+Mostly stick to those provided in the D specification.
+
+Indentation: Preferably indent with four spaces and align comments either with tabs or spaces. If you do use tabs, use a tab-width of 8.
+
+Long lines: Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code so just try to keep it neat.
+
+Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not underscores. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Use CAPS for consts (with underscores as word separators), Capitalisation for class/interface/struct/union names, smallLetters for variables and class instances.
+
+Module/file names: If the module corresponds directly to a single class, use the class name with correct Capitalisation as the module name. Otherwise, preferably use lower case. Always use the correct case when importing a module to keep code portable.
+
+Spelling (within code): I am not going to stipulate British/American/other spellings, but keep to good English and use some consistancy (e.g. don't use both "defense" and "instance" together, unless unavoidable due to existing code).
+
+
+
+--- Documentation ---
+Documentation falls in to the following four categories:
+
+Source file comments: Code comments only aimed at yourself or other programmers.
+
+Source file DDoc: Documentation of the general working of modules, function purposes, syntaxes and operations, enums, classes, etc. Often only for public items. Also mostly only relevant to programmers.
+
+codeDoc directory: For large amounts of documentation on the general working/principle/whatever of a module/package or mde in general. Directory hierarchy should mirror that of the source directory and files should have appropriate names and extensions (.txt if plain text). The most important difference between the codeDoc and doc directories is that information in codeDoc is only aimed at programmers, wheras information in doc is aimed at all users.
+
+doc directory: Documentation aimed at the end user covering building, running, modding (excluding by programming), etc. Files should be appropriately named with an appropriate extension (.txt for plain text). Documentation regarding modding should be in a "modding" subdirectory. Documentation regarding translating may also be added.
+
+
+
+--- Package design principle ---
+Use a separate package for each module of the engine. In most packages where there is only one module (file) imported by other parts of the engine, that module should have the same name as the package and be designed to have a standardised interface to the package so that the package could be replaced with another as a drop-in replacement (written with the same interface). Of course in many cases it may not be possible to swich one package for another quite this easily, but holding to this principle should at least minimise the amount of work necessary when doing so.
+
+Module imports: don't use public imports much, although for importing interfaces it may make sense. Use static/renamed/selective imports when importing a module containing a lot of module-level symbols unless it is a very closely related module.
+
+Chain dependancies: If at all possible, avoid chain dependancies (e.g. use interfaces). Chain dependancies can cause many problems.
+
+
+
+--- Initialisation / cleanup ---
+This can be done in two ways:
+
+mde.Init: This class is designed to handle all but the simplist startup and cleanup code, and thus allow it to be threaded where appropriate and allow user feedback of loading progress.
+
+static [~]this(): Use of static CTORs/DTORs should be limited to very small operations which can definitely be run safely at this stage.
+
+
+
+--- Testing ---
+Testing should, as far as reasonably possible, be done by unittests, defined either in the appropriate module or another module. Any modules containing unittests must be imported by test.mdeTest.
+
+Unittests must be wrapped in "debug(mdeUnitTest)" statements. (These may also be used to wrap imports and "static this()" only needed by the unittest.)
+
+Unittests (last block in the module where multiple unittest blocks are used) should end with:
+    logger.info ("Unittest complete.");
+No more logging should be needed, since if it fails whoever runs the unittest will know about it, and logging messages cannot be used to tell how complete the unittesting is. These messages just confirm that the unittests ran really.
+
+
+
+--- Logging ---
+Logging should be handled by tango's Logger class. A logger with the name of the form mde.package.module or mde.package.module.X where X is a symbol within the module should be used for each module.
+
+In general the levels should be used as follows:
+	Trace	Where required or thought highly useful for debugging, and only compiled in debugging mode.
+	Info	Sparingly, for informational purposes (e.g. when parsing a file). Should not generally be used repetitively (within loops, etc.). Not for reporting unexpected behaviour.
+	Warn	For small errors which can be overlooked, even if they MAY cause bigger problems later. I.e. something unexpected, but not necessarily a major problem, happens.
+	Error	For errors which directly:
+		• cut-short a (reasonably large) operation (e.g. reading a file).
+		• cause a significant change in program operation, but do not directly cause the program to terminate.
+	Fatal	For errors directly (i.e. definately and almost immediately) ending the program.
+
+For all levels bar trace, messages should if possible be understandable to end users, while (for warn and above) including enough information to fix the problem when it is due to data files rather than code.
+Thus:
+	Trace output should only be available when compiled in debug mode.
+	When run by an end-user (with info-level logging enabled),
+	• info messages normally occur and should be understandable to end users;
+	• warn messages may occur, and may or may not indicate problems;
+	• error messages indicate that something big is wrong, and if the program still runs it is unlikely to be usable as intended;
+	• fatal messages indicate a problem preventing the program from running.
+
+Log/exception messages can be divided into two categories: those aimed at end users or modders and those only aimed at developers. A short string, either a brief English message or just a code, should be defined in the code, which can either be translated to a full message by I18nTranslation or output directly. The code string need not be a long description since it can be looked up in the code.
+
+
+
+--- Exceptions ---
+Thrown errors should, where documented, be documented with a log message; an exception message may be used to produce the final log message but must be output via a log message.
+
+Thrown errors should use an exception class specific to at least the package involved to enable specific catching of errors. Exception classes should be defined within a module exception.d in the package directory. Exception classes should generally follow the conventions within mde/exception.d to aid in providing reasonable error messages.
+
+
+
+--- Translating strings ---
+User output (internationalization support): i18n.I18nTranslation is designed to fetch a full string and optionally an associated descripion given an identifier. The identifier may also be a code symbol, and should be brief, in English, and give at least some idea of its meaning, since if no translation is available the identifier will be output. For example:  "example message" or "exampleMessage", not "An example of a message". For any new entry created, at least one full entry should be added to the i18n database for some form of English so that:
+    (a) there is something available to translate to other locales
+    (b) English developers can understand what it means
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/resource/paths.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,163 @@
+### mde paths ###
+
++++ Basic directories +++
+Note: multiple paths may be used for each "directory", with their contents merged upon loading.
+Below, there is a list of each path to merge content from for each "directory", for each OS.
+However, "A or B" doesn't mean merge from paths A and B, but use the first existing path out of A
+and B only (creating A when necessary if neither exist), and merging this path with any other
+existing entries. When writing data, it should be written to whichever path is most appropriate.
+
+Marks:
+    +   merged
+    -   chosen between
+    *   not applicable
+
+* Binaries          Where the executables are located (not needed to be known by the engine).
++ Static data       Static data (translations, levels, meshes, sounds, and probably much more)
++ Configuration     Generally defined as variable data not tied to any particular save and not a
+                        cache or log
+- Cache             Generated and locally regeneratable data, either only dependant on static data
+                        or specifying any variable data
+- Log               Log file (may or may not be written depending on configuration). FILE not DIR.
+- Save              Save games. Some type of grouping is needed where multiple users use the same
+                    location for easier access, and auto/quick/manual saves are game specific
+                    (grouped by game so that auto/quick saves from different games don't conflict).
+
+Paths below are listed in the format:
+# x   path
+#     description
+Where x is the priority of path; data loaded from paths with a higher priority overrides that of
+lower priority paths (when merging applies), or is the preferred path to use.
+
+Items described as optional are not normally used, however mde can be made to use them, in some
+cases by creating the paths (and possibly removing higher priority locations) and in some cases via
+configuration.
+
+Items described as shared are system-specific and shared between users on that system.
+
+
++++ Common paths +++
+(meaning common to a platform-dependant path)
+
+STATIC
+    System dependant base data directory. Falls back to current directory if all else fails.
+
+USER
+    A directory specific to the machine user-account and writable to that user.
+
+Static data:
+0   [STATIC]
+    Base data dir (also contains supplied conf dir). Shared.
+1   [USER]/data
+    User-supplied static data (e.g. mods)
+
+Configuration:
+0   [STATIC]/conf
+    Packaged/preconfigured config files (shared)
+2   [USER]/conf
+    User-specific config files
+
+Cache:
+0   [USER]/cache
+    Alternate cache location
+
+Log:
+1   [USER]/log
+    Optional log file location (logging in itself is optional)
+
+Save:
+1   [USER]/save
+    Usual save-file directory
+
+
+
++++ Linux paths +++
+--- Relevant policies (Filesystem Hierarchy Standard) ---
+http://www.debian.org/doc/packaging-manuals/fhs/fhs-2.3.html
+
+* /usr/local        mostly as for /usr, but for locally installed software
+* /etc              configuration files/directory for local configuration (done by admin)
+* $HOME             user-specific config in a file or under a directory starting with a dot
+* /var/cache        Application cache data
+* /var/crash        System crash dumps*
+* /var/log          Log files and directories
+* /var/local        Variable data for /usr/local
+
+Game specific:
+* /usr/games        binaries
+* /usr/share/games  architechture-independant static data
+* /var/games        non-static data, e.g. high scores, saves
+
+--- what to use ---
+
+USER:
+*   (HOME)/.config/mde or (HOME)/.mde
+    Base dir for user-specific data
+
+STATIC:
+*   /usr/share/games/mde or /usr/local/share/games/mde or ./data
+    Base data dir (shared)
+
+Binaries:
+*   /usr/games or /usr/local/games
+    All binaries (suggested locations only since it doesn't matter to mde)
+
+Configuration:
+1   /etc/mde
+    Optional shared config files
+
+Cache:
+1   /var/cache/mde
+    Shared cache. Use this location if possible, since system can handle the cache and it's shared.
+
+Log:
+0   /var/log/mde
+    Optional shared log file location
+
+Save:
+0   /var/games/mde/save
+    Optional shared save-file directory
+
+
+
++++ Windows paths +++
+--- Relevant policies (?) ---
+http://technet2.microsoft.com/windowsserver/en/library/093238f3-5064-470e-a281-0eb1c28b9cf01033.mspx?mfr=true
+
+Note: Local Settings contains:
+    Application settings and data that do not roam with the profile. Usually either machine specific, or too large to roam effectively.
+Thus this is probably the best place for user data.
+
+--- what to use ---
+
+USER:
+*   Docs&Settings/USER/Local Settings/Application data/mde
+    Base dir for user-specific data
+
+INSTALL:
+*   [from registry] or .
+    Base dir from install (shared)
+
+STATIC:
+*   [INSTALL]/data
+    Base data dir (shared)
+
+Binaries:
+*   [INSTALL]/bin
+    All binaries (suggested locations only since it doesn't matter to mde).
+
+Configuration:
+1   [INSTALL]/conf
+    Optional shared config files
+
+Cache:
+1   [INSTALL]/cache
+    Preferred cache location because it's shared.
+
+Log:
+0   [INSTALL]/log
+    Optional shared log file location
+
+Save:
+0   [INSTALL]/save
+    Optional shared save-file directory
--- a/conf/L10n/i18nUnitTest.mtt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-{MT01}
-{test-1}
-<entry|Str1=["Test 1"]>
-<char[][]|depends=["test-2"]>
-{test-2}
-<entry|Str1=["Test 2"]>
-<entry|Str2=["Test 3","Description",bogus,"entries",56]>
--- a/conf/L10n/mde.mtt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-{MT01}
-{en-GB}
-<entry|greeting=["Hello, and welcome to mde!"]>
--- a/conf/input.mtt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-{MT01}
-!<char[][]|Configs=["Default"]>
-{Default}
-<uint[][uint]|B=[ 0x8800001B : [[0x1000, 0x0]] ]>
-{UnitTest}
-<uint[][uint]|B=[
-	0x88000124 : [[0x1000, 0x00F0]],
-	0x40000003 : [[0x1000, 0x01F0]],
-	0x20002005 : [ [0x1000,0x02F0],[0x1000,0x03F0] ],
-	0x110D600C : [[0x1000,0x04F0]],
-	0x120D600C : [[0x1000,0x05F0]],
-	0x140D600C : [[0x1000,0x06F0]],
-	0x180D600C : [[0x1000,0x07F0]] ]>
-<uint[][uint]|A=[0x80001008 : [[0x1000,0x20F0]],0x130D600C : [[0x1000,0x21F0]] , 0x1C0D600C : [[0x1000,0x22F0]]]>
-<uint[][uint]|M=[ 0x88000000 : [[0x1000, 0x10F0], [0x1000, 0x11F0]], 0x40016064:[[0x1000,0x12F0]]]>
--- a/conf/options.mtt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-{MT01}
-{Default}
-<char[]|L10n="en-GB">
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/i18nUnitTest.mtt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,7 @@
+{MT01}
+{test-1}
+<entry|Str1=["Test 1"]>
+<char[][]|depends=["test-2"]>
+{test-2}
+<entry|Str1=["Test 2"]>
+<entry|Str2=["Test 3","Description",bogus,"entries",56]>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/mde.mtt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,3 @@
+{MT01}
+{en-GB}
+<entry|greeting=["Hello, and welcome to mde!"]>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/conf/input.mtt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,15 @@
+{MT01}
+!<char[][]|Configs=["Default"]>
+{Default}
+<uint[][uint]|B=[ 0x8800001B : [[0x1000, 0x0]] ]>
+{UnitTest}
+<uint[][uint]|B=[
+	0x88000124 : [[0x1000, 0x00F0]],
+	0x40000003 : [[0x1000, 0x01F0]],
+	0x20002005 : [ [0x1000,0x02F0],[0x1000,0x03F0] ],
+	0x110D600C : [[0x1000,0x04F0]],
+	0x120D600C : [[0x1000,0x05F0]],
+	0x140D600C : [[0x1000,0x06F0]],
+	0x180D600C : [[0x1000,0x07F0]] ]>
+<uint[][uint]|A=[0x80001008 : [[0x1000,0x20F0]],0x130D600C : [[0x1000,0x21F0]] , 0x1C0D600C : [[0x1000,0x22F0]]]>
+<uint[][uint]|M=[ 0x88000000 : [[0x1000, 0x10F0], [0x1000, 0x11F0]], 0x40016064:[[0x1000,0x12F0]]]>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/conf/options.mtt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,4 @@
+{MT01}
+{Default}
+<char[]|L10n="en-GB">
+<bool|useThreads=false>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Changelog.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,2 @@
+See git log (run "git log" from base dir).
+Also you could look at the "jobs.txt" file.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Readme.txt	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,21 @@
+There is no proper readme for mde yet.
+
+Platforms:
+I work on mde using linux and perform most testing on this platform.
+I have done a little testing on Windows, but currently is highly likely there may be problems on this platform.
+
+Build instructions:
+dsss build
+Then run the executable in bin/
+
+Testing:
+dsss clean		(this is important if a previous build exists!)
+dsss build bin/mdeTest
+
+Credits:
+Me (Diggory Hardy) for just about everything in mde as of now.
+
+Also thanks to:
+Walter Bright and Digital Mars for D and DMD.
+The tango team for Tango.
+
--- a/doc/changelog	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-See git log (run "git log" from base dir).
-Also you could look at the "jobs" file.
--- a/doc/input_ID_assignments	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-Event input status and callbacks are differentiated via IDs of type Input.inputID (currently uint).
-
-Multi-user support requiers some way to make these specific to particular users; this is intended to be implemented via each user having their own Input class object; user-common callbacks are added by each user's class, thus enabling these to have extra user-specific functionality added later. Thus inputIDs don't need to contain a user ID.
-
-4 bytes are available; of this the lowest 4 bits specifies which part of the engine uses these IDs, the rest is available for the engine part to use as it wants. The reason the lowest 4 bits are used for the engine part is so that if the inputID type is changed to a different sized integer, these 4 bits remain at the end of the block (i.e. the IDs available for the subsystem are still in one block).
-
-Bit:	31		   4  3		0
-	[ engine subsystem ]  [subsystem]
-
-
-Subsystem values:
-0	Direct engine commands:
-		Main commands handled by mde.mde (may be moved), e.g. direct quit
-1	GUI/User interface:
-		Interface commands, including quit dialog box
-2	Physics system:
-		Direct control of thrusters, etc (control also possible via scripts)
-3	Scripting system:
-		Indirect control of thrusters, etc. from physics objects
-		Other game-world interaction & scripting uses
-4-F	Unassigned
-
-
-This is not enforced by the engine (except perhaps for scripts?), but simply followed.
-
-
-
-Subsystem 0:
-xx_xx_xx_00	Quitting:
-00_00_00_00		End main loop normally
-xx_xx_xx_E0	Debug dumps/commands
-xx_xx_xx_F0	Test events
-
-
-
-
-For stream functions, codes are used as follows. Only the last (lowest) two bytes are used; of this the highest 4 bits categorise the rough type of the stream function, and the next 4 bits subdivide this. The lowest byte has no meaning but is just a number.
-
-Categorisation:
-
-1*_**	Output as event
-2*_**	Adjustments, keeping same output type
-3*_**	Adjustments with a different output type
-F*_**	Debug types
-    *
-2X_**	Adjustments:
-	OR'd flags for X:
-	1	uses timers
-	2	uses other events from same button/axis/etc.
-	4	uses other events from different button/axis/etc.
-	8	no output, only sets values for use by other adjusters
-    *
-3X_**	Adjustments with different output types (e.g. axis -> button):
-	OR'd flags as for 2X_**
-
-F*_**	Debug outputs:
-	F0_00	No output, doesn't do anything
-	F1_**	Logs output, passing event on
-
-Actual codes, with config values used:
-
-1000	Standard output functions
-		Use 1 config value for output ID
-2000	Reverse value (axes only)
-		No config values
--- a/doc/jobs	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-In progress:
-
-To do:
-*   Control logging level/output via options.
-*   Read config from system and user paths.
-*   Windows building/compatibility (currently partial)
-*   gdc building/compatibility (wait for tango 0.99.5 release?)
-*   Config loaded from correct places
-*	OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
-*	Sensitivity adjustments. From es_a_out:
-        /+ FIXME: revise.
-        + I can't see any point using HALF_RANGE here, since it should really be used dependant on
-        + the device attached, not the axis. Also what about adjusted range like X3's throttle?
-        +
-        + Sensitivity: is this the right place to adjust it? For things like throttle where the
-        + ends of the interval must remain fixed, multiplying cannot be used to adjust and adjusting
-        + the curve via a power function doesn't seem to be what we want. For things where the
-        + end points needn't remain fixed, multiplying seems the right thing to do, but cannot be
-        + done here since we don't know the end points can be changed.
-        
-        real y = x;
-        uint conf = s.pop();
-        enum : uint {
-            HALF_RANGE	= 0x8000_0000u,
-            SENSITIVITY	= 0x0080_0000u,
-        }
-        // Convert ranges into standard intervals (with or without reverse values)
-        if (conf & HALF_RANGE) y = (y + 32767.0) * 1.5259254737998596e-05;	// range  0.0 - 1.0
-        else y *= 3.0518509475997192e-05;					// range -1.0 - 1.0
-        real a;
-        if (conf & SENSITIVITY) a = s.pop();
-        /+ When a global sensitivity is available (possibly only use if it's enabled)...
-        else a = axis.sensitivity;
-        y = sign(y) * pow(abs(y), a);		// sensitivity adjustment by a +/
-        myThis.axis[cast(inputID) s.pop()] = y;
-        +/
-
-Done (for git log message):
-Changes to mergetag Reader methods. New functionality allowing a dataSecCreator to cause sections to be skipped.
-Moved several of the mergetag modules and some of their contents around. Moved all interfaces to separate modules in iface/ .
-Mergetag: IReader & IWriter interfaces exist; MTTReader, MTBReader, MTTWriter, MTBWriter & DualWriter all now exist and implement IReader/IWriter (although the MTB variants are dummy classes); makeReader & makeWriter should both be fully functional.
-Tested building on windows with partial success (works but window won't open).
-Included a temporary hack from windows to get supported resolutions information.
--- a/doc/policies.txt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-This is a collection of all coding policies for the mde engine as a whole. Policies for individual packages should be put in the individual package directory or elsewhere.
-
-These are principles, not cast-iron rules, which I (Diggory Hardy) generally try to adhere to. If any other programmers have better principles to apply over these rules, they may do so for their own coding providing they have a good reason (i.e. not simply wanting to be a little different).
-
-A warning: I have several times changed my mind about items I've written here. So don't expect all existing code to conform, and if you feel that a principle listed is not a good idea don't think you have to conform to it.
-
-
-
-Coding conventions: Mostly stick to those provided in the D specification. Generally indent with four spaces and use tabs to align comments. Keep editor's tab-width at 8. Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code.
-
-Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not _s. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Again, this is only really a guideline.
-
-Module/file names: unless you have a good reason, keep names all lower-case. And if you're programming on windows, make sure you always use the correct capitalisation (yeah, of course...).
-
-Spelling (within code): Use whichever spellings you like (so long as it's good English), but use these spellings consistantly, at least for code symbols within packages (i.e. if you write your own package you can choose the spellings, and for comments it doesn't really matter, unless it's actually refering to a symbol).
-
-
-
-Package design principle: use a separate package for each module of the engine. In most packages where there is only one module (file) imported by other parts of the engine, that module should have the same name as the package and be designed to have a standardised interface to the package so that the package could be replaced with another as a drop-in replacement (written with the same interface). Of course in many cases it may not be possible to swich one package for another quite this easily, but holding to this principle should at least minimise the amount of work necessary when doing so.
-
-
-Module imports: modules should publically import dependancy modules likely to be needed by dependant modules, in particular the package-level exception module (where used & use by dependants is expected).
-
-
-Engine-wide initialisation and cleanup should be handled or invoked by mde.init.Init's CTOR and DTOR methods where this is viable.
-
-
-
-Unittests (last block in the module where multiple unittest blocks are used) should end with:
-    logger.info ("Unittest complete.");
-No more logging should be needed, since if it fails whoever runs the unittest will know about it, and logging messages cannot be used to tell how complete the unittesting is. These messages just confirm that the unittests ran really.
-
-Unittests may be defined in their own modules or other modules. Unittests should be wrapped in
-    debug(mdeUnitTest)
-statements (these may also be used to wrap imports only needed by the unittest). Any modules containing unittests should be imported by test.mdeTest.
-
-
-
-Logging should be handled by tango's Logger class. A logger with the name mde.package.module or mde.package.module.X where X is a symbol within the module should be used for each module. Thrown errors should, where documented, be documented with a log message; an exception message may be used to produce the final log message but must be output via a log message.
-
-In general the levels should be used as follows:
-	Trace	Where required or thought highly useful for debugging, and only compiled in debugging mode.
-	Info	Sparingly, for informational purposes (e.g. when parsing a file). Should not generally be used repetitively (within loops, etc.). Not for reporting unexpected behaviour.
-	Warn	For small errors which can be overlooked, even if they MAY cause bigger problems later. I.e. something unexpected, but not necessarily a major problem, happens.
-	Error	For errors which directly:
-		• cut-short a (reasonably large) operation (e.g. reading a file).
-		• cause a significant change in program operation, but do not directly cause the program to terminate.
-	Fatal	For errors directly (i.e. definately and almost immediately) ending the program.
-
-For all levels bar trace, messages should if possible be understandable to end users, while (for warn and above) including enough information to fix the problem, including code symbols if necessary.
-Thus:
-	Trace output should only be available when compiled in debug mode.
-	When run by an end-user (with info-level logging enabled),
-	• info messages normally occur and should be understandable to end users;
-	• warn messages may occur, and may or may not indicate problems;
-	• error messages indicate that something big is wrong, and if the program still runs it is unlikely to be usable as intended;
-	• fatal messages indicate a problem preventing the program from running.
-
-
-Thrown errors should use an exception class specific to at least the package involved to enable specific catching of errors. Exception classes should be defined within a module exception.d in the package directory. Exception classes should generally follow the conventions within mde/exception.d to aid in providing reasonable error messages.
-
-
-Log/exception messages should be in English. They should include a brief description, but do not need a long description since the message can be looked up in the code. Currently there is no plan for translating log messages, however if someone wants to implement I18nTranslation to provide the messages they can.
-
-
-
-User output (internationalization support): i18n.I18nTranslation is designed to fetch a full string and optionally an associated descripion given an identifier. The identifier may also be a code symbol, and should be brief, in English, and give at least some idea of its meaning, since if no translation is available the identifier will be output. For example:  "example message" or "exampleMessage", not "An example of a message". For any new entry created, at least one full entry should be added to the i18n database for some form of English so that:
-    (a) there is something available to translate to other locales
-    (b) English developers can understand what it means
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/Init.d	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,309 @@
+/**************************************************************************************************
+ * Initialisation setup and exit cleanup module.
+ *
+ * This module controls most of the initialisation and deinitialisation of the program.
+ *************************************************************************************************/
+module mde.Init;
+
+import mde.exception;
+
+import mde.options;
+import mde.events;
+import global = mde.global;
+import mde.input.input;
+import mde.input.joystick;
+
+// tango imports
+import tango.core.Thread;
+import tango.core.Exception;
+import tango.util.log.Log : Log, Logger;
+import tango.util.log.ConsoleAppender : ConsoleAppender;
+import tango.stdc.stringz : fromStringz;
+
+import derelict.sdl.sdl;
+import derelict.util.exception;
+
+/**
+ * Static CTOR
+ *
+ * This should handle a minimal amount of functionality where useful. For instance, configuring the
+ * logger here and not in Init allows unittests to use the logger.
+ */
+static this()
+{
+    version (mdeTest) {}   // test.mdeTest sets up its own root logger
+    else {
+        // For now, just log to the console:
+        Logger root = Log.getRootLogger();
+        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);
+    }
+}
+static ~this()
+{
+}
+
+/**
+ * Init class
+ *
+ * A scope class created at beginning of the program and destroyed at the end; thus the CTOR
+ * handles program initialisation and the DTOR handles program cleanup.
+ */
+scope class Init
+{
+    private static Logger logger;
+    static this() {
+        logger = Log.getLogger ("mde.init.Init");
+    }
+    
+    /** CTOR − initialisation
+    *
+    * Runs general initialisation code, in a threaded manner where this isn't difficult.
+    *
+    * If any init fails, it must run necessary cleanup first since the DTOR cannot be run. */
+    /* In a single-threaded function this could be done with:
+    * scope(failure) cleanup;
+    * This won't work with a threaded init function since any threads completing succesfully will
+    * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions
+    * 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()
+    {
+        //BEGIN Methods
+        /* Initialisation functions.
+        *
+        * These should each handle a separate area of initialisation so that these functions can
+        * be run simultaneously in separate threads. */
+        
+        void setFailure () {		// For synchronization, although shouldn't be necessary
+            synchronized initFailure = true;
+        }
+        void delegate() [] initFuncs = [
+        delegate void() {
+            Logger logger = Log.getLogger ("mde.init.Init.SDL");
+            
+            // Inits SDL and related stuff (joystick).
+            try {
+                DerelictSDL.load();
+            } catch (DerelictException de) {
+                logger.fatal ("Loading dynamic library failed:");
+                logger.fatal (de.msg);
+                
+                setFailure ();		// abort
+                return;
+            }
+            logger.trace ("Derelict: loaded SDL");
+            
+            if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
+                logger.fatal ("SDL initialisation failed:");
+                char* msg = SDL_GetError ();
+                logger.fatal (msg ? fromStringz(msg) : "no reason available");
+                
+                setFailure ();		// abort
+                return;
+            }
+            
+            addCleanupFct (&cleanupSDL);
+            logger.trace ("SDL initialised");
+            
+            /+ Load of info-printing stuff
+            // Print a load of info:
+            logger.info ("Available video modes:");
+            char[128] tmp;
+            SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
+            if (modes is null) logger.info ("None!");
+            else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
+            else {
+                for (uint i = 0; modes[i] !is null; ++i) {
+                    logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h));
+                }
+            }
+            
+            SDL_VideoInfo* vi = SDL_GetVideoInfo ();
+            if (vi !is null) {
+                logger.info ("Video info:");
+                logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
+                logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem));
+                
+                if (vi.vfmt !is null) {
+                    logger.info ("Best video mode:");
+                    logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel));
+                }
+            }
+            +/
+            
+            // FIXME: make this non-fatal and provide a way to re-set video mode
+            if (SDL_SetVideoMode (800, 600, 0, 0) is null) {   // Can't open in windows!!
+                logger.fatal ("Unable to set video mode:");
+                char* msg = SDL_GetError ();
+                logger.fatal (msg ? fromStringz(msg) : "no reason available");
+                
+                setFailure ();
+                return;
+            }
+            
+            openJoysticks ();		// after SDL init
+            addCleanupFct (&closeJoysticks);
+        },
+        delegate void() {
+            /* Some miscellaneous short calls.
+            *
+            * Was executed in the main loop, but for the sake of simplicity use another thread.
+            */
+            try {
+                global.input = new Input();
+                global.input.loadConfig ();		// (may also create instance)
+                addEventsSchedule ();
+            } catch (Exception e) {
+                setFailure ();                      // must clean up properly
+            }
+        }
+        ];
+        //END Methods
+        
+        logger.info ("Init: starting");
+        //BEGIN Pre-init (loading options)
+        // Load options FIRST. Should be fast, most work is probably disk access,
+        // and 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; message: " ~ e.msg);
+        }
+        addCleanupFct (&Options.save);  // not strictly cleanup, but needs to be called somewhere
+        
+        Log.getRootLogger.setLevel (cast(Log.Level) options.logLevel, true);  // set the stored log level
+        char[128] tmp;
+        logger.info (logger.format (tmp, "Set logging level: {}", options.logLevel));
+        //END Pre-init
+        
+        
+        //BEGIN Init (actual function calling)
+        /* Call init functions.
+        *
+        * Current method is to try using threads, and on failure assume no threads were actually
+        * created and run functions in a non-threaded manner.
+        */
+        ThreadGroup tg;                     // Not necessarily used
+        
+        if (options.useThreads) {           // Threaded init (thread creation)
+            try {                           // creating/starting threads could fail
+                tg = new ThreadGroup;
+                foreach (func; initFuncs) tg.create(func);  // Start all threads
+            } catch (ThreadException e) {   // Problem with threading; try without threads
+                logger.warn ("Caught exception while trying to create threads:");
+                logger.warn (e.msg);
+                logger.warn ("Will continue in a non-threaded manner.");
+            
+                options.useThreads = false;
+                options.changed = true;
+            }
+        }
+        
+        if (!options.useThreads) {          // Non-threaded init
+            
+            foreach (func; initFuncs) {     // for each delegate
+                // since we can stop at any point, we might as well if there's a problem:
+                if (initFailure) break;
+            
+                func();
+            }
+        }
+        
+        if (options.useThreads) {           // Threaded init (thread joining)
+            /* Wait for all threads to complete.
+            *
+            * If something went wrong, we still need to do this before cleaning up.
+            *
+            * For non-threaded usage, tg contains no threads so loop won't run.
+            */
+            foreach (t; tg) {
+                try {
+                    t.join (true);
+                    /+ We might as well catch and report any exception, rather than just ThreadExceptions:
+                    } catch (ThreadException e) {	// Any threading exception
+                    +/
+                } catch (Exception e) {		// Any other exception, i.e. caught from thread.
+                    // Relying on catching exceptions thrown by other threads is a bad idea.
+                    // Hence all threads should catch their own exceptions and return safely.
+                
+                    logger.fatal ("Exception caught during init:");
+                    logger.fatal (e.msg);
+                    logger.fatal ("Please report this; it should NEVER happen.");
+                
+                    setFailure ();		// abort (but join other threads first)
+                }
+            }
+        }
+        
+        if (initFailure) {
+            // All cleanup-on-failure must be done here.
+            runCleanupFcts();
+            
+            logger.fatal ("Init: failed");
+            // Throw an exception to signal failure and prevent DTOR from also running.
+            throw new InitException ("Initialisation failed due to above exceptions.");
+        } else logger.info ("Init: done");
+        //END Init
+    }
+    
+    /** DTOR - runs cleanup.
+    *
+    * Currently unthreaded; probably might as well stay that way. */
+    ~this()
+    {
+        if (!initFailure) {
+            logger.trace ("Init: cleaning up");
+            runCleanupFcts();	// if threading, note not all functions can be called simultaeneously
+            logger.trace ("Init: done");
+        }
+    }
+    
+    /* Cleanup Functions.
+    *
+    * These may exist simply as something to add to the cleanup list... */
+    static void cleanupSDL () {
+        SDL_Quit();
+    }
+    
+    private static {
+        void function ()[] cleanup;		// all functions to be run for cleanup
+        // Adding cleanup functions must be synchronized; use:
+        void addCleanupFct (void function () fct) {
+            synchronized cleanup ~= fct;
+        }
+        // Clean-up fcts are run in reverse order to how they're added:
+        void runCleanupFcts () {
+            foreach_reverse (fct; cleanup) fct();
+        }
+    }
+    
+    /* This is set true by this() if something goes wrong and we need to abort.
+    * If it's true, no cleanup should be done by the DTOR (since the DTOR still runs after an
+    * exception has been thrown). */
+    private bool initFailure = false;
+    
+    debug (mdeUnitTest) unittest {
+        /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts()
+        * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause
+        * any harm either. */
+        static bool initialised = false;
+        static void cleanup () {	initialised = false;	}
+        
+        static void init () {
+            initialised = true;
+            Init.addCleanupFct (&cleanup);
+        }
+        
+        init();
+        assert (initialised);
+        Init.runCleanupFcts();
+        assert (!initialised);
+
+        logger.info ("Unittest complete.");
+    }
+}
--- a/mde/exception.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/exception.d	Fri Mar 14 11:39:45 2008 +0000
@@ -25,11 +25,11 @@
     }
 }
 
-/// Thrown when init fails.
-class initException : mdeException {
+/// Thrown when Init fails.
+class InitException : mdeException {
     // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can
     static const char[] symbol;
-    static this () {	symbol = super.symbol ~ ".init";	}
+    static this () {	symbol = super.symbol ~ ".Init";	}
     char[] getSymbol () {
         return symbol;
     }
@@ -72,19 +72,18 @@
 
     private Logger logger;
     static this() {
-        logger = Log.getLogger ("mde.events");
-        logger.info ("Got logger!");
+        logger = Log.getLogger ("mde.exception");
     }
     
     unittest {
         // Check message prepending works correctly.
-        mdeException mE = new initException("");
-        assert (mE.getSymbol() == "mde.init", mE.getSymbol());
+        mdeException mE = new InitException("");
+        assert (mE.getSymbol() == "mde.Init", mE.getSymbol());
         try {
-            throw new initException ("ABC");
+            throw new InitException ("ABC");
             assert (false);
         } catch (Exception e) {
-            assert (e.msg == "mde.init: ABC", e.msg);
+            assert (e.msg == "mde.Init: ABC", e.msg);
         }
     
         logger.info ("Unittest complete.");
--- a/mde/i18n.d	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/** i18n − internationalization module
-*
-* The idea behind this module is a class which, when asked to load symbols for a particular module/
-* package/part of the program, will load internationalized names and optional descriptions for each
-* symbol needing translation. No support for non-left-to-right scripts is currently planned, and
-* this module is currently limited to translations, although support for different date formats,
-* etc. could potentially be added later.
-*
-* Code symbols are used as identifiers for each name and its optional description. The code symbol
-* will be used as a fallback in the case no entry exists, however it is not intended to provide the
-* string for the default language (a "translation" should be used for the default language).
-*
-* Each locale may specify dependant locales/sections which will be loaded and merged in to the
-* database, to cover for symbols with a missing entry. Sections are loaded in the order specified,
-* with each section's sub-dependancies loaded before continuing with the next top-level dependancy.
-* A list of loaded sections is used to prevent any locale/section from being loaded twice, and thus
-* allow circular dependancies.
-*
-* In order that translated strings get updated correctly to reflect changes, each entry carries a
-* version number. If, for any entry, a translation exists with a higher version number, that entry
-* is out of date. A tool should be made for checking for out of date entries to take advantage of
-* this feature. Of course, out of date entries are still valid for use.
-*/
-module mde.i18n;
-
-import mde.options;
-import mde.exception;
-
-import mde.mergetag.DataSet;
-import mde.mergetag.Reader;
-import mde.mergetag.exception;
-
-import tango.util.log.Log : Log, Logger;
-import tango.scrapple.text.convert.parseTo;
-
-/** The translation class
-*
-* See module description for details.
-*
-* Encoding used is UTF-8.
-*/
-class I18nTranslation : IDataSection
-{
-    final char[] name;      /// The module/package/... which the instance is for
-    final char[] L10n;      /// The localization loaded (e.g. en-GB)
-    
-    /** Get the translation for the given identifier, and optionally the description.
-    * If no entry exists, the identifier will be returned. */
-    char[] getEntry (char[] id, out char[] description) {
-        Entry* p = id in entries;
-        if (p) {    // FIXME: check: a SEGFAULT?
-            description = p.desc;
-            return p.str;
-        } else
-            return id;
-    }
-        /** ditto */
-    char[] getEntry (char[] id) {
-        Entry* p = id in entries;
-        if (p !is null) {    // FIXME: check: a SEGFAULT?
-            return p.str;
-        } else
-            return id;
-    }
-    
-    /** Load the translation for the requested module/package/...
-    *
-    * Options (mde.options) must have been loaded before this is run.
-    *
-    * Params:
-    *   name The module/package/... to load strings for.
-    *
-    * Throws:
-    * If no localization exists for this name and the current locale (or any locale to be chain-
-    * loaded), or an error occurs while loading the database, a L10nLoadException will be thrown.
-    */
-    static I18nTranslation load (char[] name)
-    {
-        bool[ID] loadedSecs;        // set of all locales/sections loaded; used to prevent circular loading
-        ID[] secsToLoad             // locales/sections to load (dependancies may be added)
-        = [cast(ID) options.L10n];  // start by loading the current locale
-        
-        I18nTranslation transl = new I18nTranslation (name, options.L10n);
-        /* Find the file for name and load it.
-        */
-        FilePath filePath = new FilePath ("conf/L10n/"~name~".mtt");
-        // If it doesn't exist, stop.
-        // Don't bother checking it's not a folder, because it could still be a block or something.
-        if (!filePath.exists) {
-            throw new L10nLoadException ("No database file conf/L10n/"~name~".mtt exists!");
-        }
-        
-        IReader reader;
-        try {
-            reader = new MTTReader(filePath);
-            /* Note: we don't want to load every translation section depended on to its own class
-            * instance, since we want to merge them. So make every mergetag section use the same
-            * instance. */
-            reader.dataSecCreator = delegate IDataSection(ID) {
-                return transl;
-            };
-        
-            while (secsToLoad.length) {                 // while we have sections left to load
-                ID sec = secsToLoad[0];                 // take current section
-                secsToLoad = secsToLoad[1..$];          // and remove from list
-                                
-                if (sec in loadedSecs) continue;        // skip if it's already been loaded
-                loadedSecs[sec] = true;
-                
-                reader.read ([sec]);                    // Do the actual loading
-                
-                // Add dependancies to front of list:
-                secsToLoad = transl.depends ~ secsToLoad;
-            }
-            // When loop finishes, we're done loading.
-        } catch (MTException e) {
-            logger.error ("Mergetag exception occurred:");
-            logger.error (e.msg);
-            throw new L10nLoadException ("Loading aborted: mergetag exception");
-        }
-        
-        delete transl.depends;      // Free memory
-        transl.depends = [];
-        
-        return transl;
-    }
-    
-    static this() {
-        logger = Log.getLogger ("mde.input.i18n.I18nTranslation");
-    }
-    
-    /* Mergetag functionality.
-    *
-    * Merge tags in to entries, prefering existing values.
-    * Replace depends.
-    *
-    * User-defined type "entry":
-    *   first two element is string and must exist
-    *   second element is description and is optional
-    *   third element is version and is optional
-    *   no limit on number of elements to allow future extensions
-    */
-    void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "entry") {
-            char[][] fields = split (stripBrackets (dt));
-            
-            if (fields.length < 1) {
-                // This tag is invalid, but since we don't want execution to halt just log a warning:
-                logger.warn ("For name "~name~", L10n "~L10n~": tag with ID "~cast(char[])id~" has no data");
-                return;
-            }
-            // If the tag already exists, don't replace it
-            if (cast(char[]) id in entries) return;
-            
-            Entry entry;
-            entry.str = parseTo!(char[]) (fields[0]);
-            
-            if (fields.length >= 2)
-                entry.desc = parseTo!(char[]) (fields[1]);
-            
-            entries[cast(char[]) id] = entry;
-        } else if (tp == "char[][]") {
-            if (id == cast(ID)"depends") depends = cast(ID[]) parseTo!(char[][]) (dt);
-        }
-    }
-    
-    // This class is read-only and has no need of being saved.
-    void writeAll (ItemDelg) {}
-    
-private:
-    /* Sets name and L10n.
-    *
-    * Also ensures only load() can create instances. */
-    this (char[] n, char[] l) {
-        name = n;
-        L10n = l;
-    }
-    
-    //BEGIN Data
-    static Logger logger;
-    
-    /* This struct is used to store each translation entry.
-    *
-    * Note that although each entry also has a version field, this is not loaded for general use.
-    */
-    struct Entry {
-        char[] str;         // The translated string
-        char[] desc;        // An optional description
-    }
-    
-    Entry[char[]] entries;  // all entries
-    
-    ID[] depends;           // dependancy sections (only used while loading)
-    //END Data
-    
-    debug (mdeUnitTest) unittest {
-        /* Relies on file: conf/L10n/i18nUnitTest.mtt
-        * Contents:
-        *********
-        {MT01}
-        {test-1}
-        <entry|Str1=["Test 1"]>
-        <char[][]|depends=["test-2"]>
-        {test-2}
-        <entry|Str1=["Test 2"]>
-        <entry|Str2=["Test 3","Description",bogus,"entries",56]>
-        *********/
-        
-        // Hack a specific locale...
-        // Also to allow unittest to run without init.
-        char[] currentL10n = options.L10n;
-        options.L10n = "test-1";
-        
-        I18nTranslation transl = load ("i18nUnitTest");
-        
-        // Simple get-string, check dependancy's entry doesn't override
-        assert (transl.getEntry ("Str1") == "Test 1");
-        
-        // Entry included from dependancy with description
-        char[] desc;
-        assert (transl.getEntry ("Str2", desc) == "Test 3");
-        assert (desc == "Description");
-        
-        // No entry: fallback to identifier string
-        assert (transl.getEntry ("Str3") == "Str3");
-        
-        // No checks for version info since it's not functionality of this module.
-        // Only check extra entries are allowed but ignored.
-        
-        logger.info ("Unittest complete.");
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/i18n/I18nTranslation.d	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,225 @@
+/** I18nTranslation − internationalization module for translating strings
+*
+* The idea behind this module is a class which, when asked to load symbols for a particular module/
+* package/part of the program, will load internationalized names and optional descriptions for each
+* symbol needing translation. No support for non-left-to-right scripts is currently planned, and
+* this module is currently limited to translations, although support for different date formats,
+* etc. could potentially be added later.
+*
+* Code symbols are used as identifiers for each name and its optional description. The code symbol
+* will be used as a fallback in the case no entry exists, however it is not intended to provide the
+* string for the default language (a "translation" should be used for the default language).
+*
+* Each locale may specify dependant locales/sections which will be loaded and merged in to the
+* database, to cover for symbols with a missing entry. Sections are loaded in the order specified,
+* with each section's sub-dependancies loaded before continuing with the next top-level dependancy.
+* A list of loaded sections is used to prevent any locale/section from being loaded twice, and thus
+* allow circular dependancies.
+*
+* In order that translated strings get updated correctly to reflect changes, each entry carries a
+* version number. If, for any entry, a translation exists with a higher version number, that entry
+* is out of date. A tool should be made for checking for out of date entries to take advantage of
+* this feature. Of course, out of date entries are still valid for use.
+*/
+module mde.i18n.I18nTranslation;
+
+import mde.options;
+import mde.exception;
+
+import mde.mergetag.DataSet;
+import mde.mergetag.Reader;
+import mde.mergetag.exception;
+import mde.resource.paths;
+
+import tango.util.log.Log : Log, Logger;
+import tango.scrapple.text.convert.parseTo;
+
+/** The translation class
+*
+* See module description for details.
+*
+* Encoding used is UTF-8.
+*/
+class I18nTranslation : IDataSection
+{
+    final char[] name;      /// The module/package/... which the instance is for
+    final char[] L10n;      /// The localization loaded (e.g. en-GB)
+    
+    /** Get the translation for the given identifier, and optionally the description.
+    * If no entry exists, the identifier will be returned. */
+    char[] getEntry (char[] id, out char[] description) {
+        Entry* p = id in entries;
+        if (p) {    // FIXME: check: a SEGFAULT?
+            description = p.desc;
+            return p.str;
+        } else
+            return id;
+    }
+        /** ditto */
+    char[] getEntry (char[] id) {
+        Entry* p = id in entries;
+        if (p !is null) {    // FIXME: check: a SEGFAULT?
+            return p.str;
+        } else
+            return id;
+    }
+    
+    /** Load the translation for the requested module/package/...
+    *
+    * Options (mde.options) must have been loaded before this is run.
+    *
+    * Params:
+    *   name The module/package/... to load strings for.
+    *
+    * Throws:
+    * If no localization exists for this name and the current locale (or any locale to be chain-
+    * loaded), or an error occurs while loading the database, a L10nLoadException will be thrown.
+    */
+    static I18nTranslation load (char[] name)
+    {
+        bool[ID] loadedSecs;        // set of all locales/sections loaded; used to prevent circular loading
+        ID[] secsToLoad             // locales/sections to load (dependancies may be added)
+        = [cast(ID) options.L10n];  // start by loading the current locale
+        
+        I18nTranslation transl = new I18nTranslation (name, options.L10n);
+        
+        IReader reader;
+        try {
+            reader = dataDir.makeMTReader ("L10n/"~name, PRIORITY.HIGH_LOW);
+            /* Note: we don't want to load every translation section depended on to its own class
+            * instance, since we want to merge them. So make every mergetag section use the same
+            * instance. */
+            reader.dataSecCreator = delegate IDataSection(ID) {
+                return transl;
+            };
+        
+            while (secsToLoad.length) {                 // while we have sections left to load
+                ID sec = secsToLoad[0];                 // take current section
+                secsToLoad = secsToLoad[1..$];          // and remove from list
+                                
+                if (sec in loadedSecs) continue;        // skip if it's already been loaded
+                loadedSecs[sec] = true;
+                
+                reader.read ([sec]);                    // Do the actual loading
+                
+                // Add dependancies to front of list:
+                secsToLoad = transl.depends ~ secsToLoad;
+            }
+            // When loop finishes, we're done loading.
+        } catch (MTException e) {
+            // If an error occurs, log a message but continue (strings will just be untranslated)
+            logger.error ("Mergetag exception occurred:");
+            logger.error (e.msg);
+        }
+        
+        delete transl.depends;      // Free memory
+        transl.depends = [];
+        
+        return transl;
+    }
+    
+    static this() {
+        logger = Log.getLogger ("mde.input.i18n.I18nTranslation");
+    }
+    
+    /* Mergetag functionality.
+    *
+    * Merge tags in to entries, prefering existing values.
+    * Replace depends.
+    *
+    * User-defined type "entry":
+    *   first two element is string and must exist
+    *   second element is description and is optional
+    *   third element is version and is optional
+    *   no limit on number of elements to allow future extensions
+    */
+    void addTag (char[] tp, ID id, char[] dt) {
+        if (tp == "entry") {
+            char[][] fields = split (stripBrackets (dt));
+            
+            if (fields.length < 1) {
+                // This tag is invalid, but since we don't want execution to halt just log a warning:
+                logger.warn ("For name "~name~", L10n "~L10n~": tag with ID "~cast(char[])id~" has no data");
+                return;
+            }
+            // If the tag already exists, don't replace it
+            if (cast(char[]) id in entries) return;
+            
+            Entry entry;
+            entry.str = parseTo!(char[]) (fields[0]);
+            
+            if (fields.length >= 2)
+                entry.desc = parseTo!(char[]) (fields[1]);
+            
+            entries[cast(char[]) id] = entry;
+        } else if (tp == "char[][]") {
+            if (id == cast(ID)"depends") depends = cast(ID[]) parseTo!(char[][]) (dt);
+        }
+    }
+    
+    // This class is read-only and has no need of being saved.
+    void writeAll (ItemDelg) {}
+    
+private:
+    /* Sets name and L10n.
+    *
+    * Also ensures only load() can create instances. */
+    this (char[] n, char[] l) {
+        name = n;
+        L10n = l;
+    }
+    
+    //BEGIN Data
+    static Logger logger;
+    
+    /* This struct is used to store each translation entry.
+    *
+    * Note that although each entry also has a version field, this is not loaded for general use.
+    */
+    struct Entry {
+        char[] str;         // The translated string
+        char[] desc;        // An optional description
+    }
+    
+    Entry[char[]] entries;  // all entries
+    
+    ID[] depends;           // dependancy sections (only used while loading)
+    //END Data
+    
+    debug (mdeUnitTest) unittest {
+        /* Relies on file: conf/L10n/i18nUnitTest.mtt
+        * Contents:
+        *********
+        {MT01}
+        {test-1}
+        <entry|Str1=["Test 1"]>
+        <char[][]|depends=["test-2"]>
+        {test-2}
+        <entry|Str1=["Test 2"]>
+        <entry|Str2=["Test 3","Description",bogus,"entries",56]>
+        *********/
+        
+        // Hack a specific locale...
+        // Also to allow unittest to run without init.
+        char[] currentL10n = options.L10n;
+        options.L10n = "test-1";
+        
+        I18nTranslation transl = load ("i18nUnitTest");
+        
+        // Simple get-string, check dependancy's entry doesn't override
+        assert (transl.getEntry ("Str1") == "Test 1");
+        
+        // Entry included from dependancy with description
+        char[] desc;
+        assert (transl.getEntry ("Str2", desc) == "Test 3");
+        assert (desc == "Description");
+        
+        // No entry: fallback to identifier string
+        assert (transl.getEntry ("Str3") == "Str3");
+        
+        // No checks for version info since it's not functionality of this module.
+        // Only check extra entries are allowed but ignored.
+        
+        logger.info ("Unittest complete.");
+    }
+}
--- a/mde/init.d	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +0,0 @@
-/**************************************************************************************************
- * Initialisation setup and exit cleanup module.
- *
- * This module controls most of the initialisation and deinitialisation of the program.
- *************************************************************************************************/
-module mde.init;
-
-import mde.exception;
-
-import mde.options;
-import mde.events;
-import global = mde.global;
-import mde.input.input;
-import mde.input.joystick;
-
-// tango imports
-import tango.core.Thread;
-import tango.core.Exception;
-import tango.util.log.Log : Log, Logger;
-import tango.util.log.ConsoleAppender : ConsoleAppender;
-import tango.stdc.stringz : fromStringz;
-
-import derelict.sdl.sdl;
-import derelict.util.exception;
-
-/**
- * Static CTOR
- *
- * This should handle a minimal amount of functionality where useful. For instance, configuring the
- * logger here and not in Init allows unittests to use the logger.
- */
-static this()
-{
-    version (mdeTest) {}   // test.mdeTest sets up its own root logger
-    else {
-        // For now, just log to the console:
-        Logger root = Log.getRootLogger();
-        debug root.setLevel(root.Level.Trace);
-        else root.setLevel(root.Level.Info);
-        root.addAppender(new ConsoleAppender);
-    }
-}
-static ~this()
-{
-}
-
-/**
- * Init class
- *
- * A scope class created at beginning of the program and destroyed at the end; thus the CTOR
- * handles program initialisation and the DTOR handles program cleanup.
- */
-scope class Init
-{
-    private static Logger logger;
-    static this() {
-        logger = Log.getLogger ("mde.init.Init");
-    }
-    
-    /** CTOR − initialisation
-    *
-    * Runs general initialisation code, in a threaded manner where this isn't difficult.
-    *
-    * If any init fails, it must run necessary cleanup first since the DTOR cannot be run. */
-    /* In a single-threaded function this could be done with:
-    * scope(failure) cleanup;
-    * This won't work with a threaded init function since any threads completing succesfully will
-    * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions
-    * 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()
-    {
-        /* Initialisation functions.
-        *
-        * These should each handle a separate area of initialisation so that these functions can
-        * be run simultaneously in separate threads. */
-        
-        void setFailure () {		// For synchronization, although shouldn't be necessary
-            synchronized initFailure = true;
-        }
-        void delegate() [] initFuncs = [
-        delegate void() {
-            Logger logger = Log.getLogger ("mde.init.Init.SDL");
-            
-            // Inits SDL and related stuff (joystick).
-            try {
-                DerelictSDL.load();
-            } catch (DerelictException de) {
-                logger.fatal ("Loading dynamic library failed:");
-                logger.fatal (de.msg);
-                
-                setFailure ();		// abort
-                return;
-            }
-            logger.trace ("Derelict: loaded SDL");
-            
-            if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
-                logger.fatal ("SDL initialisation failed:");
-                char* msg = SDL_GetError ();
-                logger.fatal (msg ? fromStringz(msg) : "no reason available");
-                
-                setFailure ();		// abort
-                return;
-            }
-            
-            addCleanupFct (&cleanupSDL);
-            logger.trace ("SDL initialised");
-            
-            // Print a load of info:
-            logger.info ("Available video modes:");
-            char[128] tmp;
-            SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
-            if (modes is null) logger.info ("None!");
-            else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
-            else {
-                for (uint i = 0; modes[i] !is null; ++i) {
-                    logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h));
-                }
-            }
-            
-            SDL_VideoInfo* vi = SDL_GetVideoInfo ();
-            if (vi !is null) {
-                logger.info ("Video info:");
-                logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
-                logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem));
-                
-                if (vi.vfmt !is null) {
-                    logger.info ("Best video mode:");
-                    logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel));
-                }
-            }
-            
-            // FIXME: make this non-fatal and provide a way to re-set video mode
-            if (SDL_SetVideoMode (800, 600, 0, 0) is null) {   // Can't open in windows!!
-                logger.fatal ("Unable to set video mode:");
-                char* msg = SDL_GetError ();
-                logger.fatal (msg ? fromStringz(msg) : "no reason available");
-                
-                setFailure ();
-                return;
-            }
-            
-            openJoysticks ();		// after SDL init
-            addCleanupFct (&closeJoysticks);
-        },
-        delegate void() {
-            Logger logger = Log.getLogger ("mde.init.Init.Options");
-            
-            try {
-                Options.load();
-            } catch (optionsLoadException e) {
-                logger.fatal ("Loading options failed; message:");
-                logger.fatal (e.msg);
-                setFailure();
-                return;
-            }
-            addCleanupFct (&Options.save);  // not strictly cleanup, but needs to be called somewhere
-        }
-        ];
-        
-        /* Call init functions.
-        *
-        * Current method is to try using threads, and on failure assume no threads were actually
-        * created and run functions in a non-threaded mannor.
-        *
-        * Note: can't use options class to control running of threads, since options haven't been
-        * loaded yet.
-        */
-        ThreadGroup tg = new ThreadGroup;	// can't fail since it does nothing (tango 0.99.4)
-        
-        try {	// creating/starting threads can fail
-            foreach (func; initFuncs) tg.create(func);  // Start all threads
-        } catch (ThreadException e) {		// to do with threading; try without threads
-            logger.warn ("Caught exception while trying to create threads:");
-            logger.warn (e.msg);
-            logger.warn ("Will continue in a non-threaded manner.");
-            
-            foreach (func; initFuncs) func();           // Run functions without threads
-        }
-        
-        /* Do some initialisation here.
-        *
-        * We might as well make use of this thread rather than create another!
-        */
-        global.input = new Input();
-        global.input.loadConfig ();		// (may also create instance)
-        addEventsSchedule ();
-        
-        /* Wait for all threads to complete.
-        *
-        * If something went wrong, we still need to do this before cleaning up.
-        *
-        * For non-threaded usage, tg contains no threads so loop won't run.
-        */
-        foreach (t; tg) {
-            try {
-                t.join (true);
-            /+ We might as well catch and report any exception, rather than just ThreadExceptions:
-            } catch (ThreadException e) {	// Any threading exception
-            +/
-            } catch (Exception e) {		// Any other exception, i.e. caught from thread.
-                // Relying on catching exceptions thrown by other threads is a bad idea.
-                // Hence all threads should catch their own exceptions and return safely.
-                
-                logger.fatal ("Exception caught during init:");
-                logger.fatal (e.msg);
-                logger.fatal ("Please report this; it should NEVER happen.");
-                
-                setFailure ();		// abort (but join other threads first)
-            }
-        }
-        
-        if (initFailure) {
-            // All cleanup-on-failure must be done here.
-            runCleanupFcts();
-            
-            logger.fatal ("Init failed");
-            // Throw an exception to signal failure and prevent DTOR from also running.
-            throw new initException ("Initialisation failed due to above exceptions.");
-        }
-    }
-    
-    /** DTOR - runs cleanup.
-    *
-    * Currently unthreaded; probably might as well stay that way. */
-    ~this()
-    {
-        if (!initFailure) {
-            logger.trace ("Cleaning up...");
-            runCleanupFcts();	// if threading, note not all functions can be called simultaeneously
-            logger.trace ("Done!");
-        }
-    }
-    
-    /* Cleanup Functions.
-    *
-    * These may exist simply as something to add to the cleanup list... */
-    static void cleanupSDL () {
-        SDL_Quit();
-    }
-    
-    private static {
-        void function ()[] cleanup;		// all functions to be run for cleanup
-        // Adding cleanup functions must be synchronized; use:
-        void addCleanupFct (void function () fct) {
-            synchronized cleanup ~= fct;
-        }
-        // Clean-up fcts are run in reverse order to how they're added:
-        void runCleanupFcts () {
-            foreach_reverse (fct; cleanup) fct();
-        }
-    }
-    
-    /* This is set true by this() if something goes wrong and we need to abort.
-    * If it's true, no cleanup should be done by the DTOR (since the DTOR still runs after an
-    * exception has been thrown). */
-    private bool initFailure = false;
-    
-    debug (mdeUnitTest) unittest {
-        /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts()
-        * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause
-        * any harm either. */
-        static bool initialised = false;
-        static void cleanup () {	initialised = false;	}
-        
-        static void init () {
-            initialised = true;
-            Init.addCleanupFct (&cleanup);
-        }
-        
-        init();
-        assert (initialised);
-        Init.runCleanupFcts();
-        assert (!initialised);
-
-        logger.info ("Unittest complete.");
-    }
-}
--- a/mde/input/config.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/input/config.d	Fri Mar 14 11:39:45 2008 +0000
@@ -6,6 +6,7 @@
 import mde.input.exception;
 
 import MT = mde.mergetag.Reader;
+import mde.resource.paths;
 import tango.scrapple.text.convert.parseTo : parseTo;
 
 import tango.util.log.Log : Log, Logger;
@@ -120,8 +121,8 @@
         MT.IReader file;
         
         try {
-            file = new MT.MTTReader(filename, null, true);	// open and read header
-            // TODO: also load user-config file
+            // open and read header:
+            file = confDir.makeMTReader (filename, PRIORITY.LOW_HIGH, null, true);
             
             file.dataSecCreator =
             delegate MT.IDataSection (MT.ID) {	return new Config;	};
--- a/mde/input/input.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/input/input.d	Fri Mar 14 11:39:45 2008 +0000
@@ -280,7 +280,7 @@
     * Returns: true if the requested config id wasn't found.
     */
     bool loadConfig (char[] profile = "Default") {
-        Config.load("conf/input.mtt");	// FIXME: filename
+        Config.load("input");	// FIXME: filename
         Config* c_p = profile in Config.configs;
         if (c_p) {
             config = *c_p;
--- a/mde/mde.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/mde.d	Fri Mar 14 11:39:45 2008 +0000
@@ -5,11 +5,11 @@
 module mde.mde;
 
 // Package imports
-import mde.init;
+import mde.Init;
 import global = mde.global;
 import mde.events;
 import mde.scheduler;
-import mde.i18n;     // greeting message
+import mde.i18n.I18nTranslation;        // greeting message
 import mde.exception;
 
 import mde.input.input;
@@ -30,7 +30,7 @@
     scope Init init;
     try {
         init = new Init();	// initialisation
-    } catch (initException e) {
+    } catch (InitException e) {
         logger.fatal ("Initialisation failed; error was:");
         logger.fatal (e.msg);
         return 1;
--- a/mde/mergetag/Reader.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/mergetag/Reader.d	Fri Mar 14 11:39:45 2008 +0000
@@ -31,7 +31,8 @@
 /** Make an IReader class.
 *
 * If no extension is given, search for a file using each extension (.mtt and .mtb) appended to
-* path, and set path to the most recent file name.
+* path, and set path to the most recent file name. If neither suffix added resolves a valid file,
+* throw an MTFileIOException with a suitible error message.
 *
 * When an extension is available (either after the above or when supplied), use the appropriate
 * reader (MTT or MTB).
@@ -63,8 +64,7 @@
         } else {
             if (bPathExists) path = bPath;
             else {
-                logger.error ("No file exists: "~path.toString~"[.mtt|.mtb]");
-                throw new MTFileIOException;
+                throw new MTFileIOException ("No file exists: "~path.toString~"[.mtt|.mtb]");
             }
         }
     }
@@ -75,7 +75,7 @@
 }
 
 /**
- *  Class for reading a file.
+ * Class for reading a mergetag text file.
  * 
  * Use as:
  * -----------------------
@@ -244,10 +244,10 @@
     * Won't work (will return an empty array) if all sections have already been read without
     * scanning for sections.
     */
-    public uint[] getSectionNames () {
+    public ID[] getSectionNames () {
         if (fatal) return [];
         if (!secTable.length) read([]);     // scan for sections
-        return cast(uint[]) secTable.keys;
+        return secTable.keys;
     }
     
     /** Reads (some) sections of the file into data. Note that sections will never be _read twice.
@@ -363,7 +363,7 @@
         Note: there's no length restriction for the content of the quote since it could be a single
         non-ascii UTF-8 char which would look like several chars.
         */
-        void fbufLocateDataTagChar (inout uint pos, bool quotable) {
+        void fbufLocateDataTagChar (ref uint pos, bool quotable) {
             for (; pos < fbuf.length; ++pos) {
                 if ((fbuf[pos] >= '<' && fbuf[pos] <= '>') || fbuf[pos] == '|') return;
                 else if (quotable) {
@@ -455,7 +455,7 @@
     
     /* Parses fbuf for a section marker. Already knows fbuf[pos] == '{'.
     */
-    private ID fbufReadSecMarker (inout uint pos) {
+    private ID fbufReadSecMarker (ref uint pos) {
         // at this point pos is whatever a parseSection run returned
         // since we haven't hit EOF, fbuf[pos] MUST be '{' so no need to check
         fbufIncrement(pos);
@@ -473,7 +473,7 @@
     }
     
     /* Increments pos and checks it hasn't hit fbuf.length . */
-    private void fbufIncrement(inout uint pos) {
+    private void fbufIncrement(ref uint pos) {
         ++pos;
         if (pos >= fbuf.length) throwMTErr("Unexpected EOF" ~ ErrInFile, new MTSyntaxException);
     }
@@ -486,6 +486,12 @@
 //END METHODS: PRIVATE
 }
 
+
+/**
+* Class for reading a mergetag text file.
+*
+* Currently only a dummy class: a MTNotImplementedException will be thrown if created.
+*/
 class MTBReader : IReader
 {
     public this (char[] path, DataSet ds = null, bool rdHeader = false) {
@@ -502,7 +508,7 @@
     
     void dataSecCreator (IDataSection delegate (ID)) {} /// Set the dataSecCreator
     
-    uint[] getSectionNames () {         /// Get identifiers for all sections
+    ID[] getSectionNames () {           /// Get identifiers for all sections
         return [];
     }
     void read () {}                     /// Commence reading
--- a/mde/mergetag/Writer.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/mergetag/Writer.d	Fri Mar 14 11:39:45 2008 +0000
@@ -244,7 +244,7 @@
 class DualWriter : IWriter {
     /** The individual writers.
     *
-    * Potentially could be used directly, but I'd suggest not. */
+    * Potentially could be used directly, but there should be no need. */
     MTTWriter mtt;
     //MTBWriter mtb;  /** ditto */
     
--- a/mde/mergetag/doc/file-format-binary.txt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-This is the file format for mergetag binary files. The unit size is a byte. Most numbers to do with the layout (i.e. not stored data) should be stored as a 32-bit uint.
-
-
-BOM  ---  a Byte Order Mark should be used to determin endianness (MT01 (or other version) in bytes, but converted to two ushorts to detect endianness?)
-
-
-File should then consist of sections:
-
-Header data including an address for the header section data if included.
-
-Sections list. Include a list of sections with identifiers and addresses, sorted by identifier and in a suitible format to easily be converted to a D hash-map. Addresses for each section should consist of both a start and an end address; the end address should be checked upon reading the section. In addition the start address must be checked against the end of file to avoid security vulnerabilities with reading other memory blocks.
--- a/mde/mergetag/doc/file-format-requirements.txt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-Requirements:
-
----	Config - named entries (map associated by strings; sections by strings)
-Merging: chosing one entry over another
-
----	Data - list of entries, each of custom compound type (e.g. list of 3-vector over reals)
-As sub blocks within a standard item (mergable: combine the blocks of multiple items).
-
-+++	Global type for handling all this:
-File consists of sections.
-Each section consists of items.
-Items are sorted by ID and not by type, i.e. if two items with the same ID but different types exist, merging rules are used to choose between them.
-Items have a custom type, which can be a compound of:
-+	Basic types:
-++		bool
-++		int (int+uint)
-++		real (or float or double? no.)
-++		string (char)
-++		binary (ubyte[])
-+	Strings (of char, wchar or dchar)
-+	Fixed-length arrays (single type)
-+	Variable-length arrays (single type)
-+	Fixed format tuples (multiple types which are prespecified)
-+	The top-most type may be a "data list", which is identical to a variable-length array accept that merging items with identical types will combine their lists instead of choosing one over the other.
-To access an item, it should be found by ID, its type should be checked, and then it may be accessed.
-Types are specific to items. As an optimisation, a binary format may have a list of types and index them.
-
-+++	Basic types:
-All D base types, including void, with support for writing strings.
--- a/mde/mergetag/doc/file-format-text.txt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-This is the file format for mergetag text files.
-Version: 0.1 unfinalised
-
-
-The encoding should be unicode UTF-8, UTF-16 or UTF-32, and for anything other than UTF-8 must include a BOM.
-
-
-Hierarchy:
-+	Sections	(special section: see header)
-++	Data Tags
-
-
-IDs:
-IDs are used for several purposes; they are UTF-8 strings. They are stored in text files as unquoted strings; escape sequences are not supported and the strings should not contain the following characters, although this is not checked: <|=>{}
-All characters between the appropriate markers are consumed into the ID, hence whitespace is meaningful.
-Multiple section or data tags with the same ID are allowed; see the "Merging rules" section.
-
-
-Outside of tags only whitespace or valid tags is allowed. Whitespace is ignored.
-The following tags are valid (see below for details):
-tag		purpose
-{...}		section identifiers
-<...>		data items
-!{...}		simple comment block
-!<...>		comment block parsed the same as <...>
-Within tags, type specifications or data items whitespace is allowed between symbols.
-
-
-Section identifier tags:
-Format: {ID}
-The ID is the section identifier/name. The ID type is DefaultData unless overriden by the code using the reader.
-A section identifier marks the beginning of a new section, extending until the next section identifier or the end of the file.
-
-
-Data item tags:
-Format: <tp|ID=dt>
-A data item with type tp, identifier ID and data dt. If the data does not fit the given type it is an error and the tag is ignored. Once split into a type string, ID and data string, the contents are passed to an addTag() function within the DataSection class which will parse tags of a recognised format and either ignore or print a warning about other tags.
-
-
-Data item tags: Type format:
-Note:
-	The type is read as a single token terminated by any of these characters:	<>|=
-	There must not be spaces within the type, e.g. "char []".
-	Of course any character other than a | terminating the token is an error.
-Format:
-	tp		a basic type
-	tp[]		a dynamic list of sub-type tp
-	t1[t2]		an associative array with key-type t2
-Possible future additions:
-	tp()		a dynamic merging list of sub-type tp (only valid as the primary type, ie <subtype()|...>, not a sub-type of a tuple or another dynamic list)
-	{t1,t2,...,tn}	a tuple with sub-types t1, t2, ..., tn
-
-Basic types (only items with a + are currently supported, items with * are in DefaultData):
-	name
-	
-	void	--- less useful type
-+*	bool	--- integer types
-+*	byte
-+*	ubyte
-+*	short
-+*	ushort
-+*	int
-+*	uint
-+*	long
-+*	ulong
-	cent
-	ucent
-	
-+*	binary	--- alias for ubyte[]
-	
-+*	float	--- floating point types
-+*	double
-+*	real
-	ifloat
-	idouble
-	ireal
-	cfloat
-	cdouble
-	creal
-	
-+*	char	--- single character types (actually these CANNOT support UTF8 symbols with length > 1)
-	wchar
-	dchar
-+*	string	--- alias for char[] --- (DOES support UTF8)
-	wstring	--- alias for wchar[]
-	dstring	--- alias for dchar[]
-
-
-Data item tags: Data format:
-Valid chars:	[](){},+-.0-9eEixXa-fA-F '.' ".*"
-Format:
-	[d1,d2,...,dn]	data all of type t corresponding to t[]
-	(d1,d2,...,dn)	data all of type t corresponding to t()
-	{d1,d2,...,dn}	data corresponding to a type declaration of {t1,t2,...,tn}
-	d		a single data element
-
-Single data elements:
-	z		an integer number (regexp: [+-]?[0-9]+)
-	z		a floating point number (rough regexp: [+-]?[0-9]*[.]?[0-9]*(e[+-]?[0-9]+)?)
-	zi		an imaginary floating point number (z is a floating point number)
-	y+zi, y-zi	a complex number (4+0i may be written as 4, etc) (y, z are f.p.s)
-	0xz, -0xz	a hexadecimal integer z (composed of chars 0-9,a-f,A-F)
-	'c'		a char/wchar/dchar character, depending on the type specified (c may be any single character except ' or an escape sequence)
-	"string"	equivalent to ['s','t','r','i','n','g'] --- may contain the following escape sequences as defined in D: \" \' \\ \a \b \f \n \r \t \v
-	XX...XX		Binary (ubyte[]); each pair of chars is read as a hex ubyte
-	<void>		void "data" has no symbols
-
-
-Data format: Escape sequences:
-To be created and written.
-
-
-Comment tags (there are no line comments):
-Simple comment blocks:
-Format: !{...}
-This is a simple comment block, and only curly braces ({,}) are treated specially. A {, whether or not it is preceded by a !, starts an embedded comment block, and a } ends either an embedded block or the actual comment block. Note: beware commenting out anything containing curly braces which aren't in matching pairs.
-Commented data tags:
-Format: !<tp|ID=dt>
-Basically a commented out data tag. Conformance to the above spec may not be checked as strictly as normal, but the dt section is checked for strings so that a > within a string won't end the tag.
-
-
-Merging rules:
-if, when a data item is read, a data item with the same identifier
-within the same section exists in the DataSet being read into:
-+	if the types are identical:
-++		if the primary type is a tp() mergeable dynamic list:
-+++			the entries from the item being read are concatenated to those in the item
-+++			in the DataSet
-++		else:
-++-			the item already in the DataSet takes priority and is left untouched
-+	else:
-+-		a warning is issued, and the data item within the DataSet is left untouched
-This allows merging some config settings in a user config file with the remaining settings in a
-complete system config file and some support for modifications overriding or adding to some data.
-
-
-Header:
-The header is a standard section which is mandatory and must be the first section. Its section identifier must start at the beginning of the file with no whitespace, declared with:
-	{MTXY}		where XY is a two digit CAPITAL HEX version number representing the mergetag format version, e.g. {MT01} .
-If these are not the first 6 characters of the file the file will not be regarded as valid.
-This formatting is very strict to allow reliable low-level parsing.
-
-
-The data tags within the header have no special meaning; any may be used such as the following:
-	<string|"Author"="...">
-	<string|"Name"="...">
-	<string|"Description"="...">
-	<string|"Program"="...">	(which program created/uses this?)
-	<*|"Version"=...>		(use any supported type)
-	<string|"Date"="YYYYMMDD">	(reverse date format; optionally "YYYYMMDDhhmmss")
-	<{u16,u8,u8}|"Date"={YYYY,MM,DD}>	(actually this type probably won't be supported by a standard section)
-	<string|"Copyright"=...>
-
-
-Example:	!THIS IS NO LONGER VALID!
-{MT01}
-{example section}
-<u32|"num"=5>
-<{u32,UTF8[]}()|"DATA"=(
-	{1,['a']},
-	{59,['w','o','r','d']},
-	{2,"strings can be written like this"} )>
-<wchar[]|"name"="This string is stored in UTF16, regardless of the file's encoding.">
-<{u32,UTF8[]}()|"DATA"=(
-	{3,"this is appended to the previous 'DATA' item"} )>
-{"section: section identifiers and tuples are not confused since tuples only occur inside <...> items"}
-<void|Empty tag= >
-!{this is a comment {containing a comment}}
--- a/mde/mergetag/doc/issues.txt	Fri Mar 07 17:51:02 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-This is mostly just a list of potential minor issues noticed while coding but not seen worth throwing an error about.
-
-Overall:
-
-read.d:
-	Support partially loading a file.
-	parseSection(): as mentioned at end of function
-	formatting errors could be more informative; in particular say where the error is
-	No binary support.
-
-write.d:
-	Threading support?
-	There is currently no way to specify the base in which numbers are written (in text form).
-
-parse.d:
-	Doesn't support cent/ucent.
-
-format.d:
-	No support for cent/ucent or ulong where val > long.max.
--- a/mde/mergetag/exception.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/mergetag/exception.d	Fri Mar 14 11:39:45 2008 +0000
@@ -28,6 +28,9 @@
     this () {
         super ("File IO exception");
     }
+    this (char[] msg) {
+        super (msg);
+    }
 }
 
 /** Thrown on unknown format errors; when reading or writing and the filetype cannot be guessed. */
--- a/mde/mergetag/iface/IReader.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/mergetag/iface/IReader.d	Fri Mar 14 11:39:45 2008 +0000
@@ -15,7 +15,7 @@
     
     void dataSecCreator (IDataSection delegate (ID));   /// Set the dataSecCreator
     
-    uint[] getSectionNames ();          /// Get identifiers for all sections
+    ID[] getSectionNames ();            /// Get identifiers for all sections
     void read ();                       /// Commence reading
     void read (ID[] secSet);            /// ditto
     void read (View!(ID) secSet);       /// ditto
--- a/mde/options.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/mde/options.d	Fri Mar 14 11:39:45 2008 +0000
@@ -11,16 +11,18 @@
 import mde.mergetag.Writer;
 import mde.mergetag.DataSet;
 import mde.mergetag.exception;
+import mde.resource.paths;
 
 import tango.scrapple.text.convert.parseTo : parseTo;
 import tango.scrapple.text.convert.parseFrom : parseFrom;
 
-import tango.io.FilePath : FilePath;
 import tango.util.log.Log : Log, Logger;
 
 Options options;
 static this() {
-    // Since options is used in many places, creating a class of default values now makes its use safer.
+    // Since options is used in many places, creating a class of default values to use until
+    // options have been loaded makes its use safer.
+    // In addition, loading should be atomic.
     options = new Options();
 }
 
@@ -39,6 +41,8 @@
     * They can be read and set directly by other code (so not thread-safe for writing currently).
     */
     char[] L10n;        // locale, e.g. en-GB
+    bool useThreads;    // set 0 to disable threading
+    int logLevel;       // tango logger level
     
     /* Have any options been changed? Only bother writing if true.
     *
@@ -52,10 +56,16 @@
     void addTag (char[] tp, ID id, char[] dt) {
         if (tp == "char[]") {
             if (id == cast(ID)"L10n") L10n = parseTo!(char[]) (dt);
+        } else if (tp == "bool") {
+            if (id == cast(ID)"useThreads") useThreads = parseTo!(bool) (dt);
+        } else if (tp == "int") {
+            if (id == cast(ID)"logLevel") logLevel = parseTo!(int) (dt);
         }
     }
     void writeAll (ItemDelg dlg) {
         dlg ("char[]", cast(ID)"L10n", parseFrom!(char[]) (L10n));
+        dlg ("bool", cast(ID)"useThreads", parseFrom!(bool) (useThreads));
+        dlg ("int", cast(ID)"logLevel", parseFrom!(int) (logLevel));
     }
     
     
@@ -63,18 +73,16 @@
     *
     * If the file doesn't exist, no reading is attempted (options are left at default values).
     */
-    private static const fileName = "conf/options.mtt";
+    private static const fileName = "options";
     private static const secName = cast(ID)"Default";
     static void load () {
-        FilePath filePath = new FilePath (fileName);
-        
         // Check it exists (if not it should still be created on exit).
         // Don't bother checking it's not a folder, because it could still be a block or something.
-        if (!filePath.exists) return;
+        if (!confDir.exists (fileName)) return;
         
         IReader reader;
         try {
-            reader = new MTTReader(filePath);
+            reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
             reader.dataSecCreator = delegate IDataSection(ID) {
                 return new Options;
             };
@@ -86,11 +94,11 @@
         }
         
         IDataSection* secP = secName in reader.dataset.sec;
-        options = cast(Options) *secP;
-        if (options is null) {
+        Options o = cast(Options) *secP;
+        if (o is null) {
             throw new optionsLoadException ("Loading failed: expected section not found");
         }
-        // else loading was succesful.
+        else options = o;   // loading was succesful.
     }
     static void save () {
         if (!options.changed) return;   // skip
@@ -100,7 +108,7 @@
         
         IWriter writer;
         try {
-            writer = makeWriter (fileName, ds);
+            writer = confDir.makeMTWriter (fileName, ds);
             writer.write();
         } catch (MTException e) {
             logger.error ("Mergetag exception occurred; saving aborted:");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/resource/exception.d	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,32 @@
+/// This module is not currently in use.
+module mde.resource.exception;
+
+public import mde.exception;
+
+/// Base resource exception class.
+class resourceException : mdeException {
+    static const char[] symbol;
+    static this () {	symbol = super.symbol ~ ".resource";	}
+    char[] getSymbol () {
+        return symbol;
+    }
+    
+    this (char[] msg) {
+        super(msg);
+    }
+    this () {}
+}
+
+/// Thrown when a required path cannot be resolved at program startup.
+class requiredPathException : mdeException {
+    static const char[] symbol;
+    static this () {	symbol = super.symbol ~ ".requiredPath";	}
+    char[] getSymbol () {
+        return symbol;
+    }
+    
+    this (char[] msg) {
+        super(msg);
+    }
+    this () {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/resource/paths.d	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,245 @@
+/** Resource paths module.
+*
+* Internally to mde code other than code dealing directly with files and this module, paths are
+* relative to the mde directory. This module transforms those paths to absolute paths.
+*
+* Additionally, the intention is to look for all files in two directories: the installation (i.e.
+* main data) directory and a user directory (for user-specific configuration). Besides exposing
+* both paths and checking in which valid files exist, this module provides some extra mergetag
+* functionality to simplify correct reading and writing.
+*
+* Currently the paths are found as follows: (see codeDoc/paths.txt)
+*/
+/* Implementation note:
+* All paths are stored internally as strings, rather than as an instance of FilePath/PathView once
+* the FilePath has served its immediate purpose, since it's more convenient and creating new
+* FilePaths for adjusted paths should be no slower than mutating existing ones. */
+module mde.resource.paths;
+
+import mde.exception;
+import mde.mergetag.Reader;
+import mde.mergetag.Writer;
+import mde.mergetag.DataSet;
+import mde.mergetag.exception;
+
+import tango.io.FilePath;
+import tango.util.log.Log : Log, Logger;
+import tango.stdc.stdlib;
+import tango.stdc.stringz;
+
+/** Order to read files in.
+*
+* Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */
+enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY }
+
+/** This struct has one instance for each "directory".
+*
+* It is the only item within this module that you should need to interact with. */
+struct mdeDirectory
+{
+    /** Creates an MT reader for each file.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   readOrder = Read the highest priority or lowest priority files first? For correct merging,
+    *               this should be LOW_HIGH when newly-read items override old ones (as is the case
+    *               with DefaultData) and HIGH_LOW when the first-read items survive. Thus override
+    *               order needs to be the same for each section, except the header which is always
+    *               read with LOW_HIGH order.
+    *               Alternately, for files which shouldn't be
+    *               merged where only the highest priority file should be read, pass HIGH_ONLY.
+    *   ds        = The dataset, as for mergetag. Note: all actual readers share one dataset.
+    *   rdHeader  = Read the headers for each file and merge if rdHeader == true.
+    */
+    IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
+    {
+        if (readOrder == PRIORITY.HIGH_ONLY) return makeReader (paths[pathsLen-1] ~ file, ds, rdHeader);
+        else return new mdeReader (file, readOrder, ds, rdHeader, paths);
+    }
+    
+    /** Creates an MT writer for file deciding on the best path to use.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   ds        = The dataset, as for mergetag.
+    */
+    IWriter makeMTWriter (char[] file, DataSet ds = null)
+    {
+        // FIXME: use highest priority writable path
+        return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text);
+    }
+    
+    /** Check whether the given file exists under any path with either .mtt or .mtb suffix. */
+    bool exists (char[] file) {
+        for (uint i = 0; i < pathsLen; ++i) {
+            if (FilePath (paths[i]~file~".mtt").exists) return true;
+            if (FilePath (paths[i]~file~".mtb").exists) return true;
+        }
+        return false;
+    }
+    
+private:
+    
+    // Unconditionally add a path
+    void addPath (char[] path) {
+        paths[pathsLen++] = path~'/';
+    }
+    
+    // Test a path and add if is a folder
+    void tryPath (char[] path) {
+        PathView pv = FilePath (path);
+        if (pv.exists && pv.isFolder) {
+            paths[pathsLen++] = path~'/';
+            logger.info ("Path "~path~" is writable: " ~ (pv.isWritable ? "yes" : "no"));
+        }
+    }
+    
+    // Use a static array to store all possible paths with separate length counters.
+    // Lowest priority paths are first.
+    char[][MAX_PATHS] paths;
+    ubyte pathsLen = 0;
+}
+
+/** These are the actual instances, one for each of the data and conf "directories". */
+mdeDirectory dataDir, confDir;
+
+//BEGIN Path resolution
+static this() {
+    logger = Log.getLogger ("mde.resource.paths");
+    
+    // NOTE: May fail. Currently I think I'll just let the exception halt execution.
+    resolvePaths();
+    if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
+    if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
+}
+
+private:
+// The maximum number of paths for any one "directory".
+// There are NO CHECKS that this is not exceeded.
+const MAX_PATHS = 3;
+
+Logger logger;
+
+/* Try each path in succession, returning the first two exist and be a folder. Throws if none are. */
+char[] findPath (char[][] paths ...) {
+    foreach (path; paths) {
+        PathView pv = new FilePath (path);
+        if (pv.exists && pv.isFolder) return pv.toString;    // got a valid path
+    }
+    // no valid path...
+    logger.fatal ("Unable to resolve a required path! The following were tried:");
+    foreach (path; paths) logger.fatal ('\t' ~ path);
+    throw new mdeException ("Unable to resolve a required path (see log for details).");
+}
+
+// These are used several times:
+const DATA = "/data";
+const CONF = "/conf";
+
+version (linux) {
+    void resolvePaths () {
+        // Home directory:
+        char[] HOME = fromStringz (getenv (toStringz ("HOME")));
+        
+        // Base paths:
+        char[] staticPath = findPath ("/usr/share/games/mde", "/usr/local/share/games/mde", "data");
+        char[] userPath = findPath (HOME~"/.config/mde", HOME~"/.mde");
+        
+        // Static data paths:
+        dataDir.addPath (staticPath);      // we know this is valid anyway
+        dataDir.tryPath (userPath ~ DATA);
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath ~ CONF);
+        confDir.tryPath ("/etc/mde");
+        confDir.tryPath (userPath ~ CONF);
+    }
+} else version (Windows) {
+    void resolvePaths () {
+        static assert (false, "No registry code");
+        
+        // Base paths:
+        char[] userPath = `...`;
+        char[] installPath = `registryInstallPath or "."`;
+        char[] staticPath = findPath (installPath ~ DATA);
+        
+        // Static data paths:
+        dataDir.addPath[dataLength++] = staticPath;   // we know this is valid anyway
+        dataDir.tryPath (userPath ~ DATA);
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath.toString ~ CONF);
+        confDir.tryPath (installPath ~ CONF);
+        confDir.tryPath (userPath.toString ~ CONF);
+    }
+} else {
+    static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!");
+}
+//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 (char[] file, PRIORITY readOrder, DataSet ds, bool rdHeader, char[][MAX_PATHS] paths)
+    in { assert (readOrder == PRIORITY.LOW_HIGH || readOrder == PRIORITY.HIGH_LOW); }
+    body {
+        rdOrder = readOrder;
+        if (ds is null) ds = new DataSet;
+        
+        foreach (path; paths) {
+            try {
+                IReader r = makeReader (path~file, ds, rdHeader);
+                
+                readers[readersLen++] = r;
+            }
+            catch (MTFileIOException) {}    // Ignore errors regarding no file for now.
+        }
+        
+        if (readersLen == 0) {          // totally failed to find any valid files
+            throw new MTFileIOException ("Unable to find the file: "~file[1..$]~"[.mtt|mtb]");
+        }
+        
+        // This is simply the easiest way of adjusting the reading order:
+        if (readOrder == PRIORITY.HIGH_LOW) readers[0..readersLen].reverse;
+    }
+    
+    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. */
+    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 (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;
+}
--- a/test/mdeTest.d	Fri Mar 07 17:51:02 2008 +0000
+++ b/test/mdeTest.d	Fri Mar 14 11:39:45 2008 +0000
@@ -7,8 +7,8 @@
 import mde.mergetag.DataSet;
 import mde.mergetag.mtunittest;
 import mde.exception;
-import mde.init;
-import mde.i18n;
+import mde.Init;
+import mde.i18n.I18nTranslation;
 
 import tango.util.log.Log : Log, Logger;