 /*	
	N-Rage`s Dinput8 Plugin
    (C) 2002, 2006  Norbert Wladyka

	Author`s Email: norbert.wladyka@chello.at
	Website: http://go.to/nrage


    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 2 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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "commonIncludes.h"
#include <windows.h>
#include <CommDlg.h>
#include <tchar.h>
#include <stdio.h>
#include <shlobj.h>
#include "NRagePluginV2.h"
#include "PakIO.h"
#include "Interface.h"
#include "FileAccess.h"
#include "DirectInput.h"
#include <string>
using std::string;

#ifndef IDR_PROFILE_DEFAULT1
#define IDR_PROFILE_DEFAULT1 -1
#endif
#ifndef IDR_PROFILE_DEFAULT2
#define IDR_PROFILE_DEFAULT2 -1
#endif
#ifndef IDR_PROFILE_DEFAULT3
#define IDR_PROFILE_DEFAULT3 -1
#endif
#ifndef IDR_PROFILE_DEFAULT4
#define IDR_PROFILE_DEFAULT4 -1
#endif

void DumpStreams(FILE * fFile, string strMouse, string strDevs[], string strNull, bool bIsINI);
void DumpControllerSettings(FILE * fFile, int i, bool bIsINI);
void FormatControlsBlock(string * strMouse, string strDevs[], string * strNull, int i);
void FormatModifiersBlock(string * strMouse, string strDevs[], string * strNull, int i);

// return true if the file exists... let's just use CreateFile with OPEN_EXISTING
bool CheckFileExists( LPCTSTR FileName )
{
	HANDLE hFile = CreateFile(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE )
	{
		return false;
	}
	else
	{
		CloseHandle(hFile);
		return true;
	}
}

// A rather ugly function, but does its job.  Called by LoadProfile and LoadProfileFromResource.
// Parses the config data that gets written to profile files.
// returns:
//		PL_CATEGORY and removes the brackets if the line looks like a keymapping
//		PL_VERSIONSTRING and returns the version number if it's a version line
//		for other cases, another PL return value and truncates before the equal sign; so strings like "Button=blah" -> "blah"
// TODO: Perhaps buffer overflow and crash potential here... needs auditing
DWORD ParseLine( LPSTR pszLine )
{
	DWORD dwReturn = PL_NOHIT;
	char *pChar = pszLine;

	switch (pszLine[0])
	{
	case '\0':	// shortcut out on null string
	case '#':	// # indicates comment line
		return PL_NOHIT;
	case '[':
		while( *pChar != ']' && *pChar != '\0' )
		{
			*pChar = toupper(*pChar);
			++pChar;
		}
		if( *pChar == ']' )
		{
			MoveMemory( pszLine, pszLine+1, (pChar-pszLine) - 1 * sizeof(pszLine[0]) ); // TODO: please double check this --rabid
			*(pChar - 1) = '\0';	// since we moved everything back one character, we need to change this ref as well
			return PL_CATEGORY;
		}
		else
			return PL_NOHIT; // an open bracket with no closing returns nohit
	case '@':
		switch( djbHash(pszLine))	// the hash check is case sensitive, and includes the @ symbol
		{
		case CHK_PROFILEVERSION20:
			lstrcpyA( pszLine, "2.0" );
			return PL_VERSIONSTRING;
		case CHK_PROFILEVERSION21:
			lstrcpyA( pszLine, "2.1" );
			return PL_VERSIONSTRING;
		case CHK_PROFILEVERSION22:
			lstrcpyA( pszLine, "2.2" );
			return PL_VERSIONSTRING;
		default:
			DebugWriteA("Unknown version string found with hash %u: %s\n", djbHash(pszLine), pszLine);
			return PL_NOHIT;
		} // end switch (dbjHash(pszLine))
	// default: keep running
	}

	pChar = strchr(pszLine, '=');
 
	if( !pChar ) // no = sign
	{
		return PL_NOHIT;
	}
	else // there is an '=' sign
	{
		// We hash the string.  If the hash matches the hash of one of our targets, we compare strings to verify.
		// If we don't use hashes, we have to compare vs a LOT of strings.
		*pChar = '\0'; // truncate at the '=' for now
		for (char *pIter = pszLine; *pIter; pIter++)
			*pIter = toupper(*pIter);
		dwReturn = djbHash(pszLine);

		pChar++;

		MoveMemory( pszLine, pChar, (lstrlenA(pChar) + 1) * sizeof(pszLine[0]) ); // change string to match what's to the right of '='
	}

	return dwReturn;
}

// Called immediately after ParseLine to assign values based on whatever the keyname was
// notes: pszFFDevice may be overwritten with whatever is in pszLine; please make sure pszLine is not too big!
bool ProcessKey( DWORD dwKey, DWORD dwSection, LPCSTR pszLine, LPTSTR pszFFDevice, LPBYTE bFFDeviceNr, bool bIsInterface )
{
	static TCHAR pszDeviceName[MAX_PATH];
	static BYTE bDeviceNr = 0;
	static GUID gGUID;

	bool bReturn = true;
	LPCONTROLLER pController = NULL;	// used when we're assigning things in the [Controller X] category
	LPSHORTCUTS pShortcuts = NULL;
	unsigned int iLength = lstrlenA( pszLine ) / 2;	// 2 HEX characters correspond to one BYTE; thus iLength represents the length of pszLine after conversion to BYTEs

	switch (dwSection)
	{
	case CHK_CONTROLLER_1:
		if (bIsInterface)
			pController = &(g_ivConfig->Controllers[0]);
		else
			pController = &(g_pcControllers[0]);
		break;
	case CHK_CONTROLLER_2:
		if (bIsInterface)
			pController = &(g_ivConfig->Controllers[1]);
		else
			pController = &(g_pcControllers[1]);
		break;
	case CHK_CONTROLLER_3:
		if (bIsInterface)
			pController = &(g_ivConfig->Controllers[2]);
		else
			pController = &(g_pcControllers[2]);
		break;
	case CHK_CONTROLLER_4:
		if (bIsInterface)
			pController = &(g_ivConfig->Controllers[3]);
		else
			pController = &(g_pcControllers[3]);
		break;
	case CHK_SHORTCUTS:
		if (bIsInterface)
			pShortcuts = &(g_ivConfig->Shortcuts);
		else
			pShortcuts = &g_scShortcuts;
		break;
	}

	switch( dwKey )
	{
	case PL_RESET:
		ZeroMemory( pszDeviceName, sizeof(pszDeviceName) );
		gGUID = GUID_NULL;
		bDeviceNr = 0;
		break;

	case CHK_LANGUAGE:
		if (dwSection == CHK_GENERAL)
			if (bIsInterface)
				g_ivConfig->Language = atoi(pszLine);
			else
				g_strEmuInfo.Language = atoi(pszLine);
		break;
	case CHK_SHOWMESSAGES:
		if (dwSection == CHK_GENERAL)
			if (bIsInterface)
				g_ivConfig->fDisplayShortPop = (atoi(pszLine) != 0);
			else
				g_strEmuInfo.fDisplayShortPop = (atoi(pszLine) != 0);
		break;

	case CHK_MEMPAK:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_MEMPAK], pszLine, MAX_PATH);
		else if (dwSection == CHK_FOLDERS)
			CHAR_TO_TCHAR(g_aszDefFolders[BF_MEMPAK], pszLine, MAX_PATH);
		break;
	case CHK_GBXROM:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_GBROM], pszLine, MAX_PATH);
		else if (dwSection == CHK_FOLDERS)
			CHAR_TO_TCHAR(g_aszDefFolders[BF_GBROM], pszLine, MAX_PATH);
		break;
	case CHK_GBXSAVE:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_GBSAVE], pszLine, MAX_PATH);
		else if (dwSection == CHK_FOLDERS)
			CHAR_TO_TCHAR(g_aszDefFolders[BF_GBSAVE], pszLine, MAX_PATH);
		break;
	case CHK_PROFILE:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_PROFILE], pszLine, MAX_PATH);
		break;
	case CHK_NOTE:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_NOTE], pszLine, MAX_PATH);
		break;
	case CHK_SHORTCUTS:
		if (dwSection == CHK_LASTBROWSERDIR)
			CHAR_TO_TCHAR(g_aszLastBrowse[BF_SHORTCUTS], pszLine, MAX_PATH);
		break;
	case CHK_PLUGGED:
		if (pController)
			pController->fPlugged = atoi(pszLine);
		break;
	case CHK_RAWDATA:
		if (pController)
			pController->fRawData = atoi(pszLine);
		break;
	case CHK_XINPUT:
		if (pController)
			pController->fXInput = atoi(pszLine);
		break;
	case CHK_PAKTYPE:
		if (pController)
			pController->PakType = atoi(pszLine);
		break;
	case CHK_REALN64RANGE:
		if (pController)
			pController->fRealN64Range = atoi(pszLine);
		break;
	case CHK_RAPIDFIREENABLED:
		if (pController)
			pController->bRapidFireEnabled = atoi(pszLine) != 0;
		break;
	case CHK_RAPIDFIRERATE:
		if (pController)
			pController->bRapidFireRate = atoi(pszLine);
		break;
	case CHK_STICKRANGE:
		if (pController)
			pController->bStickRange = atoi(pszLine);
		break;
	case CHK_MOUSEMOVEX:
		if (pController)
			pController->bMouseMoveX = atoi(pszLine);
		break;
	case CHK_MOUSEMOVEY:
		if (pController)
			pController->bMouseMoveY = atoi(pszLine);
		break;
	case CHK_AXISSET:
		if (pController)
			pController->bAxisSet = atoi(pszLine);
		break;
	case CHK_KEYABSOLUTEX:
		if (pController)
			pController->fKeyAbsoluteX = atoi(pszLine);
		break;
	case CHK_KEYABSOLUTEY:
		if (pController)
			pController->fKeyAbsoluteY = atoi(pszLine);
		break;
	case CHK_PADDEADZONE:
		if (pController)
			pController->bPadDeadZone = atoi(pszLine);
		break;
	case CHK_MOUSESENSITIVITYX:
		if (pController)
			pController->wMouseSensitivityX = atoi(pszLine);
		break;
	case CHK_MOUSESENSITIVITYY:
		if (pController)
			pController->wMouseSensitivityY = atoi(pszLine);
		break;
	case CHK_RUMBLETYPE:
		if (pController)
			pController->bRumbleTyp = atoi(pszLine);
		break;
	case CHK_RUMBLESTRENGTH:
		if (pController)
			pController->bRumbleStrength = atoi(pszLine);
		break;
	case CHK_VISUALRUMBLE:
		if (pController)
			pController->fVisualRumble = atoi(pszLine);
		break;
	case CHK_FFDEVICEGUID:
		if (pController)
		{
			bReturn = StringtoGUIDA(&pController->guidFFDevice, pszLine);
			if (bIsInterface && bReturn)
			{
				// For some reason, we use ONLY device names and numbers inside the interface for FF device selection.  So if we don't set those,
				// FFDevice won't load properly.
				int nDevice = FindDeviceinList(pController->guidFFDevice);
				if (nDevice != -1 && pszFFDevice && bFFDeviceNr)
				{
					_tcsncpy(pszFFDevice, g_devList[nDevice].szProductName, DEFAULT_BUFFER);
					*bFFDeviceNr = g_devList[nDevice].bProductCounter;
				}
				else
				{
					pController->guidFFDevice = GUID_NULL;
					return false;
				}
			}
			else
				return bReturn;
		}
		break;
	case CHK_FFDEVICENAME:
		if( pController && pszFFDevice )
		{
			CHAR_TO_TCHAR( pszFFDevice, pszLine, MAX_PATH ); // HACK: pszLine is read from a file; could overflow easily. guessed size of pszFFDevice buffer.
			return true;
		}
		break;

	case CHK_FFDEVICENR:
		if( pController && bFFDeviceNr && ( iLength >= sizeof(BYTE) ))
		{
			*bFFDeviceNr = atoi( pszLine );
			return true;
		}
		break;
	case CHK_MEMPAKFILE:
		if( pController )
		{
			CHAR_TO_TCHAR( pController->szMempakFile, pszLine, MAX_PATH );
		}
		break;
	case CHK_GBROMFILE:
		if( pController )
		{
			CHAR_TO_TCHAR( pController->szTransferRom, pszLine, MAX_PATH );
		}
		break;
	case CHK_GBROMSAVE:
		if( pController )
		{
			CHAR_TO_TCHAR( pController->szTransferSave, pszLine, MAX_PATH );
		}
		break;

	case CHK_DINPUTNAME:
		gGUID = GUID_NULL;	// invalidate current GUID
		CHAR_TO_TCHAR( pszDeviceName, pszLine, MAX_PATH );
		break;

	case CHK_DINPUTNR:
		gGUID = GUID_NULL;	// invalidate current GUID
		if( iLength >= sizeof(BYTE) )
		{
			TexttoHexA( pszLine, &bDeviceNr, sizeof(BYTE) );
		}
		break;
	case CHK_DINPUTGUID:
		if (StringtoGUIDA(&gGUID, pszLine))
			return true;
		else
		{
			gGUID = GUID_NULL;	// invalidate current GUID
			return false;
		}
		break;

	case CHK_BUTTON:
		if ( dwSection == CHK_CONTROLS || pShortcuts || pController )
		{
			int controlnum = 0, buttonID = 0;
			BUTTON btnWorking;

			ZeroMemory(&btnWorking, sizeof(btnWorking));

			unsigned int tOffset, tAxisID, tBtnType;

			if (sscanf(pszLine, "%d %d %x %u %u", &controlnum, &buttonID, &tOffset, &tAxisID, &tBtnType) != 5)
				return false;

			// done to overcome issues with sscanf and "small" data blocks
			btnWorking.bOffset = tOffset;
			btnWorking.bAxisID = tAxisID;
			btnWorking.bBtnType = tBtnType;

			if (pController)
			{
				// special case: if we're in one of the categories CHK_CONTROLLER_n, assume we're processing a Profile file.
				// Ignore the read controlnum and use our input controller number.
				controlnum = (int)(dwSection - CHK_CONTROLLER_1);	// HACK: assume our hash reproduces these linearly
			}
			
			// Now we need to assign parentdevice. If we have a valid gGUID, we'll use that...
			int found = FindDeviceinList(gGUID);
			if (found != -1)
				btnWorking.parentDevice = &g_devList[found];
			else
			{
				// ... otherwise, we do the following in order:
				//   1. If bBtnType is of type DT_MOUSEBUTTON or DT_MOUSEAXE, set gGUID to that of g_sysMouse (ignoring the given name and number)
				if ( btnWorking.bBtnType == DT_MOUSEBUTTON || btnWorking.bBtnType == DT_MOUSEAXE )
				{
					btnWorking.parentDevice = &g_sysMouse;
				}
				//   2. If bBtnType is of type DT_KEYBUTTON, set gGUID to that of SysKeyboard
				else if ( btnWorking.bBtnType == DT_KEYBUTTON )
				{
					gGUID = GUID_SysKeyboard;
					found = FindDeviceinList(gGUID);
					if (found != -1)
						btnWorking.parentDevice = &g_devList[found];
					else
						btnWorking.parentDevice = NULL;
				}
				//   3. otherwise, look up the name and number using FindDeviceinList, and set gGUID to that
				else
				{
					found = FindDeviceinList(pszDeviceName, bDeviceNr, true);
					if (found != -1)
					{
						gGUID = g_devList[found].guidInstance;
						btnWorking.parentDevice = &g_devList[found];
					}
					else
					{
						DebugWrite(_T("ProcessKey: couldn't find a device in g_devList for %s %d\n"), pszDeviceName, bDeviceNr);
						gGUID = GUID_NULL;
						btnWorking.parentDevice = NULL;
				return false;
					}
				}
			}

			if (pShortcuts)
			{
				// bounds check on controlnum and buttonID
				if ( (controlnum == -1 && buttonID != 0) && ((controlnum < 0) || (controlnum > 3) || (buttonID < 0) || (buttonID >= SC_TOTAL)) )
				{
					gGUID = GUID_NULL;	// since we may have cached an invalid GUID, invalidate it
					return false;
				}

				// Copy the completed button to the correct shortcut
				if (bIsInterface)
					if (controlnum == -1)
						g_ivConfig->Shortcuts.bMouseLock = btnWorking;
					else
						g_ivConfig->Shortcuts.Player[controlnum].aButtons[buttonID] = btnWorking;
				else // if (!bIsInterface)
					if (controlnum == -1)
						g_scShortcuts.bMouseLock = btnWorking;
					else
						g_scShortcuts.Player[controlnum].aButtons[buttonID] = btnWorking;
			}
			else // it's a controller button
			{
				// bounds check on controlnum and buttonID
				if ( (controlnum < 0) || (controlnum > 3) || (buttonID < 0) || (buttonID >= ARRAYSIZE(g_pcControllers[0].aButton)) )
				{
					gGUID = GUID_NULL;	// since we may have cached an invalid GUID, invalidate it
					return false;
				}

				// Copy the completed button to the correct controller and buttonID
				if (bIsInterface)
					g_ivConfig->Controllers[controlnum].aButton[buttonID] = btnWorking;
				else
					g_pcControllers[controlnum].aButton[buttonID] = btnWorking;
			}
		}
		break;

	case CHK_MODIFIER:
		// Modifiers format: controlnum bOffset bAxisID bBtnType bModType fToggle fStatus dwSpecific
		if ( dwSection == CHK_MODIFIERS || pController )
		{
			int controlnum = 0;
			MODIFIER modWorking;

			ZeroMemory(&modWorking, sizeof(modWorking));

			unsigned int tOffset, tAxisID, tBtnType, tModType, tToggle, tStatus, tSpecific;

			if (sscanf(pszLine, "%u %x %u %u %u %u %u %x", &controlnum, &tOffset, &tAxisID,
					&tBtnType, &tModType, &tToggle, &tStatus, &tSpecific) != 8)
				return false;

			// done to overcome issues with sscanf and "small" data blocks
			modWorking.btnButton.bOffset = tOffset;
			modWorking.btnButton.bAxisID = tAxisID;
			modWorking.btnButton.bBtnType = tBtnType;
			modWorking.bModType = tModType;
			modWorking.fToggle = tToggle;
			modWorking.fStatus = tStatus;
			modWorking.dwSpecific = tSpecific; // looks stupid, but unsigned int might not always be DWORD32
			
			// Now we need to assign parentdevice. If we have a valid gGUID, we'll use that...
			int found = FindDeviceinList(gGUID);
			if (found != -1)
				modWorking.btnButton.parentDevice = &g_devList[found];
			else
			{
				// ... otherwise, we do the following in order:
				//   1. If bBtnType is of type DT_MOUSEBUTTON or DT_MOUSEAXE, set gGUID to that of g_sysMouse (ignoring the given name and number)
				if ( modWorking.btnButton.bBtnType == DT_MOUSEBUTTON || modWorking.btnButton.bBtnType == DT_MOUSEAXE )
				{
					modWorking.btnButton.parentDevice = &g_sysMouse;
				}
				//   2. If bBtnType is of type DT_KEYBUTTON, set gGUID to that of SysKeyboard
				else if ( modWorking.btnButton.bBtnType == DT_KEYBUTTON )
				{
					gGUID = GUID_SysKeyboard;
					int found = FindDeviceinList(gGUID);
					if (found != -1)
						modWorking.btnButton.parentDevice = &g_devList[found];
					else
						modWorking.btnButton.parentDevice = NULL;
				}
				//   3. otherwise, look up the name and number using FindDeviceinList, and set gGUID to that
				else
				{
					found = FindDeviceinList(pszDeviceName, bDeviceNr, true);
					if (found != -1)
					{
						gGUID = g_devList[found].guidInstance;
						modWorking.btnButton.parentDevice = &g_devList[found];
					}
					else
					{
						DebugWrite(_T("ProcessKey: couldn't find a device in g_devList for %s %d\n"), pszDeviceName, bDeviceNr);
						gGUID = GUID_NULL;
						modWorking.btnButton.parentDevice = NULL;
					return false;
					}
				}
			}

			// bounds check on controlnum and buttonID
			if ( (controlnum < 0) || (controlnum > 3) )
			{
				gGUID = GUID_NULL;	// since we may have cached an invalid GUID, invalidate it
				return false;
			}

			// Allocate and add the completed modifier
			if (bIsInterface)
			{
				if (g_ivConfig->Controllers[controlnum].nModifiers > 0)
				{
					g_ivConfig->Controllers[controlnum].pModifiers = (LPMODIFIER)P_realloc(g_ivConfig->Controllers[controlnum].pModifiers, (g_ivConfig->Controllers[controlnum].nModifiers + 1) * sizeof(MODIFIER));
				}
				else
				{
					g_ivConfig->Controllers[controlnum].pModifiers = (LPMODIFIER)P_malloc( sizeof(MODIFIER));
				}
				g_ivConfig->Controllers[controlnum].pModifiers[g_ivConfig->Controllers[controlnum].nModifiers] = modWorking;
				(g_ivConfig->Controllers[controlnum].nModifiers)++;
			}
			else
			{
				if (g_pcControllers[controlnum].nModifiers > 0)
				{
					g_pcControllers[controlnum].pModifiers = (LPMODIFIER)P_realloc(g_pcControllers[controlnum].pModifiers, (g_pcControllers[controlnum].nModifiers + 1) * sizeof(MODIFIER));
				}
				else
				{
					g_pcControllers[controlnum].pModifiers = (LPMODIFIER)P_malloc( sizeof(MODIFIER));
				}
				g_pcControllers[controlnum].pModifiers[g_pcControllers[controlnum].nModifiers] = modWorking;
				(g_pcControllers[controlnum].nModifiers)++;
			}

		}
		break;
	}

	return bReturn;
}

/******************
Load the default profile from the raw "resource" data (i.e. the builtin defaults contained in the dll)
******************/
bool LoadProfileFromResource( LPCTSTR pszResource, int iController, bool bIsInterface )
{
	const DWORD dwControllerSect[] = { CHK_CONTROLLER_1 , CHK_CONTROLLER_2, CHK_CONTROLLER_3, CHK_CONTROLLER_4 };
	if( iController > 3 || iController < 0 )
		return false;
	HRSRC res = FindResource( g_strEmuInfo.hinst, pszResource, _T("PROFILE") );
	if( res == NULL )
		return false;
	char *profile = (char*)LockResource( LoadResource( g_strEmuInfo.hinst, res ));
	char *profileend = profile + SizeofResource( g_strEmuInfo.hinst, res );
	
	ProcessKey( PL_RESET, 0, 0, 0, 0, bIsInterface );
	DWORD dwCommand = PL_NOHIT;
	char szLine[4096];
	while( profile < profileend )
	{
		while( profile < profileend && (CHECK_WHITESPACES( *profile ) || *profile == ' ' ))
			++profile;
		int i = 0;
		while( profile < profileend && i < sizeof(szLine)-1 && !(CHECK_WHITESPACES( *profile )) )
			szLine[i++] = *profile++;

		szLine[i] = '\0';
		dwCommand = ParseLine( szLine );
		ProcessKey( dwCommand, dwControllerSect[iController], szLine, 0, 0, bIsInterface ); // resource will not contain a FF device
	}
	return true;
}

