/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ // all windows-specific command line and config file parsing/saving/loading // this stuff was moved out of wsnes.cpp, to keep things a little tidier // note: // if you want to force all users of a new version to have a // particular setting reset to its given default, // change the name string of that setting (in WinRegisterConfigItems) // to something a little different... // but if it's not in a windows-specific category, make sure you change its name elsewhere too #include "../port.h" #include "../snes9x.h" #include "wsnes9x.h" #include "wlanguage.h" #include "../display.h" #include "../conffile.h" #include "../spc7110.h" #include "../gfx.h" #include "../snapshot.h" #ifdef NETPLAY_SUPPORT #include "../netplay.h" extern SNPServer NPServer; #endif #include static void WinDeleteRegistryEntries (); void WinSetDefaultValues (); void WinDeleteRecentGamesList (); HANDLE configMutex = NULL; extern TCHAR multiRomA[MAX_PATH]; // lazy, should put in sGUI and add init to {0} somewhere extern TCHAR multiRomB[MAX_PATH]; void S9xParseArg (char **argv, int &i, int argc) { if (strcasecmp (argv [i], "-restore") == 0) { WinDeleteRegistryEntries (); WinSetDefaultValues (); } else if (strcasecmp (argv[i], "-hidemenu") == 0) { GUI.HideMenu = true; } else if (strcasecmp (argv[i], "-fullscreen") == 0) { GUI.FullScreen = true; } else if (!strcasecmp(argv[i], "-cartb")) { Settings.Multi = TRUE; // only used to signal winmain if (i + 1 < argc) { lstrcpyn(multiRomB, _tFromChar(argv[++i]), MAX_PATH); } } } void WinSetDefaultValues () { // TODO: delete the parts that are already covered by the default values in WinRegisterConfigItems char temp[4]; strcpy(temp,"C:\\"); GUI.ControllerOption = SNES_JOYPAD; GUI.ValidControllerOptions = 0xFFFF; GUI.IgnoreNextMouseMove = false; GUI.DoubleBuffered = false; GUI.FullScreen = false; GUI.Stretch = false; GUI.FlipCounter = 0; GUI.NumFlipFrames = 1; Settings.BilinearFilter = false; GUI.LockDirectories = false; GUI.window_maximized = false; GUI.EmulatedFullscreen = false; WinDeleteRecentGamesList (); GUI.SoundChannelEnable=255; // Tracing options Settings.TraceDMA = false; Settings.TraceHDMA = false; Settings.TraceVRAM = false; Settings.TraceUnknownRegisters = false; Settings.TraceDSP = false; // ROM timing options (see also H_Max above) Settings.PAL = false; Settings.FrameTimePAL = 20000; Settings.FrameTimeNTSC = 16667; Settings.FrameTime = 16667; // CPU options Settings.Paused = false; #ifdef NETPLAY_SUPPORT Settings.Port = 1996; NetPlay.MaxFrameSkip = 10; NetPlay.MaxBehindFrameCount = 10; NPServer.SyncByReset = true; NPServer.SendROMImageOnConnect = false; #endif Settings.TakeScreenshot=false; GUI.Language=0; GUI.AllowSoundSync = true; GUI.CurrentSaveBank = 0; } static bool try_save(const char *fname, ConfigFile &conf){ STREAM fp; if((fp=OPEN_STREAM(fname, "w"))!=NULL){ fprintf(stdout, "Saving config file %s\n", fname); CLOSE_STREAM(fp); conf.SaveTo(fname); return true; } return false; } static bool S9xSaveConfigFile(ConfigFile &conf){ configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex")); int times = 0; DWORD waitVal = WAIT_TIMEOUT; while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds waitVal = WaitForSingleObject(configMutex, 100); // save over the .conf file if it already exists, otherwise save over the .cfg file std::string fname; fname=S9xGetDirectory(DEFAULT_DIR); fname+=SLASH_STR S9X_CONF_FILE_NAME; // ensure previous config file is not lost if we crash while writing the new one std::string ftemp; { CopyFileA(fname.c_str(), (fname + ".autobak").c_str(), FALSE); ftemp=S9xGetDirectory(DEFAULT_DIR); ftemp+=SLASH_STR "config_error"; FILE* tempfile = fopen(ftemp.c_str(), "wb"); if(tempfile) fclose(tempfile); } bool ret = try_save(fname.c_str(), conf); remove(ftemp.c_str()); remove((fname + ".autobak").c_str()); ReleaseMutex(configMutex); CloseHandle(configMutex); return ret; } static void WinDeleteRegistryEntries () { // WinDeleteRegKey (HKEY_CURRENT_USER, S9X_REG_KEY_BASE); } static inline char *SkipSpaces (char *p) { while (*p && isspace (*p)) p++; return (p); } const TCHAR* WinParseCommandLineAndLoadConfigFile (TCHAR *line) { // Break the command line up into an array of string pointers, each pointer // points at a separate word or character sequence enclosed in quotes. int count = 0; static TCHAR return_filename[MAX_PATH]; #ifdef UNICODE // split params into argv TCHAR **params = CommandLineToArgvW(line, &count); // convert all parameters to utf8 char **parameters = new char*[count]; for(int i = 0; i < count; i++) { int requiredChars = WideCharToMultiByte(CP_UTF8, 0, params[i], -1, NULL, 0, NULL, NULL); parameters[i] = new char[requiredChars]; WideCharToMultiByte(CP_UTF8, 0, params[i], -1, parameters[i], requiredChars, NULL, NULL); } LocalFree(params); #else #define MAX_PARAMETERS 100 char *p = line; char *parameters[MAX_PARAMETERS]; while (count < MAX_PARAMETERS && *p) { p = SkipSpaces (p); if (*p == '"') { p++; parameters [count++] = p; while (*p && *p != '"') p++; *p++ = 0; } else if (*p == '\'') { p++; parameters [count++] = p; while (*p && *p != '\'') p++; *p++ = 0; } else { parameters [count++] = p; while (*p && !isspace (*p)) p++; if (!*p) break; *p++ = 0; } } #endif configMutex = CreateMutex(NULL, FALSE, TEXT("Snes9xConfigMutex")); int times = 0; DWORD waitVal = WAIT_TIMEOUT; while(waitVal == WAIT_TIMEOUT && ++times <= 150) // wait at most 15 seconds waitVal = WaitForSingleObject(configMutex, 100); // ensure previous config file is not lost if we crashed while writing a new one { std::string ftemp; ftemp=S9xGetDirectory(DEFAULT_DIR); ftemp+=SLASH_STR "config_error"; FILE* tempfile = fopen(ftemp.c_str(), "rb"); if(tempfile) { fclose(tempfile); std::string fname; for(int i=0; i<2; i++) { fname=S9xGetDirectory(DEFAULT_DIR); if(i == 0) fname+=SLASH_STR "snes9x.conf"; else if(i == 1) fname+=SLASH_STR "snes9x.cfg"; tempfile = fopen((fname + ".autobak").c_str(), "rb"); if(tempfile) { fclose(tempfile); MoveFileExA((fname + ".autobak").c_str(), fname.c_str(), MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH); } } remove(ftemp.c_str()); } } S9xLoadConfigFiles(parameters, count); ReleaseMutex(configMutex); CloseHandle(configMutex); // check some additional common help switches for (int i = 0; i < count; i++) { if(!strcasecmp(parameters[i], "/?") || !strcasecmp(parameters[i], "-h")) S9xUsage(); } const char* rf = S9xParseArgs (parameters, count); if(rf) // save rom_filename as TCHAR if available lstrcpy(return_filename, _tFromChar(rf)); #ifdef UNICODE // free parameters for(int i = 0; i < count; i++) { delete [] parameters[i]; } delete [] parameters; #endif return rf ? return_filename : NULL; } #define S(x) GAMEDEVICE_VK_##x #define SO(x) GAMEDEVICE_VK_OEM_##x static const char* keyToString [256] = { "Unassigned","LMB","RMB","Break","MMB","XMB1","XMB2","0x07", S(BACK),S(TAB),"0x0A","0x0B",S(CLEAR),S(RETURN),"0x0E","0x0F", S(SHIFT),S(CONTROL),S(MENU),S(PAUSE),S(CAPITAL),"Kana","0x16","Junja", "Final","Kanji","0x1A",S(ESCAPE),"Convert","NonConvert","Accept","ModeChange", S(SPACE),S(PRIOR),S(NEXT),S(END),S(HOME),S(LEFT),S(UP),S(RIGHT), S(DOWN),S(SELECT),S(PRINT),S(EXECUTE),S(SNAPSHOT),S(INSERT),S(DELETE),S(HELP), "0","1","2","3","4","5","6","7", "8","9","0x3A","0x3B","0x3C","0x3D","0x3E","0x3F", "0x40","A","B","C","D","E","F","G", "H","I","J","K","L","M","N","O", "P","Q","R","S","T","U","V","W", "X","Y","Z",S(LWIN),S(RWIN),S(APPS),"0x5E","Sleep", "Pad0","Pad1","Pad2","Pad3","Pad4","Pad5","Pad6","Pad7", "Pad8","Pad9",S(MULTIPLY),S(ADD),S(SEPARATOR),S(SUBTRACT),S(DECIMAL),S(DIVIDE), S(F1),S(F2),S(F3),S(F4),S(F5),S(F6),S(F7),S(F8), S(F9),S(F10),S(F11),S(F12),"F13","F14","F15","F16", "F17","F18","F19","F20","F21","F22","F23","F24", "0x88","0x89","0x8A","0x8B","0x8C","0x8D","0x8E","0x8F", S(NUMLOCK),S(SCROLL),"PadEqual","Masshou","Touroku","Loya","Roya","0x97", "0x98","0x99","0x9A","0x9B","0x9C","0x9D","0x9E","0x9F", S(LSHIFT),S(RSHIFT),S(LCONTROL),S(RCONTROL),S(LMENU),S(RMENU),"BrowserBack","BrowserForward", "BrowserRefresh","BrowserStop","BrowserSearch","BrowserFavorites","BrowserHome","VolumeMute","VolumeDown","VolumeUp", "MediaNextTrack","MediaPrevTrack","MediaStop","MediaPlayPause","LaunchMail","MediaSelect","LaunchApp1","LaunchApp2", "0xB8","0xB9",";","+",",",SO(MINUS),".",SO(2), SO(3),"0xC1","0xC2","0xC3","0xC4","0xC5","0xC6","0xC7", "0xC8","0xC9","0xCA","0xCB","0xCC","0xCD","0xCE","0xCF", "0xD0","0xD1","0xD2","0xD3","0xD4","0xD5","0xD6","0xD7", "0xD8","0xD9","0xDA",SO(4),"Backslash",SO(6),"'","OEM8", "0xE0","AX","<>","ICOHelp","ICO00","Process","ICOClear","Packet", "0xE8","Reset","Jump","PA1_2","PA2","PA3","WSCTRL","CUSEL", "Attention2","Finish","Copy","Auto","ENLW","Backtab","Attention","CRSEL", "EXSEL","EREOF","Play","Zoom","NoName","PA1","Clear2","0xFF" }; static const char* keyToAlternateString [256] = { "none","LeftClick","RightClick","Cancel","MiddleClick","","","","Back","","","","","Return","","", "","","Menu","","","","","","","","","Escape","","","","", "","PageUp","PageDown","","","","","","","","PrintScreen","","","Ins","Del","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "NumLock","ScrollLock","","","","","","","","","","","","","","", "","","","","","","","","","","","","","","","", "","","","","","","","","","",SO(1),SO(PLUS),SO(COMMA),"Minus",SO(PERIOD),"?", "","","","","","","","","","","","","","","","", "","","","oem_pa1","","","","","","","","LBracket","|","RBracket",SO(7),"", "ATTN2","","","","","","","","","","","","","","ATTN","", "","","","","","","","","","","","","","","","" }; #undef S #undef SO // We will maintain our own list of items to put in the config file, // so that each config item has only one point of change across both saving and loading // and to allow us to manage custom types of config items, such as virtual key codes represented as strings enum ConfigItemType { CIT_BOOL, CIT_INT, CIT_UINT, CIT_STRING, CIT_INVBOOL, CIT_BOOLONOFF, CIT_INVBOOLONOFF, CIT_VKEY, CIT_VKEYMOD }; struct ConfigItem { const char* name; void* addr; int size; void* def; const char* comment; ConfigItemType type; ConfigItem(const char* nname, void* naddr, int nsize, void* ndef, const char* ncomment, ConfigItemType ntype) { addr = naddr; name = nname; size = nsize; def = ndef; comment = ncomment; type = ntype; } void Get(ConfigFile& conf) { switch(type) { case CIT_BOOL: case CIT_BOOLONOFF: if(size == 1) *(uint8 *)addr = (uint8) conf.GetBool(name, def!=0); if(size == 2) *(uint16*)addr = (uint16)conf.GetBool(name, def!=0); if(size == 4) *(uint32*)addr = (uint32)conf.GetBool(name, def!=0); if(size == 8) *(uint64*)addr = (uint64)conf.GetBool(name, def!=0); break; case CIT_INT: if(size == 1) *(uint8 *)addr = (uint8) conf.GetInt(name, PtrToInt(def)); if(size == 2) *(uint16*)addr = (uint16)conf.GetInt(name, PtrToInt(def)); if(size == 4) *(uint32*)addr = (uint32)conf.GetInt(name, PtrToInt(def)); if(size == 8) *(uint64*)addr = (uint64)conf.GetInt(name, PtrToInt(def)); break; case CIT_UINT: if(size == 1) *(uint8 *)addr = (uint8) conf.GetUInt(name, PtrToUint(def)); if(size == 2) *(uint16*)addr = (uint16)conf.GetUInt(name, PtrToUint(def)); if(size == 4) *(uint32*)addr = (uint32)conf.GetUInt(name, PtrToUint(def)); if(size == 8) *(uint64*)addr = (uint64)conf.GetUInt(name, PtrToUint(def)); break; case CIT_STRING: lstrcpyn((TCHAR*)addr, _tFromChar(conf.GetString(name, reinterpret_cast(def))), size-1); ((TCHAR*)addr)[size-1] = TEXT('\0'); break; case CIT_INVBOOL: case CIT_INVBOOLONOFF: if(size == 1) *(uint8 *)addr = (uint8) !conf.GetBool(name, def!=0); if(size == 2) *(uint16*)addr = (uint16)!conf.GetBool(name, def!=0); if(size == 4) *(uint32*)addr = (uint32)!conf.GetBool(name, def!=0); if(size == 8) *(uint64*)addr = (uint64)!conf.GetBool(name, def!=0); break; case CIT_VKEY: { uint16 keyNum = (uint16)conf.GetUInt(name, PtrToUint(def)); const char* keyStr = conf.GetString(name); if(keyStr) { for(int i=0;i<512;i++) { if(i<256) // keys { if(!strcasecmp(keyStr,keyToString[i]) || (*keyToAlternateString[i] && !strcasecmp(keyStr,keyToAlternateString[i]))) { keyNum = i; break; } } else // joystick: { char temp [128]; extern void TranslateKey(WORD keyz,char *out); TranslateKey(0x8000|(i-256),temp); if(strlen(keyStr)>3 && !strcasecmp(keyStr+3,temp+3)) { for(int j = 0 ; j < 16 ; j++) { if(keyStr[2]-'0' == j || keyStr[2]-'a' == j-10) { keyNum = 0x8000|(i-256)|(j<<8); i = 512; break; } } } } } } if(size == 1) *(uint8 *)addr = (uint8) keyNum; if(size == 2) *(uint16*)addr = (uint16)keyNum; if(size == 4) *(uint32*)addr = (uint32)keyNum; if(size == 8) *(uint64*)addr = (uint64)keyNum; } break; case CIT_VKEYMOD: { uint16 modNum = 0; const char* modStr = conf.GetString(name); if(modStr) { if(strstr(modStr, "ft") || strstr(modStr, "FT")) modNum |= CUSTKEY_SHIFT_MASK; if(strstr(modStr, "tr") || strstr(modStr, "TR")) modNum |= CUSTKEY_CTRL_MASK; if(strstr(modStr, "lt") || strstr(modStr, "LT")) modNum |= CUSTKEY_ALT_MASK; } if(!modNum && (!modStr || strcasecmp(modStr, "none"))) modNum = conf.GetUInt(name, PtrToUint(def)); if(size == 1) *(uint8 *)addr = (uint8) modNum; if(size == 2) *(uint16*)addr = (uint16)modNum; if(size == 4) *(uint32*)addr = (uint32)modNum; if(size == 8) *(uint64*)addr = (uint64)modNum; } break; } // if it had a comment, override our own with it const char* newComment = conf.GetComment(name); if(newComment && *newComment) comment = newComment; } void Set(ConfigFile& conf) { switch(type) { case CIT_BOOL: if(size == 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "TRUE","FALSE", comment); if(size == 2) conf.SetBool(name, 0!=(*(uint16*)addr), "TRUE","FALSE", comment); if(size == 4) conf.SetBool(name, 0!=(*(uint32*)addr), "TRUE","FALSE", comment); if(size == 8) conf.SetBool(name, 0!=(*(uint64*)addr), "TRUE","FALSE", comment); break; case CIT_BOOLONOFF: if(size == 1) conf.SetBool(name, 0!=(*(uint8 *)addr), "ON","OFF", comment); if(size == 2) conf.SetBool(name, 0!=(*(uint16*)addr), "ON","OFF", comment); if(size == 4) conf.SetBool(name, 0!=(*(uint32*)addr), "ON","OFF", comment); if(size == 8) conf.SetBool(name, 0!=(*(uint64*)addr), "ON","OFF", comment); break; case CIT_INT: if(size == 1) conf.SetInt(name, (int32)(*(uint8 *)addr), comment); if(size == 2) conf.SetInt(name, (int32)(*(uint16*)addr), comment); if(size == 4) conf.SetInt(name, (int32)(*(uint32*)addr), comment); if(size == 8) conf.SetInt(name, (int32)(*(uint64*)addr), comment); break; case CIT_UINT: if(size == 1) conf.SetUInt(name, (uint32)(*(uint8 *)addr), 10, comment); if(size == 2) conf.SetUInt(name, (uint32)(*(uint16*)addr), 10, comment); if(size == 4) conf.SetUInt(name, (uint32)(*(uint32*)addr), 10, comment); if(size == 8) conf.SetUInt(name, (uint32)(*(uint64*)addr), 10, comment); break; case CIT_STRING: if((TCHAR*)addr) conf.SetString(name, std::string(_tToChar((TCHAR*)addr)), comment); break; case CIT_INVBOOL: if(size == 1) conf.SetBool(name, 0==(*(uint8 *)addr), "TRUE","FALSE", comment); if(size == 2) conf.SetBool(name, 0==(*(uint16*)addr), "TRUE","FALSE", comment); if(size == 4) conf.SetBool(name, 0==(*(uint32*)addr), "TRUE","FALSE", comment); if(size == 8) conf.SetBool(name, 0==(*(uint64*)addr), "TRUE","FALSE", comment); break; case CIT_INVBOOLONOFF: if(size == 1) conf.SetBool(name, 0==(*(uint8 *)addr), "ON","OFF", comment); if(size == 2) conf.SetBool(name, 0==(*(uint16*)addr), "ON","OFF", comment); if(size == 4) conf.SetBool(name, 0==(*(uint32*)addr), "ON","OFF", comment); if(size == 8) conf.SetBool(name, 0==(*(uint64*)addr), "ON","OFF", comment); break; case CIT_VKEY: { uint16 keyNum = 0; if(size == 1) keyNum = (uint8)(*(uint8 *)addr); if(size == 2) keyNum = (uint16)(*(uint16*)addr); if(size == 4) keyNum = (uint16)(*(uint32*)addr); if(size == 8) keyNum = (uint16)(*(uint64*)addr); if(keyNum < 256) conf.SetString(name, keyToString[keyNum], comment); else if(keyNum & 0x8000) { char temp [128]; extern void TranslateKey(WORD keyz,char *out); TranslateKey(keyNum,temp); conf.SetString(name, temp, comment); } else conf.SetUInt(name, keyNum, 16, comment); } break; case CIT_VKEYMOD: { uint16 modNum = 0; if(size == 1) modNum = (uint8)(*(uint8 *)addr); if(size == 2) modNum = (uint16)(*(uint16*)addr); if(size == 4) modNum = (uint16)(*(uint32*)addr); if(size == 8) modNum = (uint16)(*(uint64*)addr); std::string modStr; if(modNum & CUSTKEY_CTRL_MASK) modStr += "Ctrl "; if(modNum & CUSTKEY_ALT_MASK) modStr += "Alt "; if(modNum & CUSTKEY_SHIFT_MASK) modStr += "Shift "; if(!(modNum & (CUSTKEY_CTRL_MASK|CUSTKEY_ALT_MASK|CUSTKEY_SHIFT_MASK))) modStr = "none"; else modStr.erase(modStr.length()-1); conf.SetString(name, modStr, comment); } break; } } }; std::vector configItems; // var must be a persistent variable. In the case of strings, it must point to a writeable character array. #define AddItemC(name, var, def, comment, type) configItems.push_back(ConfigItem((const char*)(CATEGORY "::" name), (void*)(&var), sizeof(var), (void*)(pint)def, (const char*)comment, (ConfigItemType)type)) #define AddItem(name, var, def, type) AddItemC(name,var,def,"",type) #define AddUInt(name, var, def) AddItem(name,var,def,CIT_UINT) #define AddInt(name, var, def) AddItem(name,var,def,CIT_INT) #define AddBool(name, var, def) AddItem(name,var,def,CIT_BOOL) #define AddBool2(name, var, def) AddItem(name,var,def,CIT_BOOLONOFF) #define AddInvBool(name, var, def) AddItem(name,var,def,CIT_INVBOOL) #define AddInvBool2(name, var, def) AddItem(name,var,def,CIT_INVBOOLONOFF) #define AddVKey(name, var, def) AddItem(name,var,def,CIT_VKEY) #define AddVKMod(name, var, def) AddItem(name,var,def,CIT_VKEYMOD) #define AddUIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_UINT) #define AddIntC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INT) #define AddBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOL) #define AddBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_BOOLONOFF) #define AddInvBoolC(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOL) #define AddInvBool2C(name, var, def, comment) AddItemC(name,var,def,comment,CIT_INVBOOLONOFF) #define AddStringC(name, var, buflen, def, comment) configItems.push_back(ConfigItem((const char*)(CATEGORY "::" name), (void*)var, buflen, (void*)(pint)def, (const char*)comment, CIT_STRING)) #define AddString(name, var, buflen, def) AddStringC(name, var, buflen, def, "") static char filterString [1024], filterString2 [1024], snapVerString [256]; static bool niceAlignment, showComments, readOnlyConfig; static int configSort; void WinPreSave(ConfigFile& conf) { strcpy(filterString, "output filter: "); for(int i=0;i MAX_RECENT_GAMES_LIST_SIZE) GUI.MaxRecentGames = MAX_RECENT_GAMES_LIST_SIZE; if(GUI.rewindGranularity==0) GUI.rewindGranularity = 1; bool gap = false; for(i=0;i