dolphin/Source/Core/Core/WiiRoot.cpp

424 lines
14 KiB
C++

// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/WiiRoot.h"
#include <optional>
#include <string>
#include <vector>
#include <fmt/format.h>
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Core/Boot/Boot.h"
#include "Core/CommonTitles.h"
#include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h"
#include "Core/HW/WiiSave.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h"
#include "Core/IOS/Uids.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/SysConf.h"
namespace Core
{
namespace FS = IOS::HLE::FS;
static std::string s_temp_wii_root;
static std::string s_temp_redirect_root;
static bool s_wii_root_initialized = false;
static std::vector<IOS::HLE::FS::NandRedirect> s_nand_redirects;
// When Temp NAND + Redirects are both active, we need to keep track of where each redirect path
// should be copied back to after a successful session finish.
struct TempRedirectPath
{
std::string real_path;
std::string temp_path;
};
static std::vector<TempRedirectPath> s_temp_nand_redirects;
const std::vector<IOS::HLE::FS::NandRedirect>& GetActiveNandRedirects()
{
return s_nand_redirects;
}
static bool CopyBackupFile(const std::string& path_from, const std::string& path_to)
{
if (!File::Exists(path_from))
return false;
File::CreateFullPath(path_to);
return File::Copy(path_from, path_to);
}
static void DeleteBackupFile(const std::string& file_name)
{
File::Delete(File::GetUserPath(D_BACKUP_IDX) + file_name);
}
static void BackupFile(const std::string& path_in_nand)
{
const std::string file_name = PathToFileName(path_in_nand);
const std::string original_path = File::GetUserPath(D_WIIROOT_IDX) + path_in_nand;
const std::string backup_path = File::GetUserPath(D_BACKUP_IDX) + file_name;
CopyBackupFile(original_path, backup_path);
}
static void RestoreFile(const std::string& path_in_nand)
{
const std::string file_name = PathToFileName(path_in_nand);
const std::string original_path = File::GetUserPath(D_WIIROOT_IDX) + path_in_nand;
const std::string backup_path = File::GetUserPath(D_BACKUP_IDX) + file_name;
if (CopyBackupFile(backup_path, original_path))
DeleteBackupFile(file_name);
}
static void CopySave(FS::FileSystem* source, FS::FileSystem* dest, const u64 title_id)
{
dest->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, Common::GetTitleDataPath(title_id) + '/',
0, {FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite});
const auto source_save = WiiSave::MakeNandStorage(source, title_id);
const auto dest_save = WiiSave::MakeNandStorage(dest, title_id);
WiiSave::Copy(source_save.get(), dest_save.get());
}
static bool CopyNandFile(FS::FileSystem* source_fs, const std::string& source_file,
FS::FileSystem* dest_fs, const std::string& dest_file)
{
auto source_handle =
source_fs->OpenFile(IOS::PID_KERNEL, IOS::PID_KERNEL, source_file, IOS::HLE::FS::Mode::Read);
// If the source file doesn't exist, there is nothing more to do.
// This function must not create an empty file on the destination filesystem.
if (!source_handle)
return true;
dest_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, dest_file, 0,
{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite});
auto dest_handle =
dest_fs->CreateAndOpenFile(IOS::PID_KERNEL, IOS::PID_KERNEL, source_file,
{IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite,
IOS::HLE::FS::Mode::ReadWrite});
if (!dest_handle)
return false;
std::vector<u8> buffer(source_handle->GetStatus()->size);
if (!source_handle->Read(buffer.data(), buffer.size()))
return false;
if (!dest_handle->Write(buffer.data(), buffer.size()))
return false;
return true;
}
static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs,
const BootSessionData& boot_session_data)
{
const u64 title_id = SConfig::GetInstance().GetTitleID();
const auto configured_fs = FS::MakeFileSystem(FS::Location::Configured);
if (Movie::IsRecordingInput())
{
if (NetPlay::IsNetPlayRunning() && !SConfig::GetInstance().bCopyWiiSaveNetplay)
{
Movie::SetClearSave(true);
}
else
{
// TODO: Check for the actual save data
const std::string path = Common::GetTitleDataPath(title_id) + "/banner.bin";
Movie::SetClearSave(!configured_fs->GetMetadata(IOS::PID_KERNEL, IOS::PID_KERNEL, path));
}
}
if ((NetPlay::IsNetPlayRunning() && SConfig::GetInstance().bCopyWiiSaveNetplay) ||
(Movie::IsMovieActive() && !Movie::IsStartingFromClearSave()))
{
// Copy the current user's save to the Blank NAND
auto* sync_fs = boot_session_data.GetWiiSyncFS();
auto& sync_titles = boot_session_data.GetWiiSyncTitles();
if (sync_fs)
{
for (const u64 title : sync_titles)
{
CopySave(sync_fs, session_fs, title);
}
// Copy Mii data
if (!CopyNandFile(sync_fs, Common::GetMiiDatabasePath(), session_fs,
Common::GetMiiDatabasePath()))
{
WARN_LOG_FMT(CORE, "Failed to copy Mii database to the NAND");
}
}
else
{
if (NetPlay::IsSyncingAllWiiSaves())
{
for (const u64 title : sync_titles)
{
CopySave(configured_fs.get(), session_fs, title);
}
}
else
{
CopySave(configured_fs.get(), session_fs, title_id);
}
// Copy Mii data
if (!CopyNandFile(configured_fs.get(), Common::GetMiiDatabasePath(), session_fs,
Common::GetMiiDatabasePath()))
{
WARN_LOG_FMT(CORE, "Failed to copy Mii database to the NAND");
}
}
const auto& netplay_redirect_folder = boot_session_data.GetWiiSyncRedirectFolder();
if (!netplay_redirect_folder.empty())
File::CopyDir(netplay_redirect_folder, s_temp_redirect_root + "/");
}
}
static void MoveToBackupIfExists(const std::string& path)
{
if (File::Exists(path))
{
const std::string backup_path = path.substr(0, path.size() - 1) + ".backup" DIR_SEP;
WARN_LOG_FMT(IOS_FS, "Temporary directory at {} exists, moving to backup...", path);
// If backup exists, delete it as we don't want a mess
if (File::Exists(backup_path))
{
WARN_LOG_FMT(IOS_FS, "Temporary backup directory at {} exists, deleting...", backup_path);
File::DeleteDirRecursively(backup_path);
}
File::CopyDir(path, backup_path, true);
}
}
void InitializeWiiRoot(bool use_temporary)
{
ASSERT(!s_wii_root_initialized);
if (use_temporary)
{
s_temp_wii_root = File::GetUserPath(D_USER_IDX) + "WiiSession" DIR_SEP;
s_temp_redirect_root = File::GetUserPath(D_USER_IDX) + "RedirectSession" DIR_SEP;
WARN_LOG_FMT(IOS_FS, "Using temporary directory {} for minimal Wii FS", s_temp_wii_root);
WARN_LOG_FMT(IOS_FS, "Using temporary directory {} for redirected saves", s_temp_redirect_root);
// If directory exists, make a backup
MoveToBackupIfExists(s_temp_wii_root);
MoveToBackupIfExists(s_temp_redirect_root);
File::SetUserPath(D_SESSION_WIIROOT_IDX, s_temp_wii_root);
}
else
{
File::SetUserPath(D_SESSION_WIIROOT_IDX, File::GetUserPath(D_WIIROOT_IDX));
}
s_nand_redirects.clear();
s_wii_root_initialized = true;
}
void ShutdownWiiRoot()
{
if (WiiRootIsTemporary())
{
File::DeleteDirRecursively(s_temp_wii_root);
s_temp_wii_root.clear();
File::DeleteDirRecursively(s_temp_redirect_root);
s_temp_redirect_root.clear();
s_temp_nand_redirects.clear();
}
s_nand_redirects.clear();
s_wii_root_initialized = false;
}
bool WiiRootIsInitialized()
{
return s_wii_root_initialized;
}
bool WiiRootIsTemporary()
{
return !s_temp_wii_root.empty();
}
void BackupWiiSettings()
{
// Back up files which Dolphin can modify at boot, so that we can preserve the original contents.
// For SYSCONF, the backup is only needed in case Dolphin crashes or otherwise exists unexpectedly
// during emulation, since the config system will restore the SYSCONF settings at emulation end.
// For setting.txt, there is no other code that restores the original values for us.
BackupFile(Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING);
BackupFile("/shared2/sys/SYSCONF");
}
void RestoreWiiSettings(RestoreReason reason)
{
RestoreFile(Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING);
// We must not restore the SYSCONF backup when ending emulation cleanly, since the user may have
// edited the SYSCONF file in the NAND using the emulated software (e.g. the Wii Menu settings).
if (reason == RestoreReason::CrashRecovery)
RestoreFile("/shared2/sys/SYSCONF");
else
DeleteBackupFile("SYSCONF");
}
/// Copy a directory from host_source_path (on the host FS) to nand_target_path on the NAND.
///
/// Both paths should not have trailing slashes. To specify the NAND root, use "".
static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_source_path,
const std::string& nand_target_path)
{
const auto entries = File::ScanDirectoryTree(host_source_path, false);
for (const File::FSTEntry& entry : entries.children)
{
const std::string host_path = host_source_path + '/' + entry.virtualName;
const std::string nand_path = nand_target_path + '/' + entry.virtualName;
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
if (entry.isDirectory)
{
fs->CreateDirectory(IOS::SYSMENU_UID, IOS::SYSMENU_GID, nand_path, 0, public_modes);
if (!CopySysmenuFilesToFS(fs, host_path, nand_path))
return false;
}
else
{
// Do not overwrite any existing files.
if (fs->GetMetadata(IOS::SYSMENU_UID, IOS::SYSMENU_UID, nand_path).Succeeded())
continue;
File::IOFile host_file{host_path, "rb"};
std::vector<u8> file_data(host_file.GetSize());
if (!host_file.ReadBytes(file_data.data(), file_data.size()))
return false;
const auto nand_file =
fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, nand_path, public_modes);
if (!nand_file || !nand_file->Write(file_data.data(), file_data.size()))
return false;
}
}
return true;
}
void InitializeWiiFileSystemContents(
std::optional<DiscIO::Riivolution::SavegameRedirect> save_redirect,
const BootSessionData& boot_session_data)
{
const auto fs = IOS::HLE::GetIOS()->GetFS();
// Some games (such as Mario Kart Wii) assume that NWC24 files will always be present
// even upon the first launch as they are normally created by the system menu.
// Because we do not require the system menu to be run, WiiConnect24 files must be copied
// to the NAND manually.
if (!CopySysmenuFilesToFS(fs.get(), File::GetSysDirectory() + WII_USER_DIR, ""))
WARN_LOG_FMT(CORE, "Failed to copy initial System Menu files to the NAND");
const bool is_temp_nand = WiiRootIsTemporary();
if (is_temp_nand)
{
// Generate a SYSCONF with default settings for the temporary Wii NAND.
SysConf sysconf{fs};
sysconf.Save();
InitializeDeterministicWiiSaves(fs.get(), boot_session_data);
}
if (save_redirect)
{
const u64 title_id = SConfig::GetInstance().GetTitleID();
std::string source_path = Common::GetTitleDataPath(title_id);
if (is_temp_nand)
{
// remember the actual path for copying back on shutdown and redirect to a temp folder instead
s_temp_nand_redirects.emplace_back(
TempRedirectPath{save_redirect->m_target_path, s_temp_redirect_root});
save_redirect->m_target_path = s_temp_redirect_root;
}
if (!File::IsDirectory(save_redirect->m_target_path))
{
File::CreateFullPath(save_redirect->m_target_path + "/");
if (save_redirect->m_clone)
{
File::CopyDir(Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT),
save_redirect->m_target_path);
}
}
s_nand_redirects.emplace_back(IOS::HLE::FS::NandRedirect{
std::move(source_path), std::move(save_redirect->m_target_path)});
fs->SetNandRedirects(s_nand_redirects);
}
}
void CleanUpWiiFileSystemContents(const BootSessionData& boot_session_data)
{
if (!WiiRootIsTemporary() || !Config::Get(Config::SESSION_SAVE_DATA_WRITABLE) ||
boot_session_data.GetWiiSyncFS())
{
return;
}
// copy back the temp nand redirected files to where they should normally be redirected to
for (const auto& redirect : s_temp_nand_redirects)
File::CopyDir(redirect.temp_path, redirect.real_path + "/", true);
IOS::HLE::EmulationKernel* ios = IOS::HLE::GetIOS();
// clear the redirects in the session FS, otherwise the back-copy might grab redirected files
s_nand_redirects.clear();
ios->GetFS()->SetNandRedirects({});
const auto configured_fs = FS::MakeFileSystem(FS::Location::Configured);
// Copy back Mii data
if (!CopyNandFile(ios->GetFS().get(), Common::GetMiiDatabasePath(), configured_fs.get(),
Common::GetMiiDatabasePath()))
{
WARN_LOG_FMT(CORE, "Failed to copy Mii database to the NAND");
}
for (const u64 title_id : ios->GetES()->GetInstalledTitles())
{
const auto session_save = WiiSave::MakeNandStorage(ios->GetFS().get(), title_id);
// FS won't write the save if the directory doesn't exist
const std::string title_path = Common::GetTitleDataPath(title_id);
configured_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path + '/', 0,
{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite});
const auto user_save = WiiSave::MakeNandStorage(configured_fs.get(), title_id);
const std::string backup_path =
fmt::format("{}/{:016x}.bin", File::GetUserPath(D_BACKUP_IDX), title_id);
const auto backup_save = WiiSave::MakeDataBinStorage(&ios->GetIOSC(), backup_path, "w+b");
// Backup the existing save just in case it's still needed.
WiiSave::Copy(user_save.get(), backup_save.get());
WiiSave::Copy(session_save.get(), user_save.get());
}
}
} // namespace Core