/* テスト */
/*
 * Debug Status: Not checked.
 * - Fixed __T on this page.
 */
/*
 * File: prefs.c
 * Purpose: Pref file handling code
 *
 * Copyright (c) 2003 Takeshi Mogami, Robert Ruehlmann
 * Copyright (c) 2007 Pete Mack
 *
 * This work is free software; you can redistribute it and/or modify it
 * under the terms of either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation, version 2, or
 *
 * b) the "Angband licence":
 *    This software may be copied and distributed for educational, research,
 *    and not for profit purposes provided that this copyright and statement
 *    are included in all such copies.  Other copyrights may also apply.
 */
#include "angband.h"

/*** Pref file saving code ***/

/*
 * Header and footer marker string for pref file dumps
 */
static const _TCHAR *dump_separator = __T("#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#");

/*
 * Remove old lines from pref files
 */
static void remove_old_dump(const _TCHAR *cur_fname, const _TCHAR *mark)
{
	bool between_marks = FALSE;
	bool changed = FALSE;

	_TCHAR buf[1024];

	_TCHAR start_line[1024];
	_TCHAR end_line[1024];

	_TCHAR new_fname[1024];

	ang_file *new_file;
	ang_file *cur_file;

	/* Format up some filenames */
	strnfmt(new_fname, _countof(new_fname), __T("%s.new"), cur_fname);

	/* Work out what we expect to find */
	strnfmt(start_line, _countof(start_line), __T("%s begin %s"),
			dump_separator, mark);
	strnfmt(end_line,   _countof(end_line),   __T("%s end %s"),
			dump_separator, mark);

	/* Open current file */
	cur_file = file_open(cur_fname, MODE_READ, -1);
	if (!cur_file) return;

	/* Open new file */
	new_file = file_open(new_fname, MODE_WRITE, FTYPE_TEXT);
	if (!new_file)
	{
		msg_format(__T("Failed to create file %s"), new_fname);
		return;
	}
	/* Loop for every line */
	while (file_getl(cur_file, buf, _countof(buf)))
	{
		/* If we find the start line, turn on */
		if (!_tcscmp(buf, start_line))
		{
			between_marks = TRUE;
		}

		/* If we find the finish line, turn off */
		else if (!_tcscmp(buf, end_line))
		{
			between_marks = FALSE;
			changed = TRUE;
		}
		if (!between_marks)
		{
			/* Copy orginal line */
			file_putf(new_file, __T("%s\n"), buf);
		}
	}
	/* Close files */
	file_close(cur_file);
	file_close(new_file);

	/* If there are changes, move things around */
	if (changed)
	{
		_TCHAR old_fname[1024];
		strnfmt(old_fname, _countof(old_fname), __T("%s.old"), cur_fname);

		if (file_move(cur_fname, old_fname))
		{
			file_move(new_fname, cur_fname);
			file_delete(old_fname);
		}
	}
	else /* Otherwise just destroy the new file */
	{
		file_delete(new_fname);
	}
}

/*
 * Output the header of a pref-file dump
 */
static void pref_header(ang_file *fff, const _TCHAR *mark)
{
	/* Start of dump */
	file_putf(fff, __T("%s begin %s\n"), dump_separator, mark);

	file_putf(fff, __T("# *Warning!*  The lines below are an automatic dump.\n"));
	file_putf(fff, __T("# Don't edit them; changes will be deleted and replaced automatically.\n"));
}

/*
 * Output the footer of a pref-file dump
 */
static void pref_footer(ang_file *fff, const _TCHAR *mark)
{
	file_putf(fff, __T("# *Warning!*  The lines above are an automatic dump.\n"));
	file_putf(fff, __T("# Don't edit them; changes will be deleted and replaced automatically.\n"));

	/* End of dump */
	file_putf(fff, __T("%s end %s\n"), dump_separator, mark);
}

/*
 * Save autoinscription data to a pref file.
 */
void autoinsc_dump(ang_file *fff)
{
	int i;
	if (!inscriptions) return;

	file_putf(fff, __T("# Autoinscription settings\n"));
	file_putf(fff, __T("# B:item kind:inscription\n\n"));

	for (i = 0; i < inscriptions_count; i++)
	{
		object_kind *k_ptr = &k_info[inscriptions[i].kind_idx];

		file_putf(fff, __T("# Autoinscription for %s\n"), k_name + k_ptr->name);
		file_putf(fff, __T("B:%d:%s\n\n"), inscriptions[i].kind_idx,
		        quark_str(inscriptions[i].inscription_idx));
	}
	file_putf(fff, __T("\n"));
}

