/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

// all windows-specific command line and config file parsing/saving/loading
// this stuff was moved out of wsnes.cpp, to keep things a little tidier

// note:
//  if you want to force all users of a new version to have a
//  particular setting reset to its given default,
//  change the name string of that setting (in WinRegisterConfigItems)
//  to something a little different...
//  but if it's not in a windows-specific category, make sure you change its name elsewhere too

#include "../port.h"
#include "../snes9x.h"
#include "wsnes9x.h"
#include "wlanguage.h"
#include "../display.h"
#include "../conffile.h"
#include "../spc7110.h"
#include "../gfx.h"
#include "../snapshot.h"
#ifdef NETPLAY_SUPPORT
	#include "../netplay.h"
	extern SNPServer NPServer;
#endif
#include <assert.h>

static void WinDeleteRegistryEntries ();
void WinSetDefaultValues ();
void WinDeleteRecentGamesList ();

HANDLE configMutex = NULL;

extern TCHAR multiRomA[MAX_PATH]; // lazy, should put in sGUI and add init to {0} somewhere
extern TCHAR multiRomB[MAX_PATH];

void S9xParseArg (char **argv, int &i, int argc)
{
	if (strcasecmp (argv [i], "-removeregistrykeys") == 0)
	{
		S9xWinRemoveRegistryKeys();
	}
	else if (strcasecmp (argv [i], "-restore") == 0)
	{
		WinDeleteRegistryEntries ();
		WinSetDefaultValues	();
	}
	else if (strcasecmp (argv[i], "-hidemenu") == 0)
	{
		GUI.HideMenu = true;
	}
	else if (strcasecmp (argv[i], "-fullscreen") == 0)
	{
		GUI.FullScreen = true;
	}
	else if (!strcasecmp(argv[i], "-cartb"))
	{
		Settings.Multi = TRUE; // only used to signal winmain
		if (i + 1 < argc)
		{
			lstrcpyn(multiRomB, _tFromChar(argv[++i]), MAX_PATH);
		}
	}
}

void WinSetDefaultValues ()
{
	// TODO: delete the parts that are already covered by the default values in WinRegisterConfigItems

	char temp[4];
	strcpy(temp,"C:\\");

	GUI.ControllerOption = SNES_JOYPAD;
	GUI.ValidControllerOptions = 0xFFFF;
	GUI.IgnoreNextMouseMove	= false;

	GUI.DoubleBuffered = false;
	GUI.FullScreen = false;
	GUI.Stretch	= false;
	GUI.FlipCounter	= 0;
	GUI.NumFlipFrames =	1;
	Settings.BilinearFilter	= false;
	GUI.LockDirectories = false;
	GUI.window_maximized = false;
	GUI.EmulatedFullscreen = false;
	GUI.FullscreenOnOpen = false;

	WinDeleteRecentGamesList ();

	GUI.SoundChannelEnable=255;

	// Tracing options
	Settings.TraceDMA =	false;
	Settings.TraceHDMA = false;
	Settings.TraceVRAM = false;
	Settings.TraceUnknownRegisters = false;
	Settings.TraceDSP =	false;

	// ROM timing options (see also	H_Max above)
	Settings.PAL = false;
	Settings.FrameTimePAL =	20000;
	Settings.FrameTimeNTSC = 16667;
	Settings.FrameTime = 16667;

	// CPU options
	Settings.Paused	= false;

#ifdef NETPLAY_SUPPORT
	Settings.Port =	1996;
	NetPlay.MaxFrameSkip = 10;
	NetPlay.MaxBehindFrameCount	= 10;
	NPServer.SyncByReset = true;
	NPServer.SendROMImageOnConnect = false;
#endif

	Settings.TakeScreenshot=false;

	GUI.Language=0;
    GUI.AllowSoundSync = true;
	GUI.CurrentSaveBank = 0;
}


static bool	try_save(const char	*fname,	ConfigFile &conf){
	STREAM fp;
	if((fp=OPEN_STREAM(fname, "w"))!=NULL){
		fprintf(stdout,	"Saving	config file	%s\n", fname);
		CLOSE_STREAM(fp);
		conf.SaveTo(fname);
		return true;
	}
	return false;
}

static bool S9xSaveConfigFile(ConfigFile &conf){

	configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex"));
	int times = 0;
	DWORD waitVal = WAIT_TIMEOUT;
	while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds
		waitVal = WaitForSingleObject(configMutex, 100);

	// save over the .conf file if it already exists, otherwise save over the .cfg file
	std::string	fname;
	fname=S9xGetDirectory(DEFAULT_DIR);
	fname+=SLASH_STR S9X_CONF_FILE_NAME;

	// ensure previous config file is not lost if we crash while writing the new one
	std::string	ftemp;
	{
		CopyFileA(fname.c_str(), (fname + ".autobak").c_str(), FALSE);

		ftemp=S9xGetDirectory(DEFAULT_DIR);
		ftemp+=SLASH_STR "config_error";
		FILE* tempfile = fopen(ftemp.c_str(), "wb");
		if(tempfile) fclose(tempfile);
	}

	bool ret = try_save(fname.c_str(), conf);

	remove(ftemp.c_str());
	remove((fname + ".autobak").c_str());

	ReleaseMutex(configMutex);
	CloseHandle(configMutex);

	return ret;
}


static void	WinDeleteRegistryEntries ()
{
//	WinDeleteRegKey (HKEY_CURRENT_USER, S9X_REG_KEY_BASE);
}

static inline char *SkipSpaces (char *p)
{
	while (*p && isspace (*p))
		p++;

	return (p);
}

const TCHAR*	WinParseCommandLineAndLoadConfigFile (TCHAR *line)
{
	// Break the command line up into an array of string pointers, each	pointer
	// points at a separate	word or	character sequence enclosed	in quotes.

    int count = 0;
    static TCHAR return_filename[MAX_PATH];

#ifdef UNICODE
    // split params into argv
	TCHAR **params = CommandLineToArgvW(line, &count);

    // convert all parameters to utf8
	char **parameters = new char*[count];
    for(int i = 0; i < count; i++) {
        int requiredChars = WideCharToMultiByte(CP_UTF8, 0, params[i], -1, NULL, 0, NULL, NULL);
	    parameters[i] = new char[requiredChars];
	    WideCharToMultiByte(CP_UTF8, 0, params[i], -1, parameters[i], requiredChars, NULL, NULL);
    }
    LocalFree(params);
#else
#define	MAX_PARAMETERS 100
	char *p	= line;
	char *parameters[MAX_PARAMETERS];
	while (count < MAX_PARAMETERS && *p)
	{
		p =	SkipSpaces (p);
		if (*p == '"')
		{
			p++;
			parameters [count++] = p;
			while (*p && *p	!= '"')
				p++;
			*p++ = 0;
		}
		else
			if (*p == '\'')
			{
				p++;
				parameters [count++] = p;
				while (*p && *p	!= '\'')
					p++;
				*p++ = 0;
			}
			else
			{
				parameters [count++] = p;
				while (*p && !isspace (*p))
					p++;
				if (!*p)
					break;
				*p++ = 0;
			}
	}

#endif
	configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex"));
	int times = 0;
	DWORD waitVal = WAIT_TIMEOUT;
	while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds
		waitVal = WaitForSingleObject(configMutex, 100);

	// ensure previous config file is not lost if we crashed while writing a new one
	{
		std::string	ftemp;
		ftemp=S9xGetDirectory(DEFAULT_DIR);
		ftemp+=SLASH_STR "config_error";
		FILE* tempfile = fopen(ftemp.c_str(), "rb");
		if(tempfile)
		{
			fclose(tempfile);

			std::string	fname;
			for(int i=0; i<2; i++)
			{
				fname=S9xGetDirectory(DEFAULT_DIR);
				if(i == 0)      fname+=SLASH_STR "snes9x.conf";
				else if(i == 1) fname+=SLASH_STR "snes9x.cfg";

				tempfile = fopen((fname + ".autobak").c_str(), "rb");
				if(tempfile)
				{
					fclose(tempfile);
					MoveFileExA((fname + ".autobak").c_str(), fname.c_str(), MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH);
				}
			}
		  remove(ftemp.c_str());
		}
	}

	S9xLoadConfigFiles(parameters, count);

	ReleaseMutex(configMutex);
	CloseHandle(configMutex);

	// check some additional common help switches
	for (int i = 0; i < count; i++)
	{
		if(!strcasecmp(parameters[i], "/?") || !strcasecmp(parameters[i], "-h"))
			S9xUsage();
	}

	const char* rf = S9xParseArgs (parameters, count);

    if(rf) // save rom_filename as TCHAR if available
        lstrcpy(return_filename, _tFromChar(rf));

#ifdef UNICODE
    // free parameters
    for(int i = 0; i < count; i++) {
        delete [] parameters[i];
    }
    delete [] parameters;
#endif

    return rf ? return_filename : NULL;
}

