/*

	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
	and the "Aleph One" developers.
 
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	This license is contained in the file "COPYING",
	which is included with this source code; it is available online at
	http://www.gnu.org/licenses/gpl.html

Feb 5, 2002 (Br'fin (Jeremy Parsons)):
	Default to keyboard and mouse control under Carbon
	for there are no InputSprockets

Apr 30, 2002 (Loren Petrich):
	Converting to a MML-based preferences system

May 16, 2002 (Woody Zenfell):
    Added UI/preferences elements for configurable mouse sensitivity
    Support for "don't auto-recenter" behavior modifier
    Routines to let other code disable/reenable/query behavior modification
   
Jul 21, 2002 (Loren Petrich):
	AS had added some code to fix the OSX preferences behavior;
	I modified it so that it would not be used in the Classic version

Apr 10-22, 2003 (Woody Zenfell):
        Join hinting and autogathering have Preferences entries now
        Being less obnoxious with unrecognized Prefs stuff
        Macintosh Enviroprefs popup style can be set in Preferences file

May 22, 2003 (Woody Zenfell):
	Support for preferences for multiple network game protocols; configurable local game port.

 May 27, 2003 (Gregory Smith):
	Preferences for speex netmic

 August 27, 2003 (Woody Zenfell):
	Preferences for netscript.  Some reworking of index-based Mac FSSpec reading along the way.
 */

/*
 *  preferences.cpp - Preferences handling
 */

#include "cseries.h"
#include "FileHandler.h"

#include "map.h"
#include "shell.h" /* For the screen_mode structure */
#include "interface.h"
#include "SoundManager.h"

#include "preferences.h"
#include "wad.h"
#include "wad_prefs.h"
#include "game_errors.h"
#include "network.h" // for _ethernet, etc.
#include "find_files.h"
#include "game_wad.h" // for set_map_file
#include "screen.h"
#include "fades.h"
#include "extensions.h"
#include "Console.h"
#include "Plugins.h"

#include "InfoTree.h"
#include "StarGameProtocol.h"
#include "RingGameProtocol.h"

#include "tags.h"
#include "Logging.h"

#include <string.h>
#include <stdlib.h>

#include "sdl_dialogs.h"
#include "sdl_fonts.h"
#include "sdl_widgets.h"
#include "images.h"
#include "preference_dialogs.h"
#include "preferences_widgets_sdl.h"
#include "mouse.h"

#include "Music.h"
#include "HTTP.h"
#include "alephversion.h"

#include <cmath>
#include <sstream>
#include <boost/algorithm/hex.hpp>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef __WIN32__
#include <windows.h> // for GetUserName()
#endif

#include "joystick.h"

// 8-bit support is still here if you undefine this, but you'll need to fix it
#define TRUE_COLOR_ONLY 1

using namespace alephone;

static const char sPasswordMask[] = "reverof nohtaram";

static const char *sNetworkGameProtocolNames[] =
	{ // These should match up with _network_game_protocol_ring, etc.
		"ring",
		"star"};

static const size_t NUMBER_OF_NETWORK_GAME_PROTOCOL_NAMES = sizeof(sNetworkGameProtocolNames) / sizeof(sNetworkGameProtocolNames[0]);

// Have the prefs been inited?
static bool PrefsInited = false;

// Global preferences data
struct graphics_preferences_data *graphics_preferences = NULL;
struct network_preferences_data *network_preferences = NULL;
struct player_preferences_data *player_preferences = NULL;
struct input_preferences_data *input_preferences = NULL;
SoundManager::Parameters *sound_preferences = NULL;
struct environment_preferences_data *environment_preferences = NULL;

// LP: fake portable-files stuff
inline short memory_error() { return 0; }

static bool ethernet_active(void);
static std::string get_name_from_system(void);

// LP: getting rid of the (void *) mechanism as inelegant and non-type-safe
static void default_graphics_preferences(graphics_preferences_data *preferences);
static bool validate_graphics_preferences(graphics_preferences_data *preferences);
static void default_network_preferences(network_preferences_data *preferences);
static bool validate_network_preferences(network_preferences_data *preferences);
static void default_player_preferences(player_preferences_data *preferences);
static bool validate_player_preferences(player_preferences_data *preferences);
static void default_input_preferences(input_preferences_data *preferences);
static bool validate_input_preferences(input_preferences_data *preferences);
static void default_environment_preferences(environment_preferences_data *preferences);
static bool validate_environment_preferences(environment_preferences_data *preferences);

void parse_graphics_preferences(InfoTree root, std::string version);
void parse_player_preferences(InfoTree root, std::string version);
void parse_input_preferences(InfoTree root, std::string version);
void parse_sound_preferences(InfoTree root, std::string version);
void parse_network_preferences(InfoTree root, std::string version);
void parse_environment_preferences(InfoTree root, std::string version);

// Prototypes
static void player_dialog(void *arg);
static void online_dialog(void *arg);
static void graphics_dialog(void *arg);
static void sound_dialog(void *arg);
static void controls_dialog(void *arg);
static void environment_dialog(void *arg);
static void plugins_dialog(void *arg);
static void keyboard_dialog(void *arg);
//static void texture_options_dialog(void *arg);

/*
 *  Get user name
 */

static std::string get_name_from_system()
{
#if defined(unix) || (defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) || defined(__OpenBSD__)

	const char *login_name = getlogin();
	std::string login = (login_name ? login_name : "");
	if (login.length())
		return login;

#elif defined(__WIN32__)

	char login[17];
	DWORD len = 17;

	bool hasName = (GetUserName((LPSTR)login, &len) == TRUE);
	if (hasName && strpbrk(login, "\\/:*?\"<>|") == NULL) // Ignore illegal names
		return login;

#else
//#error get_name_from_system() not implemented for this platform
#endif

	return _SJIS("ボブ市民");
}

/*
 *  Ethernet always available
 */

static bool ethernet_active(void)
{
	return true;
}

/*
 *  Main preferences dialog
 */

void handle_preferences(void)
{
	// Save the existing preferences, in case we have to reload them
	write_preferences();

	// Create top-level dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	w_title *w_header = new w_title(_SJIS("初期設定"));
	d.add(w_header);
	w_button *w_player = new w_button(_SJIS("プレーヤー"), player_dialog, &d);
	d.add(w_player);
	w_button *w_online = new w_button(_SJIS("インターネット"), online_dialog, &d);
	d.add(w_online);
	w_button *w_graphics = new w_button(_SJIS("グラフィック"), graphics_dialog, &d);
	d.add(w_graphics);
	w_button *w_sound = new w_button(_SJIS("サウンド"), sound_dialog, &d);
	d.add(w_sound);
	w_button *w_controls = new w_button(_SJIS("操作"), controls_dialog, &d);
	d.add(w_controls);
	w_button *w_environment = new w_button(_SJIS("環境"), environment_dialog, &d);
	d.add(w_environment);

	w_button *w_return = new w_button(_SJIS("戻る"), dialog_cancel, &d);
	d.add(w_return);

	placer->add(w_header);
	placer->add(new w_spacer, true);
	placer->add(w_player);
	placer->add(w_online);
	placer->add(w_graphics);
	placer->add(w_sound);
	placer->add(w_controls);
	placer->add(w_environment);
	placer->add(new w_spacer, true);
	placer->add(w_return);

	d.set_widget_placer(placer);

	// Clear menu screen
	clear_screen();

	// Run dialog
	d.run();

	// Redraw main menu
	display_main_menu();
}

class CrosshairPref : public Bindable<int>
{
  public:
	CrosshairPref(short &pref) : m_pref(pref) {}

	virtual int bind_export()
	{
		return (m_pref - 1);
	}

	virtual void bind_import(int value)
	{
		m_pref = value + 1;
	}

  protected:
	short &m_pref;
};

class ColorComponentPref : public Bindable<int>
{
  public:
	ColorComponentPref(uint16 &pref) : m_pref(pref) {}

	virtual int bind_export()
	{
		return (m_pref >> 12);
	}

	virtual void bind_import(int value)
	{
		m_pref = value << 12;
	}

  protected:
	uint16 &m_pref;
};

class OpacityPref : public Bindable<int>
{
  public:
	OpacityPref(float &pref) : m_pref(pref) {}

	virtual int bind_export()
	{
		return (static_cast<int>(floor(m_pref * 16)));
	}

	virtual void bind_import(int value)
	{
		m_pref = ((float)value / 16.0);
	}

  protected:
	float &m_pref;
};

static const char *shape_labels[3] = {
	_SJIS("十字"), _SJIS("八角形"), NULL};

enum
{
	kCrosshairWidget
};

static std::unique_ptr<BinderSet> crosshair_binders;

struct update_crosshair_display
{
	void operator()(dialog *d)
	{
		crosshair_binders->migrate_all_first_to_second();
	}
};

class w_crosshair_slider : public w_slider
{
  public:
	w_crosshair_slider(int num_items, int sel) : w_slider(num_items, sel)
	{
		init_formatted_value();
	}

	virtual std::string formatted_value(void)
	{
		std::ostringstream ss;
		ss << (selection + 1);
		return ss.str();
	}
};

static void crosshair_dialog(void *arg)
{
	CrosshairData OldCrosshairs = player_preferences->Crosshairs;
	crosshair_binders.reset(new BinderSet);

	dialog *parent = (dialog *)arg;
	(void)parent;

	dialog d;
	vertical_placer *placer = new vertical_placer;
	w_title *w_header = new w_title(_SJIS("クロスヘアー設定"));
	placer->dual_add(w_header, d);
	placer->add(new w_spacer, true);

	placer->dual_add(new w_static_text(_SJIS("クロスヘアーを表示")), d);
	placer->add(new w_spacer, true);

	w_crosshair_display *crosshair_w = new w_crosshair_display();
	placer->dual_add(crosshair_w, d);

	placer->add(new w_spacer, true);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET));
	table->col_flags(0, placeable::kAlignRight);

	// Shape
	w_select *shape_w = new w_select(0, shape_labels);
	SelectSelectorWidget shapeWidget(shape_w);
	Int16Pref shapePref(player_preferences->Crosshairs.Shape);
	crosshair_binders->insert<int>(&shapeWidget, &shapePref);
	table->dual_add(shape_w->label(_SJIS("形")), d);
	table->dual_add(shape_w, d);

	table->add_row(new w_spacer(), true);

	// Thickness
	w_slider *thickness_w = new w_crosshair_slider(7, 0);
	SliderSelectorWidget thicknessWidget(thickness_w);
	CrosshairPref thicknessPref(player_preferences->Crosshairs.Thickness);
	crosshair_binders->insert<int>(&thicknessWidget, &thicknessPref);
	table->dual_add(thickness_w->label(_SJIS("太さ")), d);
	table->dual_add(thickness_w, d);

	// From Center
	w_slider *from_center_w = new w_slider(15, 0);
	SliderSelectorWidget fromCenterWidget(from_center_w);
	Int16Pref fromCenterPref(player_preferences->Crosshairs.FromCenter);
	crosshair_binders->insert<int>(&fromCenterWidget, &fromCenterPref);
	table->dual_add(from_center_w->label(_SJIS("間隔")), d);
	table->dual_add(from_center_w, d);

	// Length
	w_slider *length_w = new w_crosshair_slider(15, 0);
	SliderSelectorWidget lengthWidget(length_w);
	CrosshairPref lengthPref(player_preferences->Crosshairs.Length);
	crosshair_binders->insert<int>(&lengthWidget, &lengthPref);
	table->dual_add(length_w->label(_SJIS("大きさ")), d);
	table->dual_add(length_w, d);

	table->add_row(new w_spacer(), true);
	table->dual_add_row(new w_static_text(_SJIS("色")), d);

	// Color
	w_slider *red_w = new w_percentage_slider(16, 0);
	SliderSelectorWidget redWidget(red_w);
	ColorComponentPref redPref(player_preferences->Crosshairs.Color.red);
	crosshair_binders->insert<int>(&redWidget, &redPref);
	table->dual_add(red_w->label(_SJIS("赤")), d);
	table->dual_add(red_w, d);

	w_slider *green_w = new w_percentage_slider(16, 0);
	SliderSelectorWidget greenWidget(green_w);
	;
	ColorComponentPref greenPref(player_preferences->Crosshairs.Color.green);
	crosshair_binders->insert<int>(&greenWidget, &greenPref);
	table->dual_add(green_w->label(_SJIS("緑")), d);
	table->dual_add(green_w, d);

	w_slider *blue_w = new w_percentage_slider(16, 0);
	SliderSelectorWidget blueWidget(blue_w);
	ColorComponentPref bluePref(player_preferences->Crosshairs.Color.blue);
	crosshair_binders->insert<int>(&blueWidget, &bluePref);
	table->dual_add(blue_w->label(_SJIS("青")), d);
	table->dual_add(blue_w, d);

	table->add_row(new w_spacer(), true);
	table->dual_add_row(new w_static_text(_SJIS("OpenGLのみ（プレビューなし）")), d);

	w_slider *opacity_w = new w_percentage_slider(16, 0);
	SliderSelectorWidget opacityWidget(opacity_w);
	OpacityPref opacityPref(player_preferences->Crosshairs.Opacity);
	crosshair_binders->insert<int>(&opacityWidget, &opacityPref);
	table->dual_add(opacity_w->label(_SJIS("透過度")), d);
	table->dual_add(opacity_w, d);

	placer->add(table, true);
	placer->add(new w_spacer, true);

	horizontal_placer *button_placer = new horizontal_placer;
	w_button *w_accept = new w_button("OK", dialog_ok, &d);
	button_placer->dual_add(w_accept, d);
	w_button *w_cancel = new w_button(_SJIS("キャンセル"), dialog_cancel, &d);
	button_placer->dual_add(w_cancel, d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);
	d.set_processing_function(update_crosshair_display());

	crosshair_binders->migrate_all_second_to_first();

	clear_screen();

	if (d.run() == 0) // Accepted
	{
		crosshair_binders->migrate_all_first_to_second();
		player_preferences->Crosshairs.PreCalced = false;
		write_preferences();
	}
	else
	{
		player_preferences->Crosshairs = OldCrosshairs;
	}

	crosshair_binders.reset(0);
}

/*
 *  Player dialog
 */

enum
{
	NAME_W
};

static void player_dialog(void *arg)
{
	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("プレーヤー設定")), d);
	placer->add(new w_spacer());

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);
	table->col_flags(1, placeable::kAlignLeft);

	w_select *level_w = new w_select(player_preferences->difficulty_level, NULL /*level_labels*/);
	level_w->set_labels_stringset(kDifficultyLevelsStringSetID);
	table->dual_add(level_w->label(_SJIS("難易度")), d);
	table->dual_add(level_w, d);

	table->add_row(new w_spacer(), true);

	table->dual_add_row(new w_static_text(_SJIS("外観")), d);

	w_text_entry *name_w = new w_text_entry(PREFERENCES_NAME_LENGTH, player_preferences->name);
	name_w->set_identifier(NAME_W);
	name_w->set_enter_pressed_callback(dialog_try_ok);
	name_w->set_value_changed_callback(dialog_disable_ok_if_empty);
	name_w->enable_mac_roman_input();
	table->dual_add(name_w->label(_SJIS("名前")), d);
	table->dual_add(name_w, d);

	w_player_color *pcolor_w = new w_player_color(player_preferences->color);
	table->dual_add(pcolor_w->label(_SJIS("色")), d);
	table->dual_add(pcolor_w, d);

	w_player_color *tcolor_w = new w_player_color(player_preferences->team);
	table->dual_add(tcolor_w->label(_SJIS("チーム")), d);
	table->dual_add(tcolor_w, d);

	table->add_row(new w_spacer(), true);

	w_toggle *crosshairs_active_w = new w_toggle(player_preferences->crosshairs_active);
	table->dual_add(crosshairs_active_w->label(_SJIS("クロスヘアーを表示")), d);
	table->dual_add(crosshairs_active_w, d);

	placer->add(table, true);

	placer->add(new w_spacer(), true);

	w_button *crosshair_button = new w_button(_SJIS("クロスヘアー設定"), crosshair_dialog, &d);
	placer->dual_add(crosshair_button, d);

	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;

	w_button *ok_button = new w_button("OK", dialog_ok, &d);
	ok_button->set_identifier(iOK);
	button_placer->dual_add(ok_button, d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Clear screen
	clear_screen();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		const char *name = name_w->get_text();
		if (strcmp(name, player_preferences->name))
		{
			strncpy(player_preferences->name, name, PREFERENCES_NAME_LENGTH);
			player_preferences->name[PREFERENCES_NAME_LENGTH] = '\0';
			changed = true;
		}

		int16 level = static_cast<int16>(level_w->get_selection());
		assert(level >= 0);
		if (level != player_preferences->difficulty_level)
		{
			player_preferences->difficulty_level = level;
			changed = true;
		}

		int16 color = static_cast<int16>(pcolor_w->get_selection());
		assert(color >= 0);
		if (color != player_preferences->color)
		{
			player_preferences->color = color;
			changed = true;
		}

		int16 team = static_cast<int16>(tcolor_w->get_selection());
		assert(team >= 0);
		if (team != player_preferences->team)
		{
			player_preferences->team = team;
			changed = true;
		}

		bool crosshair = crosshairs_active_w->get_selection();
		if (crosshair != player_preferences->crosshairs_active)
		{
			player_preferences->crosshairs_active = crosshair;
			changed = true;
		}

		if (changed)
			write_preferences();
	}
}

/*
 *  Online (lhowon.org) dialog
 */

const int iONLINE_USERNAME_W = 10;
const int iONLINE_PASSWORD_W = 11;
const int iSIGNUP_EMAIL_W = 20;
const int iSIGNUP_USERNAME_W = 21;
const int iSIGNUP_PASSWORD_W = 22;

static void proc_account_link(void *arg)
{
	dialog *d = static_cast<dialog *>(arg);

	HTTPClient conn;
	HTTPClient::parameter_map params;
	w_text_entry *username_w = static_cast<w_text_entry *>(d->get_widget_by_id(iONLINE_USERNAME_W));
	w_text_entry *password_w = static_cast<w_text_entry *>(d->get_widget_by_id(iONLINE_PASSWORD_W));

	params["username"] = username_w->get_text();
	params["password"] = password_w->get_text();
	params["salt"] = "";

	std::string url = A1_METASERVER_SETTINGS_URL;
	if (conn.Post(A1_METASERVER_LOGIN_URL, params))
	{
		std::string token = boost::algorithm::hex(conn.Response());
		url += "?token=" + token;
	}

	toggle_fullscreen(false);
	launch_url_in_browser(url.c_str());
	d->draw();
}

static void signup_dialog_ok(void *arg)
{
	dialog *d = static_cast<dialog *>(arg);
	w_text_entry *email_w = static_cast<w_text_entry *>(d->get_widget_by_id(iSIGNUP_EMAIL_W));
	w_text_entry *login_w = static_cast<w_text_entry *>(d->get_widget_by_id(iSIGNUP_USERNAME_W));
	w_password_entry *password_w = static_cast<w_password_entry *>(d->get_widget_by_id(iSIGNUP_PASSWORD_W));

	// check that fields are filled out
	if (strlen(email_w->get_text()) == 0)
	{
		alert_user(_SJIS("メールアドレスを入力してください"), infoError);
	}
	else if (strlen(login_w->get_text()) == 0)
	{
		alert_user(_SJIS("ユーザ名を入力してください"), infoError);
	}
	else if (strlen(password_w->get_text()) == 0)
	{
		alert_user(_SJIS("パスワードを入力してください"), infoError);
	}
	else
	{
		// send parameters to server
		HTTPClient conn;
		HTTPClient::parameter_map params;
		params["email"] = email_w->get_text();
		params["username"] = login_w->get_text();
		params["password"] = password_w->get_text();

		if (conn.Post(A1_METASERVER_SIGNUP_URL, params))
		{
			if (conn.Response() == "OK")
			{
				// account was created successfully, save username and password
				strncpy(network_preferences->metaserver_login, login_w->get_text(), network_preferences_data::kMetaserverLoginLength);
				strncpy(network_preferences->metaserver_password, password_w->get_text(), network_preferences_data::kMetaserverLoginLength);
				write_preferences();
				d->quit(0);
			}
			else
			{
				alert_user(conn.Response().c_str(), infoError);
			}
		}
		else
		{
			alert_user(_SJIS("サーバー接続中に問題が発生しました"), infoError);
		}
	}
}

