dolphin/Source/Core/Core/ConfigManager.cpp
Léo Lam 6916a3d85b Hide non-channel WADs
These cannot be booted, so it is bad UX to show them in the UI as if
they were regular titles, and yet have different behaviour for them.

And technically, there is no reason to allow them to be used to boot
in the first place.

Another reason they should not be shown is that Dolphin fails
spectacularly with WADs that have a valid boot content index, but are
not PPC titles (e.g. IOS WADs). The only reliable way to avoid this
is to check for the title type and only show channels, just like
the Wii System Menu.
2017-05-14 15:30:42 +02:00

1188 lines
40 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <cinttypes>
#include <climits>
#include <memory>
#include "AudioCommon/AudioCommon.h"
#include "Common/CDUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Common/SysConf.h"
#include "Core/Boot/Boot.h"
#include "Core/Boot/Boot_DOL.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/DVD/DVDThread.h"
#include "Core/HW/SI/SI.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/USB/Bluetooth/BTBase.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "VideoCommon/HiresTextures.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
SConfig* SConfig::m_Instance;
SConfig::SConfig()
{
LoadDefaults();
// Make sure we have log manager
LoadSettings();
LoadSettingsFromSysconf();
}
void SConfig::Init()
{
m_Instance = new SConfig;
}
void SConfig::Shutdown()
{
delete m_Instance;
m_Instance = nullptr;
}
SConfig::~SConfig()
{
SaveSettings();
SaveSettingsToSysconf();
}
void SConfig::SaveSettings()
{
NOTICE_LOG(BOOT, "Saving settings to %s", File::GetUserPath(F_DOLPHINCONFIG_IDX).c_str());
IniFile ini;
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); // load first to not kill unknown stuff
SaveGeneralSettings(ini);
SaveInterfaceSettings(ini);
SaveDisplaySettings(ini);
SaveGameListSettings(ini);
SaveCoreSettings(ini);
SaveMovieSettings(ini);
SaveDSPSettings(ini);
SaveInputSettings(ini);
SaveFifoPlayerSettings(ini);
SaveAnalyticsSettings(ini);
SaveNetworkSettings(ini);
SaveBluetoothPassthroughSettings(ini);
SaveUSBPassthroughSettings(ini);
SaveSysconfSettings(ini);
ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX));
}
namespace
{
void CreateDumpPath(const std::string& path)
{
if (path.empty())
return;
File::SetUserPath(D_DUMP_IDX, path + '/');
File::CreateFullPath(File::GetUserPath(D_DUMPAUDIO_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPSSL_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPFRAMES_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX));
}
} // namespace
void SConfig::SaveGeneralSettings(IniFile& ini)
{
IniFile::Section* general = ini.GetOrCreateSection("General");
// General
general->Set("LastFilename", m_LastFilename);
general->Set("ShowLag", m_ShowLag);
general->Set("ShowFrameCount", m_ShowFrameCount);
// ISO folders
// Clear removed folders
int oldPaths;
int numPaths = (int)m_ISOFolder.size();
general->Get("ISOPaths", &oldPaths, 0);
for (int i = numPaths; i < oldPaths; i++)
{
ini.DeleteKey("General", StringFromFormat("ISOPath%i", i));
}
general->Set("ISOPaths", numPaths);
for (int i = 0; i < numPaths; i++)
{
general->Set(StringFromFormat("ISOPath%i", i), m_ISOFolder[i]);
}
general->Set("RecursiveISOPaths", m_RecursiveISOFolder);
general->Set("NANDRootPath", m_NANDPath);
general->Set("DumpPath", m_DumpPath);
CreateDumpPath(m_DumpPath);
general->Set("WirelessMac", m_WirelessMac);
general->Set("WiiSDCardPath", m_strWiiSDCardPath);
#ifdef USE_GDBSTUB
#ifndef _WIN32
general->Set("GDBSocket", gdb_socket);
#endif
general->Set("GDBPort", iGDBPort);
#endif
}
void SConfig::SaveInterfaceSettings(IniFile& ini)
{
IniFile::Section* interface = ini.GetOrCreateSection("Interface");
interface->Set("ConfirmStop", bConfirmStop);
interface->Set("UsePanicHandlers", bUsePanicHandlers);
interface->Set("OnScreenDisplayMessages", bOnScreenDisplayMessages);
interface->Set("HideCursor", bHideCursor);
interface->Set("AutoHideCursor", bAutoHideCursor);
interface->Set("MainWindowPosX", iPosX);
interface->Set("MainWindowPosY", iPosY);
interface->Set("MainWindowWidth", iWidth);
interface->Set("MainWindowHeight", iHeight);
interface->Set("LanguageCode", m_InterfaceLanguage);
interface->Set("ShowToolbar", m_InterfaceToolbar);
interface->Set("ShowStatusbar", m_InterfaceStatusbar);
interface->Set("ShowLogWindow", m_InterfaceLogWindow);
interface->Set("ShowLogConfigWindow", m_InterfaceLogConfigWindow);
interface->Set("ExtendedFPSInfo", m_InterfaceExtendedFPSInfo);
interface->Set("ThemeName", theme_name);
interface->Set("PauseOnFocusLost", m_PauseOnFocusLost);
interface->Set("DisableTooltips", m_DisableTooltips);
}
void SConfig::SaveDisplaySettings(IniFile& ini)
{
IniFile::Section* display = ini.GetOrCreateSection("Display");
display->Set("FullscreenResolution", strFullscreenResolution);
display->Set("Fullscreen", bFullscreen);
display->Set("RenderToMain", bRenderToMain);
display->Set("RenderWindowXPos", iRenderWindowXPos);
display->Set("RenderWindowYPos", iRenderWindowYPos);
display->Set("RenderWindowWidth", iRenderWindowWidth);
display->Set("RenderWindowHeight", iRenderWindowHeight);
display->Set("RenderWindowAutoSize", bRenderWindowAutoSize);
display->Set("KeepWindowOnTop", bKeepWindowOnTop);
display->Set("ProgressiveScan", bProgressive);
display->Set("PAL60", bPAL60);
display->Set("DisableScreenSaver", bDisableScreenSaver);
display->Set("ForceNTSCJ", bForceNTSCJ);
}
void SConfig::SaveGameListSettings(IniFile& ini)
{
IniFile::Section* gamelist = ini.GetOrCreateSection("GameList");
gamelist->Set("ListDrives", m_ListDrives);
gamelist->Set("ListWad", m_ListWad);
gamelist->Set("ListElfDol", m_ListElfDol);
gamelist->Set("ListWii", m_ListWii);
gamelist->Set("ListGC", m_ListGC);
gamelist->Set("ListJap", m_ListJap);
gamelist->Set("ListPal", m_ListPal);
gamelist->Set("ListUsa", m_ListUsa);
gamelist->Set("ListAustralia", m_ListAustralia);
gamelist->Set("ListFrance", m_ListFrance);
gamelist->Set("ListGermany", m_ListGermany);
gamelist->Set("ListItaly", m_ListItaly);
gamelist->Set("ListKorea", m_ListKorea);
gamelist->Set("ListNetherlands", m_ListNetherlands);
gamelist->Set("ListRussia", m_ListRussia);
gamelist->Set("ListSpain", m_ListSpain);
gamelist->Set("ListTaiwan", m_ListTaiwan);
gamelist->Set("ListWorld", m_ListWorld);
gamelist->Set("ListUnknown", m_ListUnknown);
gamelist->Set("ListSort", m_ListSort);
gamelist->Set("ListSortSecondary", m_ListSort2);
gamelist->Set("ColumnPlatform", m_showSystemColumn);
gamelist->Set("ColumnBanner", m_showBannerColumn);
gamelist->Set("ColumnDescription", m_showDescriptionColumn);
gamelist->Set("ColumnTitle", m_showTitleColumn);
gamelist->Set("ColumnNotes", m_showMakerColumn);
gamelist->Set("ColumnFileName", m_showFileNameColumn);
gamelist->Set("ColumnID", m_showIDColumn);
gamelist->Set("ColumnRegion", m_showRegionColumn);
gamelist->Set("ColumnSize", m_showSizeColumn);
gamelist->Set("ColumnState", m_showStateColumn);
}
void SConfig::SaveCoreSettings(IniFile& ini)
{
IniFile::Section* core = ini.GetOrCreateSection("Core");
core->Set("HLE_BS2", bHLE_BS2);
core->Set("TimingVariance", iTimingVariance);
core->Set("CPUCore", iCPUCore);
core->Set("Fastmem", bFastmem);
core->Set("CPUThread", bCPUThread);
core->Set("DSPHLE", bDSPHLE);
core->Set("SyncOnSkipIdle", bSyncGPUOnSkipIdleHack);
core->Set("SyncGPU", bSyncGPU);
core->Set("SyncGpuMaxDistance", iSyncGpuMaxDistance);
core->Set("SyncGpuMinDistance", iSyncGpuMinDistance);
core->Set("SyncGpuOverclock", fSyncGpuOverclock);
core->Set("FPRF", bFPRF);
core->Set("AccurateNaNs", bAccurateNaNs);
core->Set("DefaultISO", m_strDefaultISO);
core->Set("DVDRoot", m_strDVDRoot);
core->Set("Apploader", m_strApploader);
core->Set("EnableCheats", bEnableCheats);
core->Set("SelectedLanguage", SelectedLanguage);
core->Set("OverrideGCLang", bOverrideGCLanguage);
core->Set("DPL2Decoder", bDPL2Decoder);
core->Set("Latency", iLatency);
core->Set("AudioStretch", m_audio_stretch);
core->Set("AudioStretchMaxLatency", m_audio_stretch_max_latency);
core->Set("MemcardAPath", m_strMemoryCardA);
core->Set("MemcardBPath", m_strMemoryCardB);
core->Set("AgpCartAPath", m_strGbaCartA);
core->Set("AgpCartBPath", m_strGbaCartB);
core->Set("SlotA", m_EXIDevice[0]);
core->Set("SlotB", m_EXIDevice[1]);
core->Set("SerialPort1", m_EXIDevice[2]);
core->Set("BBA_MAC", m_bba_mac);
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
core->Set(StringFromFormat("SIDevice%i", i), m_SIDevice[i]);
core->Set(StringFromFormat("AdapterRumble%i", i), m_AdapterRumble[i]);
core->Set(StringFromFormat("SimulateKonga%i", i), m_AdapterKonga[i]);
}
core->Set("WiiSDCard", m_WiiSDCard);
core->Set("WiiKeyboard", m_WiiKeyboard);
core->Set("WiimoteContinuousScanning", m_WiimoteContinuousScanning);
core->Set("WiimoteEnableSpeaker", m_WiimoteEnableSpeaker);
core->Set("RunCompareServer", bRunCompareServer);
core->Set("RunCompareClient", bRunCompareClient);
core->Set("EmulationSpeed", m_EmulationSpeed);
core->Set("FrameSkip", m_FrameSkip);
core->Set("Overclock", m_OCFactor);
core->Set("OverclockEnable", m_OCEnable);
core->Set("GFXBackend", m_strVideoBackend);
core->Set("GPUDeterminismMode", m_strGPUDeterminismMode);
core->Set("PerfMapDir", m_perfDir);
core->Set("EnableCustomRTC", bEnableCustomRTC);
core->Set("CustomRTCValue", m_customRTCValue);
}
void SConfig::SaveMovieSettings(IniFile& ini)
{
IniFile::Section* movie = ini.GetOrCreateSection("Movie");
movie->Set("PauseMovie", m_PauseMovie);
movie->Set("Author", m_strMovieAuthor);
movie->Set("DumpFrames", m_DumpFrames);
movie->Set("DumpFramesSilent", m_DumpFramesSilent);
movie->Set("ShowInputDisplay", m_ShowInputDisplay);
movie->Set("ShowRTC", m_ShowRTC);
}
void SConfig::SaveDSPSettings(IniFile& ini)
{
IniFile::Section* dsp = ini.GetOrCreateSection("DSP");
dsp->Set("EnableJIT", m_DSPEnableJIT);
dsp->Set("DumpAudio", m_DumpAudio);
dsp->Set("DumpAudioSilent", m_DumpAudioSilent);
dsp->Set("DumpUCode", m_DumpUCode);
dsp->Set("Backend", sBackend);
dsp->Set("Volume", m_Volume);
dsp->Set("CaptureLog", m_DSPCaptureLog);
}
void SConfig::SaveInputSettings(IniFile& ini)
{
IniFile::Section* input = ini.GetOrCreateSection("Input");
input->Set("BackgroundInput", m_BackgroundInput);
}
void SConfig::SaveFifoPlayerSettings(IniFile& ini)
{
IniFile::Section* fifoplayer = ini.GetOrCreateSection("FifoPlayer");
fifoplayer->Set("LoopReplay", bLoopFifoReplay);
}
void SConfig::SaveNetworkSettings(IniFile& ini)
{
IniFile::Section* network = ini.GetOrCreateSection("Network");
network->Set("SSLDumpRead", m_SSLDumpRead);
network->Set("SSLDumpWrite", m_SSLDumpWrite);
network->Set("SSLVerifyCertificates", m_SSLVerifyCert);
network->Set("SSLDumpRootCA", m_SSLDumpRootCA);
network->Set("SSLDumpPeerCert", m_SSLDumpPeerCert);
}
void SConfig::SaveAnalyticsSettings(IniFile& ini)
{
IniFile::Section* analytics = ini.GetOrCreateSection("Analytics");
analytics->Set("ID", m_analytics_id);
analytics->Set("Enabled", m_analytics_enabled);
analytics->Set("PermissionAsked", m_analytics_permission_asked);
}
void SConfig::SaveBluetoothPassthroughSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("BluetoothPassthrough");
section->Set("Enabled", m_bt_passthrough_enabled);
section->Set("VID", m_bt_passthrough_vid);
section->Set("PID", m_bt_passthrough_pid);
section->Set("LinkKeys", m_bt_passthrough_link_keys);
}
void SConfig::SaveUSBPassthroughSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("USBPassthrough");
std::ostringstream oss;
for (const auto& device : m_usb_passthrough_devices)
oss << StringFromFormat("%04x:%04x", device.first, device.second) << ',';
std::string devices_string = oss.str();
if (!devices_string.empty())
devices_string.pop_back();
section->Set("Devices", devices_string);
}
void SConfig::SaveSysconfSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("Sysconf");
section->Set("SensorBarPosition", m_sensor_bar_position);
section->Set("SensorBarSensitivity", m_sensor_bar_sensitivity);
section->Set("SpeakerVolume", m_speaker_volume);
section->Set("WiimoteMotor", m_wiimote_motor);
section->Set("WiiLanguage", m_wii_language);
section->Set("AspectRatio", m_wii_aspect_ratio);
section->Set("Screensaver", m_wii_screensaver);
}
void SConfig::SaveSettingsToSysconf()
{
SysConf sysconf{Common::FromWhichRoot::FROM_CONFIGURED_ROOT};
sysconf.SetData<u8>("IPL.SSV", m_wii_screensaver);
sysconf.SetData<u8>("IPL.LNG", m_wii_language);
sysconf.SetData<u8>("IPL.AR", m_wii_aspect_ratio);
sysconf.SetData<u8>("BT.BAR", m_sensor_bar_position);
sysconf.SetData<u32>("BT.SENS", m_sensor_bar_sensitivity);
sysconf.SetData<u8>("BT.SPKV", m_speaker_volume);
sysconf.SetData("BT.MOT", m_wiimote_motor);
sysconf.SetData("IPL.PGS", bProgressive);
sysconf.SetData("IPL.E60", bPAL60);
// Disable WiiConnect24's standby mode. If it is enabled, it prevents us from receiving
// shutdown commands in the State Transition Manager (STM).
// TODO: remove this if and once Dolphin supports WC24 standby mode.
sysconf.SetData<u8>("IPL.IDL", 0x00);
NOTICE_LOG(COMMON, "Disabling WC24 'standby' (shutdown to idle) to avoid hanging on shutdown");
IOS::HLE::RestoreBTInfoSection(&sysconf);
sysconf.Save();
}
void SConfig::LoadSettings()
{
INFO_LOG(BOOT, "Loading Settings from %s", File::GetUserPath(F_DOLPHINCONFIG_IDX).c_str());
IniFile ini;
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
LoadGeneralSettings(ini);
LoadInterfaceSettings(ini);
LoadDisplaySettings(ini);
LoadGameListSettings(ini);
LoadCoreSettings(ini);
LoadMovieSettings(ini);
LoadDSPSettings(ini);
LoadInputSettings(ini);
LoadFifoPlayerSettings(ini);
LoadNetworkSettings(ini);
LoadAnalyticsSettings(ini);
LoadBluetoothPassthroughSettings(ini);
LoadUSBPassthroughSettings(ini);
LoadSysconfSettings(ini);
}
void SConfig::LoadGeneralSettings(IniFile& ini)
{
IniFile::Section* general = ini.GetOrCreateSection("General");
general->Get("LastFilename", &m_LastFilename);
general->Get("ShowLag", &m_ShowLag, false);
general->Get("ShowFrameCount", &m_ShowFrameCount, false);
#ifdef USE_GDBSTUB
#ifndef _WIN32
general->Get("GDBSocket", &gdb_socket, "");
#endif
general->Get("GDBPort", &(iGDBPort), -1);
#endif
m_ISOFolder.clear();
int numISOPaths;
if (general->Get("ISOPaths", &numISOPaths, 0))
{
for (int i = 0; i < numISOPaths; i++)
{
std::string tmpPath;
general->Get(StringFromFormat("ISOPath%i", i), &tmpPath, "");
m_ISOFolder.push_back(std::move(tmpPath));
}
}
general->Get("RecursiveISOPaths", &m_RecursiveISOFolder, false);
general->Get("NANDRootPath", &m_NANDPath);
File::SetUserPath(D_WIIROOT_IDX, m_NANDPath);
general->Get("DumpPath", &m_DumpPath);
CreateDumpPath(m_DumpPath);
general->Get("WirelessMac", &m_WirelessMac);
general->Get("WiiSDCardPath", &m_strWiiSDCardPath, File::GetUserPath(F_WIISDCARD_IDX));
File::SetUserPath(F_WIISDCARD_IDX, m_strWiiSDCardPath);
}
void SConfig::LoadInterfaceSettings(IniFile& ini)
{
IniFile::Section* interface = ini.GetOrCreateSection("Interface");
interface->Get("ConfirmStop", &bConfirmStop, true);
interface->Get("UsePanicHandlers", &bUsePanicHandlers, true);
interface->Get("OnScreenDisplayMessages", &bOnScreenDisplayMessages, true);
interface->Get("HideCursor", &bHideCursor, false);
interface->Get("AutoHideCursor", &bAutoHideCursor, false);
interface->Get("MainWindowPosX", &iPosX, INT_MIN);
interface->Get("MainWindowPosY", &iPosY, INT_MIN);
interface->Get("MainWindowWidth", &iWidth, -1);
interface->Get("MainWindowHeight", &iHeight, -1);
interface->Get("LanguageCode", &m_InterfaceLanguage, "");
interface->Get("ShowToolbar", &m_InterfaceToolbar, true);
interface->Get("ShowStatusbar", &m_InterfaceStatusbar, true);
interface->Get("ShowLogWindow", &m_InterfaceLogWindow, false);
interface->Get("ShowLogConfigWindow", &m_InterfaceLogConfigWindow, false);
interface->Get("ExtendedFPSInfo", &m_InterfaceExtendedFPSInfo, false);
interface->Get("ThemeName", &theme_name, DEFAULT_THEME_DIR);
interface->Get("PauseOnFocusLost", &m_PauseOnFocusLost, false);
interface->Get("DisableTooltips", &m_DisableTooltips, false);
}
void SConfig::LoadDisplaySettings(IniFile& ini)
{
IniFile::Section* display = ini.GetOrCreateSection("Display");
display->Get("Fullscreen", &bFullscreen, false);
display->Get("FullscreenResolution", &strFullscreenResolution, "Auto");
display->Get("RenderToMain", &bRenderToMain, false);
display->Get("RenderWindowXPos", &iRenderWindowXPos, -1);
display->Get("RenderWindowYPos", &iRenderWindowYPos, -1);
display->Get("RenderWindowWidth", &iRenderWindowWidth, 640);
display->Get("RenderWindowHeight", &iRenderWindowHeight, 480);
display->Get("RenderWindowAutoSize", &bRenderWindowAutoSize, false);
display->Get("KeepWindowOnTop", &bKeepWindowOnTop, false);
display->Get("ProgressiveScan", &bProgressive, false);
display->Get("PAL60", &bPAL60, true);
display->Get("DisableScreenSaver", &bDisableScreenSaver, true);
display->Get("ForceNTSCJ", &bForceNTSCJ, false);
}
void SConfig::LoadGameListSettings(IniFile& ini)
{
IniFile::Section* gamelist = ini.GetOrCreateSection("GameList");
gamelist->Get("ListDrives", &m_ListDrives, false);
gamelist->Get("ListWad", &m_ListWad, true);
gamelist->Get("ListElfDol", &m_ListElfDol, true);
gamelist->Get("ListWii", &m_ListWii, true);
gamelist->Get("ListGC", &m_ListGC, true);
gamelist->Get("ListJap", &m_ListJap, true);
gamelist->Get("ListPal", &m_ListPal, true);
gamelist->Get("ListUsa", &m_ListUsa, true);
gamelist->Get("ListAustralia", &m_ListAustralia, true);
gamelist->Get("ListFrance", &m_ListFrance, true);
gamelist->Get("ListGermany", &m_ListGermany, true);
gamelist->Get("ListItaly", &m_ListItaly, true);
gamelist->Get("ListKorea", &m_ListKorea, true);
gamelist->Get("ListNetherlands", &m_ListNetherlands, true);
gamelist->Get("ListRussia", &m_ListRussia, true);
gamelist->Get("ListSpain", &m_ListSpain, true);
gamelist->Get("ListTaiwan", &m_ListTaiwan, true);
gamelist->Get("ListWorld", &m_ListWorld, true);
gamelist->Get("ListUnknown", &m_ListUnknown, true);
gamelist->Get("ListSort", &m_ListSort, 3);
gamelist->Get("ListSortSecondary", &m_ListSort2, 0);
// Gamelist columns toggles
gamelist->Get("ColumnPlatform", &m_showSystemColumn, true);
gamelist->Get("ColumnDescription", &m_showDescriptionColumn, false);
gamelist->Get("ColumnBanner", &m_showBannerColumn, true);
gamelist->Get("ColumnTitle", &m_showTitleColumn, true);
gamelist->Get("ColumnNotes", &m_showMakerColumn, true);
gamelist->Get("ColumnFileName", &m_showFileNameColumn, false);
gamelist->Get("ColumnID", &m_showIDColumn, false);
gamelist->Get("ColumnRegion", &m_showRegionColumn, true);
gamelist->Get("ColumnSize", &m_showSizeColumn, true);
gamelist->Get("ColumnState", &m_showStateColumn, true);
}
void SConfig::LoadCoreSettings(IniFile& ini)
{
IniFile::Section* core = ini.GetOrCreateSection("Core");
core->Get("HLE_BS2", &bHLE_BS2, false);
#ifdef _M_X86
core->Get("CPUCore", &iCPUCore, PowerPC::CORE_JIT64);
#elif _M_ARM_64
core->Get("CPUCore", &iCPUCore, PowerPC::CORE_JITARM64);
#else
core->Get("CPUCore", &iCPUCore, PowerPC::CORE_INTERPRETER);
#endif
core->Get("Fastmem", &bFastmem, true);
core->Get("DSPHLE", &bDSPHLE, true);
core->Get("TimingVariance", &iTimingVariance, 40);
core->Get("CPUThread", &bCPUThread, true);
core->Get("SyncOnSkipIdle", &bSyncGPUOnSkipIdleHack, true);
core->Get("DefaultISO", &m_strDefaultISO);
core->Get("DVDRoot", &m_strDVDRoot);
core->Get("Apploader", &m_strApploader);
core->Get("EnableCheats", &bEnableCheats, false);
core->Get("SelectedLanguage", &SelectedLanguage, 0);
core->Get("OverrideGCLang", &bOverrideGCLanguage, false);
core->Get("DPL2Decoder", &bDPL2Decoder, false);
core->Get("Latency", &iLatency, 5);
core->Get("AudioStretch", &m_audio_stretch, false);
core->Get("AudioStretchMaxLatency", &m_audio_stretch_max_latency, 80);
core->Get("MemcardAPath", &m_strMemoryCardA);
core->Get("MemcardBPath", &m_strMemoryCardB);
core->Get("AgpCartAPath", &m_strGbaCartA);
core->Get("AgpCartBPath", &m_strGbaCartB);
core->Get("SlotA", (int*)&m_EXIDevice[0], ExpansionInterface::EXIDEVICE_MEMORYCARD);
core->Get("SlotB", (int*)&m_EXIDevice[1], ExpansionInterface::EXIDEVICE_NONE);
core->Get("SerialPort1", (int*)&m_EXIDevice[2], ExpansionInterface::EXIDEVICE_NONE);
core->Get("BBA_MAC", &m_bba_mac);
core->Get("TimeProfiling", &bJITILTimeProfiling, false);
core->Get("OutputIR", &bJITILOutputIR, false);
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
core->Get(StringFromFormat("SIDevice%i", i), (u32*)&m_SIDevice[i],
(i == 0) ? SerialInterface::SIDEVICE_GC_CONTROLLER : SerialInterface::SIDEVICE_NONE);
core->Get(StringFromFormat("AdapterRumble%i", i), &m_AdapterRumble[i], true);
core->Get(StringFromFormat("SimulateKonga%i", i), &m_AdapterKonga[i], false);
}
core->Get("WiiSDCard", &m_WiiSDCard, false);
core->Get("WiiKeyboard", &m_WiiKeyboard, false);
core->Get("WiimoteContinuousScanning", &m_WiimoteContinuousScanning, false);
core->Get("WiimoteEnableSpeaker", &m_WiimoteEnableSpeaker, false);
core->Get("RunCompareServer", &bRunCompareServer, false);
core->Get("RunCompareClient", &bRunCompareClient, false);
core->Get("MMU", &bMMU, false);
core->Get("BBDumpPort", &iBBDumpPort, -1);
core->Get("SyncGPU", &bSyncGPU, false);
core->Get("SyncGpuMaxDistance", &iSyncGpuMaxDistance, 200000);
core->Get("SyncGpuMinDistance", &iSyncGpuMinDistance, -200000);
core->Get("SyncGpuOverclock", &fSyncGpuOverclock, 1.0f);
core->Get("FastDiscSpeed", &bFastDiscSpeed, false);
core->Get("DCBZ", &bDCBZOFF, false);
core->Get("LowDCBZHack", &bLowDCBZHack, false);
core->Get("FPRF", &bFPRF, false);
core->Get("AccurateNaNs", &bAccurateNaNs, false);
core->Get("EmulationSpeed", &m_EmulationSpeed, 1.0f);
core->Get("Overclock", &m_OCFactor, 1.0f);
core->Get("OverclockEnable", &m_OCEnable, false);
core->Get("FrameSkip", &m_FrameSkip, 0);
core->Get("GFXBackend", &m_strVideoBackend, "");
core->Get("GPUDeterminismMode", &m_strGPUDeterminismMode, "auto");
core->Get("PerfMapDir", &m_perfDir, "");
core->Get("EnableCustomRTC", &bEnableCustomRTC, false);
// Default to seconds between 1.1.1970 and 1.1.2000
core->Get("CustomRTCValue", &m_customRTCValue, 946684800);
}
void SConfig::LoadMovieSettings(IniFile& ini)
{
IniFile::Section* movie = ini.GetOrCreateSection("Movie");
movie->Get("PauseMovie", &m_PauseMovie, false);
movie->Get("Author", &m_strMovieAuthor, "");
movie->Get("DumpFrames", &m_DumpFrames, false);
movie->Get("DumpFramesSilent", &m_DumpFramesSilent, false);
movie->Get("ShowInputDisplay", &m_ShowInputDisplay, false);
movie->Get("ShowRTC", &m_ShowRTC, false);
}
void SConfig::LoadDSPSettings(IniFile& ini)
{
IniFile::Section* dsp = ini.GetOrCreateSection("DSP");
dsp->Get("EnableJIT", &m_DSPEnableJIT, true);
dsp->Get("DumpAudio", &m_DumpAudio, false);
dsp->Get("DumpAudioSilent", &m_DumpAudioSilent, false);
dsp->Get("DumpUCode", &m_DumpUCode, false);
dsp->Get("Backend", &sBackend, AudioCommon::GetDefaultSoundBackend());
dsp->Get("Volume", &m_Volume, 100);
dsp->Get("CaptureLog", &m_DSPCaptureLog, false);
m_IsMuted = false;
}
void SConfig::LoadInputSettings(IniFile& ini)
{
IniFile::Section* input = ini.GetOrCreateSection("Input");
input->Get("BackgroundInput", &m_BackgroundInput, false);
}
void SConfig::LoadFifoPlayerSettings(IniFile& ini)
{
IniFile::Section* fifoplayer = ini.GetOrCreateSection("FifoPlayer");
fifoplayer->Get("LoopReplay", &bLoopFifoReplay, true);
}
void SConfig::LoadNetworkSettings(IniFile& ini)
{
IniFile::Section* network = ini.GetOrCreateSection("Network");
network->Get("SSLDumpRead", &m_SSLDumpRead, false);
network->Get("SSLDumpWrite", &m_SSLDumpWrite, false);
network->Get("SSLVerifyCertificates", &m_SSLVerifyCert, true);
network->Get("SSLDumpRootCA", &m_SSLDumpRootCA, false);
network->Get("SSLDumpPeerCert", &m_SSLDumpPeerCert, false);
}
void SConfig::LoadAnalyticsSettings(IniFile& ini)
{
IniFile::Section* analytics = ini.GetOrCreateSection("Analytics");
analytics->Get("ID", &m_analytics_id, "");
analytics->Get("Enabled", &m_analytics_enabled, false);
analytics->Get("PermissionAsked", &m_analytics_permission_asked, false);
}
void SConfig::LoadBluetoothPassthroughSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("BluetoothPassthrough");
section->Get("Enabled", &m_bt_passthrough_enabled, false);
section->Get("VID", &m_bt_passthrough_vid, -1);
section->Get("PID", &m_bt_passthrough_pid, -1);
section->Get("LinkKeys", &m_bt_passthrough_link_keys, "");
}
void SConfig::LoadUSBPassthroughSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("USBPassthrough");
m_usb_passthrough_devices.clear();
std::string devices_string;
std::vector<std::string> pairs;
section->Get("Devices", &devices_string, "");
SplitString(devices_string, ',', pairs);
for (const auto& pair : pairs)
{
const auto index = pair.find(':');
if (index == std::string::npos)
continue;
const u16 vid = static_cast<u16>(strtol(pair.substr(0, index).c_str(), nullptr, 16));
const u16 pid = static_cast<u16>(strtol(pair.substr(index + 1).c_str(), nullptr, 16));
if (vid && pid)
m_usb_passthrough_devices.emplace(vid, pid);
}
}
void SConfig::LoadSysconfSettings(IniFile& ini)
{
IniFile::Section* section = ini.GetOrCreateSection("Sysconf");
section->Get("SensorBarPosition", &m_sensor_bar_position, m_sensor_bar_position);
section->Get("SensorBarSensitivity", &m_sensor_bar_sensitivity, m_sensor_bar_sensitivity);
section->Get("SpeakerVolume", &m_speaker_volume, m_speaker_volume);
section->Get("WiimoteMotor", &m_wiimote_motor, m_wiimote_motor);
section->Get("WiiLanguage", &m_wii_language, m_wii_language);
section->Get("AspectRatio", &m_wii_aspect_ratio, m_wii_aspect_ratio);
section->Get("Screensaver", &m_wii_screensaver, m_wii_screensaver);
}
void SConfig::LoadSettingsFromSysconf()
{
SysConf sysconf{Common::FromWhichRoot::FROM_CONFIGURED_ROOT};
m_wii_screensaver = sysconf.GetData<u8>("IPL.SSV");
m_wii_language = sysconf.GetData<u8>("IPL.LNG");
m_wii_aspect_ratio = sysconf.GetData<u8>("IPL.AR");
m_sensor_bar_position = sysconf.GetData<u8>("BT.BAR");
m_sensor_bar_sensitivity = sysconf.GetData<u32>("BT.SENS");
m_speaker_volume = sysconf.GetData<u8>("BT.SPKV");
m_wiimote_motor = sysconf.GetData<u8>("BT.MOT") != 0;
bProgressive = sysconf.GetData<u8>("IPL.PGS") != 0;
bPAL60 = sysconf.GetData<u8>("IPL.E60") != 0;
}
void SConfig::ResetRunningGameMetadata()
{
SetRunningGameMetadata("00000000", 0, 0);
}
void SConfig::SetRunningGameMetadata(const DiscIO::IVolume& volume)
{
u64 title_id = 0;
volume.GetTitleID(&title_id);
SetRunningGameMetadata(volume.GetGameID(), title_id, volume.GetRevision());
}
void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd)
{
const u64 tmd_title_id = tmd.GetTitleId();
// If we're launching a disc game, we want to read the revision from
// the disc header instead of the TMD. They can differ.
// (IOS HLE ES calls us with a TMDReader rather than a volume when launching
// a disc game, because ES has no reason to be accessing the disc directly.)
if (DVDInterface::IsDiscInside())
{
DVDThread::WaitUntilIdle();
const DiscIO::IVolume& volume = DVDInterface::GetVolume();
u64 volume_title_id;
if (volume.GetTitleID(&volume_title_id) && volume_title_id == tmd_title_id)
{
SetRunningGameMetadata(volume.GetGameID(), volume_title_id, volume.GetRevision());
return;
}
}
// If not launching a disc game, just read everything from the TMD.
SetRunningGameMetadata(tmd.GetGameID(), tmd_title_id, tmd.GetTitleVersion());
}
void SConfig::SetRunningGameMetadata(const std::string& game_id, u64 title_id, u16 revision)
{
const bool was_changed = m_game_id != game_id || m_title_id != title_id || m_revision != revision;
m_game_id = game_id;
m_title_id = title_id;
m_revision = revision;
if (was_changed)
{
NOTICE_LOG(BOOT, "Game ID set to %s", game_id.c_str());
if (Core::IsRunning())
{
// TODO: have a callback mechanism for title changes?
g_symbolDB.Clear();
CBoot::LoadMapFromFilename();
HLE::Reload();
PatchEngine::Reload();
HiresTexture::Update();
}
}
}
void SConfig::LoadDefaults()
{
bEnableDebugging = false;
bAutomaticStart = false;
bBootToPause = false;
#ifdef USE_GDBSTUB
iGDBPort = -1;
#ifndef _WIN32
gdb_socket = "";
#endif
#endif
iCPUCore = PowerPC::DefaultCPUCore();
iTimingVariance = 40;
bCPUThread = false;
bSyncGPUOnSkipIdleHack = true;
bRunCompareServer = false;
bDSPHLE = true;
bFastmem = true;
bFPRF = false;
bAccurateNaNs = false;
bMMU = false;
bDCBZOFF = false;
bLowDCBZHack = false;
iBBDumpPort = -1;
bSyncGPU = false;
bFastDiscSpeed = false;
m_strWiiSDCardPath = File::GetUserPath(F_WIISDCARD_IDX);
bEnableMemcardSdWriting = true;
SelectedLanguage = 0;
bOverrideGCLanguage = false;
bWii = false;
bDPL2Decoder = false;
iLatency = 14;
m_audio_stretch = false;
m_audio_stretch_max_latency = 80;
iPosX = INT_MIN;
iPosY = INT_MIN;
iWidth = -1;
iHeight = -1;
m_analytics_id = "";
m_analytics_enabled = false;
m_analytics_permission_asked = false;
bLoopFifoReplay = true;
bJITOff = false; // debugger only settings
bJITLoadStoreOff = false;
bJITLoadStoreFloatingOff = false;
bJITLoadStorePairedOff = false;
bJITFloatingPointOff = false;
bJITIntegerOff = false;
bJITPairedOff = false;
bJITSystemRegistersOff = false;
bJITBranchOff = false;
ResetRunningGameMetadata();
}
bool SConfig::IsUSBDeviceWhitelisted(const std::pair<u16, u16> vid_pid) const
{
return m_usb_passthrough_devices.find(vid_pid) != m_usb_passthrough_devices.end();
}
const char* SConfig::GetDirectoryForRegion(DiscIO::Region region)
{
switch (region)
{
case DiscIO::Region::NTSC_J:
return JAP_DIR;
case DiscIO::Region::NTSC_U:
return USA_DIR;
case DiscIO::Region::PAL:
return EUR_DIR;
case DiscIO::Region::NTSC_K:
// This function can't return a Korean directory name, because this
// function is only used for GameCube things (memory cards, IPL), and
// GameCube has no NTSC-K region. Since NTSC-K doesn't correspond to any
// GameCube region, let's return an arbitrary pick. Returning nullptr like
// with unknown regions would be inappropriate, because Dolphin expects
// to get valid memory card paths even when running an NTSC-K Wii game.
return JAP_DIR;
default:
return nullptr;
}
}
// Sets m_region to the region parameter, or to PAL if the region parameter
// is invalid. Set directory_name to the value returned by GetDirectoryForRegion
// for m_region. Returns false if the region parameter is invalid.
bool SConfig::SetRegion(DiscIO::Region region, std::string* directory_name)
{
const char* retrieved_region_dir = GetDirectoryForRegion(region);
m_region = retrieved_region_dir ? region : DiscIO::Region::PAL;
*directory_name = retrieved_region_dir ? retrieved_region_dir : EUR_DIR;
return !!retrieved_region_dir;
}
bool SConfig::AutoSetup(EBootBS2 _BootBS2)
{
std::string set_region_dir(EUR_DIR);
switch (_BootBS2)
{
case BOOT_DEFAULT:
{
bool bootDrive = cdio_is_cdrom(m_strFilename);
// Check if the file exist, we may have gotten it from a --elf command line
// that gave an incorrect file name
if (!bootDrive && !File::Exists(m_strFilename))
{
PanicAlertT("The specified file \"%s\" does not exist", m_strFilename.c_str());
return false;
}
std::string Extension;
SplitPath(m_strFilename, nullptr, nullptr, &Extension);
if (!strcasecmp(Extension.c_str(), ".gcm") || !strcasecmp(Extension.c_str(), ".iso") ||
!strcasecmp(Extension.c_str(), ".tgc") || !strcasecmp(Extension.c_str(), ".wbfs") ||
!strcasecmp(Extension.c_str(), ".ciso") || !strcasecmp(Extension.c_str(), ".gcz") ||
bootDrive)
{
m_BootType = BOOT_ISO;
std::unique_ptr<DiscIO::IVolume> pVolume(DiscIO::CreateVolumeFromFilename(m_strFilename));
if (pVolume == nullptr)
{
if (bootDrive)
PanicAlertT("Could not read \"%s\". "
"There is no disc in the drive or it is not a GameCube/Wii backup. "
"Please note that Dolphin cannot play games directly from the original "
"GameCube and Wii discs.",
m_strFilename.c_str());
else
PanicAlertT("\"%s\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.",
m_strFilename.c_str());
return false;
}
SetRunningGameMetadata(*pVolume);
// Check if we have a Wii disc
bWii = pVolume->GetVolumeType() == DiscIO::Platform::WII_DISC;
if (!SetRegion(pVolume->GetRegion(), &set_region_dir))
if (!PanicYesNoT("Your GCM/ISO file seems to be invalid (invalid country)."
"\nContinue with PAL region?"))
return false;
}
else if (!strcasecmp(Extension.c_str(), ".elf"))
{
bWii = CBoot::IsElfWii(m_strFilename);
// TODO: Right now GC homebrew boots in NTSC and Wii homebrew in PAL.
// This is intentional so that Wii homebrew can boot in both 50Hz and 60Hz, without forcing
// all GC homebrew to 50Hz.
// In the future, it probably makes sense to add a Region setting for homebrew somewhere in
// the emulator config.
SetRegion(bWii ? DiscIO::Region::PAL : DiscIO::Region::NTSC_U, &set_region_dir);
m_BootType = BOOT_ELF;
}
else if (!strcasecmp(Extension.c_str(), ".dol"))
{
CDolLoader dolfile(m_strFilename);
bWii = dolfile.IsWii();
// TODO: See the ELF code above.
SetRegion(bWii ? DiscIO::Region::PAL : DiscIO::Region::NTSC_U, &set_region_dir);
m_BootType = BOOT_DOL;
}
else if (!strcasecmp(Extension.c_str(), ".dff"))
{
bWii = true;
SetRegion(DiscIO::Region::NTSC_U, &set_region_dir);
m_BootType = BOOT_DFF;
std::unique_ptr<FifoDataFile> ddfFile(FifoDataFile::Load(m_strFilename, true));
if (ddfFile)
{
bWii = ddfFile->GetIsWii();
}
}
else if (DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename).IsValid())
{
const DiscIO::CNANDContentLoader& content_loader =
DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename);
const IOS::ES::TMDReader& tmd = content_loader.GetTMD();
if (!IOS::ES::IsChannel(tmd.GetTitleId()))
{
PanicAlertT("This WAD is not bootable.");
return false;
}
SetRegion(tmd.GetRegion(), &set_region_dir);
SetRunningGameMetadata(tmd);
bWii = true;
m_BootType = BOOT_WII_NAND;
}
else
{
PanicAlertT("Could not recognize ISO file %s", m_strFilename.c_str());
return false;
}
}
break;
case BOOT_BS2_USA:
SetRegion(DiscIO::Region::NTSC_U, &set_region_dir);
m_strFilename.clear();
break;
case BOOT_BS2_JAP:
SetRegion(DiscIO::Region::NTSC_J, &set_region_dir);
m_strFilename.clear();
break;
case BOOT_BS2_EUR:
SetRegion(DiscIO::Region::PAL, &set_region_dir);
m_strFilename.clear();
break;
}
// Setup paths
CheckMemcardPath(SConfig::GetInstance().m_strMemoryCardA, set_region_dir, true);
CheckMemcardPath(SConfig::GetInstance().m_strMemoryCardB, set_region_dir, false);
m_strSRAM = File::GetUserPath(F_GCSRAM_IDX);
if (!bWii)
{
if (!bHLE_BS2)
{
m_strBootROM = File::GetUserPath(D_GCUSER_IDX) + DIR_SEP + set_region_dir + DIR_SEP GC_IPL;
if (!File::Exists(m_strBootROM))
m_strBootROM =
File::GetSysDirectory() + GC_SYS_DIR + DIR_SEP + set_region_dir + DIR_SEP GC_IPL;
if (!File::Exists(m_strBootROM))
{
WARN_LOG(BOOT, "Bootrom file %s not found - using HLE.", m_strBootROM.c_str());
bHLE_BS2 = true;
}
}
}
else if (bWii && !bHLE_BS2)
{
WARN_LOG(BOOT, "GC bootrom file will not be loaded for Wii mode.");
bHLE_BS2 = true;
}
return true;
}
void SConfig::CheckMemcardPath(std::string& memcardPath, const std::string& gameRegion,
bool isSlotA)
{
std::string ext("." + gameRegion + ".raw");
if (memcardPath.empty())
{
// Use default memcard path if there is no user defined name
std::string defaultFilename = isSlotA ? GC_MEMCARDA : GC_MEMCARDB;
memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext;
}
else
{
std::string filename = memcardPath;
std::string region = filename.substr(filename.size() - 7, 3);
bool hasregion = false;
hasregion |= region.compare(USA_DIR) == 0;
hasregion |= region.compare(JAP_DIR) == 0;
hasregion |= region.compare(EUR_DIR) == 0;
if (!hasregion)
{
// filename doesn't have region in the extension
if (File::Exists(filename))
{
// If the old file exists we are polite and ask if we should copy it
std::string oldFilename = filename;
filename.replace(filename.size() - 4, 4, ext);
if (PanicYesNoT("Memory Card filename in Slot %c is incorrect\n"
"Region not specified\n\n"
"Slot %c path was changed to\n"
"%s\n"
"Would you like to copy the old file to this new location?\n",
isSlotA ? 'A' : 'B', isSlotA ? 'A' : 'B', filename.c_str()))
{
if (!File::Copy(oldFilename, filename))
PanicAlertT("Copy failed");
}
}
memcardPath = filename; // Always correct the path!
}
else if (region.compare(gameRegion) != 0)
{
// filename has region, but it's not == gameRegion
// Just set the correct filename, the EXI Device will create it if it doesn't exist
memcardPath = filename.replace(filename.size() - ext.size(), ext.size(), ext);
}
}
}
DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const
{
int language_value;
if (wii)
language_value = SConfig::GetInstance().m_wii_language;
else
language_value = SConfig::GetInstance().SelectedLanguage + 1;
DiscIO::Language language = static_cast<DiscIO::Language>(language_value);
// Get rid of invalid values (probably doesn't matter, but might as well do it)
if (language > DiscIO::Language::LANGUAGE_UNKNOWN ||
language < DiscIO::Language::LANGUAGE_JAPANESE)
language = DiscIO::Language::LANGUAGE_UNKNOWN;
return language;
}
IniFile SConfig::LoadDefaultGameIni() const
{
return LoadDefaultGameIni(GetGameID(), m_revision);
}
IniFile SConfig::LoadLocalGameIni() const
{
return LoadLocalGameIni(GetGameID(), m_revision);
}
IniFile SConfig::LoadGameIni() const
{
return LoadGameIni(GetGameID(), m_revision);
}
IniFile SConfig::LoadDefaultGameIni(const std::string& id, u16 revision)
{
IniFile game_ini;
for (const std::string& filename : GetGameIniFilenames(id, revision))
game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true);
return game_ini;
}
IniFile SConfig::LoadLocalGameIni(const std::string& id, u16 revision)
{
IniFile game_ini;
for (const std::string& filename : GetGameIniFilenames(id, revision))
game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true);
return game_ini;
}
IniFile SConfig::LoadGameIni(const std::string& id, u16 revision)
{
IniFile game_ini;
for (const std::string& filename : GetGameIniFilenames(id, revision))
game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true);
for (const std::string& filename : GetGameIniFilenames(id, revision))
game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true);
return game_ini;
}
// Returns all possible filenames in ascending order of priority
std::vector<std::string> SConfig::GetGameIniFilenames(const std::string& id, u16 revision)
{
std::vector<std::string> filenames;
if (id.empty())
return filenames;
// INIs that match the system code (unique for each Virtual Console system)
filenames.push_back(id.substr(0, 1) + ".ini");
// INIs that match all regions
if (id.size() >= 4)
filenames.push_back(id.substr(0, 3) + ".ini");
// Regular INIs
filenames.push_back(id + ".ini");
// INIs with specific revisions
filenames.push_back(id + StringFromFormat("r%d", revision) + ".ini");
return filenames;
}