#define S(x) GAMEDEVICE_VK_##x
#define SO(x) GAMEDEVICE_VK_OEM_##x
static const char* keyToString [256] =
{
	"Unassigned","LMB","RMB","Break","MMB","XMB1","XMB2","0x07",
	S(BACK),S(TAB),"0x0A","0x0B",S(CLEAR),S(RETURN),"0x0E","0x0F",
	S(SHIFT),S(CONTROL),S(MENU),S(PAUSE),S(CAPITAL),"Kana","0x16","Junja",
	"Final","Kanji","0x1A",S(ESCAPE),"Convert","NonConvert","Accept","ModeChange",
	S(SPACE),S(PRIOR),S(NEXT),S(END),S(HOME),S(LEFT),S(UP),S(RIGHT),
	S(DOWN),S(SELECT),S(PRINT),S(EXECUTE),S(SNAPSHOT),S(INSERT),S(DELETE),S(HELP),
	"0","1","2","3","4","5","6","7",
	"8","9","0x3A","0x3B","0x3C","0x3D","0x3E","0x3F",
	"0x40","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",S(LWIN),S(RWIN),S(APPS),"0x5E","Sleep",
	"Pad0","Pad1","Pad2","Pad3","Pad4","Pad5","Pad6","Pad7",
	"Pad8","Pad9",S(MULTIPLY),S(ADD),S(SEPARATOR),S(SUBTRACT),S(DECIMAL),S(DIVIDE),
	S(F1),S(F2),S(F3),S(F4),S(F5),S(F6),S(F7),S(F8),
	S(F9),S(F10),S(F11),S(F12),"F13","F14","F15","F16",
	"F17","F18","F19","F20","F21","F22","F23","F24",
	"0x88","0x89","0x8A","0x8B","0x8C","0x8D","0x8E","0x8F",
	S(NUMLOCK),S(SCROLL),"PadEqual","Masshou","Touroku","Loya","Roya","0x97",
	"0x98","0x99","0x9A","0x9B","0x9C","0x9D","0x9E","0x9F",
	S(LSHIFT),S(RSHIFT),S(LCONTROL),S(RCONTROL),S(LMENU),S(RMENU),"BrowserBack","BrowserForward",
	"BrowserRefresh","BrowserStop","BrowserSearch","BrowserFavorites","BrowserHome","VolumeMute","VolumeDown","VolumeUp",
	"MediaNextTrack","MediaPrevTrack","MediaStop","MediaPlayPause","LaunchMail","MediaSelect","LaunchApp1","LaunchApp2",
	"0xB8","0xB9",";","+",",",SO(MINUS),".",SO(2),
	SO(3),"0xC1","0xC2","0xC3","0xC4","0xC5","0xC6","0xC7",
	"0xC8","0xC9","0xCA","0xCB","0xCC","0xCD","0xCE","0xCF",
	"0xD0","0xD1","0xD2","0xD3","0xD4","0xD5","0xD6","0xD7",
	"0xD8","0xD9","0xDA",SO(4),"Backslash",SO(6),"'","OEM8",
	"0xE0","AX","<>","ICOHelp","ICO00","Process","ICOClear","Packet",
	"0xE8","Reset","Jump","PA1_2","PA2","PA3","WSCTRL","CUSEL",
	"Attention2","Finish","Copy","Auto","ENLW","Backtab","Attention","CRSEL",
	"EXSEL","EREOF","Play","Zoom","NoName","PA1","Clear2","0xFF"
};
static const char* keyToAlternateString [256] =
{
	"none","LeftClick","RightClick","Cancel","MiddleClick","","","","Back","","","","","Return","","",
	"","","Menu","","","","","","","","","Escape","","","","",
	"","PageUp","PageDown","","","","","","","","PrintScreen","","","Ins","Del","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"NumLock","ScrollLock","","","","","","","","","","","","","","",
	"","","","","","","","","","","","","","","","",
	"","","","","","","","","","",SO(1),SO(PLUS),SO(COMMA),"Minus",SO(PERIOD),"?",
	"","","","","","","","","","","","","","","","",
	"","","","oem_pa1","","","","","","","","LBracket","|","RBracket",SO(7),"",
	"ATTN2","","","","","","","","","","","","","","ATTN","",
	"","","","","","","","","","","","","","","",""
};
#undef S
#undef SO




// We will maintain	our	own	list of	items to put in	the	config file,
// so that each	config item	has	only one point of change across	both saving	and	loading
// and to allow us to manage custom types of config items, such as virtual key codes represented as strings

enum ConfigItemType	{
	CIT_BOOL, CIT_INT, CIT_UINT, CIT_STRING, CIT_INVBOOL, CIT_BOOLONOFF, CIT_INVBOOLONOFF, CIT_VKEY, CIT_VKEYMOD
};

struct ConfigItem
{
	const char* name;
	void* addr;
	int	size;
	void* def;
	const char*	comment;
	ConfigItemType type;

	ConfigItem(const char* nname, void*	naddr, int nsize, void*	ndef, const	char* ncomment,	ConfigItemType ntype) {
		addr = naddr; name = nname;	size = nsize; def =	ndef; comment =	ncomment; type = ntype;
	}