static void signup_dialog(void *arg)
{
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("アカウント登録")), d);
	placer->add(new w_spacer());

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);
	table->col_flags(1, placeable::kAlignLeft);

	w_text_entry *email_w = new w_text_entry(256, "");
	email_w->set_identifier(iSIGNUP_EMAIL_W);
	table->dual_add(email_w->label(_SJIS("メールアドレス")), d);
	table->dual_add(email_w, d);

	w_text_entry *login_w = new w_text_entry(network_preferences_data::kMetaserverLoginLength, network_preferences->metaserver_login);
	login_w->set_identifier(iSIGNUP_USERNAME_W);
	table->dual_add(login_w->label(_SJIS("ユーザ名")), d);
	table->dual_add(login_w, d);

	w_password_entry *password_w = new w_password_entry(network_preferences_data::kMetaserverLoginLength, network_preferences->metaserver_password);
	password_w->set_identifier(iSIGNUP_PASSWORD_W);
	table->dual_add(password_w->label(_SJIS("パスワード")), d);
	table->dual_add(password_w, d);

	table->add_row(new w_spacer(), true);
	placer->add(table, true);

	horizontal_placer *button_placer = new horizontal_placer;

	w_button *ok_button = new w_button(_SJIS("登録"), signup_dialog_ok, &d);
	ok_button->set_identifier(iOK);
	button_placer->dual_add(ok_button, d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	clear_screen();

	if (d.run() == 0)
	{
		// account was successfully created, update parent fields with new account info
		dialog *parent = static_cast<dialog *>(arg);
		w_text_entry *login_w = static_cast<w_text_entry *>(parent->get_widget_by_id(iONLINE_USERNAME_W));
		login_w->set_text(network_preferences->metaserver_login);
		w_password_entry *password_w = static_cast<w_password_entry *>(parent->get_widget_by_id(iONLINE_PASSWORD_W));
		password_w->set_text(network_preferences->metaserver_password);
	}
}

static void online_dialog(void *arg)
{
	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("インターネットゲーム設定")), d);
	placer->add(new w_spacer());

	tab_placer *tabs = new tab_placer();

	std::vector<std::string> labels;
	labels.push_back(_SJIS("アカウント"));
	labels.push_back(_SJIS("ゲーム前ロビー"));
	labels.push_back(_SJIS("統計"));
	w_tab *tab_w = new w_tab(labels, tabs);

	placer->dual_add(tab_w, d);
	placer->add(new w_spacer(), true);

	vertical_placer *account = new vertical_placer();
	table_placer *account_table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	account_table->col_flags(0, placeable::kAlignRight);
	account_table->col_flags(1, placeable::kAlignLeft);

	w_text_entry *login_w = new w_text_entry(network_preferences_data::kMetaserverLoginLength, network_preferences->metaserver_login);
	login_w->set_identifier(iONLINE_USERNAME_W);
	account_table->dual_add(login_w->label(_SJIS("ユーザ名")), d);
	account_table->dual_add(login_w, d);

	w_password_entry *password_w = new w_password_entry(network_preferences_data::kMetaserverLoginLength, network_preferences->metaserver_password);
	password_w->set_identifier(iONLINE_PASSWORD_W);
	account_table->dual_add(password_w->label(_SJIS("パスワード")), d);
	account_table->dual_add(password_w, d);

	w_hyperlink *account_link_w = new w_hyperlink("", _SJIS("私のlhowon.orgアカウントページへ飛ぶ"));
	account_link_w->set_callback(proc_account_link, &d);
	account_table->dual_add_row(account_link_w, d);

	account_table->add_row(new w_spacer(), true);

	w_button *signup_button = new w_button(_SJIS("登録"), signup_dialog, &d);
	account_table->dual_add_row(signup_button, d);

	account_table->add_row(new w_spacer(), true);

	account->add(account_table, true);

	vertical_placer *lobby = new vertical_placer();
	table_placer *lobby_table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	lobby_table->col_flags(0, placeable::kAlignRight);
	lobby_table->col_flags(1, placeable::kAlignLeft);

	w_text_entry *name_w = new w_text_entry(PREFERENCES_NAME_LENGTH, player_preferences->name);
	name_w->set_identifier(NAME_W);
	name_w->set_enter_pressed_callback(dialog_try_ok);
	name_w->set_value_changed_callback(dialog_disable_ok_if_empty);
	name_w->enable_mac_roman_input();
	lobby_table->dual_add(name_w->label(_SJIS("名前")), d);
	lobby_table->dual_add(name_w, d);

	w_enabling_toggle *custom_colors_w = new w_enabling_toggle(network_preferences->use_custom_metaserver_colors);
	lobby_table->dual_add(custom_colors_w->label(_SJIS("カスタムチャットカラー")), d);
	lobby_table->dual_add(custom_colors_w, d);

	w_color_picker *primary_w = new w_color_picker(network_preferences->metaserver_colors[0]);
	lobby_table->dual_add(primary_w->label(_SJIS("プライマリ")), d);
	lobby_table->dual_add(primary_w, d);

	w_color_picker *secondary_w = new w_color_picker(network_preferences->metaserver_colors[1]);
	lobby_table->dual_add(secondary_w->label(_SJIS("セカンダリ")), d);
	lobby_table->dual_add(secondary_w, d);

	custom_colors_w->add_dependent_widget(primary_w);
	custom_colors_w->add_dependent_widget(secondary_w);

	w_toggle *mute_guests_w = new w_toggle(network_preferences->mute_metaserver_guests);
	lobby_table->dual_add(mute_guests_w->label(_SJIS("全てのゲストチャットを表示ししない")), d);
	lobby_table->dual_add(mute_guests_w, d);

	lobby_table->add_row(new w_spacer(), true);

	w_toggle *join_meta_w = new w_toggle(network_preferences->join_metaserver_by_default);
	lobby_table->dual_add(join_meta_w->label(_SJIS("ゲーム前ロビーにデフォルトで参加する")), d);
	lobby_table->dual_add(join_meta_w, d);

	lobby_table->add_row(new w_spacer(), true);

	lobby->add(lobby_table, true);

	vertical_placer *stats = new vertical_placer();
	stats->dual_add(new w_hyperlink(A1_LEADERBOARD_URL, _SJIS("リーダーボードへ飛ぶ")), d);
	stats->add(new w_spacer(), true);

	horizontal_placer *stats_box = new horizontal_placer();

	w_toggle *allow_stats_w = new w_toggle(network_preferences->allow_stats);
	stats_box->dual_add(allow_stats_w, d);
	stats_box->dual_add(allow_stats_w->label(_SJIS("Lhowon.orgへ統計を送信")), d);

	stats->add(stats_box, true);
	stats->add(new w_spacer(), true);

	stats->dual_add(new w_static_text(_SJIS("リーダーボードで競争するためには、")), d);
	stats->dual_add(new w_static_text(_SJIS("オンラインアカウントを作成の上、統計プラグインを")), d);
	stats->dual_add(new w_static_text(_SJIS("インストールして有効にする必要があります。")), d);

	stats->add(new w_spacer(), true);
	stats->dual_add(new w_button(_SJIS("プラグイン"), plugins_dialog, &d), d);

	stats->add(new w_spacer(), true);

	tabs->add(account, true);
	tabs->add(lobby, true);
	tabs->add(stats, true);

	placer->add(tabs, true);
	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;

	w_button *ok_button = new w_button("OK", dialog_ok, &d);
	ok_button->set_identifier(iOK);
	button_placer->dual_add(ok_button, d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Clear screen
	clear_screen();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		const char *name = name_w->get_text();
		if (strcmp(name, player_preferences->name))
		{
			strncpy(player_preferences->name, name, PREFERENCES_NAME_LENGTH);
			player_preferences->name[PREFERENCES_NAME_LENGTH] = '\0';
			changed = true;
		}

		const char *metaserver_login = login_w->get_text();
		if (strcmp(metaserver_login, network_preferences->metaserver_login))
		{
			strncpy(network_preferences->metaserver_login, metaserver_login, network_preferences_data::kMetaserverLoginLength - 1);
			network_preferences->metaserver_login[network_preferences_data::kMetaserverLoginLength - 1] = '\0';
			changed = true;
		}

		// clear password if login has been cleared
		if (!strlen(metaserver_login))
		{
			if (strlen(network_preferences->metaserver_password))
			{
				network_preferences->metaserver_password[0] = '\0';
				changed = true;
			}
		}
		else
		{
			const char *metaserver_password = password_w->get_text();
			if (strcmp(metaserver_password, network_preferences->metaserver_password))
			{
				strncpy(network_preferences->metaserver_password, metaserver_password, network_preferences_data::kMetaserverLoginLength - 1);
				network_preferences->metaserver_password[network_preferences_data::kMetaserverLoginLength - 1] = '\0';
				changed = true;
			}
		}

		bool use_custom_metaserver_colors = custom_colors_w->get_selection();
		if (use_custom_metaserver_colors != network_preferences->use_custom_metaserver_colors)
		{
			network_preferences->use_custom_metaserver_colors = use_custom_metaserver_colors;
			changed = true;
		}

		if (use_custom_metaserver_colors)
		{
			rgb_color primary_color = primary_w->get_selection();
			if (primary_color.red != network_preferences->metaserver_colors[0].red || primary_color.green != network_preferences->metaserver_colors[0].green || primary_color.blue != network_preferences->metaserver_colors[0].blue)
			{
				network_preferences->metaserver_colors[0] = primary_color;
				changed = true;
			}

			rgb_color secondary_color = secondary_w->get_selection();
			if (secondary_color.red != network_preferences->metaserver_colors[1].red || secondary_color.green != network_preferences->metaserver_colors[1].green || secondary_color.blue != network_preferences->metaserver_colors[1].blue)
			{
				network_preferences->metaserver_colors[1] = secondary_color;
				changed = true;
			}
		}

		bool mute_metaserver_guests = mute_guests_w->get_selection() == 1;
		if (mute_metaserver_guests != network_preferences->mute_metaserver_guests)
		{
			network_preferences->mute_metaserver_guests = mute_metaserver_guests;
			changed = true;
		}

		bool join_meta = join_meta_w->get_selection() == 1;
		if (join_meta != network_preferences->join_metaserver_by_default)
		{
			network_preferences->join_metaserver_by_default = join_meta;
			changed = true;
		}

		bool allow_stats = allow_stats_w->get_selection() == 1;
		if (allow_stats != network_preferences->allow_stats)
		{
			network_preferences->allow_stats = allow_stats;
			Plugins::instance()->invalidate();
			changed = true;
		}

		if (changed)
			write_preferences();
	}
}

/*
 *  Handle graphics dialog
 */
#ifdef TRUE_COLOR_ONLY
static const char *depth_labels[3] = {
	_SJIS("16ビット"), _SJIS("32ビット"), NULL};
#else
static const char *depth_labels[4] = {
	_SJIS("8ビット"), _SJIS("16ビット"), _SJIS("32ビット"), NULL};
#endif

static const char *resolution_labels[3] = {
	_SJIS("低"), _SJIS("高"), NULL};

static const char *sw_alpha_blending_labels[4] = {
	_SJIS("なし"), _SJIS("速度優先"), _SJIS("画質優先"), NULL};

static const char *sw_sdl_driver_labels[5] = {
	_SJIS("デフォルト"), _SJIS("なし"), "Direct3D", "OpenGL", NULL};

static const char *gamma_labels[9] = {
	_SJIS("とても暗い"), _SJIS("暗い"), _SJIS("やや暗い"), _SJIS("通常"), _SJIS("やや明るい"), _SJIS("明るい"), _SJIS("より明るい"), _SJIS("とても明るい"), NULL};

static const char *renderer_labels[] = {
	_SJIS("ソフトウェア"), "OpenGL", NULL};

static const char *hud_scale_labels[] = {
	_SJIS("通常"), _SJIS("２倍"), _SJIS("最大"), NULL};

static const char *term_scale_labels[] = {
	_SJIS("通常"), _SJIS("２倍"), _SJIS("最大"), NULL};

static const char *mouse_accel_labels[] = {
	_SJIS("切"), _SJIS("クラシック"), NULL};

static const char *max_saves_labels[] = {
	"20", "100", "500", _SJIS("無制限"), NULL};

static const uint32 max_saves_values[] = {
	20, 100, 500, 0};

enum
{
	iRENDERING_SYSTEM = 1000
};

static const vector<string> build_stringvector_from_cstring_array(const char **label_array)
{
	std::vector<std::string> label_vector;
	for (int i = 0; label_array[i] != NULL; ++i)
		label_vector.push_back(std::string(label_array[i]));

	return label_vector;
}

static void software_rendering_options_dialog(void *arg)
{
	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("ソフトウェアレンダリング時のオプション")), d);
	placer->add(new w_spacer(), true);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

#ifdef TRUE_COLOR_ONLY
	w_select *depth_w = new w_select(graphics_preferences->screen_mode.bit_depth == 16 ? 0 : 1, depth_labels);
#else
	w_select *depth_w = new w_select(graphics_preferences->screen_mode.bit_depth == 8 ? 0 : graphics_preferences->screen_mode.bit_depth == 16 ? 1 : 2, depth_labels);
#endif
	table->dual_add(depth_w->label(_SJIS("色深度")), d);
	table->dual_add(depth_w, d);

	w_toggle *resolution_w = new w_toggle(graphics_preferences->screen_mode.high_resolution, resolution_labels);
	table->dual_add(resolution_w->label(_SJIS("解像度")), d);
	table->dual_add(resolution_w, d);

	table->add_row(new w_spacer(), true);

	w_select *sw_alpha_blending_w = new w_select(graphics_preferences->software_alpha_blending, sw_alpha_blending_labels);
	table->dual_add(sw_alpha_blending_w->label(_SJIS("液面を半透明化")), d);
	table->dual_add(sw_alpha_blending_w, d);

	w_select *sw_driver_w = new w_select(graphics_preferences->software_sdl_driver, sw_sdl_driver_labels);
	table->dual_add(sw_driver_w->label(_SJIS("アクセラーション")), d);
	table->dual_add(sw_driver_w, d);

	placer->add(table, true);

	placer->add(new w_spacer(), true);
	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);
	// Clear screen
	clear_screen();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

#ifdef TRUE_COLOR_ONLY
		int depth = (depth_w->get_selection() == 0 ? 16 : 32);
#else
		int depth = (depth_w->get_selection() == 0 ? 8 : depth_w->get_selection() == 1 ? 16 : 32);
#endif
		if (depth != graphics_preferences->screen_mode.bit_depth)
		{
			graphics_preferences->screen_mode.bit_depth = depth;
			changed = true;
			// don't change mode now; it will be changed when the game starts
		}

		bool hi_res = resolution_w->get_selection() != 0;
		if (hi_res != graphics_preferences->screen_mode.high_resolution)
		{
			graphics_preferences->screen_mode.high_resolution = hi_res;
			changed = true;
		}

		if (sw_alpha_blending_w->get_selection() != graphics_preferences->software_alpha_blending)
		{
			graphics_preferences->software_alpha_blending = sw_alpha_blending_w->get_selection();
			changed = true;
		}

		if (sw_driver_w->get_selection() != graphics_preferences->software_sdl_driver)
		{
			graphics_preferences->software_sdl_driver = sw_driver_w->get_selection();
			changed = true;
		}

		if (changed)
			write_preferences();
	}
}

// ZZZ addition: bounce to correct renderer-config box based on selected rendering system.
static void rendering_options_dialog_demux(void *arg)
{
	int theSelectedRenderer = get_selection_control_value((dialog *)arg, iRENDERING_SYSTEM) - 1;

	switch (theSelectedRenderer)
	{
	case _no_acceleration:
		software_rendering_options_dialog(arg);
		break;

	case _opengl_acceleration:
		OpenGLDialog::Create(theSelectedRenderer)->OpenGLPrefsByRunning();
		break;

	default:
		assert(false);
		break;
	}
}

std::vector<std::string> build_resolution_labels()
{
	std::vector<std::string> result;
	bool first_mode = true;
	for (std::vector<std::pair<int, int>>::const_iterator it = Screen::instance()->GetModes().begin(); it != Screen::instance()->GetModes().end(); ++it)
	{
		std::ostringstream os;
		os << it->first << "x" << it->second;
		if (first_mode)
		{
			result.push_back(_SJIS("自動"));
			first_mode = false;
		}
		result.push_back(os.str());
	}

	return result;
}

static void graphics_dialog(void *arg)
{
	dialog *parent = (dialog *)arg;

	// Create dialog
	dialog d;

	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("グラフィック設定")), d);
	placer->add(new w_spacer(), true);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

	w_select *renderer_w = new w_select(graphics_preferences->screen_mode.acceleration, renderer_labels);
	renderer_w->set_identifier(iRENDERING_SYSTEM);
#ifndef HAVE_OPENGL
	renderer_w->set_selection(_no_acceleration);
	renderer_w->set_enabled(false);
#endif
	table->dual_add(renderer_w->label(_SJIS("レンダリングシステム")), d);
	table->dual_add(renderer_w, d);

	w_select_popup *size_w = new w_select_popup();
	size_w->set_labels(build_resolution_labels());
	if (graphics_preferences->screen_mode.auto_resolution)
		size_w->set_selection(0);
	else
		size_w->set_selection(Screen::instance()->FindMode(graphics_preferences->screen_mode.width, graphics_preferences->screen_mode.height) + 1);
	table->dual_add(size_w->label(_SJIS("画面の大きさ")), d);
	table->dual_add(size_w, d);

	w_toggle *high_dpi_w = NULL;
	high_dpi_w = new w_toggle(graphics_preferences->screen_mode.high_dpi);
#if (defined(__APPLE__) && defined(__MACH__))
	// SDL's DPI support only enabled on macOS
	table->dual_add(high_dpi_w->label(_SJIS("高DPIを使う")), d);
	table->dual_add(high_dpi_w, d);
#endif

	w_toggle *fixh_w = new w_toggle(!graphics_preferences->screen_mode.fix_h_not_v);
	table->dual_add(fixh_w->label(_SJIS("垂直方向の視野を制限")), d);
	table->dual_add(fixh_w, d);

	w_toggle *bob_w = new w_toggle(graphics_preferences->screen_mode.camera_bob);
	table->dual_add(bob_w->label(_SJIS("カメラ振動")), d);
	table->dual_add(bob_w, d);

	w_select_popup *gamma_w = new w_select_popup();
	gamma_w->set_labels(build_stringvector_from_cstring_array(gamma_labels));
	gamma_w->set_selection(graphics_preferences->screen_mode.gamma_level);
	table->dual_add(gamma_w->label(_SJIS("明るさ")), d);
	table->dual_add(gamma_w, d);

	table->add_row(new w_spacer(), true);

	w_toggle *fullscreen_w = new w_toggle(!graphics_preferences->screen_mode.fullscreen);
	table->dual_add(fullscreen_w->label(_SJIS("ウィンドウモード")), d);
	table->dual_add(fullscreen_w, d);

	table->add_row(new w_spacer(), true);
	table->dual_add_row(new w_static_text(_SJIS("ヘッドアップディスプレイ(HUD)")), d);
	w_enabling_toggle *hud_w = new w_enabling_toggle(graphics_preferences->screen_mode.hud);
	table->dual_add(hud_w->label(_SJIS("HUDを表示")), d);
	table->dual_add(hud_w, d);

	w_select_popup *hud_scale_w = new w_select_popup();
	hud_scale_w->set_labels(build_stringvector_from_cstring_array(hud_scale_labels));
	hud_scale_w->set_selection(graphics_preferences->screen_mode.hud_scale_level);
	table->dual_add(hud_scale_w->label(_SJIS("HUDサイズ")), d);
	table->dual_add(hud_scale_w, d);
	hud_w->add_dependent_widget(hud_scale_w);

	w_select_popup *term_scale_w = new w_select_popup();
	term_scale_w->set_labels(build_stringvector_from_cstring_array(term_scale_labels));
	term_scale_w->set_selection(graphics_preferences->screen_mode.term_scale_level);
	table->dual_add(term_scale_w->label(_SJIS("ターミナルサイズ")), d);
	table->dual_add(term_scale_w, d);

	w_toggle *map_w = new w_toggle(graphics_preferences->screen_mode.translucent_map);
	table->dual_add(map_w->label(_SJIS("マップをオーバーレイ表示にする")), d);
	table->dual_add(map_w, d);

	placer->add(table, true);

	placer->add(new w_spacer(), true);
	placer->dual_add(new w_button(_SJIS("レンダリングオプション"), rendering_options_dialog_demux, &d), d);
	placer->add(new w_spacer(), true);

#ifndef HAVE_OPENGL
	expand_app_variables(temporary, _SJIS("この$appName$のコピーは、OpenGLをサポートしていません。"));
	placer->dual_add(new w_static_text(temporary), d);