/*
 * Save squelch data to a pref file.
 */
void squelch_dump(ang_file *fff)
{
	int i;
	file_putf(fff, __T("# Squelch settings\n"));

	for (i = 1; i < z_info->k_max; i++)
	{
		int tval = k_info[i].tval;
		int sval = k_info[i].sval;
		bool squelch = k_info[i].squelch;

		/* Dump the squelch info */
		if (tval || sval)
			file_putf(fff, __T("Q:%d:%d:%d:%d\n"), i, tval, sval, squelch);
	}
	file_putf(fff, __T("\n"));
}

/*
 * Write all current options to a user preference file.
 */
void option_dump(ang_file *fff)
{
	int i, j;

	/* Dump options (skip cheat, adult, score) */
	for (i = 0; i < OPT_CHEAT; i++)
	{
		const _TCHAR *name = option_name(i);
		if (!name) continue;

		/* Comment */
		file_putf(fff, __T("# Option '%s'\n"), option_desc(i));

		/* Dump the option */
		if (op_ptr->opt[i])
			file_putf(fff, __T("Y:%s\n"), name);
		else
			file_putf(fff, __T("X:%s\n"), name);

		/* Skip a line */
		file_putf(fff, __T("\n"));
	}
	/* Dump window flags */
	for (i = 1; i < ANGBAND_TERM_MAX; i++)
	{
		/* Require a real window */
		if (!angband_term[i]) continue;

		/* Check each flag */
		for (j = 0; j < (int)N_ELEMENTS(window_flag_desc); j++)
		{
			/* Require a real flag */
			if (!window_flag_desc[j]) continue;

			/* Comment */
			file_putf(fff, __T("# Window '%s', Flag '%s'\n"),
				angband_term_name[i], window_flag_desc[j]);

			/* Dump the flag */
			if (op_ptr->window_flag[i] & (1L << j))
				file_putf(fff, __T("W:%d:%d:1\n"), i, j);
			else
				file_putf(fff, __T("W:%d:%d:0\n"), i, j);

			/* Skip a line */
			file_putf(fff, __T("\n"));
		}
	}
	autoinsc_dump(fff);
#if 0
	/* Dumping squelch settings caused problems, see #784 */
	squelch_dump(fff);
#endif
}

#ifdef ALLOW_MACROS

/*
 * Append all current macros to the given file
 */
void macro_dump(ang_file *fff)
{
	int i;
	_TCHAR buf[1024];

	/* Dump them */
	for (i = 0; i < macro__num; i++)
	{
		/* Start the macro */
		file_putf(fff, __T("# Macro '%d'\n"), i);

		/* Extract the macro action */
		ascii_to_text(buf, _countof(buf), macro__act[i]);
		file_putf(fff, __T("A:%s\n"), buf);

		/* Extract the macro pattern */
		ascii_to_text(buf, _countof(buf), macro__pat[i]);
		file_putf(fff, __T("P:%s\n"), buf);

		file_putf(fff, __T("\n"));
	}
}

/*
 * Hack -- Append all keymaps to the given file.
 *
 * Hack -- We only append the keymaps for the "active" mode.
 */
void keymap_dump(ang_file *fff)
{
	size_t i;
	int mode;
	_TCHAR buf[1024];

	if (OPT(rogue_like_commands))
		mode = KEYMAP_MODE_ROGUE;
	else
		mode = KEYMAP_MODE_ORIG;

	for (i = 0; i < N_ELEMENTS(keymap_act[mode]); i++)
	{
		_TCHAR key[2] = __T("?");
		const _TCHAR *act;

		/* Loop up the keymap */
		act = keymap_act[mode][i];

		/* Skip empty keymaps */
		if (!act) continue;

		/* Encode the action */
		ascii_to_text(buf, _countof(buf), act);

		/* Dump the keymap action */
		file_putf(fff, __T("A:%s\n"), buf);

		/* Convert the key into a string */
		key[0] = i;

		/* Encode the key */
		ascii_to_text(buf, _countof(buf), key);

		/* Dump the keymap pattern */
		file_putf(fff, __T("C:%d:%s\n"), mode, buf);

		/* Skip a line */
		file_putf(fff, __T("\n"));
	}
}

#endif