	void Get(ConfigFile& conf) {
		switch(type)
		{
			case CIT_BOOL:
			case CIT_BOOLONOFF:
				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetBool(name, def!=0);
				if(size	== 2) *(uint16*)addr = (uint16)conf.GetBool(name, def!=0);
				if(size	== 4) *(uint32*)addr = (uint32)conf.GetBool(name, def!=0);
				if(size	== 8) *(uint64*)addr = (uint64)conf.GetBool(name, def!=0);
				break;
			case CIT_INT:
				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetInt(name, PtrToInt(def));
				if(size	== 2) *(uint16*)addr = (uint16)conf.GetInt(name, PtrToInt(def));
				if(size	== 4) *(uint32*)addr = (uint32)conf.GetInt(name, PtrToInt(def));
				if(size	== 8) *(uint64*)addr = (uint64)conf.GetInt(name, PtrToInt(def));
				break;
			case CIT_UINT:
				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetUInt(name, PtrToUint(def));
				if(size	== 2) *(uint16*)addr = (uint16)conf.GetUInt(name, PtrToUint(def));
				if(size	== 4) *(uint32*)addr = (uint32)conf.GetUInt(name, PtrToUint(def));
				if(size	== 8) *(uint64*)addr = (uint64)conf.GetUInt(name, PtrToUint(def));
				break;
			case CIT_STRING:
				lstrcpyn((TCHAR*)addr, _tFromChar(conf.GetString(name, reinterpret_cast<const char*>(def))), size-1);
				((TCHAR*)addr)[size-1] = TEXT('\0');
				break;
			case CIT_INVBOOL:
			case CIT_INVBOOLONOFF:
				if(size	== 1) *(uint8 *)addr = (uint8) !conf.GetBool(name, def!=0);
				if(size	== 2) *(uint16*)addr = (uint16)!conf.GetBool(name, def!=0);
				if(size	== 4) *(uint32*)addr = (uint32)!conf.GetBool(name, def!=0);
				if(size	== 8) *(uint64*)addr = (uint64)!conf.GetBool(name, def!=0);
				break;
			case CIT_VKEY:
				{
					uint16 keyNum = (uint16)conf.GetUInt(name, PtrToUint(def));
					const char* keyStr = conf.GetString(name);
					if(keyStr)
					{
						for(int i=0;i<512;i++)
						{
							if(i<256) // keys
							{
								if(!strcasecmp(keyStr,keyToString[i]) ||
								(*keyToAlternateString[i] && !strcasecmp(keyStr,keyToAlternateString[i])))
								{
									keyNum = i;
									break;
								}
							}
							else // joystick:
							{
								char temp [128];
								extern void TranslateKey(WORD keyz,char *out);
								TranslateKey(0x8000|(i-256),temp);
								if(strlen(keyStr)>3 && !strcasecmp(keyStr+3,temp+3))
								{
									for(int j = 0 ; j < 16 ; j++)
									{
										if(keyStr[2]-'0' == j || keyStr[2]-'a' == j-10)
										{
											keyNum = 0x8000|(i-256)|(j<<8);
											i = 512;
											break;
										}
									}
								}
							}
						}
					}
					if(size	== 1) *(uint8 *)addr = (uint8) keyNum;
					if(size	== 2) *(uint16*)addr = (uint16)keyNum;
					if(size	== 4) *(uint32*)addr = (uint32)keyNum;
					if(size	== 8) *(uint64*)addr = (uint64)keyNum;
				}
				break;
			case CIT_VKEYMOD:
				{
					uint16 modNum = 0;
					const char* modStr = conf.GetString(name);
					if(modStr) {
						if(strstr(modStr, "ft") || strstr(modStr, "FT")) modNum |= CUSTKEY_SHIFT_MASK;
						if(strstr(modStr, "tr") || strstr(modStr, "TR")) modNum |= CUSTKEY_CTRL_MASK;
						if(strstr(modStr, "lt") || strstr(modStr, "LT")) modNum |= CUSTKEY_ALT_MASK;
					}
					if(!modNum && (!modStr || strcasecmp(modStr, "none")))
						modNum = conf.GetUInt(name, PtrToUint(def));
					if(size	== 1) *(uint8 *)addr = (uint8) modNum;
					if(size	== 2) *(uint16*)addr = (uint16)modNum;
					if(size	== 4) *(uint32*)addr = (uint32)modNum;
					if(size	== 8) *(uint64*)addr = (uint64)modNum;
				}
				break;
		}

		// if it had a comment, override our own with it
		const char* newComment = conf.GetComment(name);
		if(newComment && *newComment)
			comment = newComment;
	}

	void Set(ConfigFile& conf) {
		switch(type)
		{
			case CIT_BOOL:
				if(size	== 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "TRUE","FALSE", comment);
				if(size	== 2) conf.SetBool(name, 0!=(*(uint16*)addr), "TRUE","FALSE", comment);
				if(size	== 4) conf.SetBool(name, 0!=(*(uint32*)addr), "TRUE","FALSE", comment);
				if(size	== 8) conf.SetBool(name, 0!=(*(uint64*)addr), "TRUE","FALSE", comment);
				break;
			case CIT_BOOLONOFF:
				if(size	== 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "ON","OFF", comment);
				if(size	== 2) conf.SetBool(name, 0!=(*(uint16*)addr), "ON","OFF", comment);
				if(size	== 4) conf.SetBool(name, 0!=(*(uint32*)addr), "ON","OFF", comment);
				if(size	== 8) conf.SetBool(name, 0!=(*(uint64*)addr), "ON","OFF", comment);
				break;
			case CIT_INT:
				if(size	== 1) conf.SetInt(name,	(int32)(*(uint8	*)addr), comment);
				if(size	== 2) conf.SetInt(name,	(int32)(*(uint16*)addr), comment);
				if(size	== 4) conf.SetInt(name,	(int32)(*(uint32*)addr), comment);
				if(size	== 8) conf.SetInt(name,	(int32)(*(uint64*)addr), comment);
				break;
			case CIT_UINT:
				if(size	== 1) conf.SetUInt(name, (uint32)(*(uint8 *)addr), 10, comment);
				if(size	== 2) conf.SetUInt(name, (uint32)(*(uint16*)addr), 10, comment);
				if(size	== 4) conf.SetUInt(name, (uint32)(*(uint32*)addr), 10, comment);
				if(size	== 8) conf.SetUInt(name, (uint32)(*(uint64*)addr), 10, comment);
				break;
			case CIT_STRING:
				if((TCHAR*)addr)
					conf.SetString(name, std::string(_tToChar((TCHAR*)addr)), comment);
				break;
			case CIT_INVBOOL:
				if(size	== 1) conf.SetBool(name, 0==(*(uint8 *)addr), "TRUE","FALSE", comment);
				if(size	== 2) conf.SetBool(name, 0==(*(uint16*)addr), "TRUE","FALSE", comment);
				if(size	== 4) conf.SetBool(name, 0==(*(uint32*)addr), "TRUE","FALSE", comment);
				if(size	== 8) conf.SetBool(name, 0==(*(uint64*)addr), "TRUE","FALSE", comment);
				break;
			case CIT_INVBOOLONOFF:
				if(size	== 1) conf.SetBool(name, 0==(*(uint8 *)addr), "ON","OFF", comment);
				if(size	== 2) conf.SetBool(name, 0==(*(uint16*)addr), "ON","OFF", comment);
				if(size	== 4) conf.SetBool(name, 0==(*(uint32*)addr), "ON","OFF", comment);
				if(size	== 8) conf.SetBool(name, 0==(*(uint64*)addr), "ON","OFF", comment);
				break;
			case CIT_VKEY:
				{
					uint16 keyNum = 0;
					if(size	== 1) keyNum = (uint8)(*(uint8 *)addr);
					if(size	== 2) keyNum = (uint16)(*(uint16*)addr);
					if(size	== 4) keyNum = (uint16)(*(uint32*)addr);
					if(size	== 8) keyNum = (uint16)(*(uint64*)addr);
					if(keyNum < 256) conf.SetString(name, keyToString[keyNum], comment);
					else if(keyNum & 0x8000) {
						char temp [128];
						extern void TranslateKey(WORD keyz,char *out);
						TranslateKey(keyNum,temp);
						conf.SetString(name, temp, comment);
					}
					else conf.SetUInt(name, keyNum, 16, comment);
				}
				break;
			case CIT_VKEYMOD:
				{
					uint16 modNum = 0;
					if(size	== 1) modNum = (uint8)(*(uint8 *)addr);
					if(size	== 2) modNum = (uint16)(*(uint16*)addr);
					if(size	== 4) modNum = (uint16)(*(uint32*)addr);
					if(size	== 8) modNum = (uint16)(*(uint64*)addr);
					std::string modStr;
					if(modNum & CUSTKEY_CTRL_MASK) modStr += "Ctrl ";
					if(modNum & CUSTKEY_ALT_MASK) modStr += "Alt ";
					if(modNum & CUSTKEY_SHIFT_MASK) modStr += "Shift ";
					if(!(modNum & (CUSTKEY_CTRL_MASK|CUSTKEY_ALT_MASK|CUSTKEY_SHIFT_MASK))) modStr = "none";
					else modStr.erase(modStr.length()-1);
					conf.SetString(name, modStr, comment);
				}
				break;
		}
	}
};