#endif
	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Clear screen
	clear_screen();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		bool fullscreen = fullscreen_w->get_selection() == 0;
		if (fullscreen != graphics_preferences->screen_mode.fullscreen)
		{
			graphics_preferences->screen_mode.fullscreen = fullscreen;
			changed = true;
		}

		short renderer = static_cast<short>(renderer_w->get_selection());
		assert(renderer >= 0);
		if (renderer != graphics_preferences->screen_mode.acceleration)
		{
			graphics_preferences->screen_mode.acceleration = renderer;
			if (renderer)
				graphics_preferences->screen_mode.bit_depth = 32;
			changed = true;
		}

		short resolution = static_cast<short>(size_w->get_selection());
		if (resolution == 0)
		{
			if (!graphics_preferences->screen_mode.auto_resolution)
			{
				graphics_preferences->screen_mode.auto_resolution = true;
				changed = true;
			}
		}
		else if (Screen::instance()->ModeWidth(resolution - 1) != graphics_preferences->screen_mode.width || Screen::instance()->ModeHeight(resolution - 1) != graphics_preferences->screen_mode.height || graphics_preferences->screen_mode.auto_resolution)
		{
			graphics_preferences->screen_mode.width = Screen::instance()->ModeWidth(resolution - 1);
			graphics_preferences->screen_mode.height = Screen::instance()->ModeHeight(resolution - 1);
			graphics_preferences->screen_mode.auto_resolution = false;
			changed = true;
		}

		bool high_dpi = high_dpi_w->get_selection() != 0;
		if (high_dpi != graphics_preferences->screen_mode.high_dpi)
		{
			graphics_preferences->screen_mode.high_dpi = high_dpi;
			changed = true;
		}

		short gamma = static_cast<short>(gamma_w->get_selection());
		if (gamma != graphics_preferences->screen_mode.gamma_level)
		{
			graphics_preferences->screen_mode.gamma_level = gamma;
			changed = true;
		}

		bool fix_h_not_v = fixh_w->get_selection() == 0;
		if (fix_h_not_v != graphics_preferences->screen_mode.fix_h_not_v)
		{
			graphics_preferences->screen_mode.fix_h_not_v = fix_h_not_v;
			changed = true;
		}

		bool hud = hud_w->get_selection() != 0;
		if (hud != graphics_preferences->screen_mode.hud)
		{
			graphics_preferences->screen_mode.hud = hud;
			changed = true;
		}

		short hud_scale = static_cast<short>(hud_scale_w->get_selection());
		if (hud_scale != graphics_preferences->screen_mode.hud_scale_level)
		{
			graphics_preferences->screen_mode.hud_scale_level = hud_scale;
			changed = true;
		}

		short term_scale = static_cast<short>(term_scale_w->get_selection());
		if (term_scale != graphics_preferences->screen_mode.term_scale_level)
		{
			graphics_preferences->screen_mode.term_scale_level = term_scale;
			changed = true;
		}

		bool translucent_map = map_w->get_selection() != 0;
		if (translucent_map != graphics_preferences->screen_mode.translucent_map)
		{
			graphics_preferences->screen_mode.translucent_map = translucent_map;
			changed = true;
		}

		bool camera_bob = bob_w->get_selection() != 0;
		if (camera_bob != graphics_preferences->screen_mode.camera_bob)
		{
			graphics_preferences->screen_mode.camera_bob = camera_bob;
			changed = true;
		}

		if (changed)
		{
			write_preferences();
			change_screen_mode(&graphics_preferences->screen_mode, true);
			clear_screen(true);
			parent->layout();
			parent->draw(); // DirectX seems to need this
		}
	}
}

/*
 *  Sound dialog
 */

class w_toggle *stereo_w, *dynamic_w;

class w_stereo_toggle : public w_toggle
{
  public:
	w_stereo_toggle(bool selection) : w_toggle(selection) {}

	void selection_changed(void)
	{
		// Turning off stereo turns off dynamic tracking
		w_toggle::selection_changed();
		if (selection == false)
			dynamic_w->set_selection(false);
	}
};

class w_dynamic_toggle : public w_toggle
{
  public:
	w_dynamic_toggle(bool selection) : w_toggle(selection) {}

	void selection_changed(void)
	{
		// Turning on dynamic tracking turns on stereo
		w_toggle::selection_changed();
		if (selection == true)
			stereo_w->set_selection(true);
	}
};

static const char *channel_labels[] = {"1", "2", "4", "8", "16", "32", NULL};

class w_volume_slider : public w_percentage_slider
{
  public:
	w_volume_slider(int vol) : w_percentage_slider(NUMBER_OF_SOUND_VOLUME_LEVELS, vol) {}
	~w_volume_slider() {}

	void item_selected(void)
	{
		SoundManager::instance()->TestVolume(selection, _snd_adjust_volume);
	}
};

static void sound_dialog(void *arg)
{
	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("サウンド設定")), d);
	placer->add(new w_spacer(), true);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

	static const char *quality_labels[3] = {_SJIS("8ビット"), _SJIS("16ビット"), NULL};
	w_toggle *quality_w = new w_toggle(TEST_FLAG(sound_preferences->flags, _16bit_sound_flag), quality_labels);
	table->dual_add(quality_w->label(_SJIS("音質")), d);
	table->dual_add(quality_w, d);

	stereo_w = new w_stereo_toggle(sound_preferences->flags & _stereo_flag);
	table->dual_add(stereo_w->label(_SJIS("ステレオ")), d);
	table->dual_add(stereo_w, d);

	dynamic_w = new w_dynamic_toggle(TEST_FLAG(sound_preferences->flags, _dynamic_tracking_flag));
	table->dual_add(dynamic_w->label(_SJIS("音の定位を変化させる")), d);
	table->dual_add(dynamic_w, d);

	w_toggle *ambient_w = new w_toggle(TEST_FLAG(sound_preferences->flags, _ambient_sound_flag));
	table->dual_add(ambient_w->label(_SJIS("周辺サウンド")), d);
	table->dual_add(ambient_w, d);

	w_toggle *more_w = new w_toggle(TEST_FLAG(sound_preferences->flags, _more_sounds_flag));
	table->dual_add(more_w->label(_SJIS("追加のサウンド")), d);
	table->dual_add(more_w, d);

	w_toggle *button_sounds_w = new w_toggle(TEST_FLAG(input_preferences->modifiers, _inputmod_use_button_sounds));
	table->dual_add(button_sounds_w->label(_SJIS("インターフェースボタンのサウンド")), d);
	table->dual_add(button_sounds_w, d);

	w_select *channels_w = new w_select(static_cast<int>(std::floor(std::log(static_cast<float>(sound_preferences->channel_count)) / std::log(2.0) + 0.5)), channel_labels);
	table->dual_add(channels_w->label(_SJIS("チャンネル数")), d);
	table->dual_add(channels_w, d);

	w_volume_slider *volume_w = new w_volume_slider(sound_preferences->volume);
	table->dual_add(volume_w->label(_SJIS("音量")), d);
	table->dual_add(volume_w, d);

	w_slider *music_volume_w = new w_percentage_slider(NUMBER_OF_SOUND_VOLUME_LEVELS, sound_preferences->music);
	table->dual_add(music_volume_w->label(_SJIS("音楽の音量")), d);
	table->dual_add(music_volume_w, d);

	table->add_row(new w_spacer(), true);
	table->dual_add_row(new w_static_text(_SJIS("ネットワークマイク")), d);

	w_toggle *mute_while_transmitting_w = new w_toggle(!sound_preferences->mute_while_transmitting);
	table->dual_add(mute_while_transmitting_w->label(_SJIS("ヘッドセットマイクモード")), d);
	table->dual_add(mute_while_transmitting_w, d);

	table->add_row(new w_spacer(), true);
	table->dual_add_row(new w_static_text(_SJIS("実験中のサウンドオプション")), d);
	w_toggle *zrd_w = new w_toggle(TEST_FLAG(sound_preferences->flags, _zero_restart_delay));
	table->dual_add(zrd_w->label(_SJIS("リスタートディレイをゼロに")), d);
	table->dual_add(zrd_w, d);

	placer->add(table, true);

	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);
	// Clear screen
	clear_screen();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		uint16 flags = 0;
		if (quality_w->get_selection())
			flags |= _16bit_sound_flag;
		if (stereo_w->get_selection())
			flags |= _stereo_flag;
		if (dynamic_w->get_selection())
			flags |= _dynamic_tracking_flag;
		if (ambient_w->get_selection())
			flags |= _ambient_sound_flag;
		if (more_w->get_selection())
			flags |= _more_sounds_flag;
		if (zrd_w->get_selection())
			flags |= _zero_restart_delay;

		if (flags != sound_preferences->flags)
		{
			sound_preferences->flags = flags;
			changed = true;
		}

		flags = input_preferences->modifiers & ~_inputmod_use_button_sounds;
		if (button_sounds_w->get_selection())
			flags |= _inputmod_use_button_sounds;
		if (flags != input_preferences->modifiers)
		{
			input_preferences->modifiers = flags;
			changed = true;
		}

		int16 channel_count = 1 << (channels_w->get_selection() == UNONE ? 1 : channels_w->get_selection());
		if (channel_count != sound_preferences->channel_count)
		{
			sound_preferences->channel_count = channel_count;
			changed = true;
		}

		int volume = volume_w->get_selection();
		if (volume != sound_preferences->volume)
		{
			sound_preferences->volume = volume;
			changed = true;
		}

		int music_volume = music_volume_w->get_selection();
		if (music_volume != sound_preferences->music)
		{
			sound_preferences->music = music_volume;
			changed = true;
		}

		bool mute_while_transmitting = !mute_while_transmitting_w->get_selection();
		if (mute_while_transmitting != sound_preferences->mute_while_transmitting)
		{
			sound_preferences->mute_while_transmitting = mute_while_transmitting;
			changed = true;
		}

		if (changed)
		{
			//			set_sound_manager_parameters(sound_preferences);
			SoundManager::instance()->SetParameters(*sound_preferences);
			write_preferences();
		}
	}
}

/*
 *  Controls dialog
 */

const float kMinSensitivityLog = -3.0f;
const float kMaxSensitivityLog = 3.0f;
const float kSensitivityLogRange = kMaxSensitivityLog - kMinSensitivityLog;

class w_sens_slider : public w_slider
{
  public:
	w_sens_slider(int num_items, int sel) : w_slider(num_items, sel)
	{
		init_formatted_value();
	}

	virtual std::string formatted_value(void)
	{
		std::ostringstream ss;
		float val = std::exp(selection * kSensitivityLogRange / 1000.0f + kMinSensitivityLog);
		if (val >= 1.f)
			ss.precision(4);
		else if (val >= 0.1f)
			ss.precision(3);
		else if (val >= 0.01f)
			ss.precision(2);
		else
			ss.precision(1);
		ss << std::showpoint << val;
		return ss.str();
	}
};

class w_deadzone_slider : public w_slider
{
  public:
	w_deadzone_slider(int num_items, int sel) : w_slider(num_items, sel)
	{
		init_formatted_value();
	}

	virtual std::string formatted_value(void)
	{
		std::ostringstream ss;
		ss << selection << "%";
		return ss.str();
	}
};

const int NUM_KEYS = 21;

static const char *action_name[NUM_KEYS] = {
	_SJIS("前進"), _SJIS("後退"), _SJIS("左を向く"), _SJIS("右を向く"), _SJIS("左に移動"), _SJIS("右に移動"),
	_SJIS("左を見る"), _SJIS("右を見る"), _SJIS("上を見る"), _SJIS("下を見る"), _SJIS("正面を見る"),
	_SJIS("前の武器"), _SJIS("次の武器"), _SJIS("主砲"), _SJIS("副砲"),
	_SJIS("サイドステップ"), _SJIS("走る／泳ぐ"), _SJIS("見る"),
	_SJIS("アクション"), _SJIS("地図"), _SJIS("マイク")};

static key_binding_map default_key_bindings = {
	{0, {SDL_SCANCODE_W, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE + SDL_CONTROLLER_AXIS_LEFTY)}},
	{1, {SDL_SCANCODE_S, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_LEFTY)}},
	{2, {SDL_SCANCODE_LEFT, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE + SDL_CONTROLLER_AXIS_RIGHTX)}},
	{3, {SDL_SCANCODE_RIGHT, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_RIGHTX)}},
	{4, {SDL_SCANCODE_A, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE + SDL_CONTROLLER_AXIS_LEFTX)}},
	{5, {SDL_SCANCODE_D, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_LEFTX)}},
	{6, {SDL_SCANCODE_Q, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_LEFT)}},
	{7, {SDL_SCANCODE_E, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_RIGHT)}},
	{8, {SDL_SCANCODE_UP, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE + SDL_CONTROLLER_AXIS_RIGHTY)}},
	{9, {SDL_SCANCODE_DOWN, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_RIGHTY)}},
	{10, {SDL_SCANCODE_V, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_RIGHTSTICK)}},
	{11, {SDL_SCANCODE_F, static_cast<SDL_Scancode>(AO_SCANCODE_MOUSESCROLL_UP), static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_LEFTSHOULDER)}},
	{12, {SDL_SCANCODE_R, static_cast<SDL_Scancode>(AO_SCANCODE_MOUSESCROLL_DOWN), static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)}},
	{13, {SDL_SCANCODE_SPACE, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_MOUSE_BUTTON + SDL_BUTTON_LEFT - 1), static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_TRIGGERRIGHT)}},
	{14, {SDL_SCANCODE_LSHIFT, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_MOUSE_BUTTON + SDL_BUTTON_RIGHT - 1), static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE + SDL_CONTROLLER_AXIS_TRIGGERLEFT)}},
	{15, {SDL_SCANCODE_LALT}},
	{16, {SDL_SCANCODE_LCTRL, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_LEFTSTICK)}},
	{17, {SDL_SCANCODE_LGUI}},
	{18, {SDL_SCANCODE_TAB, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_A)}},
	{19, {SDL_SCANCODE_M, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_X)}},
	{20, {SDL_SCANCODE_GRAVE, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_Y)}},
};

static const char *shell_action_name[NUMBER_OF_SHELL_KEYS] = {
	_SJIS("前の道具"), _SJIS("次の道具"), _SJIS("主観／客観切り替え"), _SJIS("ボリュームを上げる"), _SJIS("ボリュームを下げる"), _SJIS("マップを拡大"), _SJIS("マップを縮小"), _SJIS("FPSを表示"), _SJIS("チャット／コンソール"), _SJIS("ネットワーク状況")};

static key_binding_map default_shell_key_bindings = {
	{0, {SDL_SCANCODE_LEFTBRACKET}},
	{1, {SDL_SCANCODE_RIGHTBRACKET}},
	{2, {SDL_SCANCODE_BACKSPACE}},
	{3, {SDL_SCANCODE_PERIOD}},
	{4, {SDL_SCANCODE_COMMA}},
	{5, {SDL_SCANCODE_EQUALS, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_UP)}},
	{6, {SDL_SCANCODE_MINUS, static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + SDL_CONTROLLER_BUTTON_DPAD_DOWN)}},
	{7, {SDL_SCANCODE_SLASH}},
	{8, {SDL_SCANCODE_BACKSLASH}},
	{9, {SDL_SCANCODE_1}},
};

class w_prefs_key;

typedef std::multimap<int, w_prefs_key *> prefsKeyMap;
typedef std::pair<int, w_prefs_key *> prefsKeyMapPair;
static prefsKeyMap key_w;
static prefsKeyMap shell_key_w;

class w_prefs_key : public w_key
{
  public:
	w_prefs_key(SDL_Scancode key, w_key::Type event_type) : w_key(key, event_type) {}

	void set_key(SDL_Scancode new_key)
	{
		// Key used for in-game function?
		int error = NONE;
		switch (new_key)
		{
		case SDL_SCANCODE_F1:
		case SDL_SCANCODE_F2:
		case SDL_SCANCODE_F3:
		case SDL_SCANCODE_F4:
		case SDL_SCANCODE_F5:
		case SDL_SCANCODE_F6:
		case SDL_SCANCODE_F7:
		case SDL_SCANCODE_F8:
		case SDL_SCANCODE_F9:
		case SDL_SCANCODE_F10:
		case SDL_SCANCODE_F11:
		case SDL_SCANCODE_F12:
		case SDL_SCANCODE_ESCAPE: // (ZZZ: for quitting)
		case AO_SCANCODE_JOYSTICK_ESCAPE:
			error = keyIsUsedAlready;
			break;

		default:
			break;
		}
		if (error != NONE)
		{
			alert_user(infoError, strERRORS, error, 0);
			return;
		}

		w_key::set_key(new_key);
		dirty = true;
		if (new_key == SDL_SCANCODE_UNKNOWN)
			return;

		// Remove binding to this key from all other widgets
		for (auto it = key_w.begin(); it != key_w.end(); ++it)
		{
			if (it->second != this && it->second->get_key() == new_key)
			{
				it->second->set_key(SDL_SCANCODE_UNKNOWN);
				it->second->dirty = true;
			}
		}
		for (auto it = shell_key_w.begin(); it != shell_key_w.end(); ++it)
		{
			if (it->second != this && it->second->get_key() == new_key)
			{
				it->second->set_key(SDL_SCANCODE_UNKNOWN);
				it->second->dirty = true;
			}
		}
	}
};

static void load_default_keys(void *arg)
{
	for (int i = 0; i < NUM_KEYS; i++)
	{
		SDL_Scancode kcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode mcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode jcode = SDL_SCANCODE_UNKNOWN;
		for (auto it = default_key_bindings[i].begin(); it != default_key_bindings[i].end(); ++it)
		{
			SDL_Scancode code = *it;
			if (code == SDL_SCANCODE_UNKNOWN)
				continue;
			switch (w_key::event_type_for_key(code))
			{
			case w_key::MouseButton:
				mcode = code;
				break;
			case w_key::JoystickButton:
				jcode = code;
				break;
			case w_key::KeyboardKey:
			default:
				kcode = code;
				break;
			}
		}
		auto range = key_w.equal_range(i);
		for (auto ik = range.first; ik != range.second; ++ik)
		{
			w_prefs_key *pk = ik->second;
			switch (pk->event_type)
			{
			case w_key::MouseButton:
				pk->set_key(mcode);
				break;
			case w_key::JoystickButton:
				pk->set_key(jcode);
				break;
			case w_key::KeyboardKey:
			default:
				pk->set_key(kcode);
				break;
			}
		}
	}

	for (int i = 0; i < NUMBER_OF_SHELL_KEYS; i++)
	{
		SDL_Scancode kcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode mcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode jcode = SDL_SCANCODE_UNKNOWN;
		for (auto it = default_shell_key_bindings[i].begin(); it != default_shell_key_bindings[i].end(); ++it)
		{
			SDL_Scancode code = *it;
			if (code == SDL_SCANCODE_UNKNOWN)
				continue;
			switch (w_key::event_type_for_key(code))
			{
			case w_key::MouseButton:
				mcode = code;
				break;
			case w_key::JoystickButton:
				jcode = code;
				break;
			case w_key::KeyboardKey:
			default:
				kcode = code;
				break;
			}
		}
		auto range = shell_key_w.equal_range(i);
		for (auto ik = range.first; ik != range.second; ++ik)
		{
			w_prefs_key *pk = ik->second;
			switch (pk->event_type)
			{
			case w_key::MouseButton:
				pk->set_key(mcode);
				break;
			case w_key::JoystickButton:
				pk->set_key(jcode);
				break;
			case w_key::KeyboardKey:
			default:
				pk->set_key(kcode);
				break;
			}
		}
	}

	dialog *d = (dialog *)arg;
	d->draw();
}

static void unset_scancode(SDL_Scancode code)
{
	for (int i = 0; i < NUM_KEYS; ++i)
		input_preferences->key_bindings[i].erase(code);
	for (int i = 0; i < NUMBER_OF_SHELL_KEYS; ++i)
		input_preferences->shell_key_bindings[i].erase(code);
}

enum
{
	KEYBOARD_TABS,
	TAB_KEYS,
	TAB_MORE_KEYS
};

const std::vector<std::string> mouse_feel_labels = {_SJIS("クラシック"), _SJIS("モダン"), _SJIS("（カスタム）")};
static w_select_popup *mouse_feel_w;
static w_select_popup *mouse_feel_details_w;
static w_toggle *mouse_raw_w;
static w_toggle *mouse_vertical_w;
static w_toggle *mouse_accel_w;
static w_toggle *mouse_precision_w;
static bool inside_callback = false; // prevent circular changes

static void mouse_feel_details_changed(void *arg)
{
	if (inside_callback)
		return;
	inside_callback = true;
	switch (mouse_feel_details_w->get_selection())
	{
	case 0:
		mouse_raw_w->set_selection(0);
		mouse_accel_w->set_selection(1);
		mouse_vertical_w->set_selection(1);
		mouse_precision_w->set_selection(1);
		break;
	case 1:
		mouse_raw_w->set_selection(1);
		mouse_accel_w->set_selection(0);
		mouse_vertical_w->set_selection(0);
		mouse_precision_w->set_selection(0);
		break;
	default:
		break;
	}
	inside_callback = false;
}