/* Dump monsters */
void dump_monsters(ang_file *fff)
{
	int i;

	for (i = 0; i < z_info->r_max; i++)
	{
		monster_race *r_ptr = &r_info[i];
		byte attr = r_ptr->x_attr;
		_TCHAR chr = r_ptr->x_char;

		/* Skip non-entries */
		if (!r_ptr->name) continue;

		file_putf(fff, __T("# Monster: %s\n"), (r_name + r_ptr->name));
		file_putf(fff, __T("R:%d:0x%02X:0x%02X\n"), i, attr, chr);
	}
}

/* Dump objects */
void dump_objects(ang_file *fff)
{
	int i;

	for (i = 0; i < z_info->k_max; i++)
	{
		object_kind *k_ptr = &k_info[i];
		byte attr = k_ptr->x_attr;
		_TCHAR chr = k_ptr->x_char;

		/* Skip non-entries */
		if (!k_ptr->name) continue;

		file_putf(fff, __T("# Object: %s\n"), (k_name + k_ptr->name));
		file_putf(fff, __T("K:%d:0x%02X:0x%02X\n"), i, attr, chr);
	}
}

/* Dump features */
void dump_features(ang_file *fff)
{
	int i;

	for (i = 0; i < z_info->f_max; i++)
	{
		feature_type *f_ptr = &f_info[i];
		byte attr = f_ptr->x_attr;
		_TCHAR chr = f_ptr->x_char;

		/* Skip non-entries */
		if (!f_ptr->name) continue;

		/* Skip mimic entries -- except invisible trap */
		if ((f_ptr->mimic != i) && (i != FEAT_INVIS)) continue;

		file_putf(fff, __T("# Terrain: %s\n"), (f_name + f_ptr->name));
		file_putf(fff, __T("F:%d:0x%02X:0x%02X\n"), i, attr, chr);
	}
}

/* Dump flavors */
void dump_flavors(ang_file *fff)
{
	int i;

	for (i = 0; i < z_info->flavor_max; i++)
	{
		flavor_type *x_ptr = &flavor_info[i];
		byte attr = x_ptr->x_attr;
		_TCHAR chr = x_ptr->x_char;

		file_putf(fff, __T("# Item flavor: %s\n"), (flavor_text + x_ptr->text));
		file_putf(fff, __T("L:%d:0x%02X:0x%02X\n\n"), i, attr, chr);
	}
}

/* Dump colors */
void dump_colors(ang_file *fff)
{
	int i;

	for (i = 0; i < MAX_COLORS; i++)
	{
		int kv = angband_color_table[i][0];
		int rv = angband_color_table[i][1];
		int gv = angband_color_table[i][2];
		int bv = angband_color_table[i][3];

		const _TCHAR *name = __T("unknown");

		/* Skip non-entries */
		if (!kv && !rv && !gv && !bv) continue;

		/* Extract the color name */
		if (i < BASIC_COLORS) name = color_names[i];

		file_putf(fff, __T("# Color: %s\n"), name);
		file_putf(fff, __T("V:%d:0x%02X:0x%02X:0x%02X:0x%02X\n\n"), i, kv, rv, gv, bv);
	}
}

/**
 * Save a set of preferences to file, overwriting any old preferences with the
 * same title.
 *
 * \param path is the filename to dump to
 * \param dump is a pointer to the function that does the writing to file
 * \param title is the name of this set of preferences
 *
 * \returns TRUE on success, FALSE otherwise.
 */
bool prefs_save(const _TCHAR *path, void (*dump)(ang_file *), const _TCHAR *title)
{
	ang_file *fff;
	_TCHAR *buf;

	/* Remove old macros */
	remove_old_dump(path, title);

	fff = file_open(path, MODE_APPEND, FTYPE_TEXT);
	if (!fff) 
		return FALSE;

	/* Append the header */
	pref_header(fff, title);
	file_putf(fff, __T("\n\n"));
	buf = _tcsstr(title, __T(" "));
	file_putf(fff, __T("# %s definitions\n\n"), buf);

	dump(fff);

	file_putf(fff, __T("\n\n\n"));
	pref_footer(fff, title);
	file_close(fff);

	return TRUE;
}

/*** Pref file parsing code ***/

/*
 * Extract the first few "tokens" from a buffer
 *
 * This function uses "colon" and "slash" as the delimeter characters.
 *
 * We never extract more than "num" tokens.  The "last" token may include
 * "delimeter" characters, allowing the buffer to include a "string" token.
 *
 * We save pointers to the tokens in "tokens", and return the number found.
 *
 * Hack -- Attempt to handle the 'c' character formalism
 *
 * Hack -- An empty buffer, or a final delimeter, yields an "empty" token.
 *
 * Hack -- We will always extract at least one token
 */
