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
No known key found for this signature in database
8 changed files with 201 additions and 115 deletions

View file

@ -923,7 +923,12 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona
if (System::IsValid()) if (System::IsValid())
return; return;
System::BootSystem(std::move(params)); Error error;
if (!System::BootSystem(std::move(params), &error))
{
Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error.GetDescription()));
}
}); });
} }
@ -964,13 +969,7 @@ void FullscreenUI::DoStartFile()
void FullscreenUI::DoStartBIOS() void FullscreenUI::DoStartBIOS()
{ {
Host::RunOnCPUThread([]() { DoStartDisc(std::string());
if (System::IsValid())
return;
SystemBootParameters params;
System::BootSystem(std::move(params));
});
} }
void FullscreenUI::DoStartDisc(std::string path) void FullscreenUI::DoStartDisc(std::string path)
@ -979,9 +978,14 @@ void FullscreenUI::DoStartDisc(std::string path)
if (System::IsValid()) if (System::IsValid())
return; return;
Error error;
SystemBootParameters params; SystemBootParameters params;
params.filename = std::move(path); params.filename = std::move(path);
System::BootSystem(std::move(params)); if (!System::BootSystem(std::move(params), &error))
{
Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error.GetDescription()));
}
}); });
} }
@ -5923,14 +5927,16 @@ void FullscreenUI::DoLoadState(std::string path)
if (System::IsValid()) if (System::IsValid())
{ {
System::LoadState(path.c_str()); Error error;
if (!System::LoadState(path.c_str(), &error))
{
Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to load state: {}"), error.GetDescription()));
}
} }
else else
{ {
SystemBootParameters params; DoStartPath(std::move(boot_path), std::move(path));
params.filename = std::move(boot_path);
params.save_state = std::move(path);
System::BootSystem(std::move(params));
} }
}); });
} }
@ -5944,7 +5950,12 @@ void FullscreenUI::DoSaveState(s32 slot, bool global)
std::string filename(global ? System::GetGlobalSaveStateFileName(slot) : std::string filename(global ? System::GetGlobalSaveStateFileName(slot) :
System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
System::SaveState(filename.c_str(), g_settings.create_save_state_backups); Error error;
if (!System::SaveState(filename.c_str(), &error, g_settings.create_save_state_backups))
{
Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to save state: {}"), error.GetDescription()));
}
}); });
} }

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) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "achievements.h" #include "achievements.h"
@ -18,6 +18,7 @@
#include "util/input_manager.h" #include "util/input_manager.h"
#include "util/postprocessing.h" #include "util/postprocessing.h"
#include "common/error.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "IconsFontAwesome5.h" #include "IconsFontAwesome5.h"
@ -67,7 +68,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot)
if (!global && System::GetGameSerial().empty()) if (!global && System::GetGameSerial().empty())
{ {
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot load state for game without serial."), Host::AddKeyedOSDMessage("LoadState", TRANSLATE_STR("OSDMessage", "Cannot load state for game without serial."),
Host::OSD_ERROR_DURATION); Host::OSD_ERROR_DURATION);
return; return;
} }
@ -77,12 +78,19 @@ static void HotkeyLoadStateSlot(bool global, s32 slot)
if (!FileSystem::FileExists(path.c_str())) if (!FileSystem::FileExists(path.c_str()))
{ {
Host::AddKeyedOSDMessage("LoadState", Host::AddKeyedOSDMessage("LoadState",
fmt::format(TRANSLATE_NOOP("OSDMessage", "No save state found in slot {}."), slot), fmt::format(TRANSLATE_FS("OSDMessage", "No save state found in slot {}."), slot),
Host::OSD_INFO_DURATION); Host::OSD_INFO_DURATION);
return; return;
} }
System::LoadState(path.c_str()); Error error;
if (!System::LoadState(path.c_str(), &error))
{
Host::AddKeyedOSDMessage(
"LoadState",
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"), slot, error.GetDescription()),
Host::OSD_ERROR_DURATION);
}
} }
static void HotkeySaveStateSlot(bool global, s32 slot) static void HotkeySaveStateSlot(bool global, s32 slot)
@ -92,14 +100,21 @@ static void HotkeySaveStateSlot(bool global, s32 slot)
if (!global && System::GetGameSerial().empty()) if (!global && System::GetGameSerial().empty())
{ {
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot save state for game without serial."), Host::AddKeyedOSDMessage("SaveState", TRANSLATE_STR("OSDMessage", "Cannot save state for game without serial."),
Host::OSD_ERROR_DURATION); Host::OSD_ERROR_DURATION);
return; return;
} }
std::string path(global ? System::GetGlobalSaveStateFileName(slot) : std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
System::SaveState(path.c_str(), g_settings.create_save_state_backups); Error error;
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups))
{
Host::AddKeyedOSDMessage(
"SaveState",
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"), slot, error.GetDescription()),
Host::OSD_ERROR_DURATION);
}
} }
#ifndef __ANDROID__ #ifndef __ANDROID__

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) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#define IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS
@ -26,6 +26,7 @@
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/easing.h" #include "common/easing.h"
#include "common/error.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/intrin.h" #include "common/intrin.h"
#include "common/log.h" #include "common/log.h"
@ -1114,7 +1115,14 @@ void SaveStateSelectorUI::LoadCurrentSlot()
{ {
if (FileSystem::FileExists(path.c_str())) if (FileSystem::FileExists(path.c_str()))
{ {
System::LoadState(path.c_str()); Error error;
if (!System::LoadState(path.c_str(), &error))
{
Host::AddKeyedOSDMessage("LoadState",
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"),
GetCurrentSlot(), error.GetDescription()),
Host::OSD_ERROR_DURATION);
}
} }
else else
{ {
@ -1133,7 +1141,16 @@ void SaveStateSelectorUI::LoadCurrentSlot()
void SaveStateSelectorUI::SaveCurrentSlot() void SaveStateSelectorUI::SaveCurrentSlot()
{ {
if (std::string path = GetCurrentSlotPath(); !path.empty()) if (std::string path = GetCurrentSlotPath(); !path.empty())
System::SaveState(path.c_str(), g_settings.create_save_state_backups); {
Error error;
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups))
{
Host::AddKeyedOSDMessage("SaveState",
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
GetCurrentSlot(), error.GetDescription()),
Host::OSD_ERROR_DURATION);
}
}
Close(); Close();
} }

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