static void update_mouse_feel_details(void *arg)
{
	if (inside_callback)
		return;
	inside_callback = true;
	if (mouse_raw_w->get_selection() == 0 &&
		mouse_accel_w->get_selection() == 1 &&
		mouse_vertical_w->get_selection() == 1 &&
		mouse_precision_w->get_selection() == 1)
	{
		mouse_feel_details_w->set_selection(0);
	}
	else if (mouse_raw_w->get_selection() == 1 &&
			 mouse_accel_w->get_selection() == 0 &&
			 mouse_vertical_w->get_selection() == 0 &&
			 mouse_precision_w->get_selection() == 0)
	{
		mouse_feel_details_w->set_selection(1);
	}
	else
	{
		mouse_feel_details_w->set_selection(2);
	}
	inside_callback = false;
}

static void update_mouse_feel(void *arg)
{
	if (input_preferences->raw_mouse_input == false &&
		input_preferences->mouse_accel_type == _mouse_accel_classic &&
		input_preferences->classic_vertical_aim == true &&
		input_preferences->extra_mouse_precision == false)
	{
		mouse_feel_w->set_selection(0);
	}
	else if (input_preferences->raw_mouse_input == true &&
			 input_preferences->mouse_accel_type == _mouse_accel_none &&
			 input_preferences->classic_vertical_aim == false &&
			 input_preferences->extra_mouse_precision == true)
	{
		mouse_feel_w->set_selection(1);
	}
	else
	{
		mouse_feel_w->set_selection(2);
	}
}

static bool apply_mouse_feel(int selection)
{
	bool changed = false;
	switch (selection)
	{
	case 0:
		if (false != input_preferences->raw_mouse_input)
		{
			input_preferences->raw_mouse_input = false;
			changed = true;
		}
		if (_mouse_accel_classic != input_preferences->mouse_accel_type)
		{
			input_preferences->mouse_accel_type = _mouse_accel_classic;
			changed = true;
		}
		if (true != input_preferences->classic_vertical_aim)
		{
			input_preferences->classic_vertical_aim = true;
			changed = true;
		}
		if (false != input_preferences->extra_mouse_precision)
		{
			input_preferences->extra_mouse_precision = false;
			changed = true;
		}
		break;
	case 1:
		if (true != input_preferences->raw_mouse_input)
		{
			input_preferences->raw_mouse_input = true;
			changed = true;
		}
		if (_mouse_accel_none != input_preferences->mouse_accel_type)
		{
			input_preferences->mouse_accel_type = _mouse_accel_none;
			changed = true;
		}
		if (false != input_preferences->classic_vertical_aim)
		{
			input_preferences->classic_vertical_aim = false;
			changed = true;
		}
		if (true != input_preferences->extra_mouse_precision)
		{
			input_preferences->extra_mouse_precision = true;
			changed = true;
		}
		break;
	default:
		break;
	}
	return changed;
}

static void mouse_custom_dialog(void *arg)
{
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("マウス拡張設定")), d);
	placer->add(new w_spacer());

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

	float hSensitivity = ((float)input_preferences->sens_horizontal) / FIXED_ONE;
	if (hSensitivity <= 0.0f)
		hSensitivity = 1.0f;
	float hSensitivityLog = std::log(hSensitivity);
	int hSliderPosition =
		(int)((hSensitivityLog - kMinSensitivityLog) * (1000.0f / kSensitivityLogRange) + 0.5f);
	w_sens_slider *mouse_h_sens_w = new w_sens_slider(1000, hSliderPosition);
	table->dual_add(mouse_h_sens_w->label(_SJIS("水平方向の感度")), d);
	table->dual_add(mouse_h_sens_w, d);

	float vSensitivity = ((float)input_preferences->sens_vertical) / FIXED_ONE;
	if (vSensitivity <= 0.0f)
		vSensitivity = 1.0f;
	float vSensitivityLog = std::log(vSensitivity);
	int vSliderPosition =
		(int)((vSensitivityLog - kMinSensitivityLog) * (1000.0f / kSensitivityLogRange) + 0.5f);
	w_sens_slider *mouse_v_sens_w = new w_sens_slider(1000, vSliderPosition);
	table->dual_add(mouse_v_sens_w->label(_SJIS("垂直方向の感度")), d);
	table->dual_add(mouse_v_sens_w, d);

	w_toggle *mouse_v_invert_w = new w_toggle(input_preferences->modifiers & _inputmod_invert_mouse);
	mouse_v_invert_w->set_selection_changed_callback(update_mouse_feel_details);
	table->dual_add(mouse_v_invert_w->label(_SJIS("垂直方向のエイミングを反転")), d);
	table->dual_add(mouse_v_invert_w, d);

	table->add_row(new w_spacer(), true);

	mouse_feel_details_w = new w_select_popup();
	mouse_feel_details_w->set_labels(mouse_feel_labels);
	mouse_feel_details_w->set_selection(mouse_feel_w->get_selection());
	mouse_feel_details_w->set_popup_callback(mouse_feel_details_changed, NULL);
	table->dual_add(mouse_feel_details_w->label(_SJIS("マウスの感覚")), d);
	table->dual_add(mouse_feel_details_w, d);

	mouse_raw_w = new w_toggle(input_preferences->raw_mouse_input);
	mouse_raw_w->set_selection_changed_callback(update_mouse_feel_details);
	table->dual_add(mouse_raw_w->label(_SJIS("生の入力モード")), d);
	table->dual_add(mouse_raw_w, d);

	mouse_accel_w = new w_toggle(input_preferences->mouse_accel_type == _mouse_accel_classic);
	mouse_accel_w->set_selection_changed_callback(update_mouse_feel_details);
	table->dual_add(mouse_accel_w->label(_SJIS("マウス移動の加速")), d);
	table->dual_add(mouse_accel_w, d);

	mouse_vertical_w = new w_toggle(input_preferences->classic_vertical_aim);
	mouse_vertical_w->set_selection_changed_callback(update_mouse_feel_details);
	table->dual_add(mouse_vertical_w->label(_SJIS("垂直方向の速度の調整")), d);
	table->dual_add(mouse_vertical_w, d);

	mouse_precision_w = new w_toggle(!input_preferences->extra_mouse_precision);
	mouse_precision_w->set_selection_changed_callback(update_mouse_feel_details);
	table->dual_add(mouse_precision_w->label(_SJIS("武器照準のスナップ")), d);
	table->dual_add(mouse_precision_w, d);

	placer->add(table);
	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);
	mouse_feel_details_changed(NULL);

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		int hPos = mouse_h_sens_w->get_selection();
		float hLog = kMinSensitivityLog + ((float)hPos) * (kSensitivityLogRange / 1000.0f);
		_fixed hNorm = _fixed(std::exp(hLog) * FIXED_ONE);
		if (hNorm != input_preferences->sens_horizontal)
		{
			input_preferences->sens_horizontal = hNorm;
			changed = true;
		}

		int vPos = mouse_v_sens_w->get_selection();
		float vLog = kMinSensitivityLog + ((float)vPos) * (kSensitivityLogRange / 1000.0f);
		_fixed vNorm = _fixed(std::exp(vLog) * FIXED_ONE);
		if (vNorm != input_preferences->sens_vertical)
		{
			input_preferences->sens_vertical = vNorm;
			changed = true;
		}

		uint16 flags = input_preferences->modifiers;
		if (mouse_v_invert_w->get_selection())
		{
			flags |= _inputmod_invert_mouse;
		}
		else
		{
			flags &= ~_inputmod_invert_mouse;
		}
		if (flags != input_preferences->modifiers)
		{
			input_preferences->modifiers = flags;
			changed = true;
		}

		if (mouse_raw_w->get_selection() != input_preferences->raw_mouse_input)
		{
			input_preferences->raw_mouse_input = mouse_raw_w->get_selection();
			changed = true;
		}

		if (mouse_accel_w->get_selection() != input_preferences->mouse_accel_type)
		{
			input_preferences->mouse_accel_type = mouse_accel_w->get_selection();
			changed = true;
		}

		bool vert = mouse_vertical_w->get_selection();
		if (vert != input_preferences->classic_vertical_aim)
		{
			input_preferences->classic_vertical_aim = vert;
			changed = true;
		}

		bool precision = (mouse_precision_w->get_selection() == 0);
		if (precision != input_preferences->extra_mouse_precision)
		{
			input_preferences->extra_mouse_precision = precision;
			changed = true;
		}

		if (changed)
		{
			write_preferences();
		}
		update_mouse_feel(NULL);
	}
}

static void controller_details_dialog(void *arg)
{
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("コントローラー拡張設定")), d);
	placer->add(new w_spacer());

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

	float joySensitivity = ((float)input_preferences->controller_sensitivity) / FIXED_ONE;
	if (joySensitivity <= 0.0f)
		joySensitivity = 1.0f;
	float joySensitivityLog = std::log(joySensitivity);
	int joySliderPosition =
		(int)((joySensitivityLog - kMinSensitivityLog) * (1000.0f / kSensitivityLogRange) + 0.5f);

	w_sens_slider *sens_joy_w = new w_sens_slider(1000, joySliderPosition);
	table->dual_add(sens_joy_w->label(_SJIS("照準の感度")), d);
	table->dual_add(sens_joy_w, d);

	int joyDeadzone = (int)((input_preferences->controller_deadzone / 655.36f) + 0.5f);
	w_deadzone_slider *dead_joy_w = new w_deadzone_slider(11, joyDeadzone);
	table->dual_add(dead_joy_w->label(_SJIS("アナログスティックの遊び")), d);
	table->dual_add(dead_joy_w, d);

	table->add_row(new w_spacer(), true);
	placer->add(table, true);

	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		int sensPos = sens_joy_w->get_selection();
		float sensLog = kMinSensitivityLog + ((float)sensPos) * (kSensitivityLogRange / 1000.0f);
		_fixed sensNorm = _fixed(std::exp(sensLog) * FIXED_ONE);
		if (sensNorm != input_preferences->controller_sensitivity)
		{
			input_preferences->controller_sensitivity = sensNorm;
			changed = true;
		}

		int deadPos = dead_joy_w->get_selection();
		int deadNorm = deadPos * 655.36f;
		if (deadNorm != input_preferences->controller_deadzone)
		{
			input_preferences->controller_deadzone = deadNorm;
			changed = true;
		}

		if (changed)
		{
			write_preferences();
		}
	}
}

static void controls_dialog(void *arg)
{
	// Clear array of key widgets (because w_prefs_key::set_key() scans it)
	key_w.clear();
	shell_key_w.clear();

	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title(_SJIS("操作設定")), d);
	placer->add(new w_spacer());

	// create all key widgets
	for (int i = 0; i < NUM_KEYS; i++)
	{
		SDL_Scancode kcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode mcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode jcode = SDL_SCANCODE_UNKNOWN;
		for (std::set<SDL_Scancode>::const_iterator bit = input_preferences->key_bindings[i].begin(); bit != input_preferences->key_bindings[i].end(); ++bit)
		{
			SDL_Scancode code = *bit;
			if (code >= AO_SCANCODE_BASE_JOYSTICK_BUTTON && code < (AO_SCANCODE_BASE_JOYSTICK_BUTTON + NUM_SDL_JOYSTICK_BUTTONS))
			{
				jcode = code;
			}
			else if (code >= AO_SCANCODE_BASE_MOUSE_BUTTON && code < (AO_SCANCODE_BASE_MOUSE_BUTTON + NUM_SDL_MOUSE_BUTTONS))
			{
				mcode = code;
			}
			else
			{
				kcode = code;
			}
		}
		key_w.insert(prefsKeyMapPair(i, new w_prefs_key(kcode, w_key::KeyboardKey)));
		key_w.insert(prefsKeyMapPair(i, new w_prefs_key(mcode, w_key::MouseButton)));
		key_w.insert(prefsKeyMapPair(i, new w_prefs_key(jcode, w_key::JoystickButton)));
	}
	for (int i = 0; i < NUMBER_OF_SHELL_KEYS; i++)
	{
		SDL_Scancode kcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode mcode = SDL_SCANCODE_UNKNOWN;
		SDL_Scancode jcode = SDL_SCANCODE_UNKNOWN;
		for (std::set<SDL_Scancode>::const_iterator bit = input_preferences->shell_key_bindings[i].begin(); bit != input_preferences->shell_key_bindings[i].end(); ++bit)
		{
			SDL_Scancode code = *bit;
			if (code >= AO_SCANCODE_BASE_JOYSTICK_BUTTON && code < (AO_SCANCODE_BASE_JOYSTICK_BUTTON + NUM_SDL_JOYSTICK_BUTTONS))
			{
				jcode = code;
			}
			else if (code >= AO_SCANCODE_BASE_MOUSE_BUTTON && code < (AO_SCANCODE_BASE_MOUSE_BUTTON + NUM_SDL_MOUSE_BUTTONS))
			{
				mcode = code;
			}
			else
			{
				kcode = code;
			}
		}
		shell_key_w.insert(prefsKeyMapPair(i, new w_prefs_key(kcode, w_key::KeyboardKey)));
		shell_key_w.insert(prefsKeyMapPair(i, new w_prefs_key(mcode, w_key::MouseButton)));
		shell_key_w.insert(prefsKeyMapPair(i, new w_prefs_key(jcode, w_key::JoystickButton)));
	}

	tab_placer *tabs = new tab_placer();

	std::vector<std::string> labels = {_SJIS("照準"), _SJIS("移動"), _SJIS("アクション"), _SJIS("インターフェース"), _SJIS("その他")};
	w_tab *tab_w = new w_tab(labels, tabs);

	placer->dual_add(tab_w, d);
	placer->add(new w_spacer(), true);

	vertical_placer *move = new vertical_placer();
	table_placer *move_table = new table_placer(4, get_theme_space(ITEM_WIDGET), true);
	move_table->col_flags(0, placeable::kAlignRight);
	move_table->col_flags(1, placeable::kAlignLeft);
	move_table->col_flags(2, placeable::kAlignLeft);
	move_table->col_flags(3, placeable::kAlignLeft);
	move_table->add(new w_spacer(), true);
	move_table->dual_add(new w_label(_SJIS("キーボード")), d);
	move_table->dual_add(new w_label(_SJIS("マウス")), d);
	move_table->dual_add(new w_label(_SJIS("コントローラー")), d);

	std::vector<int> move_keys = {0, 1, 4, 5, -1, 16, 15, 17};
	for (auto it = move_keys.begin(); it != move_keys.end(); ++it)
	{
		if (*it < 0)
		{
			move_table->add_row(new w_spacer(), true);
		}
		else if (*it >= 100)
		{
			int i = *it - 100;
			move_table->dual_add(new w_label(shell_action_name[i]), d);
			auto range = shell_key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				move_table->dual_add(ik->second, d);
			}
		}
		else
		{
			int i = *it;
			move_table->dual_add(new w_label(action_name[i]), d);
			auto range = key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				if ((ik->second->event_type == w_key::MouseButton) &&
					(i == 0 || i == 1 || i == 4 || i == 5))
					move_table->dual_add(new w_label(""), d);
				else
					move_table->dual_add(ik->second, d);
			}
		}
	}
	move->add(move_table, true);
	move->add(new w_spacer(), true);

	table_placer *move_options = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	move_options->col_flags(0, placeable::kAlignRight);

	w_toggle *always_run_w = new w_toggle(input_preferences->modifiers & _inputmod_interchange_run_walk);
	move_options->dual_add(always_run_w->label(_SJIS("常時走行")), d);
	move_options->dual_add(always_run_w, d);

	w_toggle *always_swim_w = new w_toggle(TEST_FLAG(input_preferences->modifiers, _inputmod_interchange_swim_sink));
	move_options->dual_add(always_swim_w->label(_SJIS("常時泳ぐ")), d);
	move_options->dual_add(always_swim_w, d);

	move->add(move_options, true);

	vertical_placer *look = new vertical_placer();
	table_placer *look_table = new table_placer(4, get_theme_space(ITEM_WIDGET), true);
	look_table->col_flags(0, placeable::kAlignRight);
	look_table->col_flags(1, placeable::kAlignLeft);
	look_table->col_flags(2, placeable::kAlignLeft);
	look_table->col_flags(3, placeable::kAlignLeft);
	look_table->add(new w_spacer(), true);
	look_table->dual_add(new w_label(_SJIS("キーボード")), d);
	look_table->dual_add(new w_label(_SJIS("マウス")), d);
	look_table->dual_add(new w_label(_SJIS("コントローラー")), d);

	std::vector<int> look_keys = {8, 9, 2, 3, -1, 6, 7, 10};
	for (auto it = look_keys.begin(); it != look_keys.end(); ++it)
	{
		if (*it < 0)
		{
			look_table->add_row(new w_spacer(), true);
		}
		else if (*it >= 100)
		{
			int i = *it - 100;
			look_table->dual_add(new w_label(shell_action_name[i]), d);
			auto range = shell_key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				look_table->dual_add(ik->second, d);
			}
		}
		else
		{
			int i = *it;
			look_table->dual_add(new w_label(action_name[i]), d);
			auto range = key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				if (ik->second->event_type == w_key::MouseButton)
				{
					w_text_entry *txt = NULL;
					switch (i)
					{
					case 8:
						txt = new w_text_entry(12, _SJIS("上"));
						break;
					case 9:
						txt = new w_text_entry(12, _SJIS("下"));
						break;
					case 2:
						txt = new w_text_entry(12, _SJIS("左"));
						break;
					case 3:
						txt = new w_text_entry(12, _SJIS("右"));
						break;
					default:
						break;
					}
					if (txt)
					{
						txt->set_enabled(false);
						txt->set_min_width(50);
						look_table->dual_add(txt, d);
						continue;
					}
				}
				look_table->dual_add(ik->second, d);
			}
		}
	}
	look->add(look_table, true);
	look->add(new w_spacer(), true);

	table_placer *look_options = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	look_options->col_flags(0, placeable::kAlignRight);

	w_toggle *auto_recenter_w = new w_toggle(!(input_preferences->modifiers & _inputmod_dont_auto_recenter));
	look_options->dual_add(auto_recenter_w->label(_SJIS("視点の自動中央補正")), d);
	look_options->dual_add(auto_recenter_w, d);

	look_options->add_row(new w_spacer(), true);

	table_placer *mouse_options = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	mouse_options->col_flags(0, placeable::kAlignRight);
	mouse_options->col_flags(1, placeable::kAlignLeft);

	w_toggle *enable_mouse_w = new w_toggle(input_preferences->input_device == _mouse_yaw_pitch);
	mouse_options->dual_add(enable_mouse_w->label(_SJIS("マウス操作")), d);
	mouse_options->dual_add(enable_mouse_w, d);

	mouse_feel_w = new w_select_popup();
	mouse_feel_w->set_labels(mouse_feel_labels);
	update_mouse_feel(NULL);
	mouse_options->dual_add(mouse_feel_w->label(_SJIS("マウスの左右")), d);
	mouse_options->dual_add(mouse_feel_w, d);

	mouse_options->add_row(new w_spacer(), true);
	mouse_options->dual_add_row(new w_button(_SJIS("マウス拡張"), mouse_custom_dialog, &d), d);

	look_options->add(mouse_options, true);

	table_placer *controller_options = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	controller_options->col_flags(0, placeable::kAlignRight);
	controller_options->col_flags(1, placeable::kAlignLeft);

	controller_options->dual_add_row(new w_label(""), d);
	std::vector<std::string> joystick_aiming_labels = {_SJIS("アナログスティック"), _SJIS("十字キー")};
	w_select_popup *joystick_aiming_w = new w_select_popup();
	joystick_aiming_w->set_labels(joystick_aiming_labels);
	joystick_aiming_w->set_selection(input_preferences->controller_analog ? 0 : 1);
	controller_options->dual_add(joystick_aiming_w->label(_SJIS("コントローラーの照準")), d);
	controller_options->dual_add(joystick_aiming_w, d);

	controller_options->add_row(new w_spacer(), true);
	controller_options->dual_add_row(new w_button(_SJIS("コントローラー拡張"), controller_details_dialog, &d), d);

	look_options->add(controller_options, true);

	look->add(look_options, true);

	vertical_placer *actions = new vertical_placer();
	table_placer *actions_table = new table_placer(4, get_theme_space(ITEM_WIDGET), true);
	actions_table->col_flags(0, placeable::kAlignRight);
	actions_table->col_flags(1, placeable::kAlignLeft);
	actions_table->col_flags(2, placeable::kAlignLeft);
	actions_table->col_flags(3, placeable::kAlignLeft);
	actions_table->add(new w_spacer(), true);
	actions_table->dual_add(new w_label(_SJIS("キーボード")), d);
	actions_table->dual_add(new w_label(_SJIS("マウス")), d);
	actions_table->dual_add(new w_label(_SJIS("コントローラー")), d);

	std::vector<int> actions_keys = {13, 14, 11, 12, -1, 18, -1, 20, 108};
	for (auto it = actions_keys.begin(); it != actions_keys.end(); ++it)
	{
		if (*it < 0)
		{
			actions_table->add_row(new w_spacer(), true);
		}
		else if (*it >= 100)
		{
			int i = *it - 100;
			actions_table->dual_add(new w_label(shell_action_name[i]), d);
			auto range = shell_key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				actions_table->dual_add(ik->second, d);
			}
		}
		else
		{
			int i = *it;
			actions_table->dual_add(new w_label(action_name[i]), d);
			auto range = key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				actions_table->dual_add(ik->second, d);
			}
		}
	}
	actions->add(actions_table, true);
	actions->add(new w_spacer(), true);

	table_placer *actions_options = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	actions_options->col_flags(0, placeable::kAlignRight);

	w_toggle *weapon_w = new w_toggle(!(input_preferences->modifiers & _inputmod_dont_switch_to_new_weapon));
	actions_options->dual_add(weapon_w->label(_SJIS("武器の自動切り替え")), d);
	actions_options->dual_add(weapon_w, d);

	actions->add(actions_options, true);

	actions->add(new w_spacer(), true);
	actions->dual_add(new w_static_text(_SJIS("注意：武器の自動切換えと、視点の自動リセンターは、ネットワークプレイで")), d);
	actions->dual_add(new w_static_text(_SJIS("自動的にオンになります。シングルプレイヤーモードでどちらかをオフにすると、")), d);
	actions->dual_add(new w_static_text(_SJIS("映画の録画が自動的に無効化されます。")), d);

	vertical_placer *iface = new vertical_placer();
	table_placer *interface_table = new table_placer(4, get_theme_space(ITEM_WIDGET), true);
	interface_table->col_flags(0, placeable::kAlignRight);
	interface_table->col_flags(1, placeable::kAlignLeft);
	interface_table->col_flags(2, placeable::kAlignLeft);
	interface_table->col_flags(3, placeable::kAlignLeft);
	interface_table->add(new w_spacer(), true);
	interface_table->dual_add(new w_label(_SJIS("キーボード")), d);
	interface_table->dual_add(new w_label(_SJIS("マウス")), d);
	interface_table->dual_add(new w_label(_SJIS("コントローラー")), d);

	std::vector<int> interface_keys = {19, 105, 106, -1, 103, 104, -1, 100, 101, -1, 102, 107, 109, -1, -2};
	for (auto it = interface_keys.begin(); it != interface_keys.end(); ++it)
	{
		if (*it == -2)
		{
			interface_table->dual_add(new w_label(_SJIS("ゲームを終了")), d);
			w_prefs_key *kb = new w_prefs_key(SDL_SCANCODE_ESCAPE, w_key::KeyboardKey);
			kb->set_enabled(false);
			interface_table->dual_add(kb, d);
			interface_table->dual_add(new w_label(""), d);
			w_prefs_key *cn = new w_prefs_key(AO_SCANCODE_JOYSTICK_ESCAPE, w_key::JoystickButton);
			cn->set_enabled(false);
			interface_table->dual_add(cn, d);
		}
		else if (*it < 0)
		{
			interface_table->add_row(new w_spacer(), true);
		}
		else if (*it >= 100)
		{
			int i = *it - 100;
			interface_table->dual_add(new w_label(shell_action_name[i]), d);
			auto range = shell_key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				interface_table->dual_add(ik->second, d);
			}
		}
		else
		{
			int i = *it;
			interface_table->dual_add(new w_label(action_name[i]), d);
			auto range = key_w.equal_range(i);
			for (auto ik = range.first; ik != range.second; ++ik)
			{
				interface_table->dual_add(ik->second, d);
			}
		}
	}
	iface->add(interface_table, true);

	vertical_placer *other = new vertical_placer();
	other->dual_add(new w_static_text(_SJIS("これらのキーボードショートカットは変更できません。")), d);
	other->add(new w_spacer());

	table_placer *other_table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	other_table->dual_add(new w_label(_SJIS("メインメニュー")), d);
	other_table->dual_add(new w_label(_SJIS("ゲーム中")), d);

	table_placer *other_menu = new table_placer(2, get_theme_space(ITEM_WIDGET), false);
	other_menu->col_flags(0, placeable::kAlignRight);
	other_menu->col_flags(1, placeable::kAlignLeft);
	std::vector<std::string> menu_shortcuts = {
		"N",
		_SJIS("新規ゲーム開始"),
#if (defined(__APPLE__) && defined(__MACH__))
		"Cmd-Option-N",
		_SJIS("レベル選択"),
#else
		"Ctrl+Shift+N",
		_SJIS("レベル選択"),
#endif
		"O",
		_SJIS("保存したゲームを再開"),
		"G",
		_SJIS("ネットワークゲーム集合"),
		"J",
		_SJIS("ネットワークゲーム参加"),
		"R",
		_SJIS("保存した映画の再生"),
		"P",
		_SJIS("環境設定"),
		"Q",
		_SJIS("終了"),
		"C",
		_SJIS("シナリオクレジット"),
		"A",
		_SJIS("Aleph Oneについて"),
#if (defined(__APPLE__) && defined(__MACH__))
		"Cmd-Return",
		_SJIS("フルスクリーン切り替え"),
#else
		"Alt+Enter",
		_SJIS("フルスクリーン切り替え"),
#endif
	};
	for (auto it = menu_shortcuts.begin(); it != menu_shortcuts.end(); ++it)
	{
		other_menu->dual_add(new w_label(it->c_str()), d);
	}
	other_table->add(other_menu, true);

	table_placer *other_game = new table_placer(2, get_theme_space(ITEM_WIDGET), false);
	other_game->col_flags(0, placeable::kAlignRight);
	other_game->col_flags(1, placeable::kAlignLeft);
	std::vector<std::string> game_shortcuts = {
		"F1",
		_SJIS("解像度を下げる"),
		"F2",
		_SJIS("解像度を上げる"),
		"F8",
		_SJIS("クロスヘアー"),
		"F9",
		_SJIS("スクリーンショット"),
		"F10",
		_SJIS("デバッグ情報"),
		"F11",
		_SJIS("暗くする"),
		"F12",
		_SJIS("明るくする"),
#if (defined(__APPLE__) && defined(__MACH__))
		"Cmd-Return",
		_SJIS("フルスクリーン切り替え"),
#else
		"Alt+Enter",
		_SJIS("フルスクリーン切り替え"),
#endif
		"Escape",
		_SJIS("ゲーム終了")
	};
	for (auto it = game_shortcuts.begin(); it != game_shortcuts.end(); ++it)
	{
		other_game->dual_add(new w_label(it->c_str()), d);
	}
	other_table->add(other_game, true);

	other->add(other_table, true);

	tabs->add(look, true);
	tabs->add(move, true);
	tabs->add(actions, true);
	tabs->add(iface, true);
	tabs->add(other, true);
	placer->add(tabs, true);

	placer->add(new w_spacer(), true);
	placer->dual_add(new w_button(_SJIS("初期設定に戻す"), load_default_keys, &d), d);
	placer->add(new w_spacer(), true);

	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	button_placer->dual_add(new w_button(_SJIS("キャンセル"), dialog_cancel, &d), d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Clear screen
	clear_screen();

	enter_joystick();

	// Run dialog
	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

		uint16 flags = input_preferences->modifiers & (_inputmod_use_button_sounds | _inputmod_invert_mouse);
		if (always_run_w->get_selection())
			flags |= _inputmod_interchange_run_walk;
		if (always_swim_w->get_selection())
			flags |= _inputmod_interchange_swim_sink;
		if (!(weapon_w->get_selection()))
			flags |= _inputmod_dont_switch_to_new_weapon;
		if (!(auto_recenter_w->get_selection()))
			flags |= _inputmod_dont_auto_recenter;

		if (flags != input_preferences->modifiers)
		{
			input_preferences->modifiers = flags;
			changed = true;
		}

		for (int i = 0; i < NUM_KEYS; i++)
		{
			input_preferences->key_bindings[i].clear();
		}
		for (int i = 0; i < NUMBER_OF_SHELL_KEYS; i++)
		{
			input_preferences->shell_key_bindings[i].clear();
		}

		for (auto it = key_w.begin(); it != key_w.end(); ++it)
		{
			int i = it->first;
			SDL_Scancode key = it->second->get_key();
			if (key != SDL_SCANCODE_UNKNOWN)
			{
				unset_scancode(key);
				input_preferences->key_bindings[i].insert(key);
				changed = true;
			}
		}

		for (auto it = shell_key_w.begin(); it != shell_key_w.end(); ++it)
		{
			int i = it->first;
			SDL_Scancode key = it->second->get_key();
			if (key != SDL_SCANCODE_UNKNOWN)
			{
				unset_scancode(key);
				input_preferences->shell_key_bindings[i].insert(key);
				changed = true;
			}
		}

		int16 device = enable_mouse_w->get_selection() ? _mouse_yaw_pitch : _keyboard_or_game_pad;
		if (input_preferences->input_device != device)
		{
			input_preferences->input_device = device;
			changed = true;
		}

		bool jaim = (joystick_aiming_w->get_selection() == 1);
		if (input_preferences->controller_analog != jaim)
		{
			input_preferences->controller_analog = jaim;
			changed = true;
		}

		if (apply_mouse_feel(mouse_feel_w->get_selection()))
		{
			changed = true;
		}

		if (changed)
			write_preferences();
	}

	exit_joystick();
}