/******************
See overloaded function above
******************/
bool LoadProfileFromResource( int indexController, bool bIsInterface )
{
	const int resIds[] = { IDR_PROFILE_DEFAULT1, IDR_PROFILE_DEFAULT2, IDR_PROFILE_DEFAULT3, IDR_PROFILE_DEFAULT4 };

	TCHAR szId[20];
	wsprintf( szId, _T("#%i"), resIds[indexController] );
	return LoadProfileFromResource( szId, indexController, bIsInterface );
}

// Load a controller profile from a saved configuration file
// need to incorporate type (keyb/mouse/joy), GUID for joy, and bOffset
bool LoadProfileFile( const TCHAR *pszFileName, int iController, TCHAR *pszFFDevice, BYTE *bFFDeviceNr )
{
	const DWORD dwControllerSect[] = { CHK_CONTROLLER_1 , CHK_CONTROLLER_2, CHK_CONTROLLER_3, CHK_CONTROLLER_4 };
	FILE *proFile = NULL;
	char szLine[4096];
	int iVersion = 0;

	if ( (proFile = _tfopen(pszFileName, _T("rS")) ) == NULL)
		return false;
	
	// Test if right Version
	while( !iVersion && ( fgets(szLine, sizeof(szLine) - 1, proFile) ) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		if( ParseLine( szLine ) == PL_VERSIONSTRING )
			iVersion = (int)(atof( szLine ) * 100);
	}
	if( iVersion != 220 ) // HACK: this should probably not be a hardcoded value
	{
		fclose(proFile);
		return false;
	}

	SetControllerDefaults( &(g_ivConfig->Controllers[iController]) );
	pszFFDevice[0] = pszFFDevice[1] = '\0';
	*bFFDeviceNr = 0;

	ProcessKey( PL_RESET, 0, 0, 0, 0, true );
	DWORD dwCommand = PL_NOHIT;
	while( fgets(szLine, sizeof(szLine) - 1, proFile) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		dwCommand = ParseLine( szLine );
		ProcessKey( dwCommand, dwControllerSect[iController], szLine, pszFFDevice, bFFDeviceNr, true );
	}

	fclose(proFile);

	return true;
}