s16b tokenize(_TCHAR *buf, s16b num, _TCHAR **tokens)
{
	int i = 0;
	_TCHAR *s = buf;

	/* Process */
	while (i < num - 1)
	{
		_TCHAR *t;

		/* Scan the string */
		for (t = s; *t; t++)
		{
			/* Allow pseudo-escaping */
			if (*t == '\\') t++;

			/* Found a delimiter */
			else if (*t == ':') break;
		}
		/* Nothing left */
		if (!*t) break;

		/* Nuke and advance */
		*t++ = 0;

		/* Save the token */
		tokens[i++] = s;

		/* Advance */
		s = t;
	}
	/* Save the token */
	tokens[i++] = s;

	/* Number found */
	return (i);
}

/*
 * Parse a sub-file of the "extra info" (format shown below)
 *
 * Each "action" line has an "action symbol" in the first column,
 * followed by a colon, followed by some command specific info,
 * usually in the form of "tokens" separated by colons or slashes.
 *
 * Blank lines, lines starting with white space, and lines starting
 * with pound signs ("#") are ignored (as comments).
 *
 * Note the use of "tokenize()" to allow the use of both colons and
 * slashes as delimeters, while still allowing final tokens which
 * may contain any characters including "delimiters".
 *
 * Note the use of "_tcstol()" to allow all "integers" to be encoded
 * in decimal, hexidecimal, or octal form.
 *
 * Note that "monster zero" is used for the "player" attr/char, "object
 * zero" will be used for the "stack" attr/char, and "feature zero" is
 * used for the "nothing" attr/char.
 *
 * Specify the attr/char values for "monsters" by race index.
 *   R:<num>:<a>/<c>
 *
 * Specify the attr/char values for "objects" by kind index.
 *   K:<num>:<a>/<c>
 *
 * Specify the attr/char values for "features" by feature index.
 *   F:<num>:<a>/<c>
 *
 * Specify the attr/char values for "special" things.
 *   S:<num>:<a>/<c>
 *
 * Specify the attribute values for inventory "objects" by kind tval.
 *   E:<tv>:<a>
 *
 * Define a macro action, given an encoded macro action.
 *   A:<str>
 *
 * Create a macro, given an encoded macro trigger.
 *   P:<str>
 *
 * Create a keymap, given an encoded keymap trigger.
 *   C:<num>:<str>
 *
 * Turn an option off, given its name.
 *   X:<str>
 *
 * Turn an option on, given its name.
 *   Y:<str>
 *
 * Turn a window flag on or off, given a window, flag, and value.
 *   W:<win>:<flag>:<value>
 *
 * Specify visual information, given an index, and some data.
 *   V:<num>:<kv>:<rv>:<gv>:<bv>
 *
 * Specify colors for message-types.
 *   M:<type>:<attr>
 *
 * Specify the attr/char values for "flavors" by flavors index.
 *   L:<num>:<a>/<c>
 */
