dolphin/Source/Core/Core/BootManager.cpp

444 lines
15 KiB
C++

// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// File description
// -------------
// Purpose of this file: Collect boot settings for Core::Init()
// Call sequence: This file has one of the first function called when a game is booted,
// the boot sequence in the code is:
// DolphinWX: FrameTools.cpp StartGame
// Core BootManager.cpp BootCore
// Core.cpp Init Thread creation
// EmuThread Calls CBoot::BootUp
// Boot.cpp CBoot::BootUp()
// CBoot::EmulatedBS2_Wii() / GC() or Load_BS2()
#include "Core/BootManager.h"
#include <algorithm>
#include <array>
#include <string>
#include <fmt/format.h>
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
#include "Core/Boot/Boot.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/ConfigLoaders/BaseConfigLoader.h"
#include "Core/ConfigLoaders/NetPlayConfigLoader.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/Sram.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/Movie.h"
#include "Core/NetPlayProto.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/WiiRoot.h"
#include "DiscIO/Enums.h"
#include "VideoCommon/VideoBackendBase.h"
namespace BootManager
{
// TODO this is an ugly hack which allows us to restore values trampled by per-game settings
// Apply fire liberally
struct ConfigCache
{
public:
// fill the cache with values from the configuration
void SaveConfig(const SConfig& config);
// restore values to the configuration from the cache
void RestoreConfig(SConfig* config);
// These store if the relevant setting should be reset back later (true) or if it should be left
// alone on restore (false)
bool bSetEmulationSpeed = false;
bool bSetVolume = false;
std::array<bool, MAX_BBMOTES> bSetWiimoteSource{};
std::array<bool, SerialInterface::MAX_SI_CHANNELS> bSetPads{};
std::array<bool, ExpansionInterface::MAX_EXI_CHANNELS> bSetEXIDevice{};
private:
bool valid = false;
bool bCPUThread = false;
bool bSyncGPUOnSkipIdleHack = false;
bool bMMU = false;
bool bDisableICache = false;
bool bSyncGPU = false;
int iSyncGpuMaxDistance = 0;
int iSyncGpuMinDistance = 0;
float fSyncGpuOverclock = 0;
bool bFastDiscSpeed = false;
float m_EmulationSpeed = 0;
std::string m_strGPUDeterminismMode;
std::array<WiimoteSource, MAX_BBMOTES> iWiimoteSource{};
std::array<SerialInterface::SIDevices, SerialInterface::MAX_SI_CHANNELS> Pads{};
std::array<ExpansionInterface::TEXIDevices, ExpansionInterface::MAX_EXI_CHANNELS> m_EXIDevice{};
};
void ConfigCache::SaveConfig(const SConfig& config)
{
valid = true;
bCPUThread = config.bCPUThread;
bSyncGPUOnSkipIdleHack = config.bSyncGPUOnSkipIdleHack;
bDisableICache = config.bDisableICache;
bMMU = config.bMMU;
bSyncGPU = config.bSyncGPU;
iSyncGpuMaxDistance = config.iSyncGpuMaxDistance;
iSyncGpuMinDistance = config.iSyncGpuMinDistance;
fSyncGpuOverclock = config.fSyncGpuOverclock;
bFastDiscSpeed = config.bFastDiscSpeed;
m_EmulationSpeed = config.m_EmulationSpeed;
m_strGPUDeterminismMode = config.m_strGPUDeterminismMode;
for (int i = 0; i != MAX_BBMOTES; ++i)
iWiimoteSource[i] = WiimoteCommon::GetSource(i);
std::copy(std::begin(config.m_SIDevice), std::end(config.m_SIDevice), std::begin(Pads));
std::copy(std::begin(config.m_EXIDevice), std::end(config.m_EXIDevice), std::begin(m_EXIDevice));
bSetEmulationSpeed = false;
bSetVolume = false;
bSetWiimoteSource.fill(false);
bSetPads.fill(false);
bSetEXIDevice.fill(false);
}
void ConfigCache::RestoreConfig(SConfig* config)
{
if (!valid)
return;
valid = false;
config->bCPUThread = bCPUThread;
config->bSyncGPUOnSkipIdleHack = bSyncGPUOnSkipIdleHack;
config->bDisableICache = bDisableICache;
config->bMMU = bMMU;
config->bSyncGPU = bSyncGPU;
config->iSyncGpuMaxDistance = iSyncGpuMaxDistance;
config->iSyncGpuMinDistance = iSyncGpuMinDistance;
config->fSyncGpuOverclock = fSyncGpuOverclock;
config->bFastDiscSpeed = bFastDiscSpeed;
// Only change these back if they were actually set by game ini, since they can be changed while a
// game is running.
if (config->bWii)
{
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
{
if (bSetWiimoteSource[i])
WiimoteCommon::SetSource(i, iWiimoteSource[i]);
}
}
for (unsigned int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
if (bSetPads[i])
config->m_SIDevice[i] = Pads[i];
}
if (bSetEmulationSpeed)
config->m_EmulationSpeed = m_EmulationSpeed;
for (unsigned int i = 0; i < ExpansionInterface::MAX_EXI_CHANNELS; ++i)
{
if (bSetEXIDevice[i])
config->m_EXIDevice[i] = m_EXIDevice[i];
}
config->m_strGPUDeterminismMode = m_strGPUDeterminismMode;
}
static ConfigCache config_cache;
void SetEmulationSpeedReset(bool value)
{
config_cache.bSetEmulationSpeed = value;
}
static GPUDeterminismMode ParseGPUDeterminismMode(const std::string& mode)
{
if (mode == "auto")
return GPUDeterminismMode::Auto;
if (mode == "none")
return GPUDeterminismMode::Disabled;
if (mode == "fake-completion")
return GPUDeterminismMode::FakeCompletion;
NOTICE_LOG_FMT(BOOT, "Unknown GPU determinism mode {}", mode);
return GPUDeterminismMode::Auto;
}
// Boot the ISO or file
bool BootCore(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
{
if (!boot)
return false;
SConfig& StartUp = SConfig::GetInstance();
config_cache.SaveConfig(StartUp);
if (!StartUp.SetPathsAndGameMetadata(*boot))
return false;
// Load game specific settings
if (!std::holds_alternative<BootParameters::IPL>(boot->parameters))
{
IniFile game_ini = StartUp.LoadGameIni();
// General settings
IniFile::Section* core_section = game_ini.GetOrCreateSection("Core");
IniFile::Section* controls_section = game_ini.GetOrCreateSection("Controls");
core_section->Get("CPUThread", &StartUp.bCPUThread, StartUp.bCPUThread);
core_section->Get("SyncOnSkipIdle", &StartUp.bSyncGPUOnSkipIdleHack,
StartUp.bSyncGPUOnSkipIdleHack);
core_section->Get("DisableICache", &StartUp.bDisableICache, StartUp.bDisableICache);
core_section->Get("MMU", &StartUp.bMMU, StartUp.bMMU);
core_section->Get("SyncGPU", &StartUp.bSyncGPU, StartUp.bSyncGPU);
core_section->Get("FastDiscSpeed", &StartUp.bFastDiscSpeed, StartUp.bFastDiscSpeed);
if (core_section->Get("EmulationSpeed", &StartUp.m_EmulationSpeed, StartUp.m_EmulationSpeed))
config_cache.bSetEmulationSpeed = true;
core_section->Get("GPUDeterminismMode", &StartUp.m_strGPUDeterminismMode,
StartUp.m_strGPUDeterminismMode);
for (unsigned int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
int source;
controls_section->Get(fmt::format("PadType{}", i), &source, -1);
if (source >= SerialInterface::SIDEVICE_NONE && source < SerialInterface::SIDEVICE_COUNT)
{
StartUp.m_SIDevice[i] = static_cast<SerialInterface::SIDevices>(source);
config_cache.bSetPads[i] = true;
}
}
// Wii settings
if (StartUp.bWii)
{
int source;
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
{
controls_section->Get(fmt::format("WiimoteSource{}", i), &source, -1);
if (source != -1 && WiimoteCommon::GetSource(i) != WiimoteSource(source) &&
WiimoteSource(source) >= WiimoteSource::None &&
WiimoteSource(source) <= WiimoteSource::Real)
{
config_cache.bSetWiimoteSource[i] = true;
WiimoteCommon::SetSource(i, WiimoteSource(source));
}
}
controls_section->Get("WiimoteSourceBB", &source, -1);
if (source != -1 &&
WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) != WiimoteSource(source) &&
(WiimoteSource(source) == WiimoteSource::None ||
WiimoteSource(source) == WiimoteSource::Real))
{
config_cache.bSetWiimoteSource[WIIMOTE_BALANCE_BOARD] = true;
WiimoteCommon::SetSource(WIIMOTE_BALANCE_BOARD, WiimoteSource(source));
}
}
}
StartUp.m_GPUDeterminismMode = ParseGPUDeterminismMode(StartUp.m_strGPUDeterminismMode);
// Movie settings
if (Movie::IsPlayingInput() && Movie::IsConfigSaved())
{
// TODO: remove this once ConfigManager starts using OnionConfig.
StartUp.bCPUThread = Config::Get(Config::MAIN_CPU_THREAD);
StartUp.bFastDiscSpeed = Config::Get(Config::MAIN_FAST_DISC_SPEED);
StartUp.bSyncGPU = Config::Get(Config::MAIN_SYNC_GPU);
for (int i = 0; i < 2; ++i)
{
if (Movie::IsUsingMemcard(i) && Movie::IsStartingFromClearSave() && !StartUp.bWii)
{
const auto raw_path =
File::GetUserPath(D_GCUSER_IDX) + fmt::format("Movie{}.raw", (i == 0) ? 'A' : 'B');
if (File::Exists(raw_path))
File::Delete(raw_path);
const auto movie_path = File::GetUserPath(D_GCUSER_IDX) + "Movie";
if (File::Exists(movie_path))
File::DeleteDirRecursively(movie_path);
}
}
}
if (NetPlay::IsNetPlayRunning())
{
const NetPlay::NetSettings& netplay_settings = NetPlay::GetNetSettings();
Config::AddLayer(ConfigLoaders::GenerateNetPlayConfigLoader(netplay_settings));
StartUp.bCPUThread = netplay_settings.m_CPUthread;
StartUp.bCopyWiiSaveNetplay = netplay_settings.m_CopyWiiSave;
StartUp.m_EXIDevice[0] = netplay_settings.m_EXIDevice[0];
StartUp.m_EXIDevice[1] = netplay_settings.m_EXIDevice[1];
StartUp.m_EXIDevice[2] = netplay_settings.m_EXIDevice[2];
config_cache.bSetEXIDevice[0] = true;
config_cache.bSetEXIDevice[1] = true;
config_cache.bSetEXIDevice[2] = true;
StartUp.bDisableICache = netplay_settings.m_DisableICache;
StartUp.bSyncGPUOnSkipIdleHack = netplay_settings.m_SyncOnSkipIdle;
StartUp.bSyncGPU = netplay_settings.m_SyncGPU;
StartUp.iSyncGpuMaxDistance = netplay_settings.m_SyncGpuMaxDistance;
StartUp.iSyncGpuMinDistance = netplay_settings.m_SyncGpuMinDistance;
StartUp.fSyncGpuOverclock = netplay_settings.m_SyncGpuOverclock;
StartUp.bFastDiscSpeed = netplay_settings.m_FastDiscSpeed;
StartUp.bMMU = netplay_settings.m_MMU;
StartUp.bFastmem = netplay_settings.m_Fastmem;
if (netplay_settings.m_HostInputAuthority && !netplay_settings.m_IsHosting)
config_cache.bSetEmulationSpeed = true;
}
else
{
g_SRAM_netplay_initialized = false;
}
// Override out-of-region languages/countries to prevent games from crashing or behaving oddly
if (!Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS))
{
Config::SetCurrent(
Config::MAIN_GC_LANGUAGE,
DiscIO::ToGameCubeLanguage(StartUp.GetLanguageAdjustedForRegion(false, StartUp.m_region)));
if (StartUp.bWii)
{
const u32 wii_language =
static_cast<u32>(StartUp.GetLanguageAdjustedForRegion(true, StartUp.m_region));
if (wii_language != Config::Get(Config::SYSCONF_LANGUAGE))
Config::SetCurrent(Config::SYSCONF_LANGUAGE, wii_language);
const u8 country_code = static_cast<u8>(Config::Get(Config::SYSCONF_COUNTRY));
if (StartUp.m_region != DiscIO::SysConfCountryToRegion(country_code))
{
switch (StartUp.m_region)
{
case DiscIO::Region::NTSC_J:
Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x01); // Japan
break;
case DiscIO::Region::NTSC_U:
Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x31); // United States
break;
case DiscIO::Region::PAL:
Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x6c); // Switzerland
break;
case DiscIO::Region::NTSC_K:
Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x88); // South Korea
break;
case DiscIO::Region::Unknown:
break;
}
}
}
}
// Some NTSC Wii games such as Doc Louis's Punch-Out!! and
// 1942 (Virtual Console) crash if the PAL60 option is enabled
if (StartUp.bWii && DiscIO::IsNTSC(StartUp.m_region) && Config::Get(Config::SYSCONF_PAL60))
Config::SetCurrent(Config::SYSCONF_PAL60, false);
// Disable loading time emulation for Riivolution-patched games until we have proper emulation.
if (!boot->riivolution_patches.empty())
StartUp.bFastDiscSpeed = true;
Core::UpdateWantDeterminism(/*initial*/ true);
if (StartUp.bWii)
{
Core::InitializeWiiRoot(Core::WantsDeterminism());
// Ensure any new settings are written to the SYSCONF
if (!Core::WantsDeterminism())
{
Core::BackupWiiSettings();
ConfigLoaders::SaveToSYSCONF(Config::LayerType::Meta);
}
else
{
ConfigLoaders::SaveToSYSCONF(Config::LayerType::Meta, [](const Config::Location& location) {
return Config::GetActiveLayerForConfig(location) >= Config::LayerType::Movie;
});
}
}
const bool load_ipl = !StartUp.bWii && !Config::Get(Config::MAIN_SKIP_IPL) &&
std::holds_alternative<BootParameters::Disc>(boot->parameters);
if (load_ipl)
{
return Core::Init(
std::make_unique<BootParameters>(
BootParameters::IPL{StartUp.m_region,
std::move(std::get<BootParameters::Disc>(boot->parameters))},
std::move(boot->boot_session_data)),
wsi);
}
return Core::Init(std::move(boot), wsi);
}
// SYSCONF can be modified during emulation by the user and internally, which makes it
// a bad idea to just always overwrite it with the settings from the base layer.
//
// Conversely, we also shouldn't just accept any changes to SYSCONF, as it may cause
// temporary settings (from Movie, Netplay, game INIs, etc.) to stick around.
//
// To avoid inconveniences in most cases, we accept changes that aren't being overriden by a
// non-base layer, and restore only the overriden settings.
static void RestoreSYSCONF()
{
// This layer contains the new SYSCONF settings (including any temporary settings).
Config::Layer temp_layer(Config::LayerType::Base);
// Use a separate loader so the temp layer doesn't automatically save
ConfigLoaders::GenerateBaseConfigLoader()->Load(&temp_layer);
for (const auto& setting : Config::SYSCONF_SETTINGS)
{
std::visit(
[&](auto* info) {
// If this setting was overridden, then we copy the base layer value back to the SYSCONF.
// Otherwise we leave the new value in the SYSCONF.
if (Config::GetActiveLayerForConfig(*info) == Config::LayerType::Base)
Config::SetBase(*info, temp_layer.Get(*info));
},
setting.config_info);
}
// Save the SYSCONF.
Config::GetLayer(Config::LayerType::Base)->Save();
}
void RestoreConfig()
{
Core::ShutdownWiiRoot();
if (!Core::WiiRootIsTemporary())
{
Core::RestoreWiiSettings(Core::RestoreReason::EmulationEnd);
RestoreSYSCONF();
}
Config::ClearCurrentRunLayer();
Config::RemoveLayer(Config::LayerType::Movie);
Config::RemoveLayer(Config::LayerType::Netplay);
Config::RemoveLayer(Config::LayerType::GlobalGame);
Config::RemoveLayer(Config::LayerType::LocalGame);
SConfig::GetInstance().ResetRunningGameMetadata();
config_cache.RestoreConfig(&SConfig::GetInstance());
}
} // namespace BootManager