System: Add Error to boot/load/save state

This commit is contained in:
Stenzek
2024-04-11 13:42:10 +10:00
parent f75a5605eb
commit 1b1e42d003
8 changed files with 201 additions and 115 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "system.h"
@ -1164,28 +1164,35 @@ void System::PauseSystem(bool paused)
}
}
bool System::LoadState(const char* filename)
bool System::LoadState(const char* filename, Error* error)
{
if (!IsValid())
{
Error::SetStringView(error, "System is not booted.");
return false;
}
if (Achievements::IsHardcoreModeActive())
{
Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("Achievements", "Loading state"),
[filename = std::string(filename)](bool approved) {
if (approved)
LoadState(filename.c_str());
LoadState(filename.c_str(), nullptr);
});
return false;
return true;
}
Common::Timer load_timer;
std::unique_ptr<ByteStream> stream = ByteStream::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
std::unique_ptr<ByteStream> stream =
ByteStream::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED, error);
if (!stream)
{
Error::AddPrefixFmt(error, "Failed to open '{}': ", Path::GetFileName(filename));
return false;
}
Log_InfoPrintf("Loading state from '%s'...", filename);
Log_InfoFmt("Loading state from '{}'...", filename);
{
const std::string display_name(FileSystem::GetDisplayNameFromPath(filename));
@ -1196,11 +1203,8 @@ bool System::LoadState(const char* filename)
SaveUndoLoadState();
if (!LoadStateFromStream(stream.get(), true))
if (!LoadStateFromStream(stream.get(), error, true))
{
Host::ReportFormattedErrorAsync("Load State Error",
TRANSLATE("OSDMessage", "Loading state from '%s' failed. Resetting."), filename);
if (m_undo_load_state)
UndoLoadState();
@ -1217,7 +1221,7 @@ bool System::LoadState(const char* filename)
return true;
}
bool System::SaveState(const char* filename, bool backup_existing_save)
bool System::SaveState(const char* filename, Error* error, bool backup_existing_save)
{
if (backup_existing_save && FileSystem::FileExists(filename))
{
@ -1229,21 +1233,24 @@ bool System::SaveState(const char* filename, bool backup_existing_save)
Common::Timer save_timer;
std::unique_ptr<ByteStream> stream =
ByteStream::OpenFile(filename, BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
ByteStream::OpenFile(filename,
BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED,
error);
if (!stream)
{
Error::AddPrefixFmt(error, "Failed to save state to '{}': ", Path::GetFileName(filename));
return false;
}
Log_InfoPrintf("Saving state to '%s'...", filename);
const u32 screenshot_size = 256;
const bool result = SaveStateToStream(stream.get(), screenshot_size,
const bool result = SaveStateToStream(stream.get(), error, screenshot_size,
g_settings.compress_save_states ? SAVE_STATE_HEADER::COMPRESSION_TYPE_ZSTD :
SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE);
if (!result)
{
Host::ReportFormattedErrorAsync(TRANSLATE("OSDMessage", "Save State"),
TRANSLATE("OSDMessage", "Saving state to '%s' failed."), filename);
stream->Discard();
}
else
@ -1259,16 +1266,19 @@ bool System::SaveState(const char* filename, bool backup_existing_save)
return result;
}
bool System::SaveResumeState()
bool System::SaveResumeState(Error* error)
{
if (s_running_game_serial.empty())
{
Error::SetStringView(error, "Cannot save resume state without serial.");
return false;
}
const std::string path(GetGameSaveStateFileName(s_running_game_serial, -1));
return SaveState(path.c_str(), false);
return SaveState(path.c_str(), error, false);
}
bool System::BootSystem(SystemBootParameters parameters)
bool System::BootSystem(SystemBootParameters parameters, Error* error)
{
if (!parameters.save_state.empty())
{
@ -1279,9 +1289,9 @@ bool System::BootSystem(SystemBootParameters parameters)
}
if (parameters.filename.empty())
Log_InfoPrintf("Boot Filename: <BIOS/Shell>");
Log_InfoPrint("Boot Filename: <BIOS/Shell>");
else
Log_InfoPrintf("Boot Filename: %s", parameters.filename.c_str());
Log_InfoFmt("Boot Filename: {}", parameters.filename);
Assert(s_state == State::Shutdown);
s_state = State::Starting;
@ -1291,7 +1301,6 @@ bool System::BootSystem(SystemBootParameters parameters)
Host::OnSystemStarting();
// Load CD image up and detect region.
Error error;
std::unique_ptr<CDImage> disc;
DiscRegion disc_region = DiscRegion::NonPS1;
std::string exe_boot;
@ -1317,11 +1326,10 @@ bool System::BootSystem(SystemBootParameters parameters)
else
{
Log_InfoPrintf("Loading CD image '%s'...", parameters.filename.c_str());
disc = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, &error);
disc = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, error);
if (!disc)
{
Host::ReportErrorAsync("Error", fmt::format("Failed to load CD image '{}': {}",
Path::GetFileName(parameters.filename), error.GetDescription()));
Error::AddPrefixFmt(error, "Failed to open CD image '{}':\n", Path::GetFileName(parameters.filename));
s_state = State::Shutdown;
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1357,11 +1365,10 @@ bool System::BootSystem(SystemBootParameters parameters)
Log_InfoPrintf("Console Region: %s", Settings::GetConsoleRegionDisplayName(s_region));
// Switch subimage.
if (disc && parameters.media_playlist_index != 0 && !disc->SwitchSubImage(parameters.media_playlist_index, &error))
if (disc && parameters.media_playlist_index != 0 && !disc->SwitchSubImage(parameters.media_playlist_index, error))
{
Host::ReportErrorAsync("Error",
fmt::format("Failed to switch to subimage {} in '{}': {}", parameters.media_playlist_index,
parameters.filename, error.GetDescription()));
Error::AddPrefixFmt(error, "Failed to switch to subimage {} in '{}':\n", parameters.media_playlist_index,
Path::GetFileName(parameters.filename));
s_state = State::Shutdown;
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1375,8 +1382,8 @@ bool System::BootSystem(SystemBootParameters parameters)
{
if (!FileSystem::FileExists(parameters.override_exe.c_str()) || !IsExeFileName(parameters.override_exe))
{
Host::ReportFormattedErrorAsync("Error", "File '%s' is not a valid executable to boot.",
parameters.override_exe.c_str());
Error::SetStringFmt(error, "File '{}' is not a valid executable to boot.",
Path::GetFileName(parameters.override_exe));
s_state = State::Shutdown;
Host::OnSystemDestroyed();
Host::OnIdleStateChanged();
@ -1410,7 +1417,7 @@ bool System::BootSystem(SystemBootParameters parameters)
if (approved)
{
parameters.disable_achievements_hardcore_mode = true;
BootSystem(std::move(parameters));
BootSystem(std::move(parameters), nullptr);
}
});
cancelled = true;
@ -1462,13 +1469,13 @@ bool System::BootSystem(SystemBootParameters parameters)
// Load EXE late after BIOS.
if (!exe_boot.empty() && !LoadEXE(exe_boot.c_str()))
{
Host::ReportFormattedErrorAsync("Error", "Failed to load EXE file '%s'", exe_boot.c_str());
Error::SetStringFmt(error, "Failed to load EXE file '{}'", Path::GetFileName(exe_boot));
DestroySystem();
return false;
}
else if (!psf_boot.empty() && !PSFLoader::Load(psf_boot.c_str()))
{
Host::ReportFormattedErrorAsync("Error", "Failed to load PSF file '%s'", psf_boot.c_str());
Error::SetStringFmt(error, "Failed to load PSF file '{}'", Path::GetFileName(psf_boot));
DestroySystem();
return false;
}
@ -1509,17 +1516,16 @@ bool System::BootSystem(SystemBootParameters parameters)
if (!parameters.save_state.empty())
{
std::unique_ptr<ByteStream> stream =
ByteStream::OpenFile(parameters.save_state.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
ByteStream::OpenFile(parameters.save_state.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED, error);
if (!stream)
{
Host::ReportErrorAsync(
TRANSLATE("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to load save state file '{}' for booting."), parameters.save_state));
Error::AddPrefixFmt(error, "Failed to load save state file '{}' for booting:\n",
Path::GetFileName(parameters.save_state));
DestroySystem();
return false;
}
if (!LoadStateFromStream(stream.get(), true))
if (!LoadStateFromStream(stream.get(), error, true))
{
DestroySystem();
return false;
@ -1974,13 +1980,16 @@ void System::IncrementInternalFrameNumber()
void System::RecreateSystem()
{
Error error;
Assert(!IsShutdown());
const bool was_paused = System::IsPaused();
std::unique_ptr<ByteStream> stream = ByteStream::CreateGrowableMemoryStream(nullptr, 8 * 1024);
if (!System::SaveStateToStream(stream.get(), 0, SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE) || !stream->SeekAbsolute(0))
if (!System::SaveStateToStream(stream.get(), &error, 0, SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE) ||
!stream->SeekAbsolute(0))
{
Host::ReportErrorAsync("Error", "Failed to save state before system recreation. Shutting down.");
Host::ReportErrorAsync(
"Error", fmt::format("Failed to save state before system recreation. Shutting down:\n", error.GetDescription()));
DestroySystem();
return;
}
@ -1988,13 +1997,13 @@ void System::RecreateSystem()
DestroySystem();
SystemBootParameters boot_params;
if (!BootSystem(std::move(boot_params)))
if (!BootSystem(std::move(boot_params), &error))
{
Host::ReportErrorAsync("Error", "Failed to boot system after recreation.");
Host::ReportErrorAsync("Error", fmt::format("Failed to boot system after recreation:\n{}", error.GetDescription()));
return;
}
if (!LoadStateFromStream(stream.get(), false))
if (!LoadStateFromStream(stream.get(), &error, false))
{
DestroySystem();
return;
@ -2265,36 +2274,35 @@ std::string System::GetMediaPathFromSaveState(const char* path)
return ret;
}
bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ignore_media)
bool System::LoadStateFromStream(ByteStream* state, Error* error, bool update_display, bool ignore_media)
{
Assert(IsValid());
SAVE_STATE_HEADER header;
if (!state->Read2(&header, sizeof(header)))
return false;
if (header.magic != SAVE_STATE_MAGIC)
if (!state->Read2(&header, sizeof(header)) || header.magic != SAVE_STATE_MAGIC)
{
Error::SetStringView(error, "Incorrect file format.");
return false;
}
if (header.version < SAVE_STATE_MINIMUM_VERSION)
{
Host::ReportFormattedErrorAsync(
"Error", TRANSLATE("System", "Save state is incompatible: minimum version is %u but state is version %u."),
Error::SetStringFmt(
error, TRANSLATE_FS("System", "Save state is incompatible: minimum version is {0} but state is version {1}."),
SAVE_STATE_MINIMUM_VERSION, header.version);
return false;
}
if (header.version > SAVE_STATE_VERSION)
{
Host::ReportFormattedErrorAsync(
"Error", TRANSLATE("System", "Save state is incompatible: maximum version is %u but state is version %u."),
Error::SetStringFmt(
error, TRANSLATE_FS("System", "Save state is incompatible: maximum version is {0} but state is version {1}."),
SAVE_STATE_VERSION, header.version);
return false;
}
if (!ignore_media)
{
Error error;
std::string media_filename;
std::unique_ptr<CDImage> media;
if (header.media_filename_length > 0)
@ -2314,7 +2322,9 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ig
}
else
{
media = CDImage::Open(media_filename.c_str(), g_settings.cdrom_load_image_patches, &error);
Error local_error;
media =
CDImage::Open(media_filename.c_str(), g_settings.cdrom_load_image_patches, error ? error : &local_error);
if (!media)
{
if (old_media)
@ -2322,17 +2332,16 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ig
Host::AddOSDMessage(
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to open CD image from save state '{}': {}.\nUsing "
"existing image '{}', this may result in instability."),
media_filename, error.GetDescription(), old_media->GetFileName()),
media_filename, error ? error->GetDescription() : local_error.GetDescription(),
old_media->GetFileName()),
Host::OSD_CRITICAL_ERROR_DURATION);
media = std::move(old_media);
header.media_subimage_index = media->GetCurrentSubImage();
}
else
{
Host::ReportErrorAsync(
TRANSLATE_SV("OSDMessage", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to open CD image '{}' used by save state: {}."),
media_filename, error.GetDescription()));
Error::AddPrefixFmt(error, TRANSLATE_FS("System", "Failed to open CD image '{}' used by save state:\n"),
Path::GetFileName(media_filename));
return false;
}
}
@ -2346,18 +2355,16 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ig
const u32 num_subimages = media->HasSubImages() ? media->GetSubImageCount() : 1;
if (header.media_subimage_index >= num_subimages ||
(media->HasSubImages() && media->GetCurrentSubImage() != header.media_subimage_index &&
!media->SwitchSubImage(header.media_subimage_index, &error)))
!media->SwitchSubImage(header.media_subimage_index, error)))
{
Host::ReportErrorAsync(
TRANSLATE_SV("OSDMessage", "Error"),
fmt::format(
TRANSLATE_FS("System", "Failed to switch to subimage {} in CD image '{}' used by save state: {}."),
header.media_subimage_index + 1u, media_filename, error.GetDescription()));
Error::AddPrefixFmt(
error, TRANSLATE_FS("System", "Failed to switch to subimage {} in CD image '{}' used by save state:\n"),
header.media_subimage_index + 1u, Path::GetFileName(media_filename));
return false;
}
else
{
Log_InfoPrintf("Switched to subimage %u in '%s'", header.media_subimage_index, media_filename.c_str());
Log_InfoFmt("Switched to subimage {} in '{}'", header.media_subimage_index, media_filename.c_str());
}
}
@ -2391,18 +2398,24 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ig
{
StateWrapper sw(state, StateWrapper::Mode::Read, header.version);
if (!DoState(sw, nullptr, update_display, false))
{
Error::SetStringView(error, "Save state stream is corrupted.");
return false;
}
}
else if (header.data_compression_type == SAVE_STATE_HEADER::COMPRESSION_TYPE_ZSTD)
{
std::unique_ptr<ByteStream> dstream(ByteStream::CreateZstdDecompressStream(state, header.data_compressed_size));
StateWrapper sw(dstream.get(), StateWrapper::Mode::Read, header.version);
if (!DoState(sw, nullptr, update_display, false))
{
Error::SetStringView(error, "Save state stream is corrupted.");
return false;
}
}
else
{
Host::ReportFormattedErrorAsync("Error", "Unknown save state compression type %u", header.data_compression_type);
Error::SetStringFmt(error, "Unknown save state compression type {}", header.data_compression_type);
return false;
}
@ -2415,7 +2428,7 @@ bool System::LoadStateFromStream(ByteStream* state, bool update_display, bool ig
return true;
}
bool System::SaveStateToStream(ByteStream* state, u32 screenshot_size /* = 256 */,
bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_size /* = 256 */,
u32 compression_method /* = SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE*/,
bool ignore_media /* = false*/)
{
@ -4210,7 +4223,15 @@ void System::ShutdownSystem(bool save_resume_state)
return;
if (save_resume_state)
SaveResumeState();
{
Error error;
if (!SaveResumeState(&error))
{
Host::ReportErrorAsync(
TRANSLATE_SV("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to save resume state: {}"), error.GetDescription()));
}
}
s_state = State::Stopping;
if (!s_system_executing)
@ -4245,10 +4266,12 @@ bool System::UndoLoadState()
Assert(IsValid());
Error error;
m_undo_load_state->SeekAbsolute(0);
if (!LoadStateFromStream(m_undo_load_state.get(), true))
if (!LoadStateFromStream(m_undo_load_state.get(), &error, true))
{
Host::ReportErrorAsync("Error", "Failed to load undo state, resetting system.");
Host::ReportErrorAsync("Error",
fmt::format("Failed to load undo state, resetting system:\n", error.GetDescription()));
m_undo_load_state.reset();
ResetSystem();
return false;
@ -4264,10 +4287,13 @@ bool System::SaveUndoLoadState()
if (m_undo_load_state)
m_undo_load_state.reset();
Error error;
m_undo_load_state = ByteStream::CreateGrowableMemoryStream(nullptr, System::MAX_SAVE_STATE_SIZE);
if (!SaveStateToStream(m_undo_load_state.get()))
if (!SaveStateToStream(m_undo_load_state.get(), &error))
{
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Failed to save undo load state."), 15.0f);
Host::AddOSDMessage(
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save undo load state:\n{}"), error.GetDescription()),
Host::OSD_CRITICAL_ERROR_DURATION);
m_undo_load_state.reset();
return false;
}