dolphin/Source/Core/Core/WiiRoot.cpp
Admiral H. Curtiss 6a339cbdb3
IOS/ES: Split ESDevice into ESCore and ESDevice.
ESCore implements the core functionality that can also be used outside of emulation. ESDevice implements the IOS device and is only available during emulation.
2023-05-15 20:41:05 +02:00

442 lines
16 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::CopyRegularFile(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()))
{
auto* sync_fs = boot_session_data.GetWiiSyncFS();
auto& sync_titles = boot_session_data.GetWiiSyncTitles();
auto* source_fs = sync_fs ? sync_fs : configured_fs.get();
INFO_LOG_FMT(CORE, "Wii Save Init: Copying from {} to session_fs.",
sync_fs ? "sync_fs" : "configured_fs");
// Copy the current user's save to the Blank NAND
if (Movie::IsMovieActive() && !NetPlay::IsNetPlayRunning())
{
INFO_LOG_FMT(CORE, "Wii Save Init: Copying {0:016x}.", title_id);
CopySave(source_fs, session_fs, title_id);
}
else
{
for (const u64 title : sync_titles)
{
INFO_LOG_FMT(CORE, "Wii Save Init: Copying {0:016x}.", title);
CopySave(source_fs, session_fs, title);
}
}
// Copy Mii data
if (!CopyNandFile(source_fs, 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::CreateDirs(s_temp_redirect_root);
File::Copy(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";
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::MoveWithOverwrite(path, backup_path);
}
}
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::CreateDirs(save_redirect->m_target_path);
if (save_redirect->m_clone)
{
File::Copy(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)
{
// In TAS mode, copy back always.
// In Netplay, only copy back when we're the host and writing back is enabled.
const bool wii_root_is_temporary = WiiRootIsTemporary();
const auto* netplay_settings = boot_session_data.GetNetplaySettings();
const bool is_netplay_write = netplay_settings && netplay_settings->savedata_write;
const bool is_netplay_host = netplay_settings && netplay_settings->is_hosting;
const bool cleanup_required =
wii_root_is_temporary && (!netplay_settings || (is_netplay_write && is_netplay_host));
INFO_LOG_FMT(CORE,
"Wii FS Cleanup: cleanup_required = {} (wii_root_is_temporary = {}, "
"is netplay = {}, is_netplay_write = {}, is_netplay_host = {})",
cleanup_required, wii_root_is_temporary, !!netplay_settings, is_netplay_write,
is_netplay_host);
if (!cleanup_required)
return;
INFO_LOG_FMT(CORE, "Wii FS Cleanup: Copying from temporary FS to configured_fs.");
// copy back the temp nand redirected files to where they should normally be redirected to
for (const auto& redirect : s_temp_nand_redirects)
{
File::CreateFullPath(redirect.real_path);
File::MoveWithOverwrite(redirect.temp_path, redirect.real_path);
}
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");
}
// If we started by copying only certain saves, we also only want to copy back those exact saves.
// This prevents a situation where you change game and create a new save that was not loaded from
// the real NAND during netplay, and that then overwrites your existing local save during this
// cleanup process.
const bool copy_all = !netplay_settings || netplay_settings->savedata_sync_all_wii;
for (const u64 title_id :
(copy_all ? ios->GetESCore().GetInstalledTitles() : boot_session_data.GetWiiSyncTitles()))
{
INFO_LOG_FMT(CORE, "Wii FS Cleanup: Copying {0:016x}.", title_id);
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