// Load a controller profile from a saved configuration file
// need to incorporate type (keyb/mouse/joy), GUID for joy, and bOffset
bool LoadShortcutsFile( const TCHAR *pszFileName )
{
	FILE *fShortsFile = NULL;
	char szLine[4096];
	int iVersion = 0;

	if ( (fShortsFile = _tfopen(pszFileName, _T("rS")) ) == NULL)
		return false;
	
	// Test if right Version
	while( !iVersion && ( fgets(szLine, sizeof(szLine) - 1, fShortsFile) ) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		if( ParseLine( szLine ) == PL_VERSIONSTRING )
			iVersion = (int)(atof( szLine ) * 100);
	}
	if( iVersion != 220 ) // HACK: this should probably not be a hardcoded value
	{
		fclose(fShortsFile);
		return false;
	}

	ZeroMemory( &(g_ivConfig->Shortcuts), sizeof(SHORTCUTS) );

	ProcessKey( PL_RESET, 0, 0, 0, 0, true );
	DWORD dwCommand = PL_NOHIT;
	while( fgets(szLine, sizeof(szLine) - 1, fShortsFile) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		dwCommand = ParseLine( szLine );
		ProcessKey( dwCommand, CHK_SHORTCUTS, szLine, 0, 0, true );
	}

	fclose(fShortsFile);

	return true;
}

