mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-29 00:59:44 -06:00
Boot: Clean up the boot code
* Move out boot parameters to a separate struct, which is not part of SConfig/ConfigManager because there is no reason for it to be there. * Move out file name parsing and constructing the appropriate params from paths to a separate function that does that, and only that. * For every different boot type we support, add a proper struct with only the required parameters, with descriptive names and use std::variant to only store what we need. * Clean up the bHLE_BS2 stuff which made no sense sometimes. Now instead of using bHLE_BS2 for two different things, both for storing the user config setting and as a runtime boot parameter, we simply replace the Disc boot params with BootParameters::IPL. * Const correctness so it's clear what can or cannot update the config. * Drop unused parameters and unneeded checks. * Make a few checks a lot more concise. (Looking at you, extension checks for disc images.) * Remove a mildly terrible workaround where we needed to pass an empty string in order to boot the GC IPL without any game inserted. (Not required anymore thanks to std::variant and std::optional.) The motivation for this are multiple: cleaning up and being able to add support for booting an installed NAND title. Without this change, it'd be pretty much impossible to implement that. Also, using std::visit with std::variant makes the compiler do additional type checks: now we're guaranteed that the boot code will handle all boot types and no invalid boot type will be possible.
This commit is contained in:
@ -4,13 +4,16 @@
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/CDUtils.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
@ -22,6 +25,7 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/Debugger_SymbolMap.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
@ -39,6 +43,76 @@
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
BootParameters::BootParameters(Parameters&& parameters_) : parameters(std::move(parameters_))
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<BootParameters> BootParameters::GenerateFromFile(const std::string& path)
|
||||
{
|
||||
const bool is_drive = cdio_is_cdrom(path);
|
||||
// Check if the file exist, we may have gotten it from a --elf command line
|
||||
// that gave an incorrect file name
|
||||
if (!is_drive && !File::Exists(path))
|
||||
{
|
||||
PanicAlertT("The specified file \"%s\" does not exist", path.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string extension;
|
||||
SplitPath(path, nullptr, nullptr, &extension);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
static const std::unordered_set<std::string> disc_image_extensions = {
|
||||
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz"}};
|
||||
if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive)
|
||||
{
|
||||
auto volume = DiscIO::CreateVolumeFromFilename(path);
|
||||
if (!volume)
|
||||
{
|
||||
if (is_drive)
|
||||
{
|
||||
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.",
|
||||
path.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("\"%s\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path.c_str());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return std::make_unique<BootParameters>(Disc{path, std::move(volume)});
|
||||
}
|
||||
|
||||
if (extension == ".elf" || extension == ".dol")
|
||||
{
|
||||
return std::make_unique<BootParameters>(
|
||||
Executable{path, extension == ".elf" ? Executable::Type::ELF : Executable::Type::DOL});
|
||||
}
|
||||
|
||||
if (extension == ".dff")
|
||||
return std::make_unique<BootParameters>(DFF{path});
|
||||
|
||||
if (DiscIO::NANDContentManager::Access().GetNANDLoader(path).IsValid())
|
||||
return std::make_unique<BootParameters>(NAND{path});
|
||||
|
||||
PanicAlertT("Could not recognize file %s", path.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_)
|
||||
{
|
||||
const std::string directory = SConfig::GetInstance().GetDirectoryForRegion(region);
|
||||
path = SConfig::GetInstance().GetBootROMPath(directory);
|
||||
}
|
||||
|
||||
BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_)
|
||||
{
|
||||
disc = std::move(disc_);
|
||||
}
|
||||
|
||||
// Inserts a disc into the emulated disc drive and returns a pointer to it.
|
||||
// The returned pointer must only be used while we are still booting,
|
||||
// because DVDThread can do whatever it wants to the disc after that.
|
||||
@ -102,57 +176,24 @@ void CBoot::UpdateDebugger_MapLoaded()
|
||||
Host_NotifyMapLoaded();
|
||||
}
|
||||
|
||||
// Get map file paths for the active title.
|
||||
bool CBoot::FindMapFile(std::string* existing_map_file, std::string* writable_map_file,
|
||||
std::string* title_id)
|
||||
{
|
||||
std::string title_id_str;
|
||||
size_t name_begin_index;
|
||||
|
||||
SConfig& _StartupPara = SConfig::GetInstance();
|
||||
switch (_StartupPara.m_BootType)
|
||||
{
|
||||
case SConfig::BOOT_WII_NAND:
|
||||
{
|
||||
const DiscIO::NANDContentLoader& Loader =
|
||||
DiscIO::NANDContentManager::Access().GetNANDLoader(_StartupPara.m_strFilename);
|
||||
if (Loader.IsValid())
|
||||
{
|
||||
u64 TitleID = Loader.GetTMD().GetTitleId();
|
||||
title_id_str = StringFromFormat("%08X_%08X", (u32)(TitleID >> 32) & 0xFFFFFFFF,
|
||||
(u32)TitleID & 0xFFFFFFFF);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SConfig::BOOT_ELF:
|
||||
case SConfig::BOOT_DOL:
|
||||
// Strip the .elf/.dol file extension and directories before the name
|
||||
name_begin_index = _StartupPara.m_strFilename.find_last_of("/") + 1;
|
||||
if ((_StartupPara.m_strFilename.find_last_of("\\") + 1) > name_begin_index)
|
||||
{
|
||||
name_begin_index = _StartupPara.m_strFilename.find_last_of("\\") + 1;
|
||||
}
|
||||
title_id_str = _StartupPara.m_strFilename.substr(
|
||||
name_begin_index, _StartupPara.m_strFilename.size() - 4 - name_begin_index);
|
||||
break;
|
||||
|
||||
default:
|
||||
title_id_str = _StartupPara.GetGameID();
|
||||
break;
|
||||
}
|
||||
const std::string game_id = SConfig::GetInstance().GetGameID();
|
||||
|
||||
if (writable_map_file)
|
||||
*writable_map_file = File::GetUserPath(D_MAPS_IDX) + title_id_str + ".map";
|
||||
*writable_map_file = File::GetUserPath(D_MAPS_IDX) + game_id + ".map";
|
||||
|
||||
if (title_id)
|
||||
*title_id = title_id_str;
|
||||
*title_id = game_id;
|
||||
|
||||
bool found = false;
|
||||
static const std::string maps_directories[] = {File::GetUserPath(D_MAPS_IDX),
|
||||
File::GetSysDirectory() + MAPS_DIR DIR_SEP};
|
||||
for (size_t i = 0; !found && i < ArraySize(maps_directories); ++i)
|
||||
{
|
||||
std::string path = maps_directories[i] + title_id_str + ".map";
|
||||
std::string path = maps_directories[i] + game_id + ".map";
|
||||
if (File::Exists(path))
|
||||
{
|
||||
found = true;
|
||||
@ -270,199 +311,187 @@ bool CBoot::Load_BS2(const std::string& boot_rom_filename)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Third boot step after BootManager and Core. See Call schedule in BootManager.cpp
|
||||
bool CBoot::BootUp()
|
||||
static const DiscIO::Volume* SetDefaultDisc()
|
||||
{
|
||||
SConfig& _StartupPara = SConfig::GetInstance();
|
||||
const SConfig& config = SConfig::GetInstance();
|
||||
// load default image or create virtual drive from directory
|
||||
if (!config.m_strDVDRoot.empty())
|
||||
return SetDisc(DiscIO::CreateVolumeFromDirectory(config.m_strDVDRoot, config.bWii));
|
||||
if (!config.m_strDefaultISO.empty())
|
||||
return SetDisc(DiscIO::CreateVolumeFromFilename(config.m_strDefaultISO));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (_StartupPara.m_BootType == SConfig::BOOT_BS2)
|
||||
NOTICE_LOG(BOOT, "Booting %s", _StartupPara.m_strBootROM.c_str());
|
||||
else
|
||||
NOTICE_LOG(BOOT, "Booting %s", _StartupPara.m_strFilename.c_str());
|
||||
// Third boot step after BootManager and Core. See Call schedule in BootManager.cpp
|
||||
bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
||||
{
|
||||
SConfig& config = SConfig::GetInstance();
|
||||
|
||||
g_symbolDB.Clear();
|
||||
|
||||
// PAL Wii uses NTSC framerate and linecount in 60Hz modes
|
||||
VideoInterface::Preset(DiscIO::IsNTSC(_StartupPara.m_region) ||
|
||||
(_StartupPara.bWii && _StartupPara.bPAL60));
|
||||
VideoInterface::Preset(DiscIO::IsNTSC(config.m_region) || (config.bWii && config.bPAL60));
|
||||
|
||||
switch (_StartupPara.m_BootType)
|
||||
struct BootTitle
|
||||
{
|
||||
case SConfig::BOOT_ISO:
|
||||
{
|
||||
const DiscIO::Volume* volume =
|
||||
SetDisc(DiscIO::CreateVolumeFromFilename(_StartupPara.m_strFilename));
|
||||
|
||||
if (!volume)
|
||||
return false;
|
||||
|
||||
if ((volume->GetVolumeType() == DiscIO::Platform::WII_DISC) != _StartupPara.bWii)
|
||||
BootTitle() : config(SConfig::GetInstance()) {}
|
||||
bool operator()(const BootParameters::Disc& disc) const
|
||||
{
|
||||
PanicAlertT("Warning - starting ISO in wrong console mode!");
|
||||
}
|
||||
NOTICE_LOG(BOOT, "Booting from disc: %s", disc.path.c_str());
|
||||
const DiscIO::Volume* volume = SetDisc(DiscIO::CreateVolumeFromFilename(disc.path));
|
||||
|
||||
_StartupPara.bWii = volume->GetVolumeType() == DiscIO::Platform::WII_DISC;
|
||||
if (!volume)
|
||||
return false;
|
||||
|
||||
// We HLE the bootrom if requested or if LLEing it fails
|
||||
if (_StartupPara.bHLE_BS2 || !Load_BS2(_StartupPara.m_strBootROM))
|
||||
EmulatedBS2(_StartupPara.bWii, volume);
|
||||
if (!EmulatedBS2(config.bWii, volume))
|
||||
return false;
|
||||
|
||||
PatchEngine::LoadPatches();
|
||||
|
||||
// Scan for common HLE functions
|
||||
if (_StartupPara.bHLE_BS2 && !_StartupPara.bEnableDebugging)
|
||||
{
|
||||
PPCAnalyst::FindFunctions(0x80004000, 0x811fffff, &g_symbolDB);
|
||||
SignatureDB db(SignatureDB::HandlerType::DSY);
|
||||
if (db.Load(File::GetSysDirectory() + TOTALDB))
|
||||
// Scan for common HLE functions
|
||||
if (!config.bEnableDebugging)
|
||||
{
|
||||
db.Apply(&g_symbolDB);
|
||||
HLE::PatchFunctions();
|
||||
db.Clear();
|
||||
PPCAnalyst::FindFunctions(0x80004000, 0x811fffff, &g_symbolDB);
|
||||
SignatureDB db(SignatureDB::HandlerType::DSY);
|
||||
if (db.Load(File::GetSysDirectory() + TOTALDB))
|
||||
{
|
||||
db.Apply(&g_symbolDB);
|
||||
HLE::PatchFunctions();
|
||||
db.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Try to load the symbol map if there is one, and then scan it for
|
||||
// and eventually replace code
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to load the symbol map if there is one, and then scan it for
|
||||
// and eventually replace code
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SConfig::BOOT_DOL:
|
||||
{
|
||||
CDolLoader dolLoader(_StartupPara.m_strFilename);
|
||||
if (!dolLoader.IsValid())
|
||||
return false;
|
||||
|
||||
// Check if we have gotten a Wii file or not
|
||||
bool dolWii = dolLoader.IsWii();
|
||||
if (dolWii != _StartupPara.bWii)
|
||||
bool operator()(const BootParameters::Executable& executable) const
|
||||
{
|
||||
PanicAlertT("Warning - starting DOL in wrong console mode!");
|
||||
NOTICE_LOG(BOOT, "Booting from executable: %s", executable.path.c_str());
|
||||
|
||||
// TODO: needs more cleanup.
|
||||
if (executable.type == BootParameters::Executable::Type::DOL)
|
||||
{
|
||||
CDolLoader dolLoader(executable.path);
|
||||
if (!dolLoader.IsValid())
|
||||
return false;
|
||||
|
||||
const DiscIO::Volume* volume = nullptr;
|
||||
if (!config.m_strDVDRoot.empty())
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Setting DVDRoot %s", config.m_strDVDRoot.c_str());
|
||||
volume = SetDisc(DiscIO::CreateVolumeFromDirectory(
|
||||
config.m_strDVDRoot, config.bWii, config.m_strApploader, executable.path));
|
||||
}
|
||||
else if (!config.m_strDefaultISO.empty())
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Loading default ISO %s", config.m_strDefaultISO.c_str());
|
||||
volume = SetDisc(DiscIO::CreateVolumeFromFilename(config.m_strDefaultISO));
|
||||
}
|
||||
|
||||
// Poor man's bootup
|
||||
if (config.bWii)
|
||||
{
|
||||
HID4.SBE = 1;
|
||||
SetupMSR();
|
||||
SetupBAT(config.bWii);
|
||||
|
||||
// Because there is no TMD to get the requested system (IOS) version from,
|
||||
// we default to IOS58, which is the version used by the Homebrew Channel.
|
||||
SetupWiiMemory(volume, 0x000000010000003a);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmulatedBS2_GC(volume, true);
|
||||
}
|
||||
|
||||
Load_FST(config.bWii, volume);
|
||||
dolLoader.Load();
|
||||
PC = dolLoader.GetEntryPoint();
|
||||
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
}
|
||||
|
||||
if (executable.type == BootParameters::Executable::Type::ELF)
|
||||
{
|
||||
const DiscIO::Volume* volume = SetDefaultDisc();
|
||||
|
||||
// Poor man's bootup
|
||||
if (config.bWii)
|
||||
{
|
||||
// Because there is no TMD to get the requested system (IOS) version from,
|
||||
// we default to IOS58, which is the version used by the Homebrew Channel.
|
||||
SetupWiiMemory(volume, 0x000000010000003a);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmulatedBS2_GC(volume, true);
|
||||
}
|
||||
|
||||
Load_FST(config.bWii, volume);
|
||||
if (!Boot_ELF(executable.path))
|
||||
return false;
|
||||
|
||||
// Note: Boot_ELF calls HLE::PatchFunctions()
|
||||
|
||||
UpdateDebugger_MapLoaded();
|
||||
Dolphin_Debugger::AddAutoBreakpoints();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const DiscIO::Volume* volume = nullptr;
|
||||
if (!_StartupPara.m_strDVDRoot.empty())
|
||||
bool operator()(const BootParameters::NAND& nand) const
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Setting DVDRoot %s", _StartupPara.m_strDVDRoot.c_str());
|
||||
volume = SetDisc(DiscIO::CreateVolumeFromDirectory(_StartupPara.m_strDVDRoot, dolWii,
|
||||
_StartupPara.m_strApploader,
|
||||
_StartupPara.m_strFilename));
|
||||
NOTICE_LOG(BOOT, "Booting from NAND: %s", nand.content_path.c_str());
|
||||
SetDefaultDisc();
|
||||
return Boot_WiiWAD(nand.content_path);
|
||||
}
|
||||
else if (!_StartupPara.m_strDefaultISO.empty())
|
||||
|
||||
bool operator()(const BootParameters::IPL& ipl) const
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Loading default ISO %s", _StartupPara.m_strDefaultISO.c_str());
|
||||
volume = SetDisc(DiscIO::CreateVolumeFromFilename(_StartupPara.m_strDefaultISO));
|
||||
NOTICE_LOG(BOOT, "Booting GC IPL: %s", ipl.path.c_str());
|
||||
if (!File::Exists(ipl.path))
|
||||
{
|
||||
if (ipl.disc)
|
||||
PanicAlertT("Cannot start the game, because the GC IPL could not be found.");
|
||||
else
|
||||
PanicAlertT("Cannot find the GC IPL.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Load_BS2(ipl.path))
|
||||
return false;
|
||||
|
||||
if (ipl.disc)
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Inserting disc: %s", ipl.disc->path.c_str());
|
||||
SetDisc(DiscIO::CreateVolumeFromFilename(ipl.disc->path));
|
||||
}
|
||||
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Poor man's bootup
|
||||
if (dolWii)
|
||||
bool operator()(const BootParameters::DFF& dff) const
|
||||
{
|
||||
HID4.SBE = 1;
|
||||
SetupMSR();
|
||||
SetupBAT(dolWii);
|
||||
|
||||
// Because there is no TMD to get the requested system (IOS) version from,
|
||||
// we default to IOS58, which is the version used by the Homebrew Channel.
|
||||
SetupWiiMemory(volume, 0x000000010000003a);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmulatedBS2_GC(volume, true);
|
||||
NOTICE_LOG(BOOT, "Booting DFF: %s", dff.dff_path.c_str());
|
||||
return FifoPlayer::GetInstance().Open(dff.dff_path);
|
||||
}
|
||||
|
||||
Load_FST(dolWii, volume);
|
||||
dolLoader.Load();
|
||||
PC = dolLoader.GetEntryPoint();
|
||||
private:
|
||||
const SConfig& config;
|
||||
};
|
||||
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SConfig::BOOT_ELF:
|
||||
{
|
||||
const DiscIO::Volume* volume = nullptr;
|
||||
|
||||
// load image or create virtual drive from directory
|
||||
if (!_StartupPara.m_strDVDRoot.empty())
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Setting DVDRoot %s", _StartupPara.m_strDVDRoot.c_str());
|
||||
volume =
|
||||
SetDisc(DiscIO::CreateVolumeFromDirectory(_StartupPara.m_strDVDRoot, _StartupPara.bWii));
|
||||
}
|
||||
else if (!_StartupPara.m_strDefaultISO.empty())
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Loading default ISO %s", _StartupPara.m_strDefaultISO.c_str());
|
||||
volume = SetDisc(DiscIO::CreateVolumeFromFilename(_StartupPara.m_strDefaultISO));
|
||||
}
|
||||
|
||||
// Poor man's bootup
|
||||
if (_StartupPara.bWii)
|
||||
{
|
||||
// Because there is no TMD to get the requested system (IOS) version from,
|
||||
// we default to IOS58, which is the version used by the Homebrew Channel.
|
||||
SetupWiiMemory(volume, 0x000000010000003a);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmulatedBS2_GC(volume, true);
|
||||
}
|
||||
|
||||
Load_FST(_StartupPara.bWii, volume);
|
||||
if (!Boot_ELF(_StartupPara.m_strFilename))
|
||||
return false;
|
||||
|
||||
// Note: Boot_ELF calls HLE::PatchFunctions()
|
||||
|
||||
UpdateDebugger_MapLoaded();
|
||||
Dolphin_Debugger::AddAutoBreakpoints();
|
||||
break;
|
||||
}
|
||||
|
||||
case SConfig::BOOT_WII_NAND:
|
||||
Boot_WiiWAD(_StartupPara.m_strFilename);
|
||||
|
||||
PatchEngine::LoadPatches();
|
||||
|
||||
// Not bootstrapped yet, can't translate memory addresses. Thus, prevents Symbol Map usage.
|
||||
// if (LoadMapFromFilename())
|
||||
// HLE::PatchFunctions();
|
||||
|
||||
// load default image or create virtual drive from directory
|
||||
if (!_StartupPara.m_strDVDRoot.empty())
|
||||
SetDisc(DiscIO::CreateVolumeFromDirectory(_StartupPara.m_strDVDRoot, true));
|
||||
else if (!_StartupPara.m_strDefaultISO.empty())
|
||||
SetDisc(DiscIO::CreateVolumeFromFilename(_StartupPara.m_strDefaultISO));
|
||||
|
||||
break;
|
||||
|
||||
// Bootstrap 2 (AKA: Initial Program Loader, "BIOS")
|
||||
case SConfig::BOOT_BS2:
|
||||
{
|
||||
if (!Load_BS2(_StartupPara.m_strBootROM))
|
||||
return false;
|
||||
|
||||
if (LoadMapFromFilename())
|
||||
HLE::PatchFunctions();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SConfig::BOOT_DFF:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
PanicAlertT("Tried to load an unknown file type.");
|
||||
if (!std::visit(BootTitle(), boot->parameters))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PatchEngine::LoadPatches();
|
||||
HLE::PatchFixedFunctions();
|
||||
return true;
|
||||
}
|
||||
|
Reference in New Issue
Block a user