errr process_pref_file_command(_TCHAR *buf)
{
	long i, n1, n2, sq;

	_TCHAR *zz[16];

	/* Skip "empty" lines */
	if (!buf[0]) 
		return (0);

	/* Skip "blank" lines */
	if (_istspace(buf[0])) 
		return (0);

	/* Skip comments */
	if (buf[0] == '#') 
		return (0);

	/* Paranoia */
	/* if (_tcslen(buf) >= 1024) return (1); */

	/* Require "?:*" format */
	if (buf[1] != ':') 
		return (1);

	/* Process "R:<num>:<a>/<c>" -- attr/char for monster races */
	if (buf[0] == 'R')
	{
		if (tokenize(buf+2, 3, zz) == 3)
		{
			monster_race *r_ptr;
			i = _tcstol(zz[0], NULL, 0);
			n1 = _tcstol(zz[1], NULL, 0);
			n2 = _tcstol(zz[2], NULL, 0);
			if ((i < 0) || (i >= (long)z_info->r_max)) 
				return (1);
			r_ptr = &r_info[i];
			if (n1) 
				r_ptr->x_attr = (byte)n1;
			if (n2) 
				r_ptr->x_char = (char)n2;
			return (0);
		}
	}
	/* Process "B:<k_idx>:inscription */
	else if (buf[0] == 'B')
	{
		if (2 == tokenize(buf + 2, 2, zz))
		{
			add_autoinscription((s16b) _tcstol(zz[0], NULL, 0), zz[1]);
			return (0);
		}
	}
	/* Process "Q:<idx>:<tval>:<sval>:<y|n>"  -- squelch bits   */
	/* and     "Q:<idx>:<val>"                -- squelch levels */
	/* and     "Q:<val>"                      -- auto_destroy   */
	else if (buf[0] == 'Q')
	{
		i = tokenize(buf+2, 4, zz);
		if (i == 2)
		{
			n1 = _tcstol(zz[0], NULL, 0);
			n2 = _tcstol(zz[1], NULL, 0);
			squelch_level[n1] = (byte) n2; // TODO bound checking.
			return(0);
		}
		else if (i == 4)
		{
			i = _tcstol(zz[0], NULL, 0);
			n1 = _tcstol(zz[1], NULL, 0);
			n2 = _tcstol(zz[2], NULL, 0);
			sq = _tcstol(zz[3], NULL, 0);
			if ((k_info[i].tval == n1) && (k_info[i].sval == n2))
			{
				k_info[i].squelch = (sq != 0);
				return(0);
			}
			else
			{
				for (i = 1; i < z_info->k_max; i++)
				{
					if ((k_info[i].tval == n1) && (k_info[i].sval == n2))
					{
						k_info[i].squelch = (sq != 0);
						return(0);
					}
				}
			}
		}
	}
	/* Process "K:<tval>:<sval>:<a>/<c>"  -- attr/char for object kinds */
	else if (buf[0] == 'K')
	{
		if (tokenize(buf+2, 4, zz) == 4)
		{
			object_kind *k_ptr;

			int tval, sval;
			const _TCHAR *tval_s = zz[0];
			const _TCHAR *sval_s = zz[1];

			n1 = _tcstol(zz[2], NULL, 0);
			n2 = _tcstol(zz[3], NULL, 0);

			/* Now convert the tval into its numeric equivalent */
			if (1 != SSCANF(tval_s, __T("%d"), &tval))
			{
				tval = tval_find_idx(tval_s);
				if (tval == -1) return 1;
			}
			/* Now find the sval */
			if (1 != SSCANF(sval_s, __T("%d"), &sval))
			{
				sval = lookup_sval(tval, sval_s);
				if (sval == -1) return 1;
			}

			i = lookup_kind(tval, sval);

			if ((i < 0) || (i >= (long)z_info->k_max)) return (1);

			k_ptr = &k_info[i];

			if (n1) k_ptr->x_attr = (byte)n1;
			if (n2) k_ptr->x_char = (char)n2;

			return (0);
		}
	}
	/* Process "F:<num>:<a>/<c>" -- attr/char for terrain features */
	else if (buf[0] == 'F')
	{
		if (tokenize(buf+2, 3, zz) == 3)
		{
			feature_type *f_ptr;
			i = _tcstol(zz[0], NULL, 0);
			n1 = _tcstol(zz[1], NULL, 0);
			n2 = _tcstol(zz[2], NULL, 0);
			if ((i < 0) || (i >= (long)z_info->f_max)) return (1);
			f_ptr = &f_info[i];
			if (n1) f_ptr->x_attr = (byte)n1;
			if (n2) f_ptr->x_char = (char)n2;
			return (0);
		}
	}
	/* Process "L:<num>:<a>/<c>" -- attr/char for flavors */
	else if (buf[0] == 'L')
	{
		if (tokenize(buf+2, 3, zz) == 3)
		{
			flavor_type *flavor_ptr;
			i = _tcstol(zz[0], NULL, 0);
			n1 = _tcstol(zz[1], NULL, 0);
			n2 = _tcstol(zz[2], NULL, 0);
			if ((i < 0) || (i >= (long)z_info->flavor_max)) return (1);
			flavor_ptr = &flavor_info[i];
			if (n1) flavor_ptr->x_attr = (byte)n1;
			if (n2) flavor_ptr->x_char = (char)n2;
			return (0);
		}
	}
	/* Process "S:<num>:<a>/<c>" -- attr/char for special things */
	else if (buf[0] == 'S')
	{
		if (tokenize(buf+2, 3, zz) == 3)
		{
			i = _tcstol(zz[0], NULL, 0);
			n1 = _tcstol(zz[1], NULL, 0);
			n2 = _tcstol(zz[2], NULL, 0);
			if ((i < 0) || (i >= (long)N_ELEMENTS(misc_to_attr))) return (1);
			misc_to_attr[i] = (byte)n1;
			misc_to_char[i] = (char)n2;
			return (0);
		}
	}

	/* Process "E:<tv>:<a>" -- attribute for inventory objects */
	else if (buf[0] == 'E')
	{
		if (tokenize(buf+2, 2, zz) == 2)
		{
			i = _tcstol(zz[0], NULL, 0) % 128;
			n1 = _tcstol(zz[1], NULL, 0);
			if ((i < 0) || (i >= (long)N_ELEMENTS(tval_to_attr))) return (1);
			if (n1) tval_to_attr[i] = (byte)n1;
			return (0);
		}
	}
	/* Process "A:<str>" -- save an "action" for later */
	else if (buf[0] == 'A')
	{
		text_to_ascii(macro_buffer, _countof(macro_buffer), buf+2);
		return (0);
	}
	/* Process "P:<str>" -- create macro */
	else if (buf[0] == 'P')
	{
		_TCHAR tmp[1024];
		text_to_ascii(tmp, _countof(tmp), buf+2);
		macro_add(tmp, macro_buffer);
		return (0);
	}
	/* Process "C:<num>:<str>" -- create keymap */
	else if (buf[0] == 'C')
	{
		long mode;

		_TCHAR tmp[1024];

		if (tokenize(buf+2, 2, zz) != 2) return (1);

		mode = _tcstol(zz[0], NULL, 0);
		if ((mode < 0) || (mode >= KEYMAP_MODES)) return (1);

		text_to_ascii(tmp, _countof(tmp), zz[1]);
		if (!tmp[0] || tmp[1]) return (1);
		i = (long)tmp[0];

		string_free(keymap_act[mode][i]);

		keymap_act[mode][i] = _tcsdup(macro_buffer);

		return (0);
	}
	/* Process "V:<num>:<kv>:<rv>:<gv>:<bv>" -- visual info */
	else if (buf[0] == 'V')
	{
		if (tokenize(buf+2, 5, zz) == 5)
		{
			i = _tcstol(zz[0], NULL, 0);
			if ((i < 0) || (i >= MAX_COLORS)) return (1);
			angband_color_table[i][0] = (byte)_tcstol(zz[1], NULL, 0);
			angband_color_table[i][1] = (byte)_tcstol(zz[2], NULL, 0);
			angband_color_table[i][2] = (byte)_tcstol(zz[3], NULL, 0);
			angband_color_table[i][3] = (byte)_tcstol(zz[4], NULL, 0);
			return (0);
		}
	}
	/* set macro trigger names and a template */
	/* Process "T:<trigger>:<keycode>:<shift-keycode>" */
	/* Process "T:<template>:<modifier chr>:<modifier name>:..." */
	else if (buf[0] == 'T')
	{
		int tok;

		tok = tokenize(buf + 2, MAX_MACRO_MOD + 2, zz);

		/* Trigger template */
		if (tok >= 4)
		{
			int i;
			int num;

			/* Free existing macro triggers and trigger template */
			macro_trigger_free();

			/* Clear template done */
			if (*zz[0] == 0) return 0;

			/* Count modifier-characters */
			num = _tcslen(zz[1]);

			/* One modifier-character per modifier */
			if (num + 2 != tok) return 1;

			/* Macro template */
			macro_template = _tcsdup(zz[0]);

			/* Modifier chars */
			macro_modifier_chr = _tcsdup(zz[1]);

			/* Modifier names */
			for (i = 0; i < num; i++)
			{
				macro_modifier_name[i] = _tcsdup(zz[2+i]);
			}
		}
		/* Macro trigger */
		else if (tok >= 2)
		{
			_TCHAR *buf;
			const _TCHAR *s;
			_TCHAR *t;

			if (max_macrotrigger >= MAX_MACRO_TRIGGER)
			{
				msg_print(__T("Too many macro triggers!"));
				return 1;
			}

			/* Buffer for the trigger name */
			buf = C_ZNEW(_tcslen(zz[0]) + 1, _TCHAR);

			/* Simulate strcpy() and skip the '\' escape character */
			s = zz[0];
			t = buf;

			while (*s)
			{
				if ('\\' == *s) s++;
				*t++ = *s++;
			}
			/* Terminate the trigger name */
			*t = 0;

			/* Store the trigger name */
			macro_trigger_name[max_macrotrigger] = _tcsdup(buf);

			/* Free the buffer */
			FREE(buf);

			/* Normal keycode */
			macro_trigger_keycode[0][max_macrotrigger] = _tcsdup(zz[1]);

			/* Special shifted keycode */
			if (tok == 3)
			{
				macro_trigger_keycode[1][max_macrotrigger] = _tcsdup(zz[2]);
			}
			/* Shifted keycode is the same as the normal keycode */
			else
			{
				macro_trigger_keycode[1][max_macrotrigger] = _tcsdup(zz[1]);
			}
			/* Count triggers */
			max_macrotrigger++;
		}
		return 0;
	}
	/* Process "X:<str>" -- turn option off */
	else if (buf[0] == 'X')
	{
		/* Check non-adult options */
		for (i = 0; i < OPT_ADULT; i++)
		{
			if (option_name(i) && streq(option_name(i), buf + 2))
			{
				option_set(i, FALSE);
				return (0);
			}
		}
		/* Ignore unknown options */
		return (0);
	}
	/* Process "Y:<str>" -- turn option on */
	else if (buf[0] == 'Y')
	{
		/* Check non-adult options */
		for (i = 0; i < OPT_ADULT; i++)
		{
			if (option_name(i) && streq(option_name(i), buf + 2))
			{
				option_set(i, TRUE);
				return (0);
			}
		}
		/* Ignore unknown options */
		return (0);
	}
	/* Process "W:<win>:<flag>:<value>" -- window flags */
	else if (buf[0] == 'W')
	{
		long win, flag, value;

		if (tokenize(buf + 2, 3, zz) == 3)
		{
			win = _tcstol(zz[0], NULL, 0);
			flag = _tcstol(zz[1], NULL, 0);
			value = _tcstol(zz[2], NULL, 0);

			/* Ignore illegal windows */
			/* Hack -- Ignore the main window */
			if ((win <= 0) || (win >= ANGBAND_TERM_MAX)) return (1);

			/* Ignore illegal flags */
			if ((flag < 0) || (flag >= (int)N_ELEMENTS(window_flag_desc))) return (1);

			/* Require a real flag */
			if (window_flag_desc[flag])
			{
				if (value)
				{
					/* Turn flag on */
					op_ptr->window_flag[win] |= (1L << flag);
				}
				else
				{
					/* Turn flag off */
					op_ptr->window_flag[win] &= ~(1L << flag);
				}
			}
			/* Success */
			return (0);
		}
	}
	/* Process "M:<type>:<attr>" -- colors for message-types */
	else if (buf[0] == 'M')
	{
		if (tokenize(buf+2, 2, zz) == 2)
		{
			long type = _tcstol(zz[0], NULL, 0);
			int color = color_char_to_attr(zz[1][0]);

			/* Ignore illegal color */
			if (color < 0) return (1);

			/* Store the color */
			return (message_color_define((u16b)type, (byte)color));
		}
	}
	/* Failure */
	return (1);
}