extern void ResetAllMMLValues();

static void plugins_dialog(void *)
{
	dialog d;
	vertical_placer *placer = new vertical_placer;
	w_title *w_header = new w_title(_SJIS("プラグイン"));
	placer->dual_add(w_header, d);
	placer->add(new w_spacer, true);

	std::vector<Plugin> plugins(Plugins::instance()->begin(), Plugins::instance()->end());
	w_plugins *plugins_w = new w_plugins(plugins, 400, 7);
	placer->dual_add(plugins_w, d);

	placer->add(new w_spacer, true);

	horizontal_placer *button_placer = new horizontal_placer;
	w_button *accept_w = new w_button("OK", dialog_ok, &d);
	button_placer->dual_add(accept_w, d);
	w_button *cancel_w = new w_button(_SJIS("キャンセル"), dialog_cancel, &d);
	button_placer->dual_add(cancel_w, d);

	placer->add(button_placer, true);

	d.set_widget_placer(placer);
	d.activate_widget(plugins_w);

	if (d.run() == 0)
	{
		bool changed = false;
		Plugins::iterator plugin = Plugins::instance()->begin();
		for (Plugins::iterator it = plugins.begin(); it != plugins.end(); ++it, ++plugin)
		{
			changed |= (plugin->enabled != it->enabled);
			plugin->enabled = it->enabled;
		}

		if (changed)
		{
			Plugins::instance()->invalidate();
			write_preferences();

			ResetAllMMLValues();
			LoadBaseMMLScripts();
			Plugins::instance()->load_mml();
		}
	}
}

/*
 *  Environment dialog
 */

static const char *film_profile_labels[] = {
	"Aleph One",
	"Marathon 2",
	"Marathon Infinity",
	0};

static void environment_dialog(void *arg)
{
	dialog *parent = (dialog *)arg;

	// Create dialog
	dialog d;
	vertical_placer *placer = new vertical_placer;
	w_title *w_header = new w_title(_SJIS("環境設定"));
	placer->dual_add(w_header, d);
	placer->add(new w_spacer, true);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET), true);
	table->col_flags(0, placeable::kAlignRight);

#ifndef MAC_APP_STORE
	w_env_select *map_w = new w_env_select(environment_preferences->map_file, _SJIS("利用可能なマップ"), _typecode_scenario, &d);
	table->dual_add(map_w->label(_SJIS("マップ")), d);
	table->dual_add(map_w, d);

	w_env_select *physics_w = new w_env_select(environment_preferences->physics_file, _SJIS("利用可能な物理モデル"), _typecode_physics, &d);
	table->dual_add(physics_w->label(_SJIS("物理モデル")), d);
	table->dual_add(physics_w, d);

	w_env_select *shapes_w = new w_env_select(environment_preferences->shapes_file, _SJIS("利用可能な形態"), _typecode_shapes, &d);
	table->dual_add(shapes_w->label(_SJIS("形態")), d);
	table->dual_add(shapes_w, d);

	w_env_select *sounds_w = new w_env_select(environment_preferences->sounds_file, _SJIS("利用可能なサウンド"), _typecode_sounds, &d);
	table->dual_add(sounds_w->label(_SJIS("サウンド")), d);
	table->dual_add(sounds_w, d);

	w_env_select *resources_w = new w_env_select(environment_preferences->resources_file, _SJIS("利用可能なファイル"), _typecode_unknown, &d);
	table->dual_add(resources_w->label(_SJIS("外部リソース")), d);
	table->dual_add(resources_w, d);
#endif

	table->add_row(new w_spacer, true);
	table->dual_add_row(new w_button(_SJIS("プラグイン"), plugins_dialog, &d), d);

#ifndef MAC_APP_STORE
	table->add_row(new w_spacer, true);
	table->dual_add_row(new w_static_text(_SJIS("ソロスクリプト")), d);
	w_enabling_toggle *use_solo_lua_w = new w_enabling_toggle(environment_preferences->use_solo_lua);
	table->dual_add(use_solo_lua_w->label(_SJIS("ソロスクリプトを使用")), d);
	table->dual_add(use_solo_lua_w, d);

	w_file_chooser *solo_lua_w = new w_file_chooser(_SJIS("スクリプトを選択"), _typecode_netscript);
	solo_lua_w->set_file(environment_preferences->solo_lua_file);
	table->dual_add(solo_lua_w->label(_SJIS("スクリプトファイル")), d);
	table->dual_add(solo_lua_w, d);
	use_solo_lua_w->add_dependent_widget(solo_lua_w);
#endif

	table->add_row(new w_spacer, true);
	table->dual_add_row(new w_static_text(_SJIS("フィルム再生")), d);

	w_select *film_profile_w = new w_select(environment_preferences->film_profile, film_profile_labels);
	table->dual_add(film_profile_w->label(_SJIS("デフォルト再生プロファイル")), d);
	table->dual_add(film_profile_w, d);

#ifndef MAC_APP_STORE
	w_enabling_toggle *use_replay_net_lua_w = new w_enabling_toggle(environment_preferences->use_replay_net_lua);
	table->dual_add(use_replay_net_lua_w->label(_SJIS("フィルムでのネットスクリプト")), d);
	table->dual_add(use_replay_net_lua_w, d);

	w_file_chooser *replay_net_lua_w = new w_file_chooser("スクリプトを選択", _typecode_netscript);
	replay_net_lua_w->set_file(network_preferences->netscript_file);
	table->dual_add(replay_net_lua_w->label(_SJIS("ネットスクリプトファイル")), d);
	table->dual_add(replay_net_lua_w, d);
	use_replay_net_lua_w->add_dependent_widget(replay_net_lua_w);
#endif

	table->add_row(new w_spacer, true);
	table->dual_add_row(new w_static_text(_SJIS("オプション")), d);

#ifndef MAC_APP_STORE
	w_toggle *hide_extensions_w = new w_toggle(environment_preferences->hide_extensions);
	table->dual_add(hide_extensions_w->label(_SJIS("ファイル拡張子を隠す")), d);
	table->dual_add(hide_extensions_w, d);
#endif

	w_select *max_saves_w = new w_select(0, max_saves_labels);
	for (int i = 0; max_saves_labels[i] != NULL; ++i)
	{
		if (max_saves_values[i] == environment_preferences->maximum_quick_saves)
			max_saves_w->set_selection(i);
	}
	table->dual_add(max_saves_w->label(_SJIS("保持するデータ件数")), d);
	table->dual_add(max_saves_w, d);

	placer->add(table, true);

	placer->add(new w_spacer, true);

	horizontal_placer *button_placer = new horizontal_placer;
	w_button *w_accept = new w_button("OK", dialog_ok, &d);
	button_placer->dual_add(w_accept, d);
	w_button *w_cancel = new w_button(_SJIS("キャンセル"), dialog_cancel, &d);
	button_placer->dual_add(w_cancel, d);
	placer->add(button_placer, true);

	d.set_widget_placer(placer);

	// Clear screen
	clear_screen();

	// Run dialog
	bool theme_changed = false;
	FileSpecifier old_theme;
	const Plugin *theme_plugin = Plugins::instance()->find_theme();
	if (theme_plugin)
	{
		old_theme = theme_plugin->directory + theme_plugin->theme;
	}

	if (d.run() == 0)
	{ // Accepted
		bool changed = false;

#ifndef MAC_APP_STORE
		const char *path = map_w->get_path();
		if (strcmp(path, environment_preferences->map_file))
		{
			strncpy(environment_preferences->map_file, path, 256);
			environment_preferences->map_checksum = read_wad_file_checksum(map_w->get_file_specifier());
			changed = true;
		}

		path = physics_w->get_path();
		if (strcmp(path, environment_preferences->physics_file))
		{
			strncpy(environment_preferences->physics_file, path, 256);
			environment_preferences->physics_checksum = read_wad_file_checksum(physics_w->get_file_specifier());
			changed = true;
		}

		path = shapes_w->get_path();
		if (strcmp(path, environment_preferences->shapes_file))
		{
			strncpy(environment_preferences->shapes_file, path, 256);
			environment_preferences->shapes_mod_date = shapes_w->get_file_specifier().GetDate();
			changed = true;
		}

		path = sounds_w->get_path();
		if (strcmp(path, environment_preferences->sounds_file))
		{
			strncpy(environment_preferences->sounds_file, path, 256);
			environment_preferences->sounds_mod_date = sounds_w->get_file_specifier().GetDate();
			changed = true;
		}

		path = resources_w->get_path();
		if (strcmp(path, environment_preferences->resources_file) != 0)
		{
			strncpy(environment_preferences->resources_file, path, 256);
			changed = true;
		}

		bool use_solo_lua = use_solo_lua_w->get_selection() != 0;
		if (use_solo_lua != environment_preferences->use_solo_lua)
		{
			environment_preferences->use_solo_lua = use_solo_lua;
			changed = true;
		}

		path = solo_lua_w->get_file().GetPath();
		if (strcmp(path, environment_preferences->solo_lua_file))
		{
			strncpy(environment_preferences->solo_lua_file, path, 256);
			changed = true;
		}

		bool use_replay_net_lua = use_replay_net_lua_w->get_selection() != 0;
		if (use_replay_net_lua != environment_preferences->use_replay_net_lua)
		{
			environment_preferences->use_replay_net_lua = use_replay_net_lua;
			changed = true;
		}

		path = replay_net_lua_w->get_file().GetPath();
		if (strcmp(path, network_preferences->netscript_file))
		{
			strncpy(network_preferences->netscript_file, path, 256);
			changed = true;
		}
#endif

		FileSpecifier new_theme;
		theme_plugin = Plugins::instance()->find_theme();
		if (theme_plugin)
		{
			new_theme = theme_plugin->directory + theme_plugin->theme;
		}

		if (new_theme != old_theme)
		{
			theme_changed = true;
		}

#ifndef MAC_APP_STORE
		bool hide_extensions = hide_extensions_w->get_selection() != 0;
		if (hide_extensions != environment_preferences->hide_extensions)
		{
			environment_preferences->hide_extensions = hide_extensions;
			changed = true;
		}
#endif

		if (film_profile_w->get_selection() != environment_preferences->film_profile)
		{
			environment_preferences->film_profile = static_cast<FilmProfileType>(film_profile_w->get_selection());

			changed = true;
		}

		bool saves_changed = false;
		int saves = max_saves_values[max_saves_w->get_selection()];
		if (saves != environment_preferences->maximum_quick_saves)
		{
			environment_preferences->maximum_quick_saves = saves;
			saves_changed = true;
		}

		if (changed)
			load_environment_from_preferences();

		if (theme_changed)
		{
			load_dialog_theme();
		}

		if (changed || theme_changed || saves_changed)
			write_preferences();
	}

	// Redraw parent dialog
	if (theme_changed)
		parent->quit(0); // Quit the parent dialog so it won't draw in the old theme
}

extern void hub_set_minimum_send_period(int32);
extern int32 &hub_get_minimum_send_period();

struct set_latency_tolerance
{
	void operator()(const std::string &arg) const
	{
		hub_set_minimum_send_period(atoi(arg.c_str()));
		screen_printf("現在の待ち時間の許容範囲は、 %i です。", atoi(arg.c_str()));
		write_preferences();
	}
};

struct get_latency_tolerance
{
	void operator()(const std::string &) const
	{
		screen_printf("待ち時間の許容範囲:%i", hub_get_minimum_send_period());
	}
};

void transition_preferences(const DirectorySpecifier &legacy_preferences_dir)
{
	FileSpecifier prefs;
	prefs.SetToPreferencesDir();
	prefs += getcstr(temporary, strFILENAMES, filenamePREFERENCES);
	if (!prefs.Exists())
	{
		FileSpecifier oldPrefs;
		oldPrefs = legacy_preferences_dir;
		oldPrefs += getcstr(temporary, strFILENAMES, filenamePREFERENCES);
		if (oldPrefs.Exists())
		{
			oldPrefs.Rename(prefs);
		}
	}
}