// Serializes the profile for the CURRENT controller for saving to a file
// called in one place, from within Interface.cpp, ControllerTabProc (when you click "Save Profile")
void FormatProfileBlock( FILE * fFile, const int i )
{
	DumpControllerSettings(fFile, i, false);

	string strMouse;
	string strDevs[MAX_DEVICES];
	string strNull;

	FormatControlsBlock(&strMouse, strDevs, &strNull, i);

	DumpStreams(fFile, strMouse, strDevs, strNull, false);

	strMouse.clear();
	for (int j = 0; j < g_nDevices; j++)
		strDevs[j].clear();
	strNull.clear();

	FormatModifiersBlock(&strMouse, strDevs, &strNull, i);

	DumpStreams(fFile, strMouse, strDevs, strNull, false);
}

// same as FormatProfileBlock, but saves shortcuts instead
void FormatShortcutsBlock(FILE * fFile, bool bIsINI)
{
	// I'm going to use STL strings here because I don't want to screw with buffer management
	string strMouse;
	string strDevs[MAX_DEVICES];
	string strNull;

	for ( int i = 0; i < 4; i++ ) // Player for
	{
		for ( int j = 0; j < SC_TOTAL; j++ ) // aButtons for
		{
			if (g_ivConfig->Shortcuts.Player[i].aButtons[j].parentDevice) // possibly unbound
			{
				if ( IsEqualGUID(g_sysMouse.guidInstance, g_ivConfig->Shortcuts.Player[i].aButtons[j].parentDevice->guidInstance) )
				{
					char szBuf[DEFAULT_BUFFER];
					// add to the mouse stream
					sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", i, j, g_ivConfig->Shortcuts.Player[i].aButtons[j].bOffset, g_ivConfig->Shortcuts.Player[i].aButtons[j].bAxisID, g_ivConfig->Shortcuts.Player[i].aButtons[j].bBtnType);
					strMouse.append(szBuf);
				}
				else
					for (int match = 0; match < g_nDevices; match++)
						if ( IsEqualGUID(g_devList[match].guidInstance, g_ivConfig->Shortcuts.Player[i].aButtons[j].parentDevice->guidInstance) )
						{
							char szBuf[DEFAULT_BUFFER];
							// add to the appropriate device stream
							sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", i, j, g_ivConfig->Shortcuts.Player[i].aButtons[j].bOffset, g_ivConfig->Shortcuts.Player[i].aButtons[j].bAxisID, g_ivConfig->Shortcuts.Player[i].aButtons[j].bBtnType);
							strDevs[match].append(szBuf);
							break;
						}
			}
		} // end buttons for
	} // end Player for

	// gotta do it again for that one pesky mouselock button
	if (g_ivConfig->Shortcuts.bMouseLock.parentDevice) // possibly unbound
	{
		if ( IsEqualGUID(g_sysMouse.guidInstance, g_ivConfig->Shortcuts.bMouseLock.parentDevice->guidInstance) )
		{
			char szBuf[DEFAULT_BUFFER];
			// add to the mouse stream
			sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", -1, 0, g_ivConfig->Shortcuts.bMouseLock.bOffset, g_ivConfig->Shortcuts.bMouseLock.bAxisID, g_ivConfig->Shortcuts.bMouseLock.bBtnType);
			strMouse.append(szBuf);
		}
		else
			for (int match = 0; match < g_nDevices; match++)
				if ( IsEqualGUID(g_devList[match].guidInstance, g_ivConfig->Shortcuts.bMouseLock.parentDevice->guidInstance) )
				{
					char szBuf[DEFAULT_BUFFER];
					// add to the appropriate device stream
					sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", -1, 0, g_ivConfig->Shortcuts.bMouseLock.bOffset, g_ivConfig->Shortcuts.bMouseLock.bAxisID, g_ivConfig->Shortcuts.bMouseLock.bBtnType);
					strDevs[match].append(szBuf);
					break;
				}
	} // end shortcuts edge case

	DumpStreams(fFile, strMouse, strDevs, strNull, bIsINI);
}

// load shortcuts from "resources", i.e. builtin defaults
bool LoadShortcutsFromResource(bool bIsInterface)
{
	if (bIsInterface)
		ZeroMemory( &(g_ivConfig->Shortcuts), sizeof(SHORTCUTS) );
	TCHAR szId[20];
	wsprintf( szId, _T("#%i"), IDR_SHORTCUTS_DEFAULT );
	HRSRC res = FindResource( g_strEmuInfo.hinst, szId, _T("SHORTCUT") );
	if( res == NULL )
		return false;
	char *profile = (char*)LockResource( LoadResource( g_strEmuInfo.hinst, res ));
	char *profileend = profile + SizeofResource( g_strEmuInfo.hinst, res );
	
	ProcessKey( PL_RESET, 0, 0, 0, 0, bIsInterface );
	DWORD dwCommand = PL_NOHIT;
	char szLine[4096];
	while( profile < profileend )
	{
		while( profile < profileend && (CHECK_WHITESPACES( *profile ) || *profile == ' ' ))
			++profile;
		int i = 0;
		while( profile < profileend && i < sizeof(szLine)-1 && !(CHECK_WHITESPACES( *profile )) )
			szLine[i++] = *profile++;

		szLine[i] = '\0';
		dwCommand = ParseLine( szLine );
		ProcessKey( dwCommand, CHK_SHORTCUTS, szLine, 0, 0, bIsInterface );
	}
	return true;

}

// returns the user-chosen default directory (path) for each of the following:
//		application dir, mempak dir, gameboy rom dir, gameboyrom save dir
//		Tries to query user settings; if blank or invalid, returns their defaults
// Massages the output directory a bit
bool GetDirectory( LPTSTR pszDirectory, WORD wDirID )
{
	bool bReturn = true;
	TCHAR szBuffer[MAX_PATH + 1];
	const TCHAR szDefaultStrings[3][DEFAULT_BUFFER] = { STRING_DEF_MEMPAKFILE, STRING_DEF_GBROMFILE, STRING_DEF_GBROMSAVE };
	TCHAR *pSlash;

	pszDirectory[0] = pszDirectory[1] = '\0';

	switch( wDirID )
	{
	case DIRECTORY_MEMPAK:
	case DIRECTORY_GBROMS:
	case DIRECTORY_GBSAVES:
		if (g_aszDefFolders[wDirID][0] == 0)
			lstrcpyn( pszDirectory, szDefaultStrings[wDirID], MAX_PATH);
		else
			lstrcpyn( pszDirectory, g_aszDefFolders[wDirID], MAX_PATH);
		break;

	case DIRECTORY_DLL:
		if (GetModuleFileName(g_strEmuInfo.hinst, szBuffer, MAX_PATH))
		{
			GetFullPathName( szBuffer, MAX_PATH, pszDirectory, &pSlash );
			*pSlash = 0;
		}
		break;
	case DIRECTORY_LOG:
	case DIRECTORY_CONFIG:
	case DIRECTORY_APPLICATION:
		break;

	default:
		// we don't know what the hell you're talking about, set pszFileName to current .exe directory
		// and return false
		bReturn = false;
	}

	if( pszDirectory[1] == ':' || ( pszDirectory[1] == '\\' && pszDirectory[0] == '\\' )) // Absolute Path( x: or \\ )
		lstrcpyn( szBuffer, pszDirectory, MAX_PATH );
	else
	{
		GetModuleFileName( NULL, szBuffer, MAX_PATH );
		pSlash = _tcsrchr( szBuffer, '\\' );
		++pSlash;
		lstrcpyn( pSlash, pszDirectory, MAX_PATH );
	}
	GetFullPathName( szBuffer, MAX_PATH, pszDirectory, &pSlash );

	pSlash = &pszDirectory[lstrlen( pszDirectory ) - 1];
	if( *pSlash != '\\' )
	{
		pSlash[1] = '\\';
		pSlash[2] = '\0';
	}

	if (bReturn && wDirID == DIRECTORY_CONFIG)
	{
		strcat(pszDirectory,"Config\\");
	}
	if (bReturn && wDirID == DIRECTORY_LOG)
	{
		strcat(pszDirectory,"Logs\\");
	}
	return bReturn;
}

