1 /*****************************************************************************\
2      Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3                 This file is licensed under the Snes9x License.
4    For further information, consult the LICENSE file in the root directory.
5 \*****************************************************************************/
6 
7 // all windows-specific command line and config file parsing/saving/loading
8 // this stuff was moved out of wsnes.cpp, to keep things a little tidier
9 
10 // note:
11 //  if you want to force all users of a new version to have a
12 //  particular setting reset to its given default,
13 //  change the name string of that setting (in WinRegisterConfigItems)
14 //  to something a little different...
15 //  but if it's not in a windows-specific category, make sure you change its name elsewhere too
16 
17 #include "../port.h"
18 #include "../snes9x.h"
19 #include "wsnes9x.h"
20 #include "wlanguage.h"
21 #include "../display.h"
22 #include "../conffile.h"
23 #include "../spc7110.h"
24 #include "../gfx.h"
25 #include "../snapshot.h"
26 #ifdef NETPLAY_SUPPORT
27 	#include "../netplay.h"
28 	extern SNPServer NPServer;
29 #endif
30 #include <assert.h>
31 
32 static void WinDeleteRegistryEntries ();
33 void WinSetDefaultValues ();
34 void WinDeleteRecentGamesList ();
35 
36 HANDLE configMutex = NULL;
37 
38 extern TCHAR multiRomA[MAX_PATH]; // lazy, should put in sGUI and add init to {0} somewhere
39 extern TCHAR multiRomB[MAX_PATH];
40 
S9xParseArg(char ** argv,int & i,int argc)41 void S9xParseArg (char **argv, int &i, int argc)
42 {
43 	if (strcasecmp (argv [i], "-restore") == 0)
44 	{
45 		WinDeleteRegistryEntries ();
46 		WinSetDefaultValues	();
47 	}
48 	else if (strcasecmp (argv[i], "-hidemenu") == 0)
49 	{
50 		GUI.HideMenu = true;
51 	}
52 	else if (strcasecmp (argv[i], "-fullscreen") == 0)
53 	{
54 		GUI.FullScreen = true;
55 	}
56 	else if (!strcasecmp(argv[i], "-cartb"))
57 	{
58 		Settings.Multi = TRUE; // only used to signal winmain
59 		if (i + 1 < argc)
60 		{
61 			lstrcpyn(multiRomB, _tFromChar(argv[++i]), MAX_PATH);
62 		}
63 	}
64 }
65 
WinSetDefaultValues()66 void WinSetDefaultValues ()
67 {
68 	// TODO: delete the parts that are already covered by the default values in WinRegisterConfigItems
69 
70 	char temp[4];
71 	strcpy(temp,"C:\\");
72 
73 	GUI.ControllerOption = SNES_JOYPAD;
74 	GUI.ValidControllerOptions = 0xFFFF;
75 	GUI.IgnoreNextMouseMove	= false;
76 
77 	GUI.DoubleBuffered = false;
78 	GUI.FullScreen = false;
79 	GUI.Stretch	= false;
80 	GUI.FlipCounter	= 0;
81 	GUI.NumFlipFrames =	1;
82 	Settings.BilinearFilter	= false;
83 	GUI.LockDirectories = false;
84 	GUI.window_maximized = false;
85 	GUI.EmulatedFullscreen = false;
86 
87 	WinDeleteRecentGamesList ();
88 
89 	GUI.SoundChannelEnable=255;
90 
91 	// Tracing options
92 	Settings.TraceDMA =	false;
93 	Settings.TraceHDMA = false;
94 	Settings.TraceVRAM = false;
95 	Settings.TraceUnknownRegisters = false;
96 	Settings.TraceDSP =	false;
97 
98 	// ROM timing options (see also	H_Max above)
99 	Settings.PAL = false;
100 	Settings.FrameTimePAL =	20000;
101 	Settings.FrameTimeNTSC = 16667;
102 	Settings.FrameTime = 16667;
103 
104 	// CPU options
105 	Settings.Paused	= false;
106 
107 	Settings.SupportHiRes = true;
108 
109 #ifdef NETPLAY_SUPPORT
110 	Settings.Port =	1996;
111 	NetPlay.MaxFrameSkip = 10;
112 	NetPlay.MaxBehindFrameCount	= 10;
113 	NPServer.SyncByReset = true;
114 	NPServer.SendROMImageOnConnect = false;
115 #endif
116 
117 	Settings.TakeScreenshot=false;
118 
119 	GUI.Language=0;
120     GUI.AllowSoundSync = true;
121 }
122 
123 
try_save(const char * fname,ConfigFile & conf)124 static bool	try_save(const char	*fname,	ConfigFile &conf){
125 	STREAM fp;
126 	if((fp=OPEN_STREAM(fname, "w"))!=NULL){
127 		fprintf(stdout,	"Saving	config file	%s\n", fname);
128 		CLOSE_STREAM(fp);
129 		conf.SaveTo(fname);
130 		return true;
131 	}
132 	return false;
133 }
134 
S9xSaveConfigFile(ConfigFile & conf)135 static bool S9xSaveConfigFile(ConfigFile &conf){
136 
137 	configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex"));
138 	int times = 0;
139 	DWORD waitVal = WAIT_TIMEOUT;
140 	while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds
141 		waitVal = WaitForSingleObject(configMutex, 100);
142 
143 	// save over the .conf file if it already exists, otherwise save over the .cfg file
144 	std::string	fname;
145 	fname=S9xGetDirectory(DEFAULT_DIR);
146 	fname+=SLASH_STR S9X_CONF_FILE_NAME;
147 
148 	// ensure previous config file is not lost if we crash while writing the new one
149 	std::string	ftemp;
150 	{
151 		CopyFileA(fname.c_str(), (fname + ".autobak").c_str(), FALSE);
152 
153 		ftemp=S9xGetDirectory(DEFAULT_DIR);
154 		ftemp+=SLASH_STR "config_error";
155 		FILE* tempfile = fopen(ftemp.c_str(), "wb");
156 		if(tempfile) fclose(tempfile);
157 	}
158 
159 	bool ret = try_save(fname.c_str(), conf);
160 
161 	remove(ftemp.c_str());
162 	remove((fname + ".autobak").c_str());
163 
164 	ReleaseMutex(configMutex);
165 	CloseHandle(configMutex);
166 
167 	return ret;
168 }
169 
170 
WinDeleteRegistryEntries()171 static void	WinDeleteRegistryEntries ()
172 {
173 //	WinDeleteRegKey (HKEY_CURRENT_USER, S9X_REG_KEY_BASE);
174 }
175 
SkipSpaces(char * p)176 static inline char *SkipSpaces (char *p)
177 {
178 	while (*p && isspace (*p))
179 		p++;
180 
181 	return (p);
182 }
183 
WinParseCommandLineAndLoadConfigFile(TCHAR * line)184 const TCHAR*	WinParseCommandLineAndLoadConfigFile (TCHAR *line)
185 {
186 	// Break the command line up into an array of string pointers, each	pointer
187 	// points at a separate	word or	character sequence enclosed	in quotes.
188 
189     int count = 0;
190     static TCHAR return_filename[MAX_PATH];
191 
192 #ifdef UNICODE
193     // split params into argv
194 	TCHAR **params = CommandLineToArgvW(line, &count);
195 
196     // convert all parameters to utf8
197 	char **parameters = new char*[count];
198     for(int i = 0; i < count; i++) {
199         int requiredChars = WideCharToMultiByte(CP_UTF8, 0, params[i], -1, NULL, 0, NULL, NULL);
200 	    parameters[i] = new char[requiredChars];
201 	    WideCharToMultiByte(CP_UTF8, 0, params[i], -1, parameters[i], requiredChars, NULL, NULL);
202     }
203     LocalFree(params);
204 #else
205 #define	MAX_PARAMETERS 100
206 	char *p	= line;
207 	char *parameters[MAX_PARAMETERS];
208 	while (count < MAX_PARAMETERS && *p)
209 	{
210 		p =	SkipSpaces (p);
211 		if (*p == '"')
212 		{
213 			p++;
214 			parameters [count++] = p;
215 			while (*p && *p	!= '"')
216 				p++;
217 			*p++ = 0;
218 		}
219 		else
220 			if (*p == '\'')
221 			{
222 				p++;
223 				parameters [count++] = p;
224 				while (*p && *p	!= '\'')
225 					p++;
226 				*p++ = 0;
227 			}
228 			else
229 			{
230 				parameters [count++] = p;
231 				while (*p && !isspace (*p))
232 					p++;
233 				if (!*p)
234 					break;
235 				*p++ = 0;
236 			}
237 	}
238 
239 #endif
240 	configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex"));
241 	int times = 0;
242 	DWORD waitVal = WAIT_TIMEOUT;
243 	while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds
244 		waitVal = WaitForSingleObject(configMutex, 100);
245 
246 	// ensure previous config file is not lost if we crashed while writing a new one
247 	{
248 		std::string	ftemp;
249 		ftemp=S9xGetDirectory(DEFAULT_DIR);
250 		ftemp+=SLASH_STR "config_error";
251 		FILE* tempfile = fopen(ftemp.c_str(), "rb");
252 		if(tempfile)
253 		{
254 			fclose(tempfile);
255 
256 			std::string	fname;
257 			for(int i=0; i<2; i++)
258 			{
259 				fname=S9xGetDirectory(DEFAULT_DIR);
260 				if(i == 0)      fname+=SLASH_STR "snes9x.conf";
261 				else if(i == 1) fname+=SLASH_STR "snes9x.cfg";
262 
263 				tempfile = fopen((fname + ".autobak").c_str(), "rb");
264 				if(tempfile)
265 				{
266 					fclose(tempfile);
267 					MoveFileExA((fname + ".autobak").c_str(), fname.c_str(), MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH);
268 				}
269 			}
270 		  remove(ftemp.c_str());
271 		}
272 	}
273 
274 	S9xLoadConfigFiles(parameters, count);
275 
276 	ReleaseMutex(configMutex);
277 	CloseHandle(configMutex);
278 
279 	const char* rf = S9xParseArgs (parameters, count);
280 
281     if(rf) // save rom_filename as TCHAR if available
282         lstrcpy(return_filename, _tFromChar(rf));
283 
284 #ifdef UNICODE
285     // free parameters
286     for(int i = 0; i < count; i++) {
287         delete [] parameters[i];
288     }
289     delete [] parameters;
290 #endif
291 
292     return rf ? return_filename : NULL;
293 }
294 
295 #define S(x) GAMEDEVICE_VK_##x
296 #define SO(x) GAMEDEVICE_VK_OEM_##x
297 static const char* keyToString [256] =
298 {
299 	"Unassigned","LMB","RMB","Break","MMB","XMB1","XMB2","0x07",
300 	S(BACK),S(TAB),"0x0A","0x0B",S(CLEAR),S(RETURN),"0x0E","0x0F",
301 	S(SHIFT),S(CONTROL),S(MENU),S(PAUSE),S(CAPITAL),"Kana","0x16","Junja",
302 	"Final","Kanji","0x1A",S(ESCAPE),"Convert","NonConvert","Accept","ModeChange",
303 	S(SPACE),S(PRIOR),S(NEXT),S(END),S(HOME),S(LEFT),S(UP),S(RIGHT),
304 	S(DOWN),S(SELECT),S(PRINT),S(EXECUTE),S(SNAPSHOT),S(INSERT),S(DELETE),S(HELP),
305 	"0","1","2","3","4","5","6","7",
306 	"8","9","0x3A","0x3B","0x3C","0x3D","0x3E","0x3F",
307 	"0x40","A","B","C","D","E","F","G",
308 	"H","I","J","K","L","M","N","O",
309 	"P","Q","R","S","T","U","V","W",
310 	"X","Y","Z",S(LWIN),S(RWIN),S(APPS),"0x5E","Sleep",
311 	"Pad0","Pad1","Pad2","Pad3","Pad4","Pad5","Pad6","Pad7",
312 	"Pad8","Pad9",S(MULTIPLY),S(ADD),S(SEPARATOR),S(SUBTRACT),S(DECIMAL),S(DIVIDE),
313 	S(F1),S(F2),S(F3),S(F4),S(F5),S(F6),S(F7),S(F8),
314 	S(F9),S(F10),S(F11),S(F12),"F13","F14","F15","F16",
315 	"F17","F18","F19","F20","F21","F22","F23","F24",
316 	"0x88","0x89","0x8A","0x8B","0x8C","0x8D","0x8E","0x8F",
317 	S(NUMLOCK),S(SCROLL),"PadEqual","Masshou","Touroku","Loya","Roya","0x97",
318 	"0x98","0x99","0x9A","0x9B","0x9C","0x9D","0x9E","0x9F",
319 	S(LSHIFT),S(RSHIFT),S(LCONTROL),S(RCONTROL),S(LMENU),S(RMENU),"BrowserBack","BrowserForward",
320 	"BrowserRefresh","BrowserStop","BrowserSearch","BrowserFavorites","BrowserHome","VolumeMute","VolumeDown","VolumeUp",
321 	"MediaNextTrack","MediaPrevTrack","MediaStop","MediaPlayPause","LaunchMail","MediaSelect","LaunchApp1","LaunchApp2",
322 	"0xB8","0xB9",";","+",",",SO(MINUS),".",SO(2),
323 	SO(3),"0xC1","0xC2","0xC3","0xC4","0xC5","0xC6","0xC7",
324 	"0xC8","0xC9","0xCA","0xCB","0xCC","0xCD","0xCE","0xCF",
325 	"0xD0","0xD1","0xD2","0xD3","0xD4","0xD5","0xD6","0xD7",
326 	"0xD8","0xD9","0xDA",SO(4),"Backslash",SO(6),"'","OEM8",
327 	"0xE0","AX","<>","ICOHelp","ICO00","Process","ICOClear","Packet",
328 	"0xE8","Reset","Jump","PA1_2","PA2","PA3","WSCTRL","CUSEL",
329 	"Attention2","Finish","Copy","Auto","ENLW","Backtab","Attention","CRSEL",
330 	"EXSEL","EREOF","Play","Zoom","NoName","PA1","Clear2","0xFF"
331 };
332 static const char* keyToAlternateString [256] =
333 {
334 	"none","LeftClick","RightClick","Cancel","MiddleClick","","","","Back","","","","","Return","","",
335 	"","","Menu","","","","","","","","","Escape","","","","",
336 	"","PageUp","PageDown","","","","","","","","PrintScreen","","","Ins","Del","",
337 	"","","","","","","","","","","","","","","","",
338 	"","","","","","","","","","","","","","","","",
339 	"","","","","","","","","","","","","","","","",
340 	"","","","","","","","","","","","","","","","",
341 	"","","","","","","","","","","","","","","","",
342 	"","","","","","","","","","","","","","","","",
343 	"NumLock","ScrollLock","","","","","","","","","","","","","","",
344 	"","","","","","","","","","","","","","","","",
345 	"","","","","","","","","","",SO(1),SO(PLUS),SO(COMMA),"Minus",SO(PERIOD),"?",
346 	"","","","","","","","","","","","","","","","",
347 	"","","","oem_pa1","","","","","","","","LBracket","|","RBracket",SO(7),"",
348 	"ATTN2","","","","","","","","","","","","","","ATTN","",
349 	"","","","","","","","","","","","","","","",""
350 };
351 #undef S
352 #undef SO
353 
354 
355 
356 
357 // We will maintain	our	own	list of	items to put in	the	config file,
358 // so that each	config item	has	only one point of change across	both saving	and	loading
359 // and to allow us to manage custom types of config items, such as virtual key codes represented as strings
360 
361 enum ConfigItemType	{
362 	CIT_BOOL, CIT_INT, CIT_UINT, CIT_STRING, CIT_INVBOOL, CIT_BOOLONOFF, CIT_INVBOOLONOFF, CIT_VKEY, CIT_VKEYMOD
363 };
364 
365 struct ConfigItem
366 {
367 	const char* name;
368 	void* addr;
369 	int	size;
370 	void* def;
371 	const char*	comment;
372 	ConfigItemType type;
373 
ConfigItemConfigItem374 	ConfigItem(const char* nname, void*	naddr, int nsize, void*	ndef, const	char* ncomment,	ConfigItemType ntype) {
375 		addr = naddr; name = nname;	size = nsize; def =	ndef; comment =	ncomment; type = ntype;
376 	}
377 
GetConfigItem378 	void Get(ConfigFile& conf) {
379 		switch(type)
380 		{
381 			case CIT_BOOL:
382 			case CIT_BOOLONOFF:
383 				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetBool(name, def!=0);
384 				if(size	== 2) *(uint16*)addr = (uint16)conf.GetBool(name, def!=0);
385 				if(size	== 4) *(uint32*)addr = (uint32)conf.GetBool(name, def!=0);
386 				if(size	== 8) *(uint64*)addr = (uint64)conf.GetBool(name, def!=0);
387 				break;
388 			case CIT_INT:
389 				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetInt(name, PtrToInt(def));
390 				if(size	== 2) *(uint16*)addr = (uint16)conf.GetInt(name, PtrToInt(def));
391 				if(size	== 4) *(uint32*)addr = (uint32)conf.GetInt(name, PtrToInt(def));
392 				if(size	== 8) *(uint64*)addr = (uint64)conf.GetInt(name, PtrToInt(def));
393 				break;
394 			case CIT_UINT:
395 				if(size	== 1) *(uint8 *)addr = (uint8) conf.GetUInt(name, PtrToUint(def));
396 				if(size	== 2) *(uint16*)addr = (uint16)conf.GetUInt(name, PtrToUint(def));
397 				if(size	== 4) *(uint32*)addr = (uint32)conf.GetUInt(name, PtrToUint(def));
398 				if(size	== 8) *(uint64*)addr = (uint64)conf.GetUInt(name, PtrToUint(def));
399 				break;
400 			case CIT_STRING:
401 				lstrcpyn((TCHAR*)addr, _tFromChar(conf.GetString(name, reinterpret_cast<const char*>(def))), size-1);
402 				((TCHAR*)addr)[size-1] = TEXT('\0');
403 				break;
404 			case CIT_INVBOOL:
405 			case CIT_INVBOOLONOFF:
406 				if(size	== 1) *(uint8 *)addr = (uint8) !conf.GetBool(name, def!=0);
407 				if(size	== 2) *(uint16*)addr = (uint16)!conf.GetBool(name, def!=0);
408 				if(size	== 4) *(uint32*)addr = (uint32)!conf.GetBool(name, def!=0);
409 				if(size	== 8) *(uint64*)addr = (uint64)!conf.GetBool(name, def!=0);
410 				break;
411 			case CIT_VKEY:
412 				{
413 					uint16 keyNum = (uint16)conf.GetUInt(name, PtrToUint(def));
414 					const char* keyStr = conf.GetString(name);
415 					if(keyStr)
416 					{
417 						for(int i=0;i<512;i++)
418 						{
419 							if(i<256) // keys
420 							{
421 								if(!strcasecmp(keyStr,keyToString[i]) ||
422 								(*keyToAlternateString[i] && !strcasecmp(keyStr,keyToAlternateString[i])))
423 								{
424 									keyNum = i;
425 									break;
426 								}
427 							}
428 							else // joystick:
429 							{
430 								char temp [128];
431 								extern void TranslateKey(WORD keyz,char *out);
432 								TranslateKey(0x8000|(i-256),temp);
433 								if(strlen(keyStr)>3 && !strcasecmp(keyStr+3,temp+3))
434 								{
435 									for(int j = 0 ; j < 16 ; j++)
436 									{
437 										if(keyStr[2]-'0' == j || keyStr[2]-'a' == j-10)
438 										{
439 											keyNum = 0x8000|(i-256)|(j<<8);
440 											i = 512;
441 											break;
442 										}
443 									}
444 								}
445 							}
446 						}
447 					}
448 					if(size	== 1) *(uint8 *)addr = (uint8) keyNum;
449 					if(size	== 2) *(uint16*)addr = (uint16)keyNum;
450 					if(size	== 4) *(uint32*)addr = (uint32)keyNum;
451 					if(size	== 8) *(uint64*)addr = (uint64)keyNum;
452 				}
453 				break;
454 			case CIT_VKEYMOD:
455 				{
456 					uint16 modNum = 0;
457 					const char* modStr = conf.GetString(name);
458 					if(modStr) {
459 						if(strstr(modStr, "ft") || strstr(modStr, "FT")) modNum |= CUSTKEY_SHIFT_MASK;
460 						if(strstr(modStr, "tr") || strstr(modStr, "TR")) modNum |= CUSTKEY_CTRL_MASK;
461 						if(strstr(modStr, "lt") || strstr(modStr, "LT")) modNum |= CUSTKEY_ALT_MASK;
462 					}
463 					if(!modNum && (!modStr || strcasecmp(modStr, "none")))
464 						modNum = conf.GetUInt(name, PtrToUint(def));
465 					if(size	== 1) *(uint8 *)addr = (uint8) modNum;
466 					if(size	== 2) *(uint16*)addr = (uint16)modNum;
467 					if(size	== 4) *(uint32*)addr = (uint32)modNum;
468 					if(size	== 8) *(uint64*)addr = (uint64)modNum;
469 				}
470 				break;
471 		}
472 
473 		// if it had a comment, override our own with it
474 		const char* newComment = conf.GetComment(name);
475 		if(newComment && *newComment)
476 			comment = newComment;
477 	}
478 
SetConfigItem479 	void Set(ConfigFile& conf) {
480 		switch(type)
481 		{
482 			case CIT_BOOL:
483 				if(size	== 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "TRUE","FALSE", comment);
484 				if(size	== 2) conf.SetBool(name, 0!=(*(uint16*)addr), "TRUE","FALSE", comment);
485 				if(size	== 4) conf.SetBool(name, 0!=(*(uint32*)addr), "TRUE","FALSE", comment);
486 				if(size	== 8) conf.SetBool(name, 0!=(*(uint64*)addr), "TRUE","FALSE", comment);
487 				break;
488 			case CIT_BOOLONOFF:
489 				if(size	== 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "ON","OFF", comment);
490 				if(size	== 2) conf.SetBool(name, 0!=(*(uint16*)addr), "ON","OFF", comment);
491 				if(size	== 4) conf.SetBool(name, 0!=(*(uint32*)addr), "ON","OFF", comment);
492 				if(size	== 8) conf.SetBool(name, 0!=(*(uint64*)addr), "ON","OFF", comment);
493 				break;
494 			case CIT_INT:
495 				if(size	== 1) conf.SetInt(name,	(int32)(*(uint8	*)addr), comment);
496 				if(size	== 2) conf.SetInt(name,	(int32)(*(uint16*)addr), comment);
497 				if(size	== 4) conf.SetInt(name,	(int32)(*(uint32*)addr), comment);
498 				if(size	== 8) conf.SetInt(name,	(int32)(*(uint64*)addr), comment);
499 				break;
500 			case CIT_UINT:
501 				if(size	== 1) conf.SetUInt(name, (uint32)(*(uint8 *)addr), 10, comment);
502 				if(size	== 2) conf.SetUInt(name, (uint32)(*(uint16*)addr), 10, comment);
503 				if(size	== 4) conf.SetUInt(name, (uint32)(*(uint32*)addr), 10, comment);
504 				if(size	== 8) conf.SetUInt(name, (uint32)(*(uint64*)addr), 10, comment);
505 				break;
506 			case CIT_STRING:
507 				if((TCHAR*)addr)
508 					conf.SetString(name, std::string(_tToChar((TCHAR*)addr)), comment);
509 				break;
510 			case CIT_INVBOOL:
511 				if(size	== 1) conf.SetBool(name, 0==(*(uint8 *)addr), "TRUE","FALSE", comment);
512 				if(size	== 2) conf.SetBool(name, 0==(*(uint16*)addr), "TRUE","FALSE", comment);
513 				if(size	== 4) conf.SetBool(name, 0==(*(uint32*)addr), "TRUE","FALSE", comment);
514 				if(size	== 8) conf.SetBool(name, 0==(*(uint64*)addr), "TRUE","FALSE", comment);
515 				break;
516 			case CIT_INVBOOLONOFF:
517 				if(size	== 1) conf.SetBool(name, 0==(*(uint8 *)addr), "ON","OFF", comment);
518 				if(size	== 2) conf.SetBool(name, 0==(*(uint16*)addr), "ON","OFF", comment);
519 				if(size	== 4) conf.SetBool(name, 0==(*(uint32*)addr), "ON","OFF", comment);
520 				if(size	== 8) conf.SetBool(name, 0==(*(uint64*)addr), "ON","OFF", comment);
521 				break;
522 			case CIT_VKEY:
523 				{
524 					uint16 keyNum = 0;
525 					if(size	== 1) keyNum = (uint8)(*(uint8 *)addr);
526 					if(size	== 2) keyNum = (uint16)(*(uint16*)addr);
527 					if(size	== 4) keyNum = (uint16)(*(uint32*)addr);
528 					if(size	== 8) keyNum = (uint16)(*(uint64*)addr);
529 					if(keyNum < 256) conf.SetString(name, keyToString[keyNum], comment);
530 					else if(keyNum & 0x8000) {
531 						char temp [128];
532 						extern void TranslateKey(WORD keyz,char *out);
533 						TranslateKey(keyNum,temp);
534 						conf.SetString(name, temp, comment);
535 					}
536 					else conf.SetUInt(name, keyNum, 16, comment);
537 				}
538 				break;
539 			case CIT_VKEYMOD:
540 				{
541 					uint16 modNum = 0;
542 					if(size	== 1) modNum = (uint8)(*(uint8 *)addr);
543 					if(size	== 2) modNum = (uint16)(*(uint16*)addr);
544 					if(size	== 4) modNum = (uint16)(*(uint32*)addr);
545 					if(size	== 8) modNum = (uint16)(*(uint64*)addr);
546 					std::string modStr;
547 					if(modNum & CUSTKEY_CTRL_MASK) modStr += "Ctrl ";
548 					if(modNum & CUSTKEY_ALT_MASK) modStr += "Alt ";
549 					if(modNum & CUSTKEY_SHIFT_MASK) modStr += "Shift ";
550 					if(!(modNum & (CUSTKEY_CTRL_MASK|CUSTKEY_ALT_MASK|CUSTKEY_SHIFT_MASK))) modStr = "none";
551 					else modStr.erase(modStr.length()-1);
552 					conf.SetString(name, modStr, comment);
553 				}
554 				break;
555 		}
556 	}
557 };
558 
559 std::vector<ConfigItem> configItems;
560 // var must be a persistent variable. In the case of strings, it must point to a writeable character array.
561 #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))
562 #define AddItem(name, var, def, type) AddItemC(name,var,def,"",type)
563 #define AddUInt(name, var, def) AddItem(name,var,def,CIT_UINT)
564 #define AddInt(name, var, def) AddItem(name,var,def,CIT_INT)
565 #define AddBool(name, var, def) AddItem(name,var,def,CIT_BOOL)
566 #define AddBool2(name, var, def) AddItem(name,var,def,CIT_BOOLONOFF)
567 #define AddInvBool(name, var, def) AddItem(name,var,def,CIT_INVBOOL)
568 #define AddInvBool2(name, var, def) AddItem(name,var,def,CIT_INVBOOLONOFF)
569 #define AddVKey(name, var, def) AddItem(name,var,def,CIT_VKEY)
570 #define AddVKMod(name, var, def) AddItem(name,var,def,CIT_VKEYMOD)
571 #define AddUIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_UINT)
572 #define AddIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INT)
573 #define AddBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOL)
574 #define AddBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOLONOFF)
575 #define AddInvBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOL)
576 #define AddInvBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOLONOFF)
577 #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))
578 #define AddString(name, var, buflen, def) AddStringC(name, var, buflen, def, "")
579 
580 static char filterString [1024], filterString2 [1024], snapVerString [256];
581 static bool niceAlignment, showComments, readOnlyConfig;
582 static int configSort;
583 
WinPreSave(ConfigFile & conf)584 void WinPreSave(ConfigFile& conf)
585 {
586 	strcpy(filterString, "output filter: ");
587 	for(int i=0;i<NUM_FILTERS;i++)
588 	{
589 		static char temp [256];
590 		sprintf(temp, "%d=%s%c ", i, GetFilterName((RenderFilter)i), i!=NUM_FILTERS-1?',':' ');
591 		strcat(filterString, temp);
592 	}
593 	strcpy(filterString2, "hi-res output filter: ");
594 	for(int i=0;i<NUM_FILTERS;i++)
595 	{
596 		if(GetFilterHiResSupport((RenderFilter)i))
597 		{
598 			static char temp [256];
599 			sprintf(temp, "%d=%s%c ", i, GetFilterName((RenderFilter)i), i!=NUM_FILTERS-1?',':' ');
600 			strcat(filterString2, temp);
601 		}
602 	}
603 	sprintf(snapVerString, "Snapshot save version. Must be between 1 and %d (inclusive)", SNAPSHOT_VERSION);
604 
605 //	GetWindowRect (GUI.hWnd, &GUI.window_size);
606 	GUI.window_size.right -= GUI.window_size.left;
607 	GUI.window_size.bottom -= GUI.window_size.top;
608 
609 	int extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
610 						  GetSystemMetrics(SM_CXDLGFRAME));
611 	GUI.window_size.right -= extra_width;
612 
613 	int extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
614 						  GetSystemMetrics(SM_CYDLGFRAME)) +
615 						 GetSystemMetrics(SM_CYCAPTION) +
616 						 GetSystemMetrics(SM_CYMENU) +
617 						 (GUI.HideMenu ? 0 : (
618 						  (GUI.window_size.right <= 392 ? GetSystemMetrics(SM_CYMENU) : 0) + // HACK: accounts for menu wrapping (when width is small)
619 						  (GUI.window_size.right <= 208 ? GetSystemMetrics(SM_CYMENU) : 0) +
620 						  (GUI.window_size.right <= 148 ? GetSystemMetrics(SM_CYMENU) : 0)));
621 	GUI.window_size.bottom -= extra_height;
622 
623 	if(GUI.window_size.bottom < 10) GUI.window_size.bottom = 10;
624 	if(GUI.window_size.right < 10) GUI.window_size.right = 10;
625 
626 	GUI.customRomDlgSettings.window_size.right -= GUI.customRomDlgSettings.window_size.left;
627 	GUI.customRomDlgSettings.window_size.bottom -= GUI.customRomDlgSettings.window_size.top;
628 
629 	conf.DeleteKey("Sound::Mono");
630 	if(configSort == 2)
631 		conf.ClearLines();
632 }
WinPostSave(ConfigFile & conf)633 void WinPostSave(ConfigFile& conf)
634 {
635 	int extra_width  = 2*(GetSystemMetrics(SM_CXBORDER) +
636 						  GetSystemMetrics(SM_CXDLGFRAME));
637 	int extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
638 						  GetSystemMetrics(SM_CYDLGFRAME)) +
639 						 GetSystemMetrics(SM_CYCAPTION) +
640 						 (GUI.HideMenu ? 0 : (GetSystemMetrics(SM_CYMENU) +
641 						  (GUI.window_size.right <= 392 ? GetSystemMetrics(SM_CYMENU) : 0) + // HACK: accounts for menu wrapping (when width is small)
642 						  (GUI.window_size.right <= 208 ? GetSystemMetrics(SM_CYMENU) : 0) +
643 						  (GUI.window_size.right <= 148 ? GetSystemMetrics(SM_CYMENU) : 0)));
644 	GUI.window_size.right += GUI.window_size.left;
645 	GUI.window_size.bottom += GUI.window_size.top;
646 	GUI.window_size.right += extra_width;
647 	GUI.window_size.bottom += extra_height;
648 
649 	GUI.customRomDlgSettings.window_size.right += GUI.customRomDlgSettings.window_size.left;
650 	GUI.customRomDlgSettings.window_size.bottom += GUI.customRomDlgSettings.window_size.top;
651 }
WinPostLoad(ConfigFile & conf)652 void WinPostLoad(ConfigFile& conf)
653 {
654 	int i;
655 	if(Settings.DisplayPressedKeys) Settings.DisplayPressedKeys = 2;
656 	for(i=0;i<8;i++) Joypad[i+8].Enabled = Joypad[i].Enabled;
657 	if(GUI.MaxRecentGames < 1) GUI.MaxRecentGames = 1;
658 	if(GUI.MaxRecentGames > MAX_RECENT_GAMES_LIST_SIZE) GUI.MaxRecentGames = MAX_RECENT_GAMES_LIST_SIZE;
659     if(GUI.rewindGranularity==0) GUI.rewindGranularity = 1;
660 	bool gap = false;
661 	for(i=0;i<MAX_RECENT_GAMES_LIST_SIZE;i++) // remove gaps in recent games list
662 	{
663 		if(!*GUI.RecentGames[i])
664 			gap = true;
665 		else if(gap)
666 		{
667 			memmove(GUI.RecentGames[i-1], GUI.RecentGames[i], MAX_PATH * sizeof(TCHAR));
668 			*GUI.RecentGames[i] = TEXT('\0');
669 			gap = false;
670 			i = -1;
671 		}
672 	}
673 
674     if (Settings.MaxSpriteTilesPerLine != 34 && Settings.MaxSpriteTilesPerLine != 128)
675         Settings.MaxSpriteTilesPerLine = 34;
676 
677     switch (Settings.OverclockMode)
678     {
679         default:
680         case 0:
681             Settings.OneClockCycle = 6;
682             Settings.OneSlowClockCycle = 8;
683             Settings.TwoClockCycles = 12;
684             break;
685         case 1:
686             Settings.OneClockCycle = 6;
687             Settings.OneSlowClockCycle = 6;
688             Settings.TwoClockCycles = 12;
689             break;
690         case 2:
691             Settings.OneClockCycle = 4;
692             Settings.OneSlowClockCycle = 5;
693             Settings.TwoClockCycles = 8;
694             break;
695         case 3:
696             Settings.OneClockCycle = 3;
697             Settings.OneSlowClockCycle = 4;
698             Settings.TwoClockCycles = 6;
699             break;
700     }
701 
702 	ConfigFile::SetNiceAlignment(niceAlignment);
703 	ConfigFile::SetShowComments(showComments);
704 	ConfigFile::SetAlphaSort(configSort==2);
705 	ConfigFile::SetTimeSort(configSort==1);
706 
707 	WinPostSave(conf);
708 }
709 
WinPreLoad(ConfigFile & conf)710 void WinPreLoad(ConfigFile& conf)
711 {
712 	if(conf.Exists("Config::Sort"))
713 	{
714 		if(conf.GetUInt("Config::Sort") == 1)
715 		{
716 			configSort = 1;
717 			ConfigFile::SetAlphaSort(configSort==2);
718 			ConfigFile::SetTimeSort(configSort==1);
719 			conf.ClearLines();
720 		}
721 	}
722 	else
723 	{
724 		conf.DeleteKey("Config::Sort");
725 	}
726 }
727 
728 
729 // and now for the important part,
730 // which determines what goes into and comes out of the config file:
731 // add all of the things we want to save and load to a list
732 // (but don't actually save or load anything yet)
WinRegisterConfigItems()733 void WinRegisterConfigItems()
734 {
735 #define CATEGORY "Config"
736 	AddBool2C("NiceAlignment", niceAlignment, true, "on to line up the =, :, and # in each section of this config file");
737 	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.");
738 	AddUIntC("Sort", configSort, 1, "ordering within sections: 0=allow reordering, 1=force default order, 2=sort alphabetically");
739 	AddBoolC("Lock", readOnlyConfig, false, "if true, prevents Snes9x from editing this configuration file (or making it read-only while it is running)");
740 #undef CATEGORY
741 #define CATEGORY "Display"
742 	AddBool2("Transparency", Settings.Transparency, true);
743 	AddBoolC("MessagesInImage", Settings.AutoDisplayMessages, false, "true to draw text inside the SNES image (will get into AVIs, screenshots, and filters)");
744 	AddBool2C("FrameRate", Settings.DisplayFrameRate, false, "on to display the framerate (will be inaccurate if AutoMaxSkipFrames is too small)");
745 	AddBoolC("DisplayInput", Settings.DisplayPressedKeys, false, "true to show which buttons are pressed");
746 	AddBoolC("DisplayFrameCount", Settings.DisplayMovieFrame, true, "true to show the frame count when a movie is playing");
747 #undef CATEGORY
748 #define CATEGORY "Display\\Win"
749 	AddUIntC("OutputMethod", GUI.outputMethod, 1, "0=DirectDraw, 1=Direct3D, 2=OpenGL");
750 	AddUIntC("FilterType", GUI.Scale, 0, filterString);
751 	AddUIntC("FilterHiRes", GUI.ScaleHiRes, 0, filterString2);
752 	AddBoolC("BlendHiRes", GUI.BlendHiRes, true, "true to horizontally blend Hi-Res images (better transparency effect on filters that do not account for this)");
753 	AddBoolC("NTSCScanlines", GUI.NTSCScanlines, true, "true to use scanlines with Blargg's NTSC filters");
754 	AddBoolC("ShaderEnabled", GUI.shaderEnabled, false, "true to use pixel shader (if supported by output method)");
755 	AddStringC("Direct3D:D3DShader", GUI.D3DshaderFileName, MAX_PATH, "", "shader filename for Direct3D mode (HLSL effect file or CG shader");
756 	AddStringC("OpenGL:OGLShader", GUI.OGLshaderFileName, MAX_PATH, "", "shader filename for OpenGL mode (bsnes-style XML shader or CG shader)");
757 	AddBoolC("OpenGL:DisablePBOs", GUI.OGLdisablePBOs, false, "do not use PBOs in OpenGL mode, even if the video card supports them");
758 	AddBoolC("ExtendHeight", GUI.HeightExtend, false, "true to display an extra 15 pixels at the bottom, which few games use. Also increases AVI output size from 256x224 to 256x240.");
759 	AddBoolC("AlwaysCenterImage", GUI.AlwaysCenterImage,false, "true to center the image even if larger than window");
760 	AddIntC("Window:Width", GUI.window_size.right, 512, "256=1x, 512=2x, 768=3x, 1024=4x, etc. (usually)");
761 	AddIntC("Window:Height", GUI.window_size.bottom, 448, "224=1x, 448=2x, 672=3x,  896=4x, etc. (usually)");
762 	AddIntC("Window:Left", GUI.window_size.left, 0, "in pixels from left edge of screen");
763 	AddIntC("Window:Top", GUI.window_size.top, 0, "in pixels from top edge of screen");
764 	AddBool("Window:Maximized", GUI.window_maximized, false);
765 	AddIntC("CustomRomDialog:Width", GUI.customRomDlgSettings.window_size.right, 660, "");
766 	AddIntC("CustomRomDialog:Height", GUI.customRomDlgSettings.window_size.bottom, 400, "");
767 	AddIntC("CustomRomDialog:Left", GUI.customRomDlgSettings.window_size.left, 50, "in pixels from left edge of screen");
768 	AddIntC("CustomRomDialog:Top", GUI.customRomDlgSettings.window_size.top, 50, "in pixels from top edge of screen");
769 	AddBool("CustomRomDialog:Maximized", GUI.customRomDlgSettings.window_maximized, false);
770 	AddIntC("CustomRomDialog:FolderPaneWidth", GUI.customRomDlgSettings.folderPaneWidth, 230, "");
771 	AddIntC("CustomRomDialog:DescColumnWidth", GUI.customRomDlgSettings.columnDescription, 112, "");
772 	AddIntC("CustomRomDialog:FilenameColumnWidth", GUI.customRomDlgSettings.columnFilename, 196, "");
773 	AddIntC("CustomRomDialog:SizeColumnWidth", GUI.customRomDlgSettings.columnSize, 67, "");
774 	AddBoolC("Stretch:Enabled", GUI.Stretch, true, "true to stretch the game image to fill the window or screen");
775 	AddBoolC("Stretch:MaintainAspectRatio", GUI.AspectRatio, true, "prevents stretching from changing the aspect ratio");
776 	AddBoolC("Stretch:IntegerScaling", GUI.IntegerScaling, false, "scales image height to exact integer multiples");
777 	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");
778 	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.");
779 	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.");
780 	AddBool("Fullscreen:Enabled", GUI.FullScreen, false);
781 	AddUInt("Fullscreen:Width", GUI.FullscreenMode.width, 640);
782 	AddUInt("Fullscreen:Height", GUI.FullscreenMode.height, 480);
783 	AddUInt("Fullscreen:Depth", GUI.FullscreenMode.depth, 16);
784 	AddUInt("Fullscreen:RefreshRate", GUI.FullscreenMode.rate, 60);
785 	AddBool("Fullscreen:DoubleBuffered", GUI.DoubleBuffered, false);
786 	AddBoolC("Fullscreen:EmulateFullscreen", GUI.EmulateFullscreen, true,"true makes snes9x create a window that spans the entire screen when going fullscreen");
787 	AddBoolC("HideMenu", GUI.HideMenu, false, "true to auto-hide the menu bar on startup.");
788 	AddBoolC("Vsync", GUI.Vsync, false, "true to enable Vsync");
789 	AddBoolC("ReduceInputLag", GUI.ReduceInputLag, false, "true to reduce input lag by hard synchronization");
790     AddBoolC("DWMSync", GUI.DWMSync, false, "sync to DWM compositor if it is running");
791 	AddBoolC("FilterMessageFont", GUI.filterMessagFont, true, "true to filter message font with EPX on 2x/3x scales if MessagesInImage is false)");
792 #undef CATEGORY
793 #define CATEGORY "Settings"
794 	AddUIntC("FrameSkip", Settings.SkipFrames, AUTO_FRAMERATE, "200=automatic (limits at 50/60 fps), 0=none, 1=skip every other, ...");
795 	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");
796 	AddUIntC("TurboFrameSkip", Settings.TurboSkipFrames, 15, "how many frames to skip when in fast-forward mode");
797 	AddUInt("AutoSaveDelay", Settings.AutoSaveDelay, 30);
798 	AddBool("BlockInvalidVRAMAccess", Settings.BlockInvalidVRAMAccessMaster, true);
799 	AddBool2C("SnapshotScreenshots", Settings.SnapshotScreenshots, true, "on to save the screenshot in each snapshot, for loading-when-paused display");
800 	AddBoolC("MovieTruncateAtEnd", Settings.MovieTruncate, true, "true to truncate any leftover data in the movie file after the current frame when recording stops");
801 	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)");
802 	AddBool("DisplayWatchedAddresses", Settings.DisplayWatchedAddresses, true);
803 	AddBool2C("WrongMovieStateProtection", Settings.WrongMovieStateProtection, true, "off to allow states to be loaded for recording from a different movie than they were made in");
804 	AddUIntC("MessageDisplayTime", Settings.InitialInfoStringTimeout, 120, "display length of messages, in frames. set to 0 to disable all message text");
805 #undef CATEGORY
806 #define CATEGORY "Settings\\Win"
807     AddUIntC("RewindBufferSize", GUI.rewindBufferSize, 0, "rewind buffer size in MB - 0 disables rewind support");
808     AddUIntC("RewindGranularity", GUI.rewindGranularity, 1, "rewind granularity - rewind takes a snapshot each x frames");
809 	AddBoolC("PauseWhenInactive", GUI.InactivePause, TRUE, "true to pause Snes9x when it is not the active window");
810 	AddBoolC("CustomRomOpenDialog", GUI.CustomRomOpen, false, "false to use standard Windows open dialog for the ROM open dialog");
811 	AddBoolC("AVIHiRes", GUI.AVIHiRes, false, "true to record AVI in Hi-Res scale");
812 	AddBoolC("ConfirmSaveLoad", GUI.ConfirmSaveLoad, false, "true to ask for confirmation when saving/loading");
813 //	AddUIntC("Language", GUI.Language, 0, "0=English, 1=Nederlands"); // NYI
814 	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");
815 	AddBool("MovieDefaultClearSRAM", GUI.MovieClearSRAM, false);
816 	AddBool("MovieDefaultStartFromReset", GUI.MovieStartFromReset, false);
817 	AddBool("MovieDefaultReadOnly", GUI.MovieReadOnly, true);
818 	AddUInt("CurrentSaveSlot", GUI.CurrentSaveSlot, 0);
819 	AddUIntC("MaxRecentGames", GUI.MaxRecentGames, 14, "max recent games to show in the recent games menu (must be <= 32)");
820 #undef CATEGORY
821 #define CATEGORY "Settings\\Win\\Files"
822 	AddStringC("Dir:Roms", GUI.RomDir, _MAX_PATH, ".\\Roms", "directory where the Open ROM dialog will start");
823 	AddStringC("Dir:Screenshots", GUI.ScreensDir, _MAX_PATH, ".\\Screenshots", "directory where screenshots will be saved");
824 	AddStringC("Dir:Movies", GUI.MovieDir, _MAX_PATH, ".\\Movies", "the default directory for recorded movie (.smv) files");
825 	AddStringC("Dir:SPCs", GUI.SPCDir, _MAX_PATH, ".\\SPCs", "directory where SPCs will be saved");
826 	AddStringC("Dir:Savestates", GUI.FreezeFileDir, _MAX_PATH, ".\\Saves", "directory where savestates will be created and loaded from");
827 	AddStringC("Dir:SRAM", GUI.SRAMFileDir, _MAX_PATH, ".\\Saves", "directory where battery saves will be created and loaded from");
828 	AddStringC("Dir:Cheats", GUI.CheatDir, _MAX_PATH, ".\\Cheats", "directory in which cheats (.cht files) will be looked for");
829 	AddStringC("Dir:Patches", GUI.PatchDir, _MAX_PATH, ".\\Patches", "directory in which ROM patches (.ips/.bps/.ups files) will be looked for");
830 	AddStringC("Dir:Bios", GUI.BiosDir, _MAX_PATH, ".\\BIOS", "directory where BIOS files (such as \"BS-X.bios\") will be located");
831 	AddStringC("Dir:SatData", GUI.SatDir, _MAX_PATH, ".\\SatData", "directory where Satellaview Signal Data files will be located");
832 	AddBoolC("Dir:Lock", GUI.LockDirectories, false, "true to prevent Snes9x from changing configured directories when you browse to a new location");
833 	#define ADD(n) AddString("Rom:RecentGame" #n, GUI.RecentGames[n-1], MAX_PATH, "")
834 		ADD(1);  ADD(2);  ADD(3);  ADD(4);  ADD(5);  ADD(6);  ADD(7);  ADD(8);
835 		ADD(9);  ADD(10); ADD(11); ADD(12); ADD(13); ADD(14); ADD(15); ADD(16);
836 		ADD(17); ADD(18); ADD(19); ADD(20); ADD(21); ADD(22); ADD(23); ADD(24);
837 		ADD(25); ADD(26); ADD(27); ADD(28); ADD(29); ADD(30); ADD(31); ADD(32);
838 		assert(MAX_RECENT_GAMES_LIST_SIZE == 32);
839 	#undef ADD
840 	AddString("Rom:MultiCartA", multiRomA, _MAX_PATH, "");
841 	AddString("Rom:MultiCartB", multiRomB, _MAX_PATH, "");
842 #undef CATEGORY
843 #define	CATEGORY "Sound"
844 	AddIntC("Sync", Settings.SoundSync, 1, "1 to sync emulation to sound output, 0 to disable.");
845 	AddUIntC("Rate", Settings.SoundPlaybackRate, 48000, "sound playback quality, in Hz");
846 	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.");
847 	AddBoolC("Mute", GUI.Mute, false, "true to mute sound output (does not disable the sound CPU)");
848 	AddBool("DynamicRateControl", Settings.DynamicRateControl, false);
849 	AddBool("AutomaticInputRate", GUI.AutomaticInputRate, true);
850 	AddIntC("InterpolationMethod", Settings.InterpolationMethod, 2, "0 = None, 1 = Linear, 2 = Gaussian (accurate), 3 = Cubic, 4 = Sinc");
851 #undef CATEGORY
852 #define	CATEGORY "Sound\\Win"
853 	AddUIntC("SoundDriver", GUI.SoundDriver, 4, "4=XAudio2 (recommended), 8=WaveOut");
854 	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");
855 	AddBoolC("MuteFrameAdvance", GUI.FAMute, false, "true to prevent Snes9x from outputting sound when the Frame Advance command is in use");
856 	AddUIntC("VolumeRegular", GUI.VolumeRegular, 100, "volume during regular play (percentage between 0 and 100)");
857 	AddUIntC("VolumeTurbo", GUI.VolumeTurbo, 100, "volume during turbo mode (percentage between 0 and 100)");
858     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");
859 #undef CATEGORY
860 #define	CATEGORY "Controls"
861 	AddBoolC("AllowLeftRight", Settings.UpAndDown, false, "true to allow left+right and up+down");
862 #undef CATEGORY
863 #define	CATEGORY "ROM"
864 	AddBoolC("Cheat", Settings.ApplyCheats, true, "true to allow enabled cheats to be applied");
865 	AddInvBoolC("Patch", Settings.NoPatch, true, "true to allow IPS/UPS patches to be applied (\"soft patching\")");
866 	AddBoolC("IgnorePatchChecksum", Settings.IgnorePatchChecksum, false, "true to allow BPS patches to be applied even if the checksum fails");
867 	AddBoolC("BS", Settings.BS, false, "Broadcast Satellaview emulation");
868 #undef CATEGORY
869 #ifdef NETPLAY_SUPPORT
870 #define	CATEGORY "Netplay"
871 	AddUInt("Port", Settings.Port, NP_DEFAULT_PORT);
872 	AddString("Server", Settings.ServerName, 128, Settings.ServerName);
873 	AddBool2("SyncByReset", NPServer.SyncByReset, true);
874 	AddBool2("SendROMImageOnConnect", NPServer.SendROMImageOnConnect, false);
875 	AddUInt("MaxFrameSkip", NetPlay.MaxFrameSkip, 10);
876 	AddUInt("MaxBehindFrameCount", NetPlay.MaxBehindFrameCount, 10);
877 	AddBoolC("UseJoypad1", GUI.NetplayUseJoypad1, true, "if false, player 2 has to use their joypad #2 controls, etc.");
878 	#define ADD(n,d) AddString("RecentHost" #n, GUI.RecentHostNames[n-1], MAX_PATH, d)
879 		ADD(1,"localhost");  ADD(2,"");  ADD(3,"");  ADD(4,"");  ADD(5,"");  ADD(6,"");  ADD(7,"");  ADD(8,"");
880 		ADD(9,"");  ADD(10,"");  ADD(11,"");  ADD(12,"");  ADD(13,"");  ADD(14,"");  ADD(15,"");  ADD(16,"");
881 		assert(MAX_RECENT_HOSTS_LIST_SIZE == 16);
882 	#undef ADD
883 #undef CATEGORY
884 #endif
885 #define	CATEGORY "Controls\\Win"
886 #define ADD(n,x) AddVKey("Joypad" #n ":" #x, Joypad[n-1].x, Joypad[n-1].x)
887 #define ADDN(n,x,n2) AddVKey("Joypad" #n ":" #n2, Joypad[n-1].x, Joypad[n-1].x)
888 #define ADDB(n,x) AddBool("Joypad" #n ":" #x, Joypad[n-1].x, Joypad[n-1].x)
889 #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)
890 #define ADDT(n,x) AddVKey("Joypad" #n "Turbo:" #x, Joypad[n-1+8].x, Joypad[n-1+8].x)
891 #define ADDTN(n,x,n2) AddVKey("Joypad" #n "Turbo:" #n2, Joypad[n-1+8].x, Joypad[n-1+8].x)
892 #define ADDT2(n) ADDTN(n,Down,AutoFire); ADDTN(n,Left,AutoHold); ADDTN(n,Up,TempTurbo); ADDTN(n,Right,ClearAll)
893 #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)
894 #define ADD2T2(n) ADD2(n); ADDT2(n)
895 	// NOTE: for now, the windows port doesn't actually allow 8 different controllers to be set configured, only 5
896 	ADD2T2(1); ADD2T2(2); ADD2T2(3); ADD2T2(4); ADD2T2(5); ADD2T2(6); ADD2T2(7); ADD2T2(8);
897 	ADDT3(1); ADDT3(2); ADDT3(3); ADDT3(4); ADDT3(5); ADDT3(6); ADDT3(7); ADDT3(8);
898 #undef ADD
899 #undef ADD2
900 #undef ADDN
901 #undef ADDB
902 #undef ADDT
903 #undef ADDT2
904 #undef ADDT3
905 #undef ADDTN
906 #undef ADD2T2
907 	AddBool2C("Input:Background", GUI.BackgroundInput, false, "on to detect game keypresses and hotkeys while window is inactive, if PauseWhenInactive = FALSE.");
908 	AddBool2C("Input:BackgroundKeyHotkeys", GUI.BackgroundKeyHotkeys, true, "on to also detect keyboard hotkeys when backgroundinput is active");
909 #undef CATEGORY
910 #define	CATEGORY "Controls\\Win\\Hotkeys"
911 	AddBool2C("Handler:Joystick", GUI.JoystickHotkeys, true, "on to detect game controller buttons assigned to hotkeys. May impact performance.");
912 #define ADD(x) AddVKey("Key:" #x , CustomKeys.x.key, CustomKeys.x.key); AddVKMod("Mods:" #x, CustomKeys.x.modifiers, CustomKeys.x.modifiers)
913 #define ADDN(x,n2) AddVKey("Key:" #n2, CustomKeys.x.key, CustomKeys.x.key); AddVKMod("Mods:" #n2, CustomKeys.x.modifiers, CustomKeys.x.modifiers)
914 	ADD(SpeedUp); ADD(SpeedDown); ADD(Pause); ADD(FrameAdvance);
915 	ADD(SkipUp); ADD(SkipDown); ADD(ScopeTurbo); ADD(ScopePause);
916 	ADD(FrameCount); ADD(ReadOnly); ADD(FastForward); ADD(FastForwardToggle); ADD(ShowPressed);
917 	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);
918 	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);
919 	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);
920 	ADD(SaveScreenShot); ADD(SlotPlus); ADD(SlotMinus); ADD(SlotSave); ADD(SlotLoad);
921 	ADD(BGL1); ADD(BGL2); ADD(BGL3); ADD(BGL4); ADD(BGL5);
922 	ADD(ClippingWindows); /*ADD(BGLHack);*/ ADD(Transparency); /*ADD(HDMA)*/; /*ADD(GLCube);*/
923 	/*ADD(InterpMode7);*/ ADD(JoypadSwap); ADD(SwitchControllers); ADD(ResetGame); ADD(ToggleCheats);
924 	ADD(TurboA); ADD(TurboB); ADD(TurboY); ADD(TurboX); ADD(TurboL); ADD(TurboR); ADD(TurboStart); ADD(TurboSelect); ADD(TurboUp); ADD(TurboDown); ADD(TurboLeft); ADD(TurboRight);
925 	ADD(QuitS9X);ADD(Rewind);
926 	ADD(SaveFileSelect); ADD(LoadFileSelect);
927 	ADD(Mute);
928 #undef ADD
929 #undef ADDN
930 #undef CATEGORY
931 #define	CATEGORY "Hack"
932     AddUIntC("CPUOverclockMode", Settings.OverclockMode, 0, "CPU Overclock: 0=none, 1=min, 2=medium, 3=max");
933     AddUIntC("MaxSpriteTilesPerLine", Settings.MaxSpriteTilesPerLine, 34, "Max sprite tiles rendered per line. Default = 34, Unlimited ~= 128");
934 	AddUIntC("SuperFXClockMultiplier", Settings.SuperFXClockMultiplier, 100, "SuperFX speed, in percent (default 100)");
935     AddBoolC("SeparateEchoBuffer", Settings.SeparateEchoBuffer, false, "Separate echo buffer from APU ram. For old hacks only.");
936 #undef CATEGORY
937 }
938 
939 
940 
941 
942 // These let us give the config file read-only status to other applications while snes9x is running,
943 // to prevent the situation where the user saves a modified .cfg file while snes9x is running
944 // only to have the changes overwritten by snes9x upon closing snes9x.
945 static HANDLE locked_file = NULL;
WinLockConfigFile()946 void WinLockConfigFile ()
947 {
948 	if(readOnlyConfig) return; // if user has lock on file, don't let Snes9x lock it
949 
950 	static std::string fname;
951 	fname=S9xGetDirectory(DEFAULT_DIR);
952 	fname+=SLASH_STR "snes9x.conf";
953 	STREAM fp;
954 	if((fp=OPEN_STREAM(fname.c_str(), "r"))!=NULL){
955 		CLOSE_STREAM(fp);
956 		locked_file=CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
957 	} else {
958 		fname=S9xGetDirectory(DEFAULT_DIR);
959 		fname+=SLASH_STR "snes9x.cfg";
960 		if((fp=OPEN_STREAM(fname.c_str(), "r"))!=NULL){
961 			CLOSE_STREAM(fp);
962 			locked_file=CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
963 		}
964 	}
965 }
WinUnlockConfigFile()966 void WinUnlockConfigFile ()
967 {
968 	if(locked_file){
969 		CloseHandle(locked_file);
970 		locked_file=NULL;
971 	}
972 }
973 
974 
975 /*****************************************************************************/
976 /* WinSave/WinLoad - Save/Load the settings	to/from	the	registry			 */
977 /*****************************************************************************/
978 
979 
980 
981 static ConfigFile loaded_config_file;
982 
WinLoadConfigFile(ConfigFile & conf)983 void WinLoadConfigFile(ConfigFile& conf)
984 {
985 	WinPreLoad(conf);
986 
987 	WinSetDefaultValues();
988 
989 	for(unsigned int i = 0 ; i < configItems.size()	; i++)
990 		configItems[i].Get(conf);
991 
992 	loaded_config_file = conf;
993 
994 	WinPostLoad(conf);
995 }
996 
WinSaveConfigFile()997 void WinSaveConfigFile()
998 {
999 	if(readOnlyConfig) return; // if user has lock on file, don't let Snes9x overwrite it
1000 
1001 	ConfigFile&	conf = loaded_config_file;
1002 	conf.ClearUnused();
1003 
1004 	WinPreSave(conf);
1005 
1006 	for(unsigned int i = 0 ; i < configItems.size()	; i++)
1007 		configItems[i].Set(conf);
1008 
1009 	bool wasLocked = locked_file!=NULL;
1010 	if(wasLocked) WinUnlockConfigFile();
1011 
1012 	S9xSaveConfigFile(conf);
1013 
1014 	if(wasLocked) WinLockConfigFile();
1015 
1016 	WinPostSave(conf);
1017 }
1018 
1019 
S9xParsePortConfig(ConfigFile & conf,int pass)1020 void S9xParsePortConfig(ConfigFile &conf, int pass)
1021 {
1022 	WinLoadConfigFile(conf);
1023 	return;
1024 }
1025 
WinCleanupConfigData()1026 void WinCleanupConfigData()
1027 {
1028 	configItems.clear();
1029 }
1030