/*
 *  Initialize preferences (load from file or setup defaults)
 */

void initialize_preferences(
	void)
{
	logContext("initializing preferences");

	// In case this function gets called more than once...
	if (!PrefsInited)
	{
		graphics_preferences = new graphics_preferences_data;
		player_preferences = new player_preferences_data;
		input_preferences = new input_preferences_data;
		sound_preferences = new SoundManager::Parameters;
		network_preferences = new network_preferences_data;
		environment_preferences = new environment_preferences_data;

		for (int i = 0; i < NUM_KEYS; ++i)
			input_preferences->key_bindings[i] = std::set<SDL_Scancode>();
		for (int i = 0; i < NUMBER_OF_SHELL_KEYS; ++i)
			input_preferences->shell_key_bindings[i] = std::set<SDL_Scancode>();

		PrefsInited = true;

		CommandParser PreferenceSetCommandParser;
		PreferenceSetCommandParser.register_command("latency_tolerance", set_latency_tolerance());
		CommandParser PreferenceGetCommandParser;
		PreferenceGetCommandParser.register_command("latency_tolerance", get_latency_tolerance());

		CommandParser PreferenceCommandParser;
		PreferenceCommandParser.register_command("set", PreferenceSetCommandParser);
		PreferenceCommandParser.register_command("get", PreferenceGetCommandParser);
		Console::instance()->register_command("preferences", PreferenceCommandParser);

		read_preferences();
	}
}

void read_preferences()
{
	// Set to defaults; will be overridden by reading in the XML stuff
	default_graphics_preferences(graphics_preferences);
	default_network_preferences(network_preferences);
	default_player_preferences(player_preferences);
	default_input_preferences(input_preferences);
	*sound_preferences = SoundManager::Parameters();
	default_environment_preferences(environment_preferences);

	// Slurp in the file and parse it

	FileSpecifier FileSpec;

	FileSpec.SetToPreferencesDir();
	FileSpec += getcstr(temporary, strFILENAMES, filenamePREFERENCES);

	OpenedFile OFile;
	bool defaults = false;
	bool opened = FileSpec.Open(OFile);

	if (!opened)
	{
		defaults = true;
		FileSpec.SetNameWithPath(getcstr(temporary, strFILENAMES, filenamePREFERENCES));
		opened = FileSpec.Open(OFile);
	}

	bool parse_error = false;
	if (opened)
	{
		OFile.Close();
		try
		{
			InfoTree prefs = InfoTree::load_xml(FileSpec);
			InfoTree root = prefs.get_child("mara_prefs");

			std::string version = "";
			root.read_attr("version", version);
			if (!version.length())
				logWarning("Reading older preferences of unknown version. Preferences will be upgraded to version %s when saved. (%s)", A1_DATE_VERSION, FileSpec.GetPath());
			else if (version < A1_DATE_VERSION)
				logWarning("Reading older preferences of version %s. Preferences will be upgraded to version %s when saved. (%s)", version.c_str(), A1_DATE_VERSION, FileSpec.GetPath());
			else if (version > A1_DATE_VERSION)
				logWarning("Reading newer preferences of version %s. Preferences will be downgraded to version %s when saved. (%s)", version.c_str(), A1_DATE_VERSION, FileSpec.GetPath());

			BOOST_FOREACH (InfoTree child, root.children_named("graphics"))
				parse_graphics_preferences(child, version);
			BOOST_FOREACH (InfoTree child, root.children_named("player"))
				parse_player_preferences(child, version);
			BOOST_FOREACH (InfoTree child, root.children_named("input"))
				parse_input_preferences(child, version);
			BOOST_FOREACH (InfoTree child, root.children_named("sound"))
				parse_sound_preferences(child, version);
#if !defined(DISABLE_NETWORKING)
			BOOST_FOREACH (InfoTree child, root.children_named("network"))
				parse_network_preferences(child, version);
#endif
			BOOST_FOREACH (InfoTree child, root.children_named("environment"))
				parse_environment_preferences(child, version);
		}
		catch (InfoTree::parse_error ex)
		{
			logError("Error parsing preferences file (%s): %s", FileSpec.GetPath(), ex.what());
			parse_error = true;
		}
		catch (InfoTree::path_error ep)
		{
			logError("Could not find mara_prefs in preferences file (%s): %s", FileSpec.GetPath(), ep.what());
			parse_error = true;
		}
		catch (InfoTree::data_error ed)
		{
			logError("Unexpected data error in preferences file (%s): %s", FileSpec.GetPath(), ed.what());
			parse_error = true;
		}
		catch (InfoTree::unexpected_error ee)
		{
			logError("Unexpected error in preferences file (%s): %s", FileSpec.GetPath(), ee.what());
			parse_error = true;
		}
	}

	if (!opened || parse_error)
	{
		if (defaults)
			alert_user(expand_app_variables("There were default preferences-file parsing errors (see $appLogFile$ for details)").c_str(), infoError);
		else
			alert_user(expand_app_variables("There were preferences-file parsing errors (see $appLogFile$ for details)").c_str(), infoError);
	}

	// Check on the read-in prefs
	validate_graphics_preferences(graphics_preferences);
	validate_network_preferences(network_preferences);
	validate_player_preferences(player_preferences);
	validate_input_preferences(input_preferences);
	validate_environment_preferences(environment_preferences);

	// jkvw: If we try to load a default file, but can't, we'll have set the game error.
	//       But that's not useful, because we're just going to try loading the file
	//       from user preferences.  It used to be this code was only called in initialisation,
	//       and any game error generated here was simply clobbered by an init time
	//       clear_game_error().  Since I'm using this more generally now, I'm clearing the
	//       error right here, because it's not like we're bothered when we can't load a
	//       default file.
	//       (Problem is SDL specific - socre one for Carbon? :) )
	clear_game_error();
}

/*
 *  Write preferences to file
 */

InfoTree graphics_preferences_tree()
{
	InfoTree root;

	root.put_attr("scmode_width", graphics_preferences->screen_mode.width);
	root.put_attr("scmode_height", graphics_preferences->screen_mode.height);
	root.put_attr("scmode_auto_resolution", graphics_preferences->screen_mode.auto_resolution);
	root.put_attr("scmode_high_dpi", graphics_preferences->screen_mode.high_dpi);
	root.put_attr("scmode_hud", graphics_preferences->screen_mode.hud);
	root.put_attr("scmode_hud_scale", graphics_preferences->screen_mode.hud_scale_level);
	root.put_attr("scmode_term_scale", graphics_preferences->screen_mode.term_scale_level);
	root.put_attr("scmode_translucent_map", graphics_preferences->screen_mode.translucent_map);
	root.put_attr("scmode_camera_bob", graphics_preferences->screen_mode.camera_bob);
	root.put_attr("scmode_accel", graphics_preferences->screen_mode.acceleration);
	root.put_attr("scmode_highres", graphics_preferences->screen_mode.high_resolution);
	root.put_attr("scmode_fullscreen", graphics_preferences->screen_mode.fullscreen);
	root.put_attr("scmode_bitdepth", graphics_preferences->screen_mode.bit_depth);
	root.put_attr("scmode_gamma", graphics_preferences->screen_mode.gamma_level);
	root.put_attr("scmode_fix_h_not_v", graphics_preferences->screen_mode.fix_h_not_v);
	root.put_attr("ogl_flags", graphics_preferences->OGL_Configure.Flags);
	root.put_attr("software_alpha_blending", graphics_preferences->software_alpha_blending);
	root.put_attr("software_sdl_driver", graphics_preferences->software_sdl_driver);
	root.put_attr("anisotropy_level", graphics_preferences->OGL_Configure.AnisotropyLevel);
	root.put_attr("multisamples", graphics_preferences->OGL_Configure.Multisamples);
	root.put_attr("geforce_fix", graphics_preferences->OGL_Configure.GeForceFix);
	root.put_attr("wait_for_vsync", graphics_preferences->OGL_Configure.WaitForVSync);
	root.put_attr("gamma_corrected_blending", graphics_preferences->OGL_Configure.Use_sRGB);
	root.put_attr("use_npot", graphics_preferences->OGL_Configure.Use_NPOT);
	root.put_attr("double_corpse_limit", graphics_preferences->double_corpse_limit);
	root.put_attr("hog_the_cpu", graphics_preferences->hog_the_cpu);
	root.put_attr("movie_export_video_quality", graphics_preferences->movie_export_video_quality);
	root.put_attr("movie_export_audio_quality", graphics_preferences->movie_export_audio_quality);

	root.add_color("void.color", graphics_preferences->OGL_Configure.VoidColor);

	for (int i = 0; i < 4; ++i)
		for (int j = 0; j < 2; ++j)
			root.add_color("landscapes.color", graphics_preferences->OGL_Configure.LscpColors[i][j], 2 * i + j);

	for (int i = 0; i <= OGL_NUMBER_OF_TEXTURE_TYPES; ++i)
	{
		OGL_Texture_Configure &Config = (i == OGL_NUMBER_OF_TEXTURE_TYPES) ? graphics_preferences->OGL_Configure.ModelConfig : graphics_preferences->OGL_Configure.TxtrConfigList[i];

		InfoTree tex;
		tex.put_attr("index", i);
		tex.put_attr("near_filter", Config.NearFilter);
		tex.put_attr("far_filter", Config.FarFilter);
		tex.put_attr("resolution", Config.Resolution);
		tex.put_attr("color_format", Config.ColorFormat);
		tex.put_attr("max_size", Config.MaxSize);
		root.add_child("texture", tex);
	}
	return root;
}

InfoTree player_preferences_tree()
{
	InfoTree root;

	root.put_attr_cstr("name", player_preferences->name);
	root.put_attr("color", player_preferences->color);
	root.put_attr("team", player_preferences->team);
	root.put_attr("last_time_ran", player_preferences->last_time_ran);
	root.put_attr("difficulty", player_preferences->difficulty_level);
	root.put_attr("bkgd_music", player_preferences->background_music_on);
	root.put_attr("crosshairs_active", player_preferences->crosshairs_active);

	ChaseCamData &ChaseCam = player_preferences->ChaseCam;
	InfoTree cam;
	cam.put_attr("behind", ChaseCam.Behind);
	cam.put_attr("upward", ChaseCam.Upward);
	cam.put_attr("rightward", ChaseCam.Rightward);
	cam.put_attr("flags", ChaseCam.Flags);
	cam.put_attr("damping", ChaseCam.Damping);
	cam.put_attr("spring", ChaseCam.Spring);
	cam.put_attr("opacity", ChaseCam.Opacity);
	root.put_child("chase_cam", cam);

	CrosshairData &Crosshairs = player_preferences->Crosshairs;
	InfoTree cross;
	cross.put_attr("thickness", Crosshairs.Thickness);
	cross.put_attr("from_center", Crosshairs.FromCenter);
	cross.put_attr("length", Crosshairs.Length);
	cross.put_attr("shape", Crosshairs.Shape);
	cross.put_attr("opacity", Crosshairs.Opacity);
	cross.add_color("color", Crosshairs.Color);
	root.put_child("crosshairs", cross);

	return root;
}

// symbolic names for key and button bindings
static const char *binding_action_name[NUM_KEYS] = {
	"forward", "back", "look-left", "look-right", "strafe-left",
	"strafe-right", "glance-left", "glance-right", "look-up", "look-down",
	"look-ahead", "prev-weapon", "next-weapon", "trigger-1", "trigger-2",
	"strafe", "run", "look", "action", "map",
	"microphone"};
static const char *binding_shell_action_name[NUMBER_OF_SHELL_KEYS] = {
	"inventory-left", "inventory-right", "switch-player-view", "volume-up", "volume-down",
	"map-zoom-in", "map-zoom-out", "fps", "chat", "net-stats"};
static const char *binding_mouse_button_name[NUM_SDL_MOUSE_BUTTONS] = {
	"mouse-left", "mouse-middle", "mouse-right", "mouse-x1", "mouse-x2",
	"mouse-scroll-up", "mouse-scroll-down"};
static const char *binding_joystick_button_name[NUM_SDL_JOYSTICK_BUTTONS] = {
	"controller-a", "controller-b", "controller-x", "controller-y",
	"controller-back", "controller-guide", "controller-start",
	"controller-ls", "controller-rs", "controller-lb", "controller-rb",
	"controller-up", "controller-down", "controller-left", "controller-right",
	"controller-ls-right", "controller-ls-down", "controller-rs-right",
	"controller-rs-down", "controller-lt", "controller-rt",
	"controller-ls-left", "controller-ls-up", "controller-rs-left",
	"controller-rs-up", "controller-lt-neg", "controller-rt-neg"};
static const int binding_num_scancodes = 285;
static const char *binding_scancode_name[binding_num_scancodes] = {
	"unknown", "unknown-1", "unknown-2", "unknown-3", "a",
	"b", "c", "d", "e", "f",
	"g", "h", "i", "j", "k",
	"l", "m", "n", "o", "p",
	"q", "r", "s", "t", "u",
	"v", "w", "x", "y", "z",
	"1", "2", "3", "4", "5",
	"6", "7", "8", "9", "unknown-39",
	"return", "escape", "backspace", "tab", "space",
	"minus", "equals", "leftbracket", "rightbracket", "backslash",
	"nonushash", "semicolon", "apostrophe", "grave", "comma",
	"period", "slash", "capslock", "f1", "f2",
	"f3", "f4", "f5", "f6", "f7",
	"f8", "f9", "f10", "f11", "f12",
	"printscreen", "scrolllock", "pause", "insert", "home",
	"pageup", "delete", "end", "pagedown", "right",
	"left", "down", "up", "numlockclear", "kp-divide",
	"kp-multiply", "kp-minus", "kp-plus", "kp-enter", "kp-1",
	"kp-2", "kp-3", "kp-4", "kp-5", "kp-6",
	"kp-7", "kp-8", "kp-9", "kp-0", "kp-period",
	"nonusbackslash", "application", "power", "kp-equals", "f13",
	"f14", "f15", "f16", "f17", "f18",
	"f19", "f20", "f21", "f22", "f23",
	"f24", "execute", "help", "menu", "select",
	"stop", "again", "undo", "cut", "copy",
	"paste", "find", "mute", "volumeup", "volumedown",
	"unknown-130", "unknown-131", "unknown-132", "kp-comma", "kp-equalsas400",
	"international1", "international2", "international3", "international4", "international5",
	"international6", "international7", "international8", "international9", "lang1",
	"lang2", "lang3", "lang4", "lang5", "lang6",
	"lang7", "lang8", "lang9", "alterase", "sysreq",
	"cancel", "clear", "prior", "return2", "separator",
	"out", "oper", "clearagain", "crsel", "exsel",
	"unknown-165", "unknown-166", "unknown-167", "unknown-168", "unknown-169",
	"unknown-170", "unknown-171", "unknown-172", "unknown-173", "unknown-174",
	"unknown-175", "kp-00", "kp-000", "thousandsseparator", "decimalseparator",
	"currencyunit", "currencysubunit", "kp-leftparen", "kp-rightparen", "kp-leftbrace",
	"kp-rightbrace", "kp-tab", "kp-backspace", "kp-a", "kp-b",
	"kp-c", "kp-d", "kp-e", "kp-f", "kp-xor",
	"kp-power", "kp-percent", "kp-less", "kp-greater", "kp-ampersand",
	"kp-dblampersand", "kp-verticalbar", "kp-dblverticalbar", "kp-colon", "kp-hash",
	"kp-space", "kp-at", "kp-exclam", "kp-memstore", "kp-memrecall",
	"kp-memclear", "kp-memadd", "kp-memsubtract", "kp-memmultiply", "kp-memdivide",
	"kp-plusminus", "kp-clear", "kp-clearentry", "kp-binary", "kp-octal",
	"kp-decimal", "kp-hexadecimal", "unknown-222", "unknown-223", "lctrl",
	"lshift", "lalt", "lgui", "rctrl", "rshift",
	"ralt", "rgui", "unknown-232", "unknown-233", "unknown-234",
	"unknown-235", "unknown-236", "unknown-237", "unknown-238", "unknown-239",
	"unknown-240", "unknown-241", "unknown-242", "unknown-243", "unknown-244",
	"unknown-245", "unknown-246", "unknown-247", "unknown-248", "unknown-249",
	"unknown-250", "unknown-251", "unknown-252", "unknown-253", "unknown-254",
	"unknown-255", "unknown-256", "mode", "audionext", "audioprev",
	"audiostop", "audioplay", "audiomute", "mediaselect", "www",
	"mail", "calculator", "computer", "ac-search", "ac-home",
	"ac-back", "ac-forward", "ac-stop", "ac-refresh", "ac-bookmarks",
	"brightnessdown", "brightnessup", "displayswitch", "kbdillumtoggle", "kbdillumdown",
	"kbdillumup", "eject", "sleep", "app1", "app2"};

static const char *binding_name_for_code(SDL_Scancode code)
{
	int i = static_cast<int>(code);
	if (i >= 0 &&
		i < binding_num_scancodes)
		return binding_scancode_name[i];
	else if (i >= AO_SCANCODE_BASE_MOUSE_BUTTON &&
			 i < (AO_SCANCODE_BASE_MOUSE_BUTTON + NUM_SDL_MOUSE_BUTTONS))
		return binding_mouse_button_name[i - AO_SCANCODE_BASE_MOUSE_BUTTON];
	else if (i >= AO_SCANCODE_BASE_JOYSTICK_BUTTON &&
			 i < (AO_SCANCODE_BASE_JOYSTICK_BUTTON + NUM_SDL_JOYSTICK_BUTTONS))
		return binding_joystick_button_name[i - AO_SCANCODE_BASE_JOYSTICK_BUTTON];
	return "unknown";
}

static SDL_Scancode code_for_binding_name(std::string name)
{
	for (int i = 0; i < binding_num_scancodes; ++i)
	{
		if (name == binding_scancode_name[i])
			return static_cast<SDL_Scancode>(i);
	}
	for (int i = 0; i < NUM_SDL_MOUSE_BUTTONS; ++i)
	{
		if (name == binding_mouse_button_name[i])
			return static_cast<SDL_Scancode>(i + AO_SCANCODE_BASE_MOUSE_BUTTON);
	}
	for (int i = 0; i < NUM_SDL_JOYSTICK_BUTTONS; ++i)
	{
		if (name == binding_joystick_button_name[i])
			return static_cast<SDL_Scancode>(i + AO_SCANCODE_BASE_JOYSTICK_BUTTON);
	}
	return SDL_SCANCODE_UNKNOWN;
}

static int index_for_action_name(std::string name, bool &is_shell)
{
	for (int i = 0; i < NUM_KEYS; ++i)
	{
		if (name == binding_action_name[i])
		{
			is_shell = false;
			return i;
		}
	}
	for (int i = 0; i < NUMBER_OF_SHELL_KEYS; ++i)
	{
		if (name == binding_shell_action_name[i])
		{
			is_shell = true;
			return i;
		}
	}
	return -1;
}

InfoTree input_preferences_tree()
{
	InfoTree root;

	root.put_attr("device", input_preferences->input_device);
	root.put_attr("modifiers", input_preferences->modifiers);
	root.put_attr("sens_horizontal", input_preferences->sens_horizontal);
	root.put_attr("sens_vertical", input_preferences->sens_vertical);
	root.put_attr("classic_vertical_aim", input_preferences->classic_vertical_aim);
	root.put_attr("classic_aim_speed_limits", input_preferences->classic_aim_speed_limits);
	root.put_attr("mouse_accel_type", input_preferences->mouse_accel_type);
	root.put_attr("mouse_accel_scale", input_preferences->mouse_accel_scale);
	root.put_attr("raw_mouse_input", input_preferences->raw_mouse_input);
	root.put_attr("extra_mouse_precision", input_preferences->extra_mouse_precision);

	root.put_attr("controller_analog", input_preferences->controller_analog);
	root.put_attr("controller_sensitivity", input_preferences->controller_sensitivity);
	root.put_attr("controller_deadzone", input_preferences->controller_deadzone);

	for (int i = 0; i < (NUMBER_OF_KEYS + NUMBER_OF_SHELL_KEYS); ++i)
	{
		std::set<SDL_Scancode> codeset;
		const char *name;
		if (i < NUMBER_OF_KEYS)
		{
			codeset = input_preferences->key_bindings[i];
			name = binding_action_name[i];
		}
		else
		{
			codeset = input_preferences->shell_key_bindings[i - NUMBER_OF_KEYS];
			name = binding_shell_action_name[i - NUMBER_OF_KEYS];
		}

		BOOST_FOREACH (const SDL_Scancode &code, codeset)
		{
			if (code == SDL_SCANCODE_UNKNOWN)
				continue;
			InfoTree key;
			key.put_attr("action", name);
			key.put_attr("pressed", binding_name_for_code(code));
			root.add_child("binding", key);
		}
	}

	return root;
}