View file

@ -241,14 +241,14 @@ void ApplySettings(bool display_osd_messages);
/// Reloads game specific settings, and applys any changes present. /// Reloads game specific settings, and applys any changes present.
bool ReloadGameSettings(bool display_osd_messages); bool ReloadGameSettings(bool display_osd_messages);
bool BootSystem(SystemBootParameters parameters); bool BootSystem(SystemBootParameters parameters, Error* error);
void PauseSystem(bool paused); void PauseSystem(bool paused);
void ResetSystem(); void ResetSystem();
/// Loads state from the specified filename. /// Loads state from the specified filename.
bool LoadState(const char* filename); bool LoadState(const char* filename, Error* error);
bool SaveState(const char* filename, bool backup_existing_save); bool SaveState(const char* filename, Error* error, bool backup_existing_save);
bool SaveResumeState(); bool SaveResumeState(Error* error);
/// Memory save states - only for internal use. /// Memory save states - only for internal use.
struct MemorySaveState struct MemorySaveState
@ -258,8 +258,8 @@ struct MemorySaveState
}; };
bool SaveMemoryState(MemorySaveState* mss); bool SaveMemoryState(MemorySaveState* mss);
bool LoadMemoryState(const MemorySaveState& mss); bool LoadMemoryState(const MemorySaveState& mss);
bool LoadStateFromStream(ByteStream* stream, bool update_display, bool ignore_media = false); bool LoadStateFromStream(ByteStream* stream, Error* error, bool update_display, bool ignore_media = false);
bool SaveStateToStream(ByteStream* state, u32 screenshot_size = 256, u32 compression_method = 0, bool SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_size = 256, u32 compression_method = 0,
bool ignore_media = false); bool ignore_media = false);
/// Runs the VM until the CPU execution is canceled. /// Runs the VM until the CPU execution is canceled.

