// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <cinttypes>
#include <memory>

#include "Common/CDUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"

#include "Core/Boot/Boot.h"
#include "Core/Boot/Boot_DOL.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"  // for bWii
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/HW/SI.h"
#include "Core/PowerPC/PowerPC.h"

#include "DiscIO/NANDContentLoader.h"
#include "DiscIO/VolumeCreator.h"

SConfig* SConfig::m_Instance;

SConfig::SConfig()
    : bEnableDebugging(false), bAutomaticStart(false), bBootToPause(false), bJITNoBlockCache(false),
      bJITNoBlockLinking(false), bJITOff(false), bJITLoadStoreOff(false),
      bJITLoadStorelXzOff(false), bJITLoadStorelwzOff(false), bJITLoadStorelbzxOff(false),
      bJITLoadStoreFloatingOff(false), bJITLoadStorePairedOff(false), bJITFloatingPointOff(false),
      bJITIntegerOff(false), bJITPairedOff(false), bJITSystemRegistersOff(false),
      bJITBranchOff(false), bJITILTimeProfiling(false), bJITILOutputIR(false), bFPRF(false),
      bAccurateNaNs(false), iTimingVariance(40), bCPUThread(true), bDSPThread(false), bDSPHLE(true),
      bSkipIdle(true), bSyncGPUOnSkipIdleHack(true), bNTSC(false), bForceNTSCJ(false),
      bHLE_BS2(true), bEnableCheats(false), bEnableMemcardSdWriting(true), bDPL2Decoder(false),
      iLatency(14), bRunCompareServer(false), bRunCompareClient(false), bMMU(false),
      bDCBZOFF(false), iBBDumpPort(0), bFastDiscSpeed(false), bSyncGPU(false), SelectedLanguage(0),
      bOverrideGCLanguage(false), bWii(false), bConfirmStop(false), bHideCursor(false),
      bAutoHideCursor(false), bUsePanicHandlers(true), bOnScreenDisplayMessages(true),
      iRenderWindowXPos(-1), iRenderWindowYPos(-1), iRenderWindowWidth(640),
      iRenderWindowHeight(480), bRenderWindowAutoSize(false), bKeepWindowOnTop(false),
      bFullscreen(false), bRenderToMain(false), bProgressive(false), bPAL60(false),
      bDisableScreenSaver(false), iPosX(100), iPosY(100), iWidth(800), iHeight(600),
      m_analytics_enabled(false), m_analytics_permission_asked(false), bLoopFifoReplay(true)
{
  LoadDefaults();
  // Make sure we have log manager
  LoadSettings();
}

void SConfig::Init()
{
  m_Instance = new SConfig;
}

void SConfig::Shutdown()
{
  delete m_Instance;
  m_Instance = nullptr;
}

SConfig::~SConfig()
{
  SaveSettings();
  delete m_SYSCONF;
}

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);

  ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX));
  m_SYSCONF->Save();
}

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_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);

#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 == -32000) ? 0 : iPosX);  // TODO - HAX
  interface->Set("MainWindowPosY", (iPosY == -32000) ? 0 : iPosY);  // TODO - HAX
  interface->Set("MainWindowWidth", iWidth);
  interface->Set("MainWindowHeight", iHeight);
  interface->Set("Language", 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("ThemeName40", theme_name);
  interface->Set("PauseOnFocusLost", m_PauseOnFocusLost);
}

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("ColorCompressed", m_ColorCompressed);

  gamelist->Set("ColumnPlatform", m_showSystemColumn);
  gamelist->Set("ColumnBanner", m_showBannerColumn);
  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("SkipIdle", bSkipIdle);
  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("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 < 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);
}

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);
}