InfoTree sound_preferences_tree()
{
	InfoTree root;

	root.put_attr("channels", sound_preferences->channel_count);
	root.put_attr("volume", sound_preferences->volume);
	root.put_attr("music_volume", sound_preferences->music);
	root.put_attr("flags", sound_preferences->flags);
	root.put_attr("rate", sound_preferences->rate);
	root.put_attr("samples", sound_preferences->samples);
	root.put_attr("volume_while_speaking", sound_preferences->volume_while_speaking);
	root.put_attr("mute_while_transmitting", sound_preferences->mute_while_transmitting);

	return root;
}

InfoTree network_preferences_tree()
{
	InfoTree root;

	root.put_attr("microphone", network_preferences->allow_microphone);
	root.put_attr("untimed", network_preferences->game_is_untimed);
	root.put_attr("type", network_preferences->type);
	root.put_attr("game_type", network_preferences->game_type);
	root.put_attr("difficulty", network_preferences->difficulty_level);
	root.put_attr("game_options", network_preferences->game_options);
	root.put_attr("time_limit", network_preferences->time_limit);
	root.put_attr("kill_limit", network_preferences->kill_limit);
	root.put_attr("entry_point", network_preferences->entry_point);
	root.put_attr("autogather", network_preferences->autogather);
	root.put_attr("join_by_address", network_preferences->join_by_address);
	root.put_attr("join_address", network_preferences->join_address);
	root.put_attr("local_game_port", network_preferences->game_port);
	root.put_attr("game_protocol", sNetworkGameProtocolNames[network_preferences->game_protocol]);
	root.put_attr("use_speex_netmic_encoder", network_preferences->use_speex_encoder);
	root.put_attr("use_netscript", network_preferences->use_netscript);
	root.put_attr_path("netscript_file", network_preferences->netscript_file);
	root.put_attr("cheat_flags", network_preferences->cheat_flags);
	root.put_attr("advertise_on_metaserver", network_preferences->advertise_on_metaserver);
	root.put_attr("attempt_upnp", network_preferences->attempt_upnp);
	root.put_attr("check_for_updates", network_preferences->check_for_updates);
	root.put_attr("verify_https", network_preferences->verify_https);
	root.put_attr("metaserver_login", network_preferences->metaserver_login);

	char passwd[33];
	for (int i = 0; i < 16; i++)
		sprintf(&passwd[2 * i], "%.2x", network_preferences->metaserver_password[i] ^ sPasswordMask[i]);
	passwd[32] = '\0';
	root.put_attr("metaserver_password", passwd);

	root.put_attr("use_custom_metaserver_colors", network_preferences->use_custom_metaserver_colors);
	root.put_attr("mute_metaserver_guests", network_preferences->mute_metaserver_guests);
	root.put_attr("join_metaserver_by_default", network_preferences->join_metaserver_by_default);
	root.put_attr("allow_stats", network_preferences->allow_stats);

	for (int i = 0; i < 2; i++)
		root.add_color("color", network_preferences->metaserver_colors[i], i);

	root.put_child("star_protocol", StarPreferencesTree());
	root.put_child("ring_protocol", RingPreferencesTree());

	return root;
}

InfoTree environment_preferences_tree()
{
	InfoTree root;

	root.put_attr_path("map_file", environment_preferences->map_file);
	root.put_attr_path("physics_file", environment_preferences->physics_file);
	root.put_attr_path("shapes_file", environment_preferences->shapes_file);
	root.put_attr_path("sounds_file", environment_preferences->sounds_file);
	root.put_attr_path("resources_file", environment_preferences->resources_file);
	root.put_attr("map_checksum", environment_preferences->map_checksum);
	root.put_attr("physics_checksum", environment_preferences->physics_checksum);
	root.put_attr("shapes_mod_date", static_cast<uint32>(environment_preferences->shapes_mod_date));
	root.put_attr("sounds_mod_date", static_cast<uint32>(environment_preferences->sounds_mod_date));
	root.put_attr("group_by_directory", environment_preferences->group_by_directory);
	root.put_attr("reduce_singletons", environment_preferences->reduce_singletons);
	root.put_attr("smooth_text", environment_preferences->smooth_text);
	root.put_attr_path("solo_lua_file", environment_preferences->solo_lua_file);
	root.put_attr("use_solo_lua", environment_preferences->use_solo_lua);
	root.put_attr("use_replay_net_lua", environment_preferences->use_replay_net_lua);
	root.put_attr("hide_alephone_extensions", environment_preferences->hide_extensions);
	root.put_attr("film_profile", static_cast<uint32>(environment_preferences->film_profile));
	root.put_attr("maximum_quick_saves", environment_preferences->maximum_quick_saves);

	for (Plugins::iterator it = Plugins::instance()->begin(); it != Plugins::instance()->end(); ++it)
	{
		if (it->compatible() && !it->enabled)
		{
			InfoTree disable;
			disable.put_attr_path("path", it->directory.GetPath());
			root.add_child("disable_plugin", disable);
		}
	}

	return root;
}

void write_preferences()
{
	InfoTree root;
	root.put_attr("version", A1_DATE_VERSION);

	root.put_child("graphics", graphics_preferences_tree());
	root.put_child("player", player_preferences_tree());
	root.put_child("input", input_preferences_tree());
	root.put_child("sound", sound_preferences_tree());
#if !defined(DISABLE_NETWORKING)
	root.put_child("network", network_preferences_tree());
#endif
	root.put_child("environment", environment_preferences_tree());

	InfoTree fileroot;
	fileroot.put_child("mara_prefs", root);

	FileSpecifier FileSpec;
	FileSpec.SetToPreferencesDir();
	FileSpec += getcstr(temporary, strFILENAMES, filenamePREFERENCES);

	try
	{
		fileroot.save_xml(FileSpec);
	}
	catch (InfoTree::parse_error ex)
	{
		logError("Error saving preferences file (%s): %s", FileSpec.GetPath(), ex.what());
	}
	catch (InfoTree::unexpected_error ex)
	{
		logError("Error saving preferences file (%s): %s", FileSpec.GetPath(), ex.what());
	}
}

/*
 *  Setup default preferences
 */

static void default_graphics_preferences(graphics_preferences_data *preferences)
{
	memset(&preferences->screen_mode, '\0', sizeof(screen_mode_data));
	preferences->screen_mode.gamma_level = DEFAULT_GAMMA_LEVEL;

	preferences->screen_mode.width = 640;
	preferences->screen_mode.height = 480;
	preferences->screen_mode.auto_resolution = true;
	preferences->screen_mode.high_dpi = true;
	preferences->screen_mode.hud = true;
	preferences->screen_mode.hud_scale_level = 0;
	preferences->screen_mode.term_scale_level = 2;
	preferences->screen_mode.translucent_map = true;
	preferences->screen_mode.acceleration = _opengl_acceleration;
	preferences->screen_mode.high_resolution = true;
	preferences->screen_mode.fullscreen = true;
	preferences->screen_mode.fix_h_not_v = true;
	preferences->screen_mode.camera_bob = true;
	preferences->screen_mode.bit_depth = 32;

	preferences->screen_mode.draw_every_other_line = false;

	OGL_SetDefaults(preferences->OGL_Configure);

	preferences->double_corpse_limit = false;
	preferences->hog_the_cpu = false;

	preferences->software_alpha_blending = _sw_alpha_off;
	preferences->software_sdl_driver = _sw_driver_default;

	preferences->movie_export_video_quality = 50;
	preferences->movie_export_audio_quality = 50;
}

static void default_network_preferences(network_preferences_data *preferences)
{
	preferences->type = _ethernet;

	preferences->allow_microphone = true;
	preferences->game_is_untimed = false;
	preferences->difficulty_level = 2;
	preferences->game_options = _multiplayer_game | _ammo_replenishes | _weapons_replenish | _specials_replenish | _burn_items_on_death | _force_unique_teams | _live_network_stats;
	preferences->time_limit = 10 * TICKS_PER_SECOND * 60;
	preferences->kill_limit = 10;
	preferences->entry_point = 0;
	preferences->game_type = _game_of_kill_monsters;
	preferences->autogather = false;
	preferences->join_by_address = false;
	obj_clear(preferences->join_address);
	preferences->game_port = DEFAULT_GAME_PORT;
	preferences->game_protocol = _network_game_protocol_default;
#if !defined(DISABLE_NETWORKING)
	DefaultStarPreferences();
	DefaultRingPreferences();
#endif // !defined(DISABLE_NETWORKING)
	preferences->use_speex_encoder = true;
	preferences->use_netscript = false;
	preferences->netscript_file[0] = '\0';
	preferences->cheat_flags = _allow_tunnel_vision | _allow_crosshair | _allow_behindview | _allow_overlay_map;
	preferences->advertise_on_metaserver = false;
	preferences->attempt_upnp = false;
	preferences->check_for_updates = true;
	preferences->verify_https = false;
	strncpy(preferences->metaserver_login, "guest", preferences->kMetaserverLoginLength);
	memset(preferences->metaserver_password, 0, preferences->kMetaserverLoginLength);
	preferences->mute_metaserver_guests = false;
	preferences->use_custom_metaserver_colors = false;
	preferences->metaserver_colors[0] = get_interface_color(PLAYER_COLOR_BASE_INDEX);
	preferences->metaserver_colors[1] = get_interface_color(PLAYER_COLOR_BASE_INDEX);
	preferences->join_metaserver_by_default = false;
	preferences->allow_stats = false;
}

static void default_player_preferences(player_preferences_data *preferences)
{
	obj_clear(*preferences);

	preferences->difficulty_level = 2;
	strncpy(preferences->name, get_name_from_system().c_str(), PREFERENCES_NAME_LENGTH + 1);

	// LP additions for new fields:

	preferences->ChaseCam.Behind = 1536;
	preferences->ChaseCam.Upward = 0;
	preferences->ChaseCam.Rightward = 0;
	preferences->ChaseCam.Flags = 0;
	preferences->ChaseCam.Damping = 0.5;
	preferences->ChaseCam.Spring = 0;
	preferences->ChaseCam.Opacity = 1;

	preferences->Crosshairs.Thickness = 3;
	preferences->Crosshairs.FromCenter = 2;
	preferences->Crosshairs.Length = 1;
	preferences->Crosshairs.Shape = CHShape_RealCrosshairs;
	preferences->Crosshairs.Color = rgb_white;
	preferences->Crosshairs.Opacity = 0.5;
	preferences->Crosshairs.PreCalced = false;
}

static void default_input_preferences(input_preferences_data *preferences)
{
	preferences->input_device = _mouse_yaw_pitch;
	preferences->key_bindings = default_key_bindings;
	preferences->shell_key_bindings = default_shell_key_bindings;

	// LP addition: set up defaults for modifiers:
	// interchange run and walk, but don't interchange swim and sink.
	preferences->modifiers = _inputmod_interchange_run_walk;

	// LP: split into horizontal and vertical sensitivities
	// ZZZ addition: sensitivity factor starts at 1 (no adjustment)
	preferences->sens_horizontal = FIXED_ONE;
	preferences->sens_vertical = FIXED_ONE;
	preferences->mouse_accel_type = _mouse_accel_none;
	preferences->mouse_accel_scale = 1.f;
	preferences->raw_mouse_input = true;
	preferences->extra_mouse_precision = true;
	preferences->classic_vertical_aim = false;
	preferences->classic_aim_speed_limits = true;

	preferences->controller_analog = true;
	preferences->controller_sensitivity = FIXED_ONE;
	preferences->controller_deadzone = 3276;
}

static void default_environment_preferences(environment_preferences_data *preferences)
{
	obj_set(*preferences, NONE);

	FileSpecifier DefaultMapFile;
	FileSpecifier DefaultShapesFile;
	FileSpecifier DefaultSoundsFile;
	FileSpecifier DefaultPhysicsFile;
	FileSpecifier DefaultExternalResourcesFile;

	get_default_map_spec(DefaultMapFile);
	get_default_physics_spec(DefaultPhysicsFile);
	get_default_shapes_spec(DefaultShapesFile);
	get_default_sounds_spec(DefaultSoundsFile);
	get_default_external_resources_spec(DefaultExternalResourcesFile);

	preferences->map_checksum = read_wad_file_checksum(DefaultMapFile);
	strncpy(preferences->map_file, DefaultMapFile.GetPath(), 256);
	preferences->map_file[255] = 0;

	preferences->physics_checksum = read_wad_file_checksum(DefaultPhysicsFile);
	strncpy(preferences->physics_file, DefaultPhysicsFile.GetPath(), 256);
	preferences->physics_file[255] = 0;

	preferences->shapes_mod_date = DefaultShapesFile.GetDate();
	strncpy(preferences->shapes_file, DefaultShapesFile.GetPath(), 256);
	preferences->shapes_file[255] = 0;

	preferences->sounds_mod_date = DefaultSoundsFile.GetDate();
	strncpy(preferences->sounds_file, DefaultSoundsFile.GetPath(), 256);
	preferences->sounds_file[255] = 0;

	strncpy(preferences->resources_file, DefaultExternalResourcesFile.GetPath(), 256);
	preferences->resources_file[255] = 0;

	preferences->group_by_directory = true;
	preferences->reduce_singletons = false;
	preferences->smooth_text = true;

	preferences->solo_lua_file[0] = 0;
	preferences->use_solo_lua = false;
	preferences->use_replay_net_lua = false;
	preferences->hide_extensions = true;
	preferences->film_profile = FILM_PROFILE_DEFAULT;
	preferences->maximum_quick_saves = 0;
}

/*
 *  Validate preferences
 */

static bool validate_graphics_preferences(graphics_preferences_data *preferences)
{
	bool changed = false;

	// Fix bool options
	preferences->screen_mode.high_resolution = !!preferences->screen_mode.high_resolution;
	preferences->screen_mode.fullscreen = !!preferences->screen_mode.fullscreen;
	preferences->screen_mode.draw_every_other_line = !!preferences->screen_mode.draw_every_other_line;
	preferences->screen_mode.fix_h_not_v = !!preferences->screen_mode.fix_h_not_v;

	if (preferences->screen_mode.gamma_level < 0 || preferences->screen_mode.gamma_level >= NUMBER_OF_GAMMA_LEVELS)
	{
		preferences->screen_mode.gamma_level = DEFAULT_GAMMA_LEVEL;
		changed = true;
	}

	if (preferences->screen_mode.acceleration != _no_acceleration && preferences->screen_mode.acceleration != _opengl_acceleration)
		preferences->screen_mode.acceleration = _opengl_acceleration;

	// OpenGL requires at least 16 bit color depth
	if (preferences->screen_mode.acceleration != _no_acceleration && preferences->screen_mode.bit_depth == 8)
	{
		preferences->screen_mode.bit_depth = 16;
		changed = true;
	}

#ifdef TRUE_COLOR_ONLY
	if (preferences->screen_mode.bit_depth == 8)
	{
		preferences->screen_mode.bit_depth = 16;
		changed = true;
	}
#endif

	return changed;
}

static bool validate_network_preferences(network_preferences_data *preferences)
{
	bool changed = false;

	// Fix bool options
	preferences->allow_microphone = !!preferences->allow_microphone;
	preferences->game_is_untimed = !!preferences->game_is_untimed;

	if (preferences->type < 0 || preferences->type > _ethernet)
	{
		if (ethernet_active())
		{
			preferences->type = _ethernet;
		}
		else
		{
			preferences->type = _localtalk;
		}
		changed = true;
	}

	if (preferences->game_is_untimed != true && preferences->game_is_untimed != false)
	{
		preferences->game_is_untimed = false;
		changed = true;
	}

	if (preferences->allow_microphone != true && preferences->allow_microphone != false)
	{
		preferences->allow_microphone = true;
		changed = true;
	}

	if (preferences->game_type < 0 || preferences->game_type >= NUMBER_OF_GAME_TYPES)
	{
		preferences->game_type = _game_of_kill_monsters;
		changed = true;
	}

	// ZZZ: is this relevant anymore now with XML prefs?  if so, should validate autogather, join_by_address, and join_address.

	if (preferences->game_protocol >= NUMBER_OF_NETWORK_GAME_PROTOCOLS)
	{
		preferences->game_protocol = _network_game_protocol_default;
		changed = true;
	}

	return changed;
}

static bool validate_player_preferences(player_preferences_data *preferences)
{
	// Fix bool options
	preferences->background_music_on = !!preferences->background_music_on;

	return false;
}

static bool validate_input_preferences(input_preferences_data *preferences)
{
	(void)(preferences);
	return false;
}

static bool validate_environment_preferences(environment_preferences_data *preferences)
{
	(void)(preferences);
	return false;
}

/*
 *  Load the environment
 */

/* Load the environment.. */
void load_environment_from_preferences(
	void)
{
	FileSpecifier File;
	struct environment_preferences_data *prefs = environment_preferences;

	File = prefs->map_file;
	if (File.Exists())
	{
		set_map_file(File);
	}
	else
	{
		/* Try to find the checksum */
		if (find_wad_file_that_has_checksum(File,
											_typecode_scenario, strPATHS, prefs->map_checksum))
		{
			set_map_file(File);
		}
		else
		{
			set_to_default_map();
		}
	}

	File = prefs->physics_file;
	if (File.Exists())
	{
		set_physics_file(File);
		import_definition_structures();
	}
	else
	{
		if (find_wad_file_that_has_checksum(File,
											_typecode_physics, strPATHS, prefs->physics_checksum))
		{
			set_physics_file(File);
			import_definition_structures();
		}
		else
		{
			/* Didn't find it.  Don't change them.. */
		}
	}

	File = prefs->shapes_file;
	if (File.Exists())
	{
		open_shapes_file(File);
	}
	else
	{
		if (find_file_with_modification_date(File,
											 _typecode_shapes, strPATHS, prefs->shapes_mod_date))
		{
			open_shapes_file(File);
		}
		else
		{
			/* What should I do? */
		}
	}

	File = prefs->sounds_file;
	if (File.Exists())
	{
		SoundManager::instance()->OpenSoundFile(File);
	}
	else
	{
		if (find_file_with_modification_date(File,
											 _typecode_sounds, strPATHS, prefs->sounds_mod_date))
		{
			SoundManager::instance()->OpenSoundFile(File);
		}
		else
		{
			/* What should I do? */
		}
	}

	File = prefs->resources_file;
	if (File.Exists())
	{
		set_external_resources_file(File);
	}
	set_external_resources_images_file(File);
}

// LP addition: get these from the preferences data
ChaseCamData &GetChaseCamData() { return player_preferences->ChaseCam; }
CrosshairData &GetCrosshairData() { return player_preferences->Crosshairs; }
OGL_ConfigureData &Get_OGL_ConfigureData() { return graphics_preferences->OGL_Configure; }

// ZZZ: override player-behavior modifiers
static bool sStandardizeModifiers = false;

void standardize_player_behavior_modifiers()
{
	sStandardizeModifiers = true;
}

void restore_custom_player_behavior_modifiers()
{
	sStandardizeModifiers = false;
}

bool is_player_behavior_standard()
{
	return !dont_switch_to_new_weapon();
}

// LP addition: modification of Josh Elsasser's dont-switch-weapons patch
// so as to access preferences stuff here
bool dont_switch_to_new_weapon()
{
	// ZZZ: let game require standard modifiers for a while
	if (!sStandardizeModifiers)
		return TEST_FLAG(input_preferences->modifiers, _inputmod_dont_switch_to_new_weapon);
	else
		return false;
}

bool dont_auto_recenter()
{
	return TEST_FLAG(input_preferences->modifiers, _inputmod_dont_auto_recenter);
}

// LP additions: MML-like prefs stuff
// These parsers are intended to work correctly on both Mac and SDL prefs files;
// including one crossing over to the other platform (uninterpreted fields become defaults)

// To get around both RGBColor and rgb_color being used in the code
template <class CType1, class CType2>
void CopyColor(CType1 &Dest, CType2 &Src)
{
	Dest.red = Src.red;
	Dest.green = Src.green;
	Dest.blue = Src.blue;
}

struct ViewSizeData
{
	short Width, Height;
	bool HUD;
};

const ViewSizeData LegacyViewSizes[32] =
	{
		{320, 160, true},
		{480, 240, true},
		{640, 480, true},
		{640, 480, false},
		{800, 600, true},
		{800, 600, false},
		{1024, 768, true},
		{1024, 768, false},
		{1280, 1024, true},
		{1280, 1024, false},
		{1600, 1200, true},
		{1600, 1200, false},
		{1024, 640, true},
		{1024, 640, false},
		{1280, 800, true},
		{1280, 800, false},
		{1280, 854, true},
		{1280, 854, false},
		{1440, 900, true},
		{1440, 900, false},
		{1680, 1050, true},
		{1680, 1050, false},
		{1920, 1200, true},
		{1920, 1200, false},
		{2560, 1600, true},
		{2560, 1600, false},
		{1280, 768, true},
		{1280, 768, false},
		{1280, 960, true},
		{1280, 960, false},
		{1280, 720, true},
		{1280, 720, false}};