/*
 * Helper function for "process_pref_file()"
 *
 * Input:
 *   v: output buffer array
 *   f: final character
 *
 * Output:
 *   result
 */
static const _TCHAR *process_pref_file_expr(_TCHAR **sp, _TCHAR *fp)
{
	const _TCHAR *v;

	_TCHAR *b;
	_TCHAR *s;

	char b1 = '[';
	char b2 = ']';

	_TCHAR f = ' ';

	/* Initial */
	s = (*sp);

	/* Skip spaces */
	while (_istspace(*s)) 
		s++;

	/* Save start */
	b = s;

	/* Default */
	v = __T("?o?o?");

	/* Analyze */
	if (*s == b1)
	{
		const _TCHAR *p;
		const _TCHAR *t;

		/* Skip b1 */
		s++;

		/* First */
		t = process_pref_file_expr(&s, &f);

		/* Oops */
		if (!*t)
		{
			/* Nothing */
		}
		/* Function: IOR */
		else if (streq(t, __T("IOR")))
		{
			v = __T("0");
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(t, __T("0")))
					v = __T("1");
			}
		}
		/* Function: AND */
		else if (streq(t, __T("AND")))
		{
			v = __T("1");
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && streq(t, __T("0"))) 
					v = __T("0");
			}
		}
		/* Function: NOT */
		else if (streq(t, __T("NOT")))
		{
			v = __T("1");
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(t, __T("0"))) 
					v = __T("0");
			}
		}

		/* Function: EQU */
		else if (streq(t, __T("EQU")))
		{
			v = __T("1");
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(p, t)) 
					v = __T("0");
			}
		}

		/* Function: LEQ */
		else if (streq(t, __T("LEQ")))
		{
			v = __T("1");
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (_tcscmp(p, t) >= 0)) 
					v = __T("0");
			}
		}

		/* Function: GEQ */
		else if (streq(t, __T("GEQ")))
		{
			v = __T("1");
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (_tcscmp(p, t) <= 0)) 
					v = __T("0");
			}
		}

		/* Oops */
		else
		{
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
		}

		/* Verify ending */
		if (f != b2) v = __T("?x?x?");

		/* Extract final and Terminate */
		if ((f = *s) != 0) 
			*s++ = 0;
	}

	/* Other */
	else
	{
		/* Accept all printables except spaces and brackets */
		while (_istprint((_TCHAR)*s) && !_tcschr(__T(" []"), *s)) ++s;

		/* Extract final and Terminate */
		if ((f = *s) != 0) 
			*s++ = 0;

		/* Variable */
		if (*b == '$')
		{
			/* System */
			if (streq(b+1, __T("SYS")))
			{
				v = ANGBAND_SYS;
			}

			/* Graphics */
			else if (streq(b+1, __T("GRAF")))
			{
				v = ANGBAND_GRAF;
			}

			/* Race */
			else if (streq(b+1, __T("RACE")))
			{
				v = p_name + rp_ptr->name;
			}

			/* Class */
			else if (streq(b+1, __T("CLASS")))
			{
				v = c_name + cp_ptr->name;
			}

			/* Player */
			else if (streq(b+1, __T("PLAYER")))
			{
				v = op_ptr->base_name;
			}

			/* Game version */
			else if (streq(b+1, __T("VERSION")))
			{
				v = VERSION_STRING;
			}
		}

		/* Constant */
		else
		{
			v = b;
		}
	}

	/* Save */
	(*fp) = f;

	/* Save */
	(*sp) = s;

	/* Result */
	return (v);
}