void SConfig::SaveDSPSettings(IniFile& ini)
{
  IniFile::Section* dsp = ini.GetOrCreateSection("DSP");

  dsp->Set("EnableJIT", m_DSPEnableJIT);
  dsp->Set("DumpAudio", m_DumpAudio);
  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::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::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);
  LoadAnalyticsSettings(ini);

  m_SYSCONF = new SysConf();
}

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));
    }
  }
  // Check for old file path (Changed in 4.0-4003)
  // This can probably be removed after 5.0 stable is launched
  else if (general->Get("GCMPathes", &numISOPaths, 0))
  {
    for (int i = 0; i < numISOPaths; i++)
    {
      std::string tmpPath;
      general->Get(StringFromFormat("GCMPath%i", i), &tmpPath, "");
      bool found = false;
      for (size_t j = 0; j < m_ISOFolder.size(); ++j)
      {
        if (m_ISOFolder[j] == tmpPath)
        {
          found = true;
          break;
        }
      }
      if (!found)
        m_ISOFolder.push_back(std::move(tmpPath));
    }
  }

  if (!general->Get("RecursiveISOPaths", &m_RecursiveISOFolder, false))
  {
    // Check for old name
    general->Get("RecursiveGCMPaths", &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);
}

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, 100);
  interface->Get("MainWindowPosY", &iPosY, 100);
  interface->Get("MainWindowWidth", &iWidth, 800);
  interface->Get("MainWindowHeight", &iHeight, 600);
  interface->Get("Language", &m_InterfaceLanguage, 0);
  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("ThemeName40", &theme_name, DEFAULT_THEME_DIR);
  interface->Get("PauseOnFocusLost", &m_PauseOnFocusLost, 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);

  // Determines if compressed games display in blue
  gamelist->Get("ColorCompressed", &m_ColorCompressed, true);

  // Gamelist columns toggles
  gamelist->Get("ColumnPlatform", &m_showSystemColumn, true);
  gamelist->Get("ColumnBanner", &m_showBannerColumn, 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("SkipIdle", &bSkipIdle, 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, 2);
  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], EXIDEVICE_MEMORYCARD);
  core->Get("SlotB", (int*)&m_EXIDevice[1], EXIDEVICE_NONE);
  core->Get("SerialPort1", (int*)&m_EXIDevice[2], EXIDEVICE_NONE);
  core->Get("BBA_MAC", &m_bba_mac);
  core->Get("TimeProfiling", &bJITILTimeProfiling, false);
  core->Get("OutputIR", &bJITILOutputIR, false);
  for (int i = 0; i < MAX_SI_CHANNELS; ++i)
  {
    core->Get(StringFromFormat("SIDevice%i", i), (u32*)&m_SIDevice[i],
              (i == 0) ? SIDEVICE_GC_CONTROLLER : 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.0);
  core->Get("FastDiscSpeed", &bFastDiscSpeed, false);
  core->Get("DCBZ", &bDCBZOFF, 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, "");
}

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);
}

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("DumpUCode", &m_DumpUCode, false);
#if defined __linux__ && HAVE_ALSA
  dsp->Get("Backend", &sBackend, BACKEND_ALSA);
#elif defined __APPLE__
  dsp->Get("Backend", &sBackend, BACKEND_COREAUDIO);
#elif defined _WIN32
  dsp->Get("Backend", &sBackend, BACKEND_XAUDIO2);
#elif defined ANDROID
  dsp->Get("Backend", &sBackend, BACKEND_OPENSLES);
#else
  dsp->Get("Backend", &sBackend, BACKEND_NULLSOUND);
#endif
  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::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::LoadDefaults()
{
  bEnableDebugging = false;
  bAutomaticStart = false;
  bBootToPause = false;

#ifdef USE_GDBSTUB
  iGDBPort = -1;
#ifndef _WIN32
  gdb_socket = "";
#endif
#endif

  iCPUCore = PowerPC::CORE_JIT64;
  iTimingVariance = 40;
  bCPUThread = false;
  bSkipIdle = false;
  bSyncGPUOnSkipIdleHack = true;
  bRunCompareServer = false;
  bDSPHLE = true;
  bFastmem = true;
  bFPRF = false;
  bAccurateNaNs = false;
  bMMU = false;
  bDCBZOFF = false;
  iBBDumpPort = -1;
  bSyncGPU = false;
  bFastDiscSpeed = false;
  bEnableMemcardSdWriting = true;
  SelectedLanguage = 0;
  bOverrideGCLanguage = false;
  bWii = false;
  bDPL2Decoder = false;
  iLatency = 14;

  iPosX = 100;
  iPosY = 100;
  iWidth = 800;
  iHeight = 600;

  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;

  m_strName = "NONE";
  m_strUniqueID = "00000000";
  m_revision = 0;
}