std::vector<ConfigItem> configItems;
// var must be a persistent variable. In the case of strings, it must point to a writeable character array.
#define AddItemC(name, var, def, comment, type) configItems.push_back(ConfigItem((const char*)(CATEGORY "::" name), (void*)(&var), sizeof(var), (void*)(pint)def, (const char*)comment, (ConfigItemType)type))
#define AddItem(name, var, def, type) AddItemC(name,var,def,"",type)
#define AddUInt(name, var, def) AddItem(name,var,def,CIT_UINT)
#define AddInt(name, var, def) AddItem(name,var,def,CIT_INT)
#define AddBool(name, var, def) AddItem(name,var,def,CIT_BOOL)
#define AddBool2(name, var, def) AddItem(name,var,def,CIT_BOOLONOFF)
#define AddInvBool(name, var, def) AddItem(name,var,def,CIT_INVBOOL)
#define AddInvBool2(name, var, def) AddItem(name,var,def,CIT_INVBOOLONOFF)
#define AddVKey(name, var, def) AddItem(name,var,def,CIT_VKEY)
#define AddVKMod(name, var, def) AddItem(name,var,def,CIT_VKEYMOD)
#define AddUIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_UINT)
#define AddIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INT)
#define AddBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOL)
#define AddBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOLONOFF)
#define AddInvBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOL)
#define AddInvBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOLONOFF)
#define AddStringC(name, var, buflen, def, comment) configItems.push_back(ConfigItem((const char*)(CATEGORY "::" name), (void*)var, buflen, (void*)(pint)def, (const char*)comment, CIT_STRING))
#define AddString(name, var, buflen, def) AddStringC(name, var, buflen, def, "")

static char filterString [1024], filterString2 [1024], snapVerString [256];
static bool niceAlignment, showComments, readOnlyConfig;
static int configSort;

void WinPreSave(ConfigFile& conf)
{
	strcpy(filterString, "output filter: ");
	for(int i=0;i<NUM_FILTERS;i++)
	{
		static char temp [256];
		sprintf(temp, "%d=%s%c ", i, GetFilterName((RenderFilter)i), i!=NUM_FILTERS-1?',':' ');
		strcat(filterString, temp);
	}
	strcpy(filterString2, "hi-res output filter: ");
	for(int i=0;i<NUM_FILTERS;i++)
	{
		if(GetFilterHiResSupport((RenderFilter)i))
		{
			static char temp [256];
			sprintf(temp, "%d=%s%c ", i, GetFilterName((RenderFilter)i), i!=NUM_FILTERS-1?',':' ');
			strcat(filterString2, temp);
		}
	}
	sprintf(snapVerString, "Snapshot save version. Must be between 1 and %d (inclusive)", SNAPSHOT_VERSION);

//	GetWindowRect (GUI.hWnd, &GUI.window_size);
	GUI.window_size.right -= GUI.window_size.left;
	GUI.window_size.bottom -= GUI.window_size.top;

	int extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
						  GetSystemMetrics(SM_CXDLGFRAME));
	GUI.window_size.right -= extra_width;

	int extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
						  GetSystemMetrics(SM_CYDLGFRAME)) +
						 GetSystemMetrics(SM_CYCAPTION) +
						 GetSystemMetrics(SM_CYMENU) +
						 (GUI.HideMenu ? 0 : (
						  (GUI.window_size.right <= 392 ? GetSystemMetrics(SM_CYMENU) : 0) + // HACK: accounts for menu wrapping (when width is small)
						  (GUI.window_size.right <= 208 ? GetSystemMetrics(SM_CYMENU) : 0) +
						  (GUI.window_size.right <= 148 ? GetSystemMetrics(SM_CYMENU) : 0)));
	GUI.window_size.bottom -= extra_height;

	if(GUI.window_size.bottom < 10) GUI.window_size.bottom = 10;
	if(GUI.window_size.right < 10) GUI.window_size.right = 10;

	GUI.customRomDlgSettings.window_size.right -= GUI.customRomDlgSettings.window_size.left;
	GUI.customRomDlgSettings.window_size.bottom -= GUI.customRomDlgSettings.window_size.top;

	conf.DeleteKey("Sound::Mono");
	if(configSort == 2)
		conf.ClearLines();
}
void WinPostSave(ConfigFile& conf)
{
	int extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
						  GetSystemMetrics(SM_CXDLGFRAME));
	int extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
						  GetSystemMetrics(SM_CYDLGFRAME)) +
						 GetSystemMetrics(SM_CYCAPTION) +
						 (GUI.HideMenu ? 0 : (GetSystemMetrics(SM_CYMENU) +
						  (GUI.window_size.right <= 392 ? GetSystemMetrics(SM_CYMENU) : 0) + // HACK: accounts for menu wrapping (when width is small)
						  (GUI.window_size.right <= 208 ? GetSystemMetrics(SM_CYMENU) : 0) +
						  (GUI.window_size.right <= 148 ? GetSystemMetrics(SM_CYMENU) : 0)));
	GUI.window_size.right += GUI.window_size.left;
	GUI.window_size.bottom += GUI.window_size.top;
	GUI.window_size.right += extra_width;
	GUI.window_size.bottom += extra_height;

	GUI.customRomDlgSettings.window_size.right += GUI.customRomDlgSettings.window_size.left;
	GUI.customRomDlgSettings.window_size.bottom += GUI.customRomDlgSettings.window_size.top;
}
void WinPostLoad(ConfigFile& conf)
{
	int i;
	if(Settings.DisplayPressedKeys) Settings.DisplayPressedKeys = 2;
	for(i=0;i<8;i++) Joypad[i+8].Enabled = Joypad[i].Enabled;
	if(GUI.MaxRecentGames < 1) GUI.MaxRecentGames = 1;
	if(GUI.MaxRecentGames > MAX_RECENT_GAMES_LIST_SIZE) GUI.MaxRecentGames = MAX_RECENT_GAMES_LIST_SIZE;
    if(GUI.rewindGranularity==0) GUI.rewindGranularity = 1;
	bool gap = false;
	for(i=0;i<MAX_RECENT_GAMES_LIST_SIZE;i++) // remove gaps in recent games list
	{
		if(!*GUI.RecentGames[i])
			gap = true;
		else if(gap)
		{
			memmove(GUI.RecentGames[i-1], GUI.RecentGames[i], MAX_PATH * sizeof(TCHAR));
			*GUI.RecentGames[i] = TEXT('\0');
			gap = false;
			i = -1;
		}
	}

    if (Settings.MaxSpriteTilesPerLine != 34 && Settings.MaxSpriteTilesPerLine != 128)
        Settings.MaxSpriteTilesPerLine = 34;

    switch (Settings.OverclockMode)
    {
        default:
        case 0:
            Settings.OneClockCycle = 6;
            Settings.OneSlowClockCycle = 8;
            Settings.TwoClockCycles = 12;
            break;
        case 1:
            Settings.OneClockCycle = 6;
            Settings.OneSlowClockCycle = 6;
            Settings.TwoClockCycles = 12;
            break;
        case 2:
            Settings.OneClockCycle = 4;
            Settings.OneSlowClockCycle = 5;
            Settings.TwoClockCycles = 8;
            break;
        case 3:
            Settings.OneClockCycle = 3;
            Settings.OneSlowClockCycle = 4;
            Settings.TwoClockCycles = 6;
            break;
    }

	ConfigFile::SetNiceAlignment(niceAlignment);
	ConfigFile::SetShowComments(showComments);
	ConfigFile::SetAlphaSort(configSort==2);
	ConfigFile::SetTimeSort(configSort==1);

	WinPostSave(conf);
}