// Attempts to store the "absolute" filename for a file;
//	if szFileName is an absolute filename (starting with a letter and colon or two backslashes) it is simply copied
//	otherwise, it is concatenated with the known directory, such as mempak directory (type given by wDirID)
void GetAbsoluteFileName( TCHAR *szAbsolute, const TCHAR *szFileName, const WORD wDirID )
{
	if( szFileName[1] == ':' || (szFileName[1] == '\\' && szFileName[0] == '\\'))
		lstrcpyn( szAbsolute, szFileName, MAX_PATH );
	else
	{
		GetDirectory( szAbsolute, wDirID );
		lstrcat( szAbsolute, szFileName); // HACK: possible buffer overflow
	}
}

// Populates the list of mempak/transfer pak files from the config'd directory
BOOL SendFilestoList( HWND hDlgItem, WORD wType )
{
	HANDLE hFindFile;

	WIN32_FIND_DATA FindFile;
	TCHAR szPattern[MAX_PATH + 10];
	TCHAR *pszExtensions;
	BOOL Success;

	switch( wType )
	{
	case FILIST_MEM:
		GetDirectory( szPattern, DIRECTORY_MEMPAK );
		lstrcat( szPattern, _T("*.*") );
		pszExtensions = _T(".mpk\0.n64\0");
		break;
	case FILIST_TRANSFER:
		GetDirectory( szPattern, DIRECTORY_GBROMS );
		lstrcat( szPattern, _T("*.gb?") );
		pszExtensions = _T(".gb\0.gbc\0");
		break;
	default:
		return FALSE;
	}

	TCHAR *pcPoint;
	TCHAR *pszExt;
	bool bValidFile;

	hFindFile = FindFirstFile( szPattern, &FindFile );
	if( hFindFile != INVALID_HANDLE_VALUE )
	{
		do
		{
			pszExt = pszExtensions;
			pcPoint = _tcsrchr( FindFile.cFileName, _T('.') );
			bValidFile = false;
			do
			{
				if( !lstrcmpi( pcPoint, pszExt ))
					bValidFile = true;
				pszExt += lstrlen( pszExt ) + 1;	
			}
			while( *pszExt && !bValidFile );

			if( bValidFile )
				SendMessage( hDlgItem, LB_ADDSTRING, 0, (LPARAM)FindFile.cFileName );
		}
		while( FindNextFile( hFindFile, &FindFile ));
		FindClose( hFindFile );
		Success = TRUE;
	}
	else
		Success = FALSE;

	return Success;
}

bool BrowseFolders( HWND hwndParent, TCHAR *pszHeader, TCHAR *pszDirectory )
{
	ITEMIDLIST *piStart = NULL;

	if( pszDirectory[0] != '\0')
	{
		IShellFolder* pDesktopFolder;
		if( SUCCEEDED( SHGetDesktopFolder( &pDesktopFolder )))
		{
			OLECHAR olePath[MAX_PATH];
			ULONG chEaten;

			pDesktopFolder->ParseDisplayName( NULL, NULL, olePath, &chEaten, &piStart, NULL );

			pDesktopFolder->Release();
		}
	}

	BROWSEINFO brInfo;
	brInfo.hwndOwner = hwndParent;
	brInfo.pidlRoot = piStart;
	brInfo.pszDisplayName = pszDirectory;
	brInfo.lpszTitle  = pszHeader;
	brInfo.ulFlags = BIF_RETURNONLYFSDIRS;
	brInfo.lpfn = NULL;

	ITEMIDLIST *piList;

	piList = SHBrowseForFolder( &brInfo );
	if( piList )
	{
		SHGetPathFromIDList( (const LPITEMIDLIST)piList, pszDirectory );
		LPMALLOC pMal;
		if( SUCCEEDED( SHGetMalloc( &pMal )))
		{
			pMal->Free( piList );
			pMal->Release();
		}
		return true;
	}

	return false;
}

bool GetInitialBrowseDir( TCHAR *pszFileName, DWORD dwType )
{
	// DIRECTORY_INVALID means there's no corresponding entry in g_aszDefFolders
	const WORD wDirectory[] = { DIRECTORY_MEMPAK, DIRECTORY_GBROMS, DIRECTORY_GBSAVES,
								DIRECTORY_INVALID, DIRECTORY_INVALID, DIRECTORY_INVALID };
	switch( dwType )
	{
	case BF_PROFILE:
	case BF_MEMPAK:
	case BF_NOTE:
	case BF_GBROM:
	case BF_GBSAVE:
	case BF_SHORTCUTS:
		if (g_aszLastBrowse[dwType][0] == 0)
			return GetDirectory( pszFileName, wDirectory[dwType]);
		else
			lstrcpyn(pszFileName, g_aszLastBrowse[dwType], MAX_PATH);
		return true;

	default:	// we don't know what the hell you're talking about
		return GetDirectory( pszFileName, DIRECTORY_INVALID );
	}
}

bool SaveLastBrowseDir( TCHAR *pszFileName, DWORD dwType )
{
	TCHAR *cSlash = _tcsrchr( pszFileName, _T('\\') );
	if( cSlash )
	{
		switch( dwType )
		{
		case BF_PROFILE:
		case BF_MEMPAK:
		case BF_NOTE:
		case BF_GBROM:
		case BF_GBSAVE:
		case BF_SHORTCUTS:
			*cSlash = '\0';
			lstrcpyn(g_aszLastBrowse[dwType], pszFileName, MAX_PATH);
			*cSlash = '\\';
			return true;
		default:
			return false;
		}
	}
	else
		return true;
}