void parse_graphics_preferences(InfoTree root, std::string version)
{
	int scmode = -1;
	root.read_attr("scmode_size", scmode);
	if (scmode >= 0 && scmode < 32)
	{
		graphics_preferences->screen_mode.height = LegacyViewSizes[scmode].Height;
		graphics_preferences->screen_mode.width = LegacyViewSizes[scmode].Width;
		graphics_preferences->screen_mode.hud = LegacyViewSizes[scmode].HUD;
	}

	root.read_attr("scmode_height", graphics_preferences->screen_mode.height);
	root.read_attr("scmode_width", graphics_preferences->screen_mode.width);
	root.read_attr("scmode_auto_resolution", graphics_preferences->screen_mode.auto_resolution);
	root.read_attr("scmode_high_dpi", graphics_preferences->screen_mode.high_dpi);
	root.read_attr("scmode_hud", graphics_preferences->screen_mode.hud);
	root.read_attr("scmode_hud_scale", graphics_preferences->screen_mode.hud_scale_level);
	root.read_attr("scmode_term_scale", graphics_preferences->screen_mode.term_scale_level);
	root.read_attr("scmode_translucent_map", graphics_preferences->screen_mode.translucent_map);
	root.read_attr("scmode_camera_bob", graphics_preferences->screen_mode.camera_bob);
	root.read_attr("scmode_accel", graphics_preferences->screen_mode.acceleration);
	root.read_attr("scmode_highres", graphics_preferences->screen_mode.high_resolution);
	root.read_attr("scmode_fullscreen", graphics_preferences->screen_mode.fullscreen);

	root.read_attr("scmode_fix_h_not_v", graphics_preferences->screen_mode.fix_h_not_v);
	root.read_attr("scmode_bitdepth", graphics_preferences->screen_mode.bit_depth);
	root.read_attr("scmode_gamma", graphics_preferences->screen_mode.gamma_level);
	root.read_attr("ogl_flags", graphics_preferences->OGL_Configure.Flags);
	root.read_attr("software_alpha_blending", graphics_preferences->software_alpha_blending);
	root.read_attr("software_sdl_driver", graphics_preferences->software_sdl_driver);
	root.read_attr("anisotropy_level", graphics_preferences->OGL_Configure.AnisotropyLevel);
	root.read_attr("multisamples", graphics_preferences->OGL_Configure.Multisamples);
	root.read_attr("geforce_fix", graphics_preferences->OGL_Configure.GeForceFix);
	root.read_attr("wait_for_vsync", graphics_preferences->OGL_Configure.WaitForVSync);
	root.read_attr("gamma_corrected_blending", graphics_preferences->OGL_Configure.Use_sRGB);
	root.read_attr("use_npot", graphics_preferences->OGL_Configure.Use_NPOT);
	root.read_attr("double_corpse_limit", graphics_preferences->double_corpse_limit);
	root.read_attr("hog_the_cpu", graphics_preferences->hog_the_cpu);
	root.read_attr_bounded<int16>("movie_export_video_quality", graphics_preferences->movie_export_video_quality, 0, 100);
	root.read_attr_bounded<int16>("movie_export_audio_quality", graphics_preferences->movie_export_audio_quality, 0, 100);

	BOOST_FOREACH (InfoTree vtree, root.children_named("void"))
	{
		BOOST_FOREACH (InfoTree color, vtree.children_named("color"))
		{
			color.read_color(graphics_preferences->OGL_Configure.VoidColor);
		}
	}

	BOOST_FOREACH (InfoTree landscape, root.children_named("landscapes"))
	{
		BOOST_FOREACH (InfoTree color, root.children_named("color"))
		{
			int16 index;
			if (color.read_indexed("index", index, 8))
				color.read_color(graphics_preferences->OGL_Configure.LscpColors[index / 2][index % 2]);
		}
	}

	BOOST_FOREACH (InfoTree tex, root.children_named("texture"))
	{
		int16 index;
		if (tex.read_indexed("index", index, OGL_NUMBER_OF_TEXTURE_TYPES + 1))
		{
			OGL_Texture_Configure &Config = (index == OGL_NUMBER_OF_TEXTURE_TYPES) ? graphics_preferences->OGL_Configure.ModelConfig : graphics_preferences->OGL_Configure.TxtrConfigList[index];
			tex.read_attr("near_filter", Config.NearFilter);
			tex.read_attr("far_filter", Config.FarFilter);
			tex.read_attr("resolution", Config.Resolution);
			tex.read_attr("color_format", Config.ColorFormat);
			tex.read_attr("max_size", Config.MaxSize);
		}
	}
}

void parse_player_preferences(InfoTree root, std::string version)
{
	root.read_cstr("name", player_preferences->name, PREFERENCES_NAME_LENGTH);
	root.read_attr("color", player_preferences->color);
	root.read_attr("team", player_preferences->team);
	root.read_attr("last_time_ran", player_preferences->last_time_ran);
	root.read_attr("difficulty", player_preferences->difficulty_level);
	root.read_attr("bkgd_music", player_preferences->background_music_on);
	root.read_attr("crosshairs_active", player_preferences->crosshairs_active);

	BOOST_FOREACH (InfoTree child, root.children_named("chase_cam"))
	{
		child.read_attr("behind", player_preferences->ChaseCam.Behind);
		child.read_attr("upward", player_preferences->ChaseCam.Upward);
		child.read_attr("rightward", player_preferences->ChaseCam.Rightward);
		child.read_attr("flags", player_preferences->ChaseCam.Flags);
		child.read_attr("damping", player_preferences->ChaseCam.Damping);
		child.read_attr("spring", player_preferences->ChaseCam.Spring);
		child.read_attr("opacity", player_preferences->ChaseCam.Opacity);
	}

	BOOST_FOREACH (InfoTree child, root.children_named("crosshairs"))
	{
		child.read_attr("thickness", player_preferences->Crosshairs.Thickness);
		child.read_attr("from_center", player_preferences->Crosshairs.FromCenter);
		child.read_attr("length", player_preferences->Crosshairs.Length);
		child.read_attr("shape", player_preferences->Crosshairs.Shape);
		child.read_attr("opacity", player_preferences->Crosshairs.Opacity);

		BOOST_FOREACH (InfoTree color, child.children_named("color"))
			color.read_color(player_preferences->Crosshairs.Color);
	}
}

SDL_Scancode translate_old_key(int code)
{
	static int num_key_lookups = 323;
	static SDL_Keycode key_lookups[] = {
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_BACKSPACE, SDLK_TAB,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_CLEAR, SDLK_RETURN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_PAUSE,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_ESCAPE, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_SPACE, SDLK_EXCLAIM, SDLK_QUOTEDBL,
		SDLK_HASH, SDLK_DOLLAR, SDLK_UNKNOWN, SDLK_AMPERSAND, SDLK_QUOTE,
		SDLK_LEFTPAREN, SDLK_RIGHTPAREN, SDLK_ASTERISK, SDLK_PLUS, SDLK_COMMA,
		SDLK_MINUS, SDLK_PERIOD, SDLK_SLASH, SDLK_0, SDLK_1,
		SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6,
		SDLK_7, SDLK_8, SDLK_9, SDLK_COLON, SDLK_SEMICOLON,
		SDLK_LESS, SDLK_EQUALS, SDLK_GREATER, SDLK_QUESTION, SDLK_AT,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_LEFTBRACKET, SDLK_BACKSLASH, SDLK_RIGHTBRACKET, SDLK_CARET,
		SDLK_UNDERSCORE, SDLK_BACKQUOTE, SDLK_a, SDLK_b, SDLK_c,
		SDLK_d, SDLK_e, SDLK_f, SDLK_g, SDLK_h,
		SDLK_i, SDLK_j, SDLK_k, SDLK_l, SDLK_m,
		SDLK_n, SDLK_o, SDLK_p, SDLK_q, SDLK_r,
		SDLK_s, SDLK_t, SDLK_u, SDLK_v, SDLK_w,
		SDLK_x, SDLK_y, SDLK_z, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_DELETE, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_UNKNOWN, SDLK_KP_0, SDLK_KP_1, SDLK_KP_2, SDLK_KP_3,
		SDLK_KP_4, SDLK_KP_5, SDLK_KP_6, SDLK_KP_7, SDLK_KP_8,
		SDLK_KP_9, SDLK_KP_PERIOD, SDLK_KP_DIVIDE, SDLK_KP_MULTIPLY, SDLK_KP_MINUS,
		SDLK_KP_PLUS, SDLK_KP_ENTER, SDLK_KP_EQUALS, SDLK_UP, SDLK_DOWN,
		SDLK_RIGHT, SDLK_LEFT, SDLK_INSERT, SDLK_HOME, SDLK_END,
		SDLK_PAGEUP, SDLK_PAGEDOWN, SDLK_F1, SDLK_F2, SDLK_F3,
		SDLK_F4, SDLK_F5, SDLK_F6, SDLK_F7, SDLK_F8,
		SDLK_F9, SDLK_F10, SDLK_F11, SDLK_F12, SDLK_F13,
		SDLK_F14, SDLK_F15, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN,
		SDLK_NUMLOCKCLEAR, SDLK_CAPSLOCK, SDLK_SCROLLLOCK, SDLK_RSHIFT, SDLK_LSHIFT,
		SDLK_RCTRL, SDLK_LCTRL, SDLK_RALT, SDLK_LALT, SDLK_RGUI,
		SDLK_LGUI, SDLK_LGUI, SDLK_RGUI, SDLK_MODE, SDLK_UNKNOWN,
		SDLK_HELP, SDLK_PRINTSCREEN, SDLK_SYSREQ, SDLK_UNKNOWN, SDLK_MENU,
		SDLK_POWER, SDLK_CURRENCYUNIT, SDLK_UNDO};

	if (code >= 65 && code < 73)
		return static_cast<SDL_Scancode>(AO_SCANCODE_BASE_MOUSE_BUTTON + (code - 65));
	else if (code >= 73 && code < 91)
		return static_cast<SDL_Scancode>(AO_SCANCODE_BASE_JOYSTICK_BUTTON + (code - 73));
	else if (code < num_key_lookups)
		return SDL_GetScancodeFromKey(key_lookups[code]);
	return SDL_SCANCODE_UNKNOWN;
}

void parse_input_preferences(InfoTree root, std::string version)
{
	root.read_attr("device", input_preferences->input_device);
	root.read_attr("modifiers", input_preferences->modifiers);

	// old prefs may have combined sensitivity
	root.read_attr("sensitivity", input_preferences->sens_horizontal);
	root.read_attr("sensitivity", input_preferences->sens_vertical);
	root.read_attr("sens_horizontal", input_preferences->sens_horizontal);
	root.read_attr("sens_vertical", input_preferences->sens_vertical);

	if (!version.length() || version < "20181113")
		input_preferences->classic_vertical_aim = true;
	else if (version < "20190317")
		input_preferences->classic_vertical_aim = false;
	root.read_attr("classic_vertical_aim", input_preferences->classic_vertical_aim);

	if (!root.read_attr("classic_aim_speed_limits", input_preferences->classic_aim_speed_limits))
	{
		// Assume users with older prefs with "mouse_max_speed" above its default value don't want classic limits
		float mouse_max_speed;
		if (root.read_attr("mouse_max_speed", mouse_max_speed) && mouse_max_speed > 0.25)
			input_preferences->classic_aim_speed_limits = false;
	}

	// old prefs may have boolean acceleration flag
	bool accel = false;
	if (root.read_attr("mouse_acceleration", accel))
	{
		input_preferences->mouse_accel_type = _mouse_accel_classic;
		input_preferences->mouse_accel_scale = 1.f;
	}
	root.read_attr_bounded<int16>("mouse_accel_type",
								  input_preferences->mouse_accel_type,
								  0, NUMBER_OF_MOUSE_ACCEL_TYPES - 1);
	root.read_attr("mouse_accel_scale", input_preferences->mouse_accel_scale);

	// old prefs mixed "classic vertical aim" with acceleration type
	if (version >= "20181113" && version < "20190317")
	{
		if (input_preferences->mouse_accel_type == _mouse_accel_symmetric)
		{
			input_preferences->classic_vertical_aim = false;
			input_preferences->mouse_accel_type = _mouse_accel_classic;
		}
		else if (input_preferences->mouse_accel_type == _mouse_accel_classic)
		{
			input_preferences->classic_vertical_aim = true;
			input_preferences->sens_vertical *= 4.f;
		}
	}

	if (!version.length() || version < "20170821")
		input_preferences->raw_mouse_input = false;
	root.read_attr("raw_mouse_input", input_preferences->raw_mouse_input);
	if (!version.length() || version < "20181208")
		input_preferences->extra_mouse_precision = false;
	root.read_attr("extra_mouse_precision", input_preferences->extra_mouse_precision);
	root.read_attr("controller_analog", input_preferences->controller_analog);
	root.read_attr("controller_sensitivity", input_preferences->controller_sensitivity);
	root.read_attr("controller_deadzone", input_preferences->controller_deadzone);

	// remove default key bindings the first time we see one from these prefs
	bool seen_key[NUMBER_OF_KEYS];
	memset(seen_key, 0, sizeof(seen_key));
	bool seen_shell_key[NUMBER_OF_SHELL_KEYS];
	memset(seen_shell_key, 0, sizeof(seen_shell_key));

	// import old key bindings
	BOOST_FOREACH (InfoTree key, root.children_named("sdl_key"))
	{
		int16 index;
		if (key.read_indexed("index", index, NUMBER_OF_KEYS))
		{
			if (!seen_key[index])
			{
				input_preferences->key_bindings[index].clear();
				seen_key[index] = true;
			}
			int code;
			if (key.read_attr("value", code))
			{
				SDL_Scancode translated = translate_old_key(code);
				unset_scancode(translated);
				input_preferences->key_bindings[index].insert(translated);
			}
		}
		else if (key.read_indexed("index", index, NUMBER_OF_KEYS + NUMBER_OF_SHELL_KEYS))
		{
			int shell_index = index - NUMBER_OF_KEYS;
			if (!seen_shell_key[shell_index])
			{
				input_preferences->shell_key_bindings[shell_index].clear();
				seen_shell_key[shell_index] = true;
			}
			int code;
			if (key.read_attr("value", code))
			{
				SDL_Scancode translated = translate_old_key(code);
				unset_scancode(translated);
				input_preferences->shell_key_bindings[shell_index].insert(translated);
			}
		}
	}

	BOOST_FOREACH (InfoTree key, root.children_named("binding"))
	{
		std::string action_name, pressed_name;
		if (key.read_attr("action", action_name) &&
			key.read_attr("pressed", pressed_name))
		{
			bool shell = false;
			int index = index_for_action_name(action_name, shell);
			if (index < 0)
				continue;
			SDL_Scancode code = code_for_binding_name(pressed_name);
			if (shell)
			{
				if (!seen_shell_key[index])
				{
					input_preferences->shell_key_bindings[index].clear();
					seen_shell_key[index] = true;
				}
				unset_scancode(code);
				input_preferences->shell_key_bindings[index].insert(code);
			}
			else
			{
				if (!seen_key[index])
				{
					input_preferences->key_bindings[index].clear();
					seen_key[index] = true;
				}
				unset_scancode(code);
				input_preferences->key_bindings[index].insert(code);
			}
		}
	}
}

void parse_sound_preferences(InfoTree root, std::string version)
{
	root.read_attr("channels", sound_preferences->channel_count);
	root.read_attr("volume", sound_preferences->volume);
	root.read_attr("music_volume", sound_preferences->music);
	root.read_attr("flags", sound_preferences->flags);
	root.read_attr("rate", sound_preferences->rate);
	root.read_attr("samples", sound_preferences->samples);
	root.read_attr("volume_while_speaking", sound_preferences->volume_while_speaking);
	root.read_attr("mute_while_transmitting", sound_preferences->mute_while_transmitting);
}

void parse_network_preferences(InfoTree root, std::string version)
{
	root.read_attr("microphone", network_preferences->allow_microphone);
	root.read_attr("untimed", network_preferences->game_is_untimed);
	root.read_attr("type", network_preferences->type);
	root.read_attr("game_type", network_preferences->game_type);
	root.read_attr("difficulty", network_preferences->difficulty_level);
	root.read_attr("game_options", network_preferences->game_options);
	root.read_attr("time_limit", network_preferences->time_limit);
	root.read_attr("kill_limit", network_preferences->kill_limit);
	root.read_attr("entry_point", network_preferences->entry_point);
	root.read_attr("autogather", network_preferences->autogather);
	root.read_attr("join_by_address", network_preferences->join_by_address);
	root.read_cstr("join_address", network_preferences->join_address, 255);
	root.read_attr("local_game_port", network_preferences->game_port);

	std::string protocol;
	if (root.read_attr("game_protocol", protocol))
	{
		for (int i = 0; i < NUMBER_OF_NETWORK_GAME_PROTOCOL_NAMES; ++i)
		{
			if (protocol == sNetworkGameProtocolNames[i])
			{
				network_preferences->game_protocol = i;
				break;
			}
		}
	}

	root.read_attr("use_speex_netmic_encoder", network_preferences->use_speex_encoder);
	root.read_attr("use_netscript", network_preferences->use_netscript);
	root.read_path("netscript_file", network_preferences->netscript_file);
	root.read_attr("cheat_flags", network_preferences->cheat_flags);
	root.read_attr("advertise_on_metaserver", network_preferences->advertise_on_metaserver);
	root.read_attr("attempt_upnp", network_preferences->attempt_upnp);
	root.read_attr("check_for_updates", network_preferences->check_for_updates);
	root.read_attr("verify_https", network_preferences->verify_https);
	root.read_attr("use_custom_metaserver_colors", network_preferences->use_custom_metaserver_colors);
	root.read_cstr("metaserver_login", network_preferences->metaserver_login, 15);
	root.read_attr("mute_metaserver_guests", network_preferences->mute_metaserver_guests);
	root.read_cstr("metaserver_clear_password", network_preferences->metaserver_password, 15);

	char obscured_password[33];
	if (root.read_cstr("metaserver_password", obscured_password, 32))
	{
		for (int i = 0; i < 15; i++)
		{
			unsigned int c;
			sscanf(obscured_password + i * 2, "%2x", &c);
			network_preferences->metaserver_password[i] = (char)c ^ sPasswordMask[i];
		}
		network_preferences->metaserver_password[15] = '\0';
	}

	root.read_attr("join_metaserver_by_default", network_preferences->join_metaserver_by_default);
	root.read_attr("allow_stats", network_preferences->allow_stats);

	BOOST_FOREACH (InfoTree color, root.children_named("color"))
	{
		int16 index;
		if (color.read_indexed("index", index, 2))
			color.read_color(network_preferences->metaserver_colors[index]);
	}

	BOOST_FOREACH (InfoTree child, root.children_named("star_protocol"))
		StarGameProtocol::ParsePreferencesTree(child, version);
	BOOST_FOREACH (InfoTree child, root.children_named("ring_protocol"))
		RingGameProtocol::ParsePreferencesTree(child, version);
}

void parse_environment_preferences(InfoTree root, std::string version)
{
	root.read_path("map_file", environment_preferences->map_file);
	root.read_path("physics_file", environment_preferences->physics_file);
	root.read_path("shapes_file", environment_preferences->shapes_file);
	root.read_path("sounds_file", environment_preferences->sounds_file);
	root.read_path("resources_file", environment_preferences->resources_file);
	root.read_attr("map_checksum", environment_preferences->map_checksum);
	root.read_attr("physics_checksum", environment_preferences->physics_checksum);
	root.read_attr("shapes_mod_date", environment_preferences->shapes_mod_date);
	root.read_attr("sounds_mod_date", environment_preferences->sounds_mod_date);
	root.read_attr("group_by_directory", environment_preferences->group_by_directory);
	root.read_attr("reduce_singletons", environment_preferences->reduce_singletons);
	root.read_attr("smooth_text", environment_preferences->smooth_text);
	root.read_path("solo_lua_file", environment_preferences->solo_lua_file);
	root.read_attr("use_solo_lua", environment_preferences->use_solo_lua);
	root.read_attr("use_replay_net_lua", environment_preferences->use_replay_net_lua);
	root.read_attr("hide_alephone_extensions", environment_preferences->hide_extensions);

	uint32 profile = FILM_PROFILE_DEFAULT + 1;
	root.read_attr("film_profile", profile);
	if (profile <= FILM_PROFILE_DEFAULT)
		environment_preferences->film_profile = static_cast<FilmProfileType>(profile);

	root.read_attr("maximum_quick_saves", environment_preferences->maximum_quick_saves);

	BOOST_FOREACH (InfoTree plugin, root.children_named("disable_plugin"))
	{
		char tempstr[256];
		if (plugin.read_path("path", tempstr))
		{
			Plugins::instance()->disable(tempstr);
		}
	}
}