void WinPreLoad(ConfigFile& conf)
{
	if(conf.Exists("Config::Sort"))
	{
		if(conf.GetUInt("Config::Sort") == 1)
		{
			configSort = 1;
			ConfigFile::SetAlphaSort(configSort==2);
			ConfigFile::SetTimeSort(configSort==1);
			conf.ClearLines();
		}
	}
	else
	{
		conf.DeleteKey("Config::Sort");
	}
}


// and now for the important part,
// which determines what goes into and comes out of the config file:
// add all of the things we want to save and load to a list
// (but don't actually save or load anything yet)
void WinRegisterConfigItems()
{
#define CATEGORY "Config"
	AddBool2C("NiceAlignment", niceAlignment, true, "on to line up the =, :, and # in each section of this config file");
	AddBool2C("Comments", showComments, true, "on to keep comments such as this in this config file. To update/refresh all comments, set this to false and run Snes9x, then set it to true and run Snes9x again.");
	AddUIntC("Sort", configSort, 1, "ordering within sections: 0=allow reordering, 1=force default order, 2=sort alphabetically");
	AddBoolC("Lock", readOnlyConfig, false, "if true, prevents Snes9x from editing this configuration file (or making it read-only while it is running)");
#undef CATEGORY
#define CATEGORY "Display"
	AddBool2("Transparency", Settings.Transparency, true);
	AddBoolC("MessagesInImage", Settings.AutoDisplayMessages, false, "true to draw text inside the SNES image (will get into AVIs, screenshots, and filters)");
	AddBool2C("FrameRate", Settings.DisplayFrameRate, false, "on to display the framerate (will be inaccurate if AutoMaxSkipFrames is too small)");
	AddBoolC("DisplayInput", Settings.DisplayPressedKeys, false, "true to show which buttons are pressed");
	AddBoolC("DisplayFrameCount", Settings.DisplayMovieFrame, true, "true to show the frame count when a movie is playing");
#undef CATEGORY
#define CATEGORY "Display\\Win"
	AddUIntC("OutputMethod", GUI.outputMethod, 1, "0=DirectDraw, 1=Direct3D, 2=OpenGL");
	AddUIntC("FilterType", GUI.Scale, 0, filterString);
	AddUIntC("FilterHiRes", GUI.ScaleHiRes, 0, filterString2);
	AddBoolC("BlendHiRes", GUI.BlendHiRes, true, "true to horizontally blend Hi-Res images (better transparency effect on filters that do not account for this)");
	AddBoolC("NTSCScanlines", GUI.NTSCScanlines, true, "true to use scanlines with Blargg's NTSC filters");
	AddBoolC("ShaderEnabled", GUI.shaderEnabled, false, "true to use pixel shader (if supported by output method)");
	AddStringC("Direct3D:D3DShader", GUI.D3DshaderFileName, MAX_PATH, "", "shader filename for Direct3D mode (HLSL effect file or CG shader");
	AddStringC("OpenGL:OGLShader", GUI.OGLshaderFileName, MAX_PATH, "", "shader filename for OpenGL mode (bsnes-style XML shader or CG shader)");
	AddBoolC("OpenGL:DisablePBOs", GUI.OGLdisablePBOs, true, "do not use PBOs in OpenGL mode, even if the video card supports them");
	AddBoolC("ExtendHeight", Settings.ShowOverscan, false, "true to display an extra 15 pixels at the bottom, which few games use. Also increases AVI output size from 256x224 to 256x240.");
	AddBoolC("AlwaysCenterImage", GUI.AlwaysCenterImage,false, "true to center the image even if larger than window");
	AddIntC("Window:Width", GUI.window_size.right, 512, "256=1x, 512=2x, 768=3x, 1024=4x, etc. (usually)");
	AddIntC("Window:Height", GUI.window_size.bottom, 448, "224=1x, 448=2x, 672=3x,  896=4x, etc. (usually)");
	AddIntC("Window:Left", GUI.window_size.left, 0, "in pixels from left edge of screen");
	AddIntC("Window:Top", GUI.window_size.top, 0, "in pixels from top edge of screen");
	AddBool("Window:Maximized", GUI.window_maximized, false);
	AddIntC("CustomRomDialog:Width", GUI.customRomDlgSettings.window_size.right, 660, "");
	AddIntC("CustomRomDialog:Height", GUI.customRomDlgSettings.window_size.bottom, 400, "");
	AddIntC("CustomRomDialog:Left", GUI.customRomDlgSettings.window_size.left, 50, "in pixels from left edge of screen");
	AddIntC("CustomRomDialog:Top", GUI.customRomDlgSettings.window_size.top, 50, "in pixels from top edge of screen");
	AddBool("CustomRomDialog:Maximized", GUI.customRomDlgSettings.window_maximized, false);
	AddIntC("CustomRomDialog:FolderPaneWidth", GUI.customRomDlgSettings.folderPaneWidth, 230, "");
	AddIntC("CustomRomDialog:DescColumnWidth", GUI.customRomDlgSettings.columnDescription, 112, "");
	AddIntC("CustomRomDialog:FilenameColumnWidth", GUI.customRomDlgSettings.columnFilename, 196, "");
	AddIntC("CustomRomDialog:SizeColumnWidth", GUI.customRomDlgSettings.columnSize, 67, "");
	AddBoolC("Stretch:Enabled", GUI.Stretch, true, "true to stretch the game image to fill the window or screen");
	AddBoolC("Stretch:MaintainAspectRatio", GUI.AspectRatio, true, "prevents stretching from changing the aspect ratio");
	AddBoolC("Stretch:IntegerScaling", GUI.IntegerScaling, false, "scales image height to exact integer multiples");
	AddUIntC("Stretch:AspectRatioBaseWidth", GUI.AspectWidth, 256, "base width for aspect ratio calculation (AR=AspectRatioBaseWidth/224), default is 256 - set to 299 for 4:3 aspect ratio");
	AddBoolC("Stretch:BilinearFilter", Settings.BilinearFilter, true, "allows bilinear filtering of stretching. Depending on your video card and the window size, this may result in a lower framerate.");
	AddBoolC("Stretch:LocalVidMem", GUI.LocalVidMem, true, "determines the location of video memory in DirectDraw mode. May increase or decrease rendering performance, depending on your setup and which filter and stretching options are active.");
	AddBool("Fullscreen:Enabled", GUI.FullScreen, false);
	AddUInt("Fullscreen:Width", GUI.FullscreenMode.width, 640);
	AddUInt("Fullscreen:Height", GUI.FullscreenMode.height, 480);
	AddUInt("Fullscreen:Depth", GUI.FullscreenMode.depth, 16);
	AddUInt("Fullscreen:RefreshRate", GUI.FullscreenMode.rate, 60);
	AddBool("Fullscreen:DoubleBuffered", GUI.DoubleBuffered, false);
	AddBoolC("Fullscreen:FullscreenOnOpen", GUI.FullscreenOnOpen, false, "Change to fullscreen when opening a ROM.");
	AddBoolC("Fullscreen:EmulateFullscreen", GUI.EmulateFullscreen, true,"true makes snes9x create a window that spans the entire screen when going fullscreen");
	AddBoolC("HideMenu", GUI.HideMenu, false, "true to auto-hide the menu bar on startup.");
	AddBoolC("Vsync", GUI.Vsync, false, "true to enable Vsync");
	AddBoolC("ReduceInputLag", GUI.ReduceInputLag, false, "true to reduce input lag by hard synchronization");
    AddBoolC("DWMSync", GUI.DWMSync, false, "sync to DWM compositor if it is running");
	AddUIntC("OSDSize", GUI.OSDSize, 24, "Size of On-Screen Display");
#undef CATEGORY
#define CATEGORY "Settings"
	AddUIntC("FrameSkip", Settings.SkipFrames, AUTO_FRAMERATE, "200=automatic (limits at 50/60 fps), 0=none, 1=skip every other, ...");
	AddUIntC("AutoMaxSkipFramesAtOnce", Settings.AutoMaxSkipFrames, 0, "most frames to skip at once to maintain speed in automatic mode, don't set to more than 1 or 2 frames because the skipping algorithm isn't very smart");
	AddUIntC("TurboFrameSkip", Settings.TurboSkipFrames, 15, "how many frames to skip when in fast-forward mode");
	AddUInt("AutoSaveDelay", Settings.AutoSaveDelay, 30);
	AddBool("BlockInvalidVRAMAccess", Settings.BlockInvalidVRAMAccessMaster, true);
	AddBool2C("SnapshotScreenshots", Settings.SnapshotScreenshots, true, "on to save the screenshot in each snapshot, for loading-when-paused display");
	AddBoolC("MovieTruncateAtEnd", Settings.MovieTruncate, true, "true to truncate any leftover data in the movie file after the current frame when recording stops");
	AddBoolC("MovieNotifyIgnored", Settings.MovieNotifyIgnored, false, "true to display \"(ignored)\" in the frame counter when recording when the last frame of input was not used by the SNES (such as lag or loading frames)");
	AddBool("DisplayWatchedAddresses", Settings.DisplayWatchedAddresses, true);
	AddBool2C("WrongMovieStateProtection", Settings.WrongMovieStateProtection, true, "off to allow states to be loaded for recording from a different movie than they were made in");
	AddUIntC("MessageDisplayTime", Settings.InitialInfoStringTimeout, 120, "display length of messages, in frames. set to 0 to disable all message text");
#undef CATEGORY
#define CATEGORY "Settings\\Win"
    AddUIntC("RewindBufferSize", GUI.rewindBufferSize, 0, "rewind buffer size in MB - 0 disables rewind support");
    AddUIntC("RewindGranularity", GUI.rewindGranularity, 1, "rewind granularity - rewind takes a snapshot each x frames");
	AddBoolC("PauseWhenInactive", GUI.InactivePause, TRUE, "true to pause Snes9x when it is not the active window");
	AddBoolC("CustomRomOpenDialog", GUI.CustomRomOpen, false, "false to use standard Windows open dialog for the ROM open dialog");
	AddBoolC("AddToRegistry", GUI.AddToRegistry, true, "true to ask to add entries to registry for file type associations");
	AddBoolC("AVIHiRes", GUI.AVIHiRes, false, "true to record AVI in Hi-Res scale");
	AddBoolC("ConfirmSaveLoad", GUI.ConfirmSaveLoad, false, "true to ask for confirmation when saving/loading");
//	AddUIntC("Language", GUI.Language, 0, "0=English, 1=Nederlands"); // NYI
	AddBoolC("FrameAdvanceSkipsNonInput", GUI.FASkipsNonInput, false, "causes frame advance to fast-forward past frames where the game is definitely not checking input, such as during lag or loading time. EXPERIMENTAL");
	AddBool("MovieDefaultClearSRAM", GUI.MovieClearSRAM, false);
	AddBool("MovieDefaultStartFromReset", GUI.MovieStartFromReset, false);
	AddBool("MovieDefaultReadOnly", GUI.MovieReadOnly, true);
	AddUInt("CurrentSaveSlot", GUI.CurrentSaveSlot, 0);
	AddUIntC("MaxRecentGames", GUI.MaxRecentGames, 14, "max recent games to show in the recent games menu (must be <= 32)");
#undef CATEGORY
#define CATEGORY "Settings\\Win\\Files"
	AddStringC("Dir:Roms", GUI.RomDir, _MAX_PATH, ".\\Roms", "directory where the Open ROM dialog will start");
	AddStringC("Dir:Screenshots", GUI.ScreensDir, _MAX_PATH, ".\\Screenshots", "directory where screenshots will be saved");
	AddStringC("Dir:Movies", GUI.MovieDir, _MAX_PATH, ".\\Movies", "the default directory for recorded movie (.smv) files");
	AddStringC("Dir:SPCs", GUI.SPCDir, _MAX_PATH, ".\\SPCs", "directory where SPCs will be saved");
	AddStringC("Dir:Savestates", GUI.FreezeFileDir, _MAX_PATH, ".\\Saves", "directory where savestates will be created and loaded from");
	AddStringC("Dir:SRAM", GUI.SRAMFileDir, _MAX_PATH, ".\\Saves", "directory where battery saves will be created and loaded from");
	AddStringC("Dir:Cheats", GUI.CheatDir, _MAX_PATH, ".\\Cheats", "directory in which cheats (.cht files) will be looked for");
	AddStringC("Dir:Patches", GUI.PatchDir, _MAX_PATH, ".\\Patches", "directory in which ROM patches (.ips/.bps/.ups files) will be looked for");
	AddStringC("Dir:Bios", GUI.BiosDir, _MAX_PATH, ".\\BIOS", "directory where BIOS files (such as \"BS-X.bios\") will be located");
	AddStringC("Dir:SatData", GUI.SatDir, _MAX_PATH, ".\\SatData", "directory where Satellaview Signal Data files will be located");
	AddBoolC("Dir:Lock", GUI.LockDirectories, false, "true to prevent Snes9x from changing configured directories when you browse to a new location");
	#define ADD(n) AddString("Rom:RecentGame" #n, GUI.RecentGames[n-1], MAX_PATH, "")
		ADD(1);  ADD(2);  ADD(3);  ADD(4);  ADD(5);  ADD(6);  ADD(7);  ADD(8);
		ADD(9);  ADD(10); ADD(11); ADD(12); ADD(13); ADD(14); ADD(15); ADD(16);
		ADD(17); ADD(18); ADD(19); ADD(20); ADD(21); ADD(22); ADD(23); ADD(24);
		ADD(25); ADD(26); ADD(27); ADD(28); ADD(29); ADD(30); ADD(31); ADD(32);
		assert(MAX_RECENT_GAMES_LIST_SIZE == 32);
	#undef ADD
	AddString("Rom:MultiCartA", multiRomA, _MAX_PATH, "");
	AddString("Rom:MultiCartB", multiRomB, _MAX_PATH, "");
#undef CATEGORY
#define	CATEGORY "Sound"
	AddIntC("Sync", Settings.SoundSync, 1, "1 to sync emulation to sound output, 0 to disable.");
	AddUIntC("Rate", Settings.SoundPlaybackRate, 48000, "sound playback quality, in Hz");
	AddUIntC("InputRate", Settings.SoundInputRate, 31950, "for each 'Input rate' samples generated by the SNES, 'Playback rate' samples will produced. If you experience crackling you can try to lower this setting.");
	AddBoolC("Mute", GUI.Mute, false, "true to mute sound output (does not disable the sound CPU)");
	AddBool("DynamicRateControl", Settings.DynamicRateControl, false);
	AddBool("AutomaticInputRate", GUI.AutomaticInputRate, true);
	AddIntC("InterpolationMethod", Settings.InterpolationMethod, 2, "0 = None, 1 = Linear, 2 = Gaussian (accurate), 3 = Cubic, 4 = Sinc");
#undef CATEGORY
#define	CATEGORY "Sound\\Win"
	AddUIntC("SoundDriver", GUI.SoundDriver, 4, "4=XAudio2 (recommended), 8=WaveOut");
	AddUIntC("BufferSize", GUI.SoundBufferSize, 64, "sound buffer size in ms - determines the internal and output sound buffer sizes. actual mixing is done every SoundBufferSize/4 samples");
	AddBoolC("MuteFrameAdvance", GUI.FAMute, false, "true to prevent Snes9x from outputting sound when the Frame Advance command is in use");
	AddUIntC("VolumeRegular", GUI.VolumeRegular, 100, "volume during regular play (percentage between 0 and 100)");
	AddUIntC("VolumeTurbo", GUI.VolumeTurbo, 100, "volume during turbo mode (percentage between 0 and 100)");
    AddStringC("OutputDevice", GUI.AudioDevice, MAX_AUDIO_NAME_LENGTH, "Default", "Name of the output audio device (substring matching, XAudio2 only atm), set to 'Default' for default audio device");
#undef CATEGORY
#define	CATEGORY "Controls"
	AddBoolC("AllowLeftRight", Settings.UpAndDown, false, "true to allow left+right and up+down");
#undef CATEGORY
#define	CATEGORY "ROM"
	AddBoolC("Cheat", Settings.ApplyCheats, true, "true to allow enabled cheats to be applied");
	AddInvBoolC("Patch", Settings.NoPatch, true, "true to allow IPS/UPS patches to be applied (\"soft patching\")");
	AddBoolC("IgnorePatchChecksum", Settings.IgnorePatchChecksum, false, "true to allow BPS patches to be applied even if the checksum fails");
	AddBoolC("BS", Settings.BS, false, "Broadcast Satellaview emulation");
#undef CATEGORY
#ifdef NETPLAY_SUPPORT
#define	CATEGORY "Netplay"
	AddUInt("Port", Settings.Port, NP_DEFAULT_PORT);
	AddString("Server", Settings.ServerName, 128, Settings.ServerName);
	AddBool2("SyncByReset", NPServer.SyncByReset, true);
	AddBool2("SendROMImageOnConnect", NPServer.SendROMImageOnConnect, false);
	AddUInt("MaxFrameSkip", NetPlay.MaxFrameSkip, 10);
	AddUInt("MaxBehindFrameCount", NetPlay.MaxBehindFrameCount, 10);
	AddBoolC("UseJoypad1", GUI.NetplayUseJoypad1, true, "if false, player 2 has to use their joypad #2 controls, etc.");
	#define ADD(n,d) AddString("RecentHost" #n, GUI.RecentHostNames[n-1], MAX_PATH, d)
		ADD(1,"localhost");  ADD(2,"");  ADD(3,"");  ADD(4,"");  ADD(5,"");  ADD(6,"");  ADD(7,"");  ADD(8,"");
		ADD(9,"");  ADD(10,"");  ADD(11,"");  ADD(12,"");  ADD(13,"");  ADD(14,"");  ADD(15,"");  ADD(16,"");
		assert(MAX_RECENT_HOSTS_LIST_SIZE == 16);
	#undef ADD
#undef CATEGORY
#endif
#define	CATEGORY "Controls\\Win"
#define ADD(n,x) AddVKey("Joypad" #n ":" #x, Joypad[n-1].x, Joypad[n-1].x)
#define ADDN(n,x,n2) AddVKey("Joypad" #n ":" #n2, Joypad[n-1].x, Joypad[n-1].x)
#define ADDB(n,x) AddBool("Joypad" #n ":" #x, Joypad[n-1].x, Joypad[n-1].x)
#define ADD2(n) ADDB(n,Enabled); ADD(n,Up); ADD(n,Down); ADD(n,Left); ADD(n,Right); ADD(n,A); ADD(n,B); ADD(n,Y); ADD(n,X); ADD(n,L); ADD(n,R); ADD(n,Start); ADD(n,Select); ADDN(n,Left_Up,Left+Up); ADDN(n,Right_Up,Right+Up); ADDN(n,Right_Down,Right+Down); ADDN(n,Left_Down,Left+Down)
#define ADDT(n,x) AddVKey("Joypad" #n "Turbo:" #x, Joypad[n-1+8].x, Joypad[n-1+8].x)
#define ADDTN(n,x,n2) AddVKey("Joypad" #n "Turbo:" #n2, Joypad[n-1+8].x, Joypad[n-1+8].x)
#define ADDT2(n) ADDTN(n,Down,AutoFire); ADDTN(n,Left,AutoHold); ADDTN(n,Up,TempTurbo); ADDTN(n,Right,ClearAll)
#define ADDT3(n) ADDT(n,A); ADDT(n,B); ADDT(n,Y); ADDT(n,X); ADDT(n,L); ADDT(n,R); ADDT(n,Start); ADDT(n,Select)
#define ADD2T2(n) ADD2(n); ADDT2(n)
	// NOTE: for now, the windows port doesn't actually allow 8 different controllers to be set configured, only 5
	ADD2T2(1); ADD2T2(2); ADD2T2(3); ADD2T2(4); ADD2T2(5); ADD2T2(6); ADD2T2(7); ADD2T2(8);
	ADDT3(1); ADDT3(2); ADDT3(3); ADDT3(4); ADDT3(5); ADDT3(6); ADDT3(7); ADDT3(8);
#undef ADD
#undef ADD2
#undef ADDN
#undef ADDB
#undef ADDT
#undef ADDT2
#undef ADDT3
#undef ADDTN
#undef ADD2T2
	AddBool2C("Input:Background", GUI.BackgroundInput, false, "on to detect game keypresses and hotkeys while window is inactive, if PauseWhenInactive = FALSE.");
	AddBool2C("Input:BackgroundKeyHotkeys", GUI.BackgroundKeyHotkeys, true, "on to also detect keyboard hotkeys when backgroundinput is active");
#undef CATEGORY
#define	CATEGORY "Controls\\Win\\Hotkeys"
	AddBool2C("Handler:Joystick", GUI.JoystickHotkeys, true, "on to detect game controller buttons assigned to hotkeys. May impact performance.");
#define ADD(x) AddVKey("Key:" #x , CustomKeys.x.key, CustomKeys.x.key); AddVKMod("Mods:" #x, CustomKeys.x.modifiers, CustomKeys.x.modifiers)
#define ADDN(x,n2) AddVKey("Key:" #n2, CustomKeys.x.key, CustomKeys.x.key); AddVKMod("Mods:" #n2, CustomKeys.x.modifiers, CustomKeys.x.modifiers)
	ADD(SpeedUp); ADD(SpeedDown); ADD(Pause); ADD(FrameAdvance);
	ADD(SkipUp); ADD(SkipDown); ADD(ScopeTurbo); ADD(ScopePause);
	ADD(FrameCount); ADD(ReadOnly); ADD(FastForward); ADD(FastForwardToggle); ADD(ShowPressed);
	ADDN(Save[0],SaveSlot0); ADDN(Save[1],SaveSlot1); ADDN(Save[2],SaveSlot2); ADDN(Save[3],SaveSlot3); ADDN(Save[4],SaveSlot4); ADDN(Save[5],SaveSlot5); ADDN(Save[6],SaveSlot6); ADDN(Save[7],SaveSlot7); ADDN(Save[8],SaveSlot8); ADDN(Save[9],SaveSlot9);
	ADDN(Load[0],LoadSlot0); ADDN(Load[1],LoadSlot1); ADDN(Load[2],LoadSlot2); ADDN(Load[3],LoadSlot3); ADDN(Load[4],LoadSlot4); ADDN(Load[5],LoadSlot5); ADDN(Load[6],LoadSlot6); ADDN(Load[7],LoadSlot7); ADDN(Load[8],LoadSlot8); ADDN(Load[9],LoadSlot9);
	ADDN(SelectSave[0],SelectSlot0); ADDN(SelectSave[1],SelectSlot1); ADDN(SelectSave[2],SelectSlot2); ADDN(SelectSave[3],SelectSlot3); ADDN(SelectSave[4],SelectSlot4); ADDN(SelectSave[5],SelectSlot5); ADDN(SelectSave[6],SelectSlot6); ADDN(SelectSave[7],SelectSlot7); ADDN(SelectSave[8],SelectSlot8); ADDN(SelectSave[9],SelectSlot9);
    ADD(SaveScreenShot); ADD(SlotPlus); ADD(SlotMinus); ADD(SlotSave); ADD(SlotLoad); ADD(DialogSave); ADD(DialogLoad);
	ADD(BGL1); ADD(BGL2); ADD(BGL3); ADD(BGL4); ADD(BGL5);
	ADD(ClippingWindows); /*ADD(BGLHack);*/ ADD(Transparency); /*ADD(HDMA)*/; /*ADD(GLCube);*/
	/*ADD(InterpMode7);*/ ADD(JoypadSwap); ADD(SwitchControllers); ADD(ResetGame); ADD(ToggleCheats);
	ADD(TurboA); ADD(TurboB); ADD(TurboY); ADD(TurboX); ADD(TurboL); ADD(TurboR); ADD(TurboStart); ADD(TurboSelect); ADD(TurboUp); ADD(TurboDown); ADD(TurboLeft); ADD(TurboRight);
	ADD(QuitS9X);ADD(Rewind);
	ADD(SaveFileSelect); ADD(LoadFileSelect);
	ADD(Mute);
	ADD(ToggleBackdrop);
#undef ADD
#undef ADDN
#undef CATEGORY
#define	CATEGORY "Hack"
    AddUIntC("CPUOverclockMode", Settings.OverclockMode, 0, "CPU Overclock: 0=none, 1=min, 2=medium, 3=max");
    AddUIntC("MaxSpriteTilesPerLine", Settings.MaxSpriteTilesPerLine, 34, "Max sprite tiles rendered per line. Default = 34, Unlimited ~= 128");
	AddUIntC("SuperFXClockMultiplier", Settings.SuperFXClockMultiplier, 100, "SuperFX speed, in percent (default 100)");
    AddBoolC("SeparateEchoBuffer", Settings.SeparateEchoBuffer, false, "Separate echo buffer from APU ram. For old hacks only.");
#undef CATEGORY
}