static const char* GetRegionOfCountry(DiscIO::IVolume::ECountry country)
{
  switch (country)
  {
  case DiscIO::IVolume::COUNTRY_USA:
    return USA_DIR;

  case DiscIO::IVolume::COUNTRY_TAIWAN:
  case DiscIO::IVolume::COUNTRY_KOREA:
  // TODO: Should these have their own Region Dir?
  case DiscIO::IVolume::COUNTRY_JAPAN:
    return JAP_DIR;

  case DiscIO::IVolume::COUNTRY_AUSTRALIA:
  case DiscIO::IVolume::COUNTRY_EUROPE:
  case DiscIO::IVolume::COUNTRY_FRANCE:
  case DiscIO::IVolume::COUNTRY_GERMANY:
  case DiscIO::IVolume::COUNTRY_ITALY:
  case DiscIO::IVolume::COUNTRY_NETHERLANDS:
  case DiscIO::IVolume::COUNTRY_RUSSIA:
  case DiscIO::IVolume::COUNTRY_SPAIN:
  case DiscIO::IVolume::COUNTRY_WORLD:
    return EUR_DIR;

  case DiscIO::IVolume::COUNTRY_UNKNOWN:
  default:
    return nullptr;
  }
}

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(), ".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 GC/Wii backup.  "
                      "Please note that original GameCube and Wii discs cannot be read "
                      "by most PC DVD drives.",
                      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;
      }
      m_strName = pVolume->GetInternalName();
      m_strUniqueID = pVolume->GetUniqueID();
      m_revision = pVolume->GetRevision();

      // Check if we have a Wii disc
      bWii = pVolume->GetVolumeType() == DiscIO::IVolume::WII_DISC;

      const char* retrieved_region_dir = GetRegionOfCountry(pVolume->GetCountry());
      if (!retrieved_region_dir)
      {
        if (!PanicYesNoT("Your GCM/ISO file seems to be invalid (invalid country)."
                         "\nContinue with PAL region?"))
          return false;
        retrieved_region_dir = EUR_DIR;
      }

      set_region_dir = retrieved_region_dir;
      bNTSC = set_region_dir == USA_DIR || set_region_dir == JAP_DIR;
    }
    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.
      bNTSC = bWii ? false : true;
      set_region_dir = bNTSC ? USA_DIR : EUR_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.
      bNTSC = bWii ? false : true;
      set_region_dir = bNTSC ? USA_DIR : EUR_DIR;
      m_BootType = BOOT_DOL;
    }
    else if (!strcasecmp(Extension.c_str(), ".dff"))
    {
      bWii = true;
      set_region_dir = USA_DIR;
      bNTSC = true;
      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())
    {
      std::unique_ptr<DiscIO::IVolume> pVolume(DiscIO::CreateVolumeFromFilename(m_strFilename));
      const DiscIO::CNANDContentLoader& ContentLoader =
          DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename);

      if (ContentLoader.GetContentByIndex(ContentLoader.GetBootIndex()) == nullptr)
      {
        // WAD is valid yet cannot be booted. Install instead.
        u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename);
        if (installed)
          SuccessAlertT("The WAD has been installed successfully");
        return false;  // do not boot
      }

      const char* retrieved_region_dir = GetRegionOfCountry(ContentLoader.GetCountry());
      set_region_dir = retrieved_region_dir ? retrieved_region_dir : EUR_DIR;
      bNTSC = set_region_dir == USA_DIR || set_region_dir == JAP_DIR;

      bWii = true;
      m_BootType = BOOT_WII_NAND;

      if (pVolume)
      {
        m_strName = pVolume->GetInternalName();
        m_strUniqueID = pVolume->GetUniqueID();
      }
      else
      {
        // null pVolume means that we are loading from nand folder (Most Likely Wii Menu)
        // if this is the second boot we would be using the Name and id of the last title
        m_strName.clear();
        m_strUniqueID.clear();
      }

      // Use the TitleIDhex for name and/or unique ID if launching from nand folder
      // or if it is not ascii characters (specifically sysmenu could potentially apply to other
      // things)
      std::string titleidstr = StringFromFormat("%016" PRIx64, ContentLoader.GetTitleID());

      if (m_strName.empty())
      {
        m_strName = titleidstr;
      }
      if (m_strUniqueID.empty())
      {
        m_strUniqueID = titleidstr;
      }
    }
    else
    {
      PanicAlertT("Could not recognize ISO file %s", m_strFilename.c_str());
      return false;
    }
  }
  break;

  case BOOT_BS2_USA:
    set_region_dir = USA_DIR;
    m_strFilename.clear();
    bNTSC = true;
    break;

  case BOOT_BS2_JAP:
    set_region_dir = JAP_DIR;
    m_strFilename.clear();
    bNTSC = true;
    break;

  case BOOT_BS2_EUR:
    set_region_dir = EUR_DIR;
    m_strFilename.clear();
    bNTSC = false;
    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::IVolume::ELanguage SConfig::GetCurrentLanguage(bool wii) const
{
  DiscIO::IVolume::ELanguage language;
  if (wii)
    language = (DiscIO::IVolume::ELanguage)SConfig::GetInstance().m_SYSCONF->GetData<u8>("IPL.LNG");
  else
    language = (DiscIO::IVolume::ELanguage)(SConfig::GetInstance().SelectedLanguage + 1);

  // Get rid of invalid values (probably doesn't matter, but might as well do it)
  if (language > DiscIO::IVolume::ELanguage::LANGUAGE_UNKNOWN || language < 0)
    language = DiscIO::IVolume::ELanguage::LANGUAGE_UNKNOWN;
  return language;
}

IniFile SConfig::LoadDefaultGameIni() const
{
  return LoadDefaultGameIni(GetUniqueID(), m_revision);
}

IniFile SConfig::LoadLocalGameIni() const
{
  return LoadLocalGameIni(GetUniqueID(), m_revision);
}

IniFile SConfig::LoadGameIni() const
{
  return LoadGameIni(GetUniqueID(), 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;
}