// Pop up a dialog asking for a filename from the user.  Returns true if returning a valid filename, false if user cancelled.
// Used when either loading (fSave == false) or saving (fSave == true) some type of file.
// Handy, because it handles all our file type extensions for us.
bool BrowseFile( HWND hDlg, TCHAR *pszFileName, DWORD dwType, bool fSave )
{
	TCHAR pszFilter[DEFAULT_BUFFER];
	TCHAR pszTitle[DEFAULT_BUFFER];
	DWORD dwFlags = /*OFN_DONTADDTORECENT |*/ OFN_NOCHANGEDIR;
	dwFlags |= (fSave)	? OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT
						: OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
	TCHAR *pszExt = NULL;

	TCHAR *pszTemp = pszFilter;
	int nFilters = 0;

	switch( dwType )
	{
	case BF_PROFILE:
		LoadString( g_hResourceDLL, IDS_DLG_CPF, pszFilter, DEFAULT_BUFFER );
		pszExt = _T("cpf");
		nFilters = 1;
		break;
	case BF_MEMPAK:
		LoadString( g_hResourceDLL, IDS_DLG_MPKN64, pszFilter, DEFAULT_BUFFER );
		if( !fSave )
		{
			LoadString( g_hResourceDLL, IDS_DLG_MPCHOOSE, pszTitle, DEFAULT_BUFFER );
			dwFlags = OFN_HIDEREADONLY;
		}
		pszExt = _T("mpk");
		nFilters = 2;
		break;
	case BF_NOTE:
		LoadString( g_hResourceDLL, IDS_DLG_A64, pszFilter, DEFAULT_BUFFER );
		pszExt = _T("a64");
		nFilters = 1;
		break;
	case BF_GBROM:
		LoadString( g_hResourceDLL, IDS_DLG_GBGBC, pszFilter, DEFAULT_BUFFER );
		pszExt = _T("gb");
		nFilters = 1;
		break;
	case BF_GBSAVE:
		LoadString( g_hResourceDLL, IDS_DLG_SVSAV, pszFilter, DEFAULT_BUFFER );
		pszExt = _T("sv");
		nFilters = 1;
		break;
	case BF_SHORTCUTS:
		LoadString( g_hResourceDLL, IDS_DLG_SC, pszFilter, DEFAULT_BUFFER );
		pszExt = _T("sc");
		nFilters = 1;
		break;
	default:
		return false;
	}

	for ( ; nFilters > 0; nFilters--)
	{
		pszTemp += _tcslen(pszTemp);
		pszTemp += 1;
		pszTemp += _tcslen(pszTemp);
		pszTemp += 1;
	}
	*pszTemp = _T('\0');

	dwFlags |= OFN_NOCHANGEDIR;

	TCHAR szFileName[MAX_PATH+1] = _T(""),
		  szInitialDir[MAX_PATH+1] = _T(""),
		  *pcSlash;

	if( pszFileName[1] == _T(':') || ( pszFileName[1] == _T('\\') && pszFileName[0] == _T('\\') ))
	{
		lstrcpyn( szInitialDir, pszFileName, ARRAYSIZE(szInitialDir) );
		pcSlash = _tcsrchr( szInitialDir, _T('\\') );
		if( pcSlash )
		{
			*pcSlash = _T('\0');
			lstrcpyn( szFileName, &pcSlash[1], ARRAYSIZE(szFileName) );
		}
	}
	else
	{
		if( !GetInitialBrowseDir( szInitialDir, dwType ))
			GetDirectory( szInitialDir, DIRECTORY_APPLICATION );
		lstrcpyn( szFileName, pszFileName, ARRAYSIZE(szFileName) );
	}

	OPENFILENAME oFile;

	oFile.lStructSize		= sizeof (OPENFILENAME);
	oFile.hwndOwner			= hDlg;
	oFile.hInstance			= NULL;
	oFile.lpstrFilter		= pszFilter;
	oFile.lpstrCustomFilter	= NULL;
	oFile.nMaxCustFilter	= 0;
	oFile.nFilterIndex		= 0;
	oFile.lpstrFile			= szFileName;
	oFile.nMaxFile			= MAX_PATH;
	oFile.lpstrFileTitle	= NULL;
	oFile.nMaxFileTitle		= MAX_PATH; // ignored
	oFile.lpstrInitialDir	= szInitialDir;
	oFile.lpstrTitle		= pszTitle;
	oFile.Flags				= dwFlags;
	oFile.nFileOffset		= 0;
	oFile.nFileExtension	= 0;
	oFile.lpstrDefExt		= pszExt;
	oFile.lCustData			= 0L;
	oFile.lpfnHook			= NULL;
	oFile.lpTemplateName	= NULL;

	if( fSave )
	{
		if( !GetSaveFileName( &oFile ))
			return false;
	}
	else
	{
		if( !GetOpenFileName( &oFile ))
			return false;
	}

	lstrcpy( pszFileName, szFileName );
	SaveLastBrowseDir( szFileName, dwType );
	return true;
}

bool ReadMemPakFile( TCHAR *pszMemPakFile, BYTE *aMemPak, bool fCreate )
{
	DWORD dwCreationDisposition = fCreate ? OPEN_ALWAYS : OPEN_EXISTING;

	HANDLE hFile = CreateFile( pszMemPakFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, dwCreationDisposition, 0, NULL);
	if ( hFile != INVALID_HANDLE_VALUE )
	{
		ZeroMemory( aMemPak, PAK_MEM_SIZE );
		TCHAR *pcPoint = _tcsrchr( pszMemPakFile, '.' );
		if( !lstrcmpi( pcPoint, _T(".n64") ) )
			SetFilePointer( hFile, 0x1040, NULL, FILE_BEGIN );
		else
			SetFilePointer( hFile, 0L, NULL, FILE_BEGIN );
		
		DWORD dwBytesRead;
		bool Success = ( ReadFile( hFile, aMemPak, PAK_MEM_SIZE, &dwBytesRead, NULL) != 0 );

		CloseHandle( hFile );
		return Success;
	}
	else
		ErrorMessage( IDS_ERR_MPREAD, GetLastError(), false );
	return false;
}

// Used by Interface to create or modify mempak files (not mapped).
// pszMemPakFile is a filename, aMemPak is the data, fCreate tells whether to create a new file
bool WriteMemPakFile( TCHAR *pszMemPakFile, BYTE *aMemPak, bool fCreate )
{
	DWORD dwCreationDisposition = fCreate ? OPEN_ALWAYS : OPEN_EXISTING;

	HANDLE hFile = CreateFile( pszMemPakFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, dwCreationDisposition, 0, NULL);
	if ( hFile != INVALID_HANDLE_VALUE )
	{
		DWORD dwBytesWritten = 0;
		TCHAR *pcPoint = _tcsrchr( pszMemPakFile, _T('.') );
		if( !lstrcmpi( pcPoint, _T(".n64") ) )
		{
			if( fCreate && !GetFileSize( hFile, NULL ))
			{
				char szHeader[] = "123-456-STD";
				SetFilePointer( hFile, 0L, NULL, FILE_BEGIN );
				WriteFile( hFile, szHeader, sizeof(szHeader), &dwBytesWritten, NULL );
			}
			SetFilePointer( hFile, 0x1040, NULL, FILE_BEGIN );
		}
		else
			SetFilePointer( hFile, 0L, NULL, FILE_BEGIN );
		
		bool Success = ( WriteFile( hFile, aMemPak, PAK_MEM_SIZE, &dwBytesWritten, NULL ) != 0 );
		if( Success )
			SetEndOfFile( hFile );
		
		CloseHandle( hFile );
		return Success;
	}
	else
		ErrorMessage( IDS_ERR_MPCREATE, GetLastError(), false );

	return false;
}

// This func stores the current config data to INI.  It stores the Interface's idea of configuration
// As such, it should only be called from the config window (Interface). Otherwise, it will fail.
// Returns true if saved OK, false if there was a problem.
bool StoreConfigToINI()
{
	char szANSIBuf[DEFAULT_BUFFER];
	if (!g_ivConfig)
		return false;

	TCHAR szFilename[MAX_PATH];
	GetDirectory(szFilename, DIRECTORY_CONFIG);
	_tcscat(szFilename, _T("NRage.ini"));
	FILE *fFile = _tfopen(szFilename, _T("wS"));	// write, optimize for sequential

	if (!fFile)
	{
		DebugWriteA("Couldn't open INI file for output!\n");
		return false;
	}

	// first write out any standard header stuff here
	fputs(STRING_INI_HEADER, fFile);

	// General
	fputs("\n[" STRING_INI_GENERAL "]\n", fFile);
	fprintf(fFile, STRING_INI_LANGUAGE "=%d\n", g_ivConfig->Language);
	fprintf(fFile, STRING_INI_SHOWMESSAGES "=%d\n", (int)(g_ivConfig->fDisplayShortPop));

	// Folders
	fputs("\n[" STRING_INI_FOLDERS "]\n", fFile);
	const char szFolders[ARRAYSIZE(g_aszDefFolders)][DEFAULT_BUFFER] = {STRING_INI_BRMEMPAK "=%s\n", STRING_INI_BRGBROM "=%s\n", STRING_INI_BRGBSAVE "=%s\n"};
	for (int i = 0; i < ARRAYSIZE(szFolders); i++)
	{
		TCHAR_TO_CHAR( szANSIBuf, g_aszDefFolders[i], DEFAULT_BUFFER );
		fprintf(fFile, szFolders[i], szANSIBuf);
	}

	// lastBrowserDir
	fputs("\n[" STRING_INI_BROWSER "]\n", fFile);
	const char szBrowser[ARRAYSIZE(g_aszLastBrowse)][DEFAULT_BUFFER] = {STRING_INI_BRMEMPAK "=%s\n", STRING_INI_BRGBROM "=%s\n", STRING_INI_BRGBSAVE "=%s\n",
		STRING_INI_BRPROFILE "=%s\n", STRING_INI_BRNOTE "=%s\n", STRING_INI_SHORTCUTS "=%s\n" };
	for (int i = 0; i < ARRAYSIZE(szBrowser); i++)
	{
		TCHAR_TO_CHAR( szANSIBuf, g_aszLastBrowse[i], DEFAULT_BUFFER );
		fprintf(fFile, szBrowser[i], szANSIBuf);
	}

	// Controller 1 through 4
	for (int i = 0; i < 4; i++)
	{
		fprintf(fFile, "\n[" STRING_INI_CONTROLLER " %d]\n", i + 1);
		DumpControllerSettings(fFile, i, true);
	}

	// Controls

	// I'm going to use STL strings here because I don't want to screw with buffer management
	string strMouse;
	string strDevs[MAX_DEVICES];
	string strNull;

	fputs("\n[" STRING_INI_CONTROLS "]\n", fFile);
	fputs("# Button format: controlnum buttonID bOffset bAxisID bBtnType\n", fFile);

	for ( int i = 0; i < 4; i++ ) // controllers for
	{
		FormatControlsBlock(&strMouse, strDevs, &strNull, i);
	} // end controllers for

	DumpStreams(fFile, strMouse, strDevs, strNull, true);

	strMouse.clear();
	for (int i = 0; i < g_nDevices; i++)
		strDevs[i].clear();
	strNull.clear();

	// Shortcuts

	fputs("\n[" STRING_INI_SHORTCUTS "]\n", fFile);
	fputs("# Shortcuts format: controlnum buttonID bOffset bAxisID bBtnType\n", fFile);

	FormatShortcutsBlock(fFile, true);

	// Modifiers
	fputs("\n[" STRING_INI_MODIFIERS "]\n", fFile);
	fputs("# Modifiers format: controlnum bOffset bAxisID bBtnType bModType fToggle fStatus dwSpecific\n", fFile);

	for ( int i = 0; i < 4; i++ ) // controllers for
	{
		FormatModifiersBlock(&strMouse, strDevs, &strNull, i);
	} // end controllers for

	DumpStreams(fFile, strMouse, strDevs, strNull, true);

	fclose(fFile);
	DebugWriteA("Config stored to INI\n");
	return true;
}