// These let us give the config file read-only status to other applications while snes9x is running,
// to prevent the situation where the user saves a modified .cfg file while snes9x is running
// only to have the changes overwritten by snes9x upon closing snes9x.
static HANDLE locked_file = NULL;
void WinLockConfigFile ()
{
	if(readOnlyConfig) return; // if user has lock on file, don't let Snes9x lock it

	static std::string fname;
	fname=S9xGetDirectory(DEFAULT_DIR);
	fname+=SLASH_STR "snes9x.conf";
	STREAM fp;
	if((fp=OPEN_STREAM(fname.c_str(), "r"))!=NULL){
		CLOSE_STREAM(fp);
		locked_file=CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	} else {
		fname=S9xGetDirectory(DEFAULT_DIR);
		fname+=SLASH_STR "snes9x.cfg";
		if((fp=OPEN_STREAM(fname.c_str(), "r"))!=NULL){
			CLOSE_STREAM(fp);
			locked_file=CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		}
	}
}
void WinUnlockConfigFile ()
{
	if(locked_file){
		CloseHandle(locked_file);
		locked_file=NULL;
	}
}


/*****************************************************************************/
/* WinSave/WinLoad - Save/Load the settings	to/from	the	registry			 */
/*****************************************************************************/



static ConfigFile loaded_config_file;

void WinLoadConfigFile(ConfigFile& conf)
{
	WinPreLoad(conf);

	WinSetDefaultValues();

	for(unsigned int i = 0 ; i < configItems.size()	; i++)
		configItems[i].Get(conf);

	loaded_config_file = conf;

	WinPostLoad(conf);
}

void WinSaveConfigFile()
{
	if(readOnlyConfig) return; // if user has lock on file, don't let Snes9x overwrite it

	ConfigFile&	conf = loaded_config_file;
	conf.ClearUnused();

	WinPreSave(conf);

	for(unsigned int i = 0 ; i < configItems.size()	; i++)
		configItems[i].Set(conf);

	bool wasLocked = locked_file!=NULL;
	if(wasLocked) WinUnlockConfigFile();

	S9xSaveConfigFile(conf);

	if(wasLocked) WinLockConfigFile();

	WinPostSave(conf);
}


void S9xParsePortConfig(ConfigFile &conf, int pass)
{
	WinLoadConfigFile(conf);
	return;
}

void WinCleanupConfigData()
{
	configItems.clear();
}