/*
 * Open the "user pref file" and parse it.
 */
static errr process_pref_file_aux(_TCHAR *name)
{
	ang_file *fp;

	_TCHAR buf[1024];
	_TCHAR old[1024];

	int line = -1;

	errr err = 0;

	bool bypass = FALSE;

	/* Open the file */
	fp = file_open(name, MODE_READ, -1);
	if (!fp) return (-1);

	/* Process the file */
	while (file_getl(fp, buf, _countof(buf)))
	{
		/* Count lines */
		line++;

		/* Skip "empty" lines */
		if (!buf[0]) 
			continue;

		/* Skip "blank" lines */
		if (_istspace(buf[0])) 
			continue;

		/* Skip comments */
		if (buf[0] == '#') 
			continue;

		/* Save a copy */
		_tcscpy_s(old, _countof(old), buf);

		/* Process "?:<expr>" */
		if ((buf[0] == '?') && (buf[1] == ':'))
		{
			_TCHAR f;
			const _TCHAR *v;
			_TCHAR *s;

			/* Start */
			s = buf + 2;

			/* Parse the expr */
			v = process_pref_file_expr(&s, &f);

			/* Set flag */
			bypass = (streq(v, __T("0")) ? TRUE : FALSE);

			/* Continue */
			continue;
		}

		/* Apply conditionals */
		if (bypass) continue;

		/* Process "%:<file>" */
		if (buf[0] == '%')
		{
			/* Process that file if allowed */
			(void)process_pref_file(buf + 2);

			/* Continue */
			continue;
		}

		/* Process the line */
		err = process_pref_file_command(buf);

		/* Oops */
		if (err) break;
	}

	/* Error */
	if (err)
	{
		/* Print error message */
		/* ToDo: Add better error messages */
		msg_format(__T("Error %d in line %d of file '%s'."), err, line, name);
		msg_format(__T("Parsing '%s'"), old);
		message_flush();
	}

	/* Close the file */
	file_close(fp);

	/* Result */
	return (err);
}

/*
 * Process the "user pref file" with the given name
 *
 * See the functions above for a list of legal "commands".
 *
 * We also accept the special "?" and "%" directives, which
 * allow conditional evaluation and filename inclusion.
 */
errr process_pref_file(_TCHAR *name)
{
	_TCHAR buf[1024];

	errr err = 0;

	/* Build the filename */
	path_build(buf, _countof(buf), ANGBAND_DIR_PREF, name);

	/* Process the pref file */
	err = process_pref_file_aux(buf);

	/* Stop at parser errors, but not at non-existing file */
	if (err < 1)
	{
		/* Build the filename */
		path_build(buf, _countof(buf), ANGBAND_DIR_USER, name);

		/* Process the pref file */
		err = process_pref_file_aux(buf);
	}
	/* Result */
	return (err);
}