View file

@ -722,7 +722,12 @@ void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
setInitialState(params->override_fullscreen); setInitialState(params->override_fullscreen);
System::BootSystem(std::move(*params)); Error error;
if (!System::BootSystem(std::move(*params), &error))
{
emit errorReported(tr("Error"),
tr("Failed to boot system: %1").arg(QString::fromStdString(error.GetDescription())));
}
} }
void EmuThread::bootOrLoadState(std::string path) void EmuThread::bootOrLoadState(std::string path)
@ -731,7 +736,12 @@ void EmuThread::bootOrLoadState(std::string path)
if (System::IsValid()) if (System::IsValid())
{ {
System::LoadState(path.c_str()); Error error;
if (!System::LoadState(path.c_str(), &error))
{
emit errorReported(tr("Error"),
tr("Failed to load state: %1").arg(QString::fromStdString(error.GetDescription())));
}
} }
else else
{ {
@ -1241,7 +1251,9 @@ void EmuThread::saveState(const QString& filename, bool block_until_done /* = fa
if (!System::IsValid()) if (!System::IsValid())
return; return;
System::SaveState(filename.toUtf8().data(), g_settings.create_save_state_backups); Error error;
if (!System::SaveState(filename.toUtf8().data(), &error, g_settings.create_save_state_backups))
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
} }
void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = false */) void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = false */)
@ -1256,10 +1268,14 @@ void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = f
if (!global && System::GetGameSerial().empty()) if (!global && System::GetGameSerial().empty())
return; return;
System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) : Error error;
System::GetGameSaveStateFileName(System::GetGameSerial(), slot)) if (!System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) :
.c_str(), System::GetGameSaveStateFileName(System::GetGameSerial(), slot))
g_settings.create_save_state_backups); .c_str(),
&error, g_settings.create_save_state_backups))
{
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
}
} }
void EmuThread::undoLoadState() void EmuThread::undoLoadState()

View file

@ -662,11 +662,12 @@ int main(int argc, char* argv[])
RegTestHost::HookSignals(); RegTestHost::HookSignals();
Error error;
int result = -1; int result = -1;
Log_InfoPrintf("Trying to boot '%s'...", autoboot->filename.c_str()); Log_InfoPrintf("Trying to boot '%s'...", autoboot->filename.c_str());
if (!System::BootSystem(std::move(autoboot.value()))) if (!System::BootSystem(std::move(autoboot.value()), &error))
{ {
Log_ErrorPrint("Failed to boot system."); Log_ErrorFmt("Failed to boot system: {}", error.GetDescription());
goto cleanup; goto cleanup;
} }

View file

@ -71,7 +71,7 @@ std::unique_ptr<CDImage> CDImage::Open(const char* filename, bool allow_patches,
} }
else else
{ {
Log_ErrorPrintf("Invalid filename: '%s'", filename); Error::SetStringFmt(error, "Invalid filename: '{}'", Path::GetFileName(filename));
return nullptr; return nullptr;
} }
} }
@ -110,7 +110,7 @@ std::unique_ptr<CDImage> CDImage::Open(const char* filename, bool allow_patches,
} }
else else
{ {
Log_ErrorPrintf("Unknown extension '%s' from filename '%s'", extension, filename); Error::SetStringFmt(error, "Unknown extension '{}' from filename '{}'", extension, Path::GetFileName(filename));
return nullptr; return nullptr;
} }