// This func loads the config data from INI into working emulator space.  Does not copy into Interface;
// you need to call GetCurrentConfiguration() if you want to do that.
// Returns true if loaded OK, false if there was a problem.
bool LoadConfigFromINI()
{
	FILE *fFile = NULL;
	DWORD dwSection = 0;	// this will eval to the bracketed "[Section]" we are currently in.
	char szLine[4096];

	TCHAR szFilename[MAX_PATH];
	GetDirectory(szFilename, DIRECTORY_CONFIG);
	_tcscat(szFilename, _T("NRage.ini"));
	fFile = _tfopen(szFilename, _T("rS"));	// read, optimize for sequential

	if (!fFile)
	{
		DebugWriteA("Couldn't open INI file for input!\n");
		return false;
	}
	
	for (int i = 0; i < 4; i++)
		SetControllerDefaults( &(g_pcControllers[i]) );

	ProcessKey( PL_RESET, 0, 0, 0, 0, false );
	DWORD dwCommand = PL_NOHIT;
	while(( fgets(szLine, sizeof(szLine) - 1, fFile) ) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		dwCommand = ParseLine( szLine );
		if (dwCommand == PL_NOHIT)
			continue;
		else if (dwCommand == PL_CATEGORY)
			// section changed to szLine
			dwSection = djbHash(szLine);
		else
			ProcessKey( dwCommand, dwSection, szLine, 0, 0, false );
	}

	fclose(fFile);

	return true;
}

// basically a stripped down version of GetConfigFromINI, called at the very beginning to get our language
LANGID GetLanguageFromINI()
{
	FILE *fFile = NULL;
	char szLine[4096];

	TCHAR szFilename[MAX_PATH];
	GetDirectory(szFilename, DIRECTORY_CONFIG);
	_tcscat(szFilename, _T("NRage.ini"));
	fFile = _tfopen(szFilename, _T("rS"));	// read, optimize for sequential

	if (!fFile)
	{
		DebugWriteA("Couldn't open INI file for input!\n");
		return 0;
	}
	
	ProcessKey( PL_RESET, 0, 0, 0, 0, false );
	DWORD dwCommand = PL_NOHIT;
	while(( fgets(szLine, sizeof(szLine) - 1, fFile) ) )
	{
		szLine[strlen(szLine) - 1] = '\0'; // remove newline
		dwCommand = ParseLine( szLine );
		if (dwCommand == CHK_LANGUAGE)
		{
			LANGID lTemp = 0;
            if (sscanf(szLine, "%hu", &lTemp))
			{
				fclose(fFile);
				return lTemp;
			}
		}
	}

	fclose(fFile);
	DebugWriteA("Couldn't find a Language= line in INI file...\n");
	return 0;
}

// both the following functions assume the buffer is big enough
inline void GUIDtoStringA( char * szGUIDbuf, const GUID guid )
{
	_snprintf( szGUIDbuf, GUID_STRINGLENGTH + 1, "{%08.8lX-%04.4hX-%04.4hX-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
}

inline bool StringtoGUIDA( LPGUID guid, const char * szGUIDbuf )
{
	short unsigned int lastbyte;
	int blah = sscanf(szGUIDbuf, "{%08lX-%04hX-%04hX-%02hX%02hX-%02hX%02hX%02hX%02hX%02hX%02hX}", &guid->Data1, &guid->Data2, &guid->Data3, &guid->Data4[0], &guid->Data4[1], &guid->Data4[2], &guid->Data4[3], &guid->Data4[4], &guid->Data4[5], &guid->Data4[6], &lastbyte);
	if (blah == 11)
	{
        guid->Data4[7] = (BYTE)lastbyte;
		return true;
	}
	else
		return false;
}

// Takes in a file to dump to, and an int i telling which controller's settings to dump. Does not dump buttons or modifiers.
void DumpControllerSettings(FILE * fFile, int i, bool bIsINI)
{
	char szANSIBuf[DEFAULT_BUFFER];

	fprintf(fFile, STRING_INI_PLUGGED "=%u\n", g_ivConfig->Controllers[i].fPlugged);
	fprintf(fFile, STRING_INI_XINPUT "=%u\n", g_ivConfig->Controllers[i].fXInput);
	fprintf(fFile, STRING_INI_RAWDATA "=%u\n", g_ivConfig->Controllers[i].fRawData);
	fprintf(fFile, STRING_INI_PAKTYPE "=%u\n", g_ivConfig->Controllers[i].PakType);
	fprintf(fFile, STRING_INI_REALN64RANGE "=%u\n", g_ivConfig->Controllers[i].fRealN64Range);
	fprintf(fFile, STRING_INI_RAPIDFIREENABLED "=%u\n", g_ivConfig->Controllers[i].bRapidFireEnabled);
	fprintf(fFile, STRING_INI_RAPIDFIRERATE "=%u\n", g_ivConfig->Controllers[i].bRapidFireRate);
	fprintf(fFile, STRING_INI_STICKRANGE "=%u\n", g_ivConfig->Controllers[i].bStickRange);
	fprintf(fFile, STRING_INI_MOUSEMOVEX "=%u\n", g_ivConfig->Controllers[i].bMouseMoveX);
	fprintf(fFile, STRING_INI_MOUSEMOVEY "=%u\n", g_ivConfig->Controllers[i].bMouseMoveY);
	fprintf(fFile, STRING_INI_AXISSET "=%u\n", g_ivConfig->Controllers[i].bAxisSet);
	fprintf(fFile, STRING_INI_KEYABSOLUTEX "=%u\n", g_ivConfig->Controllers[i].fKeyAbsoluteX);
	fprintf(fFile, STRING_INI_KEYABSOLUTEY "=%u\n", g_ivConfig->Controllers[i].fKeyAbsoluteY);
	fprintf(fFile, STRING_INI_PADDEADZONE "=%u\n", g_ivConfig->Controllers[i].bPadDeadZone);
	fprintf(fFile, STRING_INI_MOUSESENSX "=%u\n", g_ivConfig->Controllers[i].wMouseSensitivityX);
	fprintf(fFile, STRING_INI_MOUSESENSY "=%u\n", g_ivConfig->Controllers[i].wMouseSensitivityY);
	fprintf(fFile, STRING_INI_RUMBLETYPE "=%u\n", g_ivConfig->Controllers[i].bRumbleTyp);
	fprintf(fFile, STRING_INI_RUMBLESTRENGTH "=%u\n", g_ivConfig->Controllers[i].bRumbleStrength);
	fprintf(fFile, STRING_INI_VISUALRUMBLE "=%u\n", g_ivConfig->Controllers[i].fVisualRumble);

	if (bIsINI)
	{
		char szGUID[DEFAULT_BUFFER];
		int iDevice = FindDeviceinList( g_ivConfig->FFDevices[i].szProductName, g_ivConfig->FFDevices[i].bProductCounter, true );
		if (iDevice == -1)
		{
			fprintf(fFile, STRING_INI_FFDEVICEGUID "=\n");
		}
		else
		{
			g_ivConfig->Controllers[i].guidFFDevice = g_devList[iDevice].guidInstance;
			GUIDtoStringA(szGUID, g_ivConfig->Controllers[i].guidFFDevice);
			fprintf(fFile, STRING_INI_FFDEVICEGUID "=%s\n", szGUID);
		}
	}
	else
	{
		TCHAR_TO_CHAR(szANSIBuf, g_ivConfig->FFDevices[i].szProductName, DEFAULT_BUFFER);
		fprintf(fFile, STRING_INI_FFDEVICENAME "=%s\n", szANSIBuf);
		fprintf(fFile, STRING_INI_FFDEVICENR "=%u\n", g_ivConfig->FFDevices[i].bProductCounter);
	}

	TCHAR_TO_CHAR( szANSIBuf, g_ivConfig->Controllers[i].szMempakFile, DEFAULT_BUFFER );
	fprintf(fFile, STRING_INI_MEMPAKFILE "=%s\n", szANSIBuf);

	TCHAR_TO_CHAR( szANSIBuf, g_ivConfig->Controllers[i].szTransferRom, DEFAULT_BUFFER );
	fprintf(fFile, STRING_INI_GBROMFILE "=%s\n", szANSIBuf);

	TCHAR_TO_CHAR( szANSIBuf, g_ivConfig->Controllers[i].szTransferSave, DEFAULT_BUFFER );
	fprintf(fFile, STRING_INI_GBROMSAVE "=%s\n", szANSIBuf);
}

// private func, called by StoreConfigToINI to dump cached Button strings to file
void DumpStreams(FILE * fFile, string strMouse, string strDevs[], string strNull, bool bIsINI)
{
	// dump all streams to file, with appropriate DInput lines and name comment
	char szANSIBuf[DEFAULT_BUFFER];
	if (!(strMouse.empty()))
	{
		TCHAR_TO_CHAR( szANSIBuf, g_sysMouse.szProductName, DEFAULT_BUFFER );
		if (bIsINI)
		{
			fprintf(fFile, "# %s\n", szANSIBuf);
			char szGUID[DEFAULT_BUFFER];
			GUIDtoStringA(szGUID, g_sysMouse.guidInstance);
			fprintf(fFile, STRING_INI_DINPUTGUID "=%s\n", szGUID);
		}
		else
		{
			fprintf(fFile, STRING_INI_DINPUTNAME "=%s\n", szANSIBuf);
			fprintf(fFile, STRING_INI_DINPUTNR "=%d\n", 0);
		}
		fputs(strMouse.c_str(), fFile);
	}
	if (!(strNull.empty()))
	{
		fputs("# NOT ASSIGNED\n", fFile);
		if (bIsINI)
		{
			char szGUID[DEFAULT_BUFFER];
			GUIDtoStringA(szGUID, GUID_NULL);
			fprintf(fFile, STRING_INI_DINPUTGUID "=%s\n", szGUID);
		}
		else
		{
			fprintf(fFile, STRING_INI_DINPUTNAME "=\n");	// leave blank
			fprintf(fFile, STRING_INI_DINPUTNR "=\n");
		}
		fputs(strNull.c_str(), fFile);
	}
	for (int i = 0; i < g_nDevices; i++)
	{
		if (!(strDevs[i].empty()))
			{
				TCHAR_TO_CHAR( szANSIBuf, g_devList[i].szProductName, DEFAULT_BUFFER );
				if (bIsINI)
				{
					if (g_devList[i].bProductCounter > 0)
						fprintf(fFile, "# %s %d\n", szANSIBuf, g_devList[i].bProductCounter);
					else
						fprintf(fFile, "# %s\n", szANSIBuf);
					char szGUID[DEFAULT_BUFFER];
					GUIDtoStringA(szGUID, g_devList[i].guidInstance);
					fprintf(fFile, STRING_INI_DINPUTGUID "=%s\n", szGUID);
				}
				else
				{
					fprintf(fFile, STRING_INI_DINPUTNAME "=%s\n", szANSIBuf);
					fprintf(fFile, STRING_INI_DINPUTNR "=%d\n", g_devList[i].bProductCounter);
				}
				fputs(strDevs[i].c_str(), fFile);
			}
	}
}

void FormatControlsBlock(string * strMouse, string strDevs[], string * strNull, int i)
{
	for ( int j = 0; j < ARRAYSIZE(g_ivConfig->Controllers[i].aButton); j++ ) // buttons for
	{
		if (g_ivConfig->Controllers[i].aButton[j].parentDevice) // possibly unbound
		{
			if ( IsEqualGUID(g_sysMouse.guidInstance, g_ivConfig->Controllers[i].aButton[j].parentDevice->guidInstance) )
			{
				char szBuf[DEFAULT_BUFFER];
				// add to the mouse stream
				sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", i, j, g_ivConfig->Controllers[i].aButton[j].bOffset, g_ivConfig->Controllers[i].aButton[j].bAxisID, g_ivConfig->Controllers[i].aButton[j].bBtnType);
				strMouse->append(szBuf);
			}
			else
			{
				for (int match = 0; match < g_nDevices; match++)
				{
					if ( IsEqualGUID(g_devList[match].guidInstance, g_ivConfig->Controllers[i].aButton[j].parentDevice->guidInstance) )
					{
						char szBuf[DEFAULT_BUFFER];
						// add to the appropriate device stream
						sprintf(szBuf, STRING_INI_BUTTON "=%d %d %02X %d %d\n", i, j, g_ivConfig->Controllers[i].aButton[j].bOffset, g_ivConfig->Controllers[i].aButton[j].bAxisID, g_ivConfig->Controllers[i].aButton[j].bBtnType);
						strDevs[match].append(szBuf);
						break;
					}
				}
			}
		}
		else if (g_ivConfig->Controllers[i].aButton[j].bBtnType != DT_UNASSIGNED)
		{
			int k = g_ivConfig->Controllers[i].aButton[j].bBtnType;
			DebugWriteA("Controller %d button %d is of bBtnType %d but has no parentDevice!\n", i, j, k);
		}
	} // end buttons for
}

void FormatModifiersBlock(string * strMouse, string strDevs[], string * strNull, int i)
{
	for ( int j = 0; j < g_ivConfig->Controllers[i].nModifiers; j++ )
	{
		if (g_ivConfig->Controllers[i].pModifiers[j].btnButton.parentDevice) // is it assigned to a key?
		{
			if ( IsEqualGUID(g_sysMouse.guidInstance, g_ivConfig->Controllers[i].pModifiers[j].btnButton.parentDevice->guidInstance) )
			{
				char szBuf[DEFAULT_BUFFER];
				// add to the mouse stream
				sprintf(szBuf, STRING_INI_MODIFIER "=%d %02X %d %d %d %d %d %08X\n", i, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bOffset, 
					g_ivConfig->Controllers[i].pModifiers[j].btnButton.bAxisID, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bBtnType,
					g_ivConfig->Controllers[i].pModifiers[j].bModType, g_ivConfig->Controllers[i].pModifiers[j].fToggle,
					g_ivConfig->Controllers[i].pModifiers[j].fStatus, g_ivConfig->Controllers[i].pModifiers[j].dwSpecific);
				strMouse->append(szBuf);
			}
			else
				for (int match = 0; match < g_nDevices; match++)
					if ( IsEqualGUID(g_devList[match].guidInstance, g_ivConfig->Controllers[i].pModifiers[j].btnButton.parentDevice->guidInstance) )
					{
						char szBuf[DEFAULT_BUFFER];
						// add to the mouse stream
						sprintf(szBuf, STRING_INI_MODIFIER "=%d %02X %d %d %d %d %d %08X\n", i, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bOffset, 
							g_ivConfig->Controllers[i].pModifiers[j].btnButton.bAxisID, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bBtnType,
							g_ivConfig->Controllers[i].pModifiers[j].bModType, g_ivConfig->Controllers[i].pModifiers[j].fToggle,
							g_ivConfig->Controllers[i].pModifiers[j].fStatus, g_ivConfig->Controllers[i].pModifiers[j].dwSpecific);
						strDevs[match].append(szBuf);
						break;
					}
		}
		else // save modifiers without a keybind
		{
			char szBuf[DEFAULT_BUFFER];
			// add to the mouse stream
			sprintf(szBuf, STRING_INI_MODIFIER "=%d %02X %d %d %d %d %d %08X\n", i, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bOffset, 
				g_ivConfig->Controllers[i].pModifiers[j].btnButton.bAxisID, g_ivConfig->Controllers[i].pModifiers[j].btnButton.bBtnType,
				g_ivConfig->Controllers[i].pModifiers[j].bModType, g_ivConfig->Controllers[i].pModifiers[j].fToggle,
				g_ivConfig->Controllers[i].pModifiers[j].fStatus, g_ivConfig->Controllers[i].pModifiers[j].dwSpecific);
			strNull->append(szBuf);
		}
	}
}

unsigned long djbHash(const char *str)
{
    unsigned long hash = 5381;
    int c;

    while ((c = *str++))
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}
