diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 6080a25ff..01348b8bc 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -923,7 +923,12 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona if (System::IsValid()) 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() { - Host::RunOnCPUThread([]() { - if (System::IsValid()) - return; - - SystemBootParameters params; - System::BootSystem(std::move(params)); - }); + DoStartDisc(std::string()); } void FullscreenUI::DoStartDisc(std::string path) @@ -979,9 +978,14 @@ void FullscreenUI::DoStartDisc(std::string path) if (System::IsValid()) return; + Error error; SystemBootParameters params; 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()) { - 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 { - SystemBootParameters params; - params.filename = std::move(boot_path); - params.save_state = std::move(path); - System::BootSystem(std::move(params)); + DoStartPath(std::move(boot_path), std::move(path)); } }); } @@ -5944,7 +5950,12 @@ void FullscreenUI::DoSaveState(s32 slot, bool global) std::string filename(global ? System::GetGlobalSaveStateFileName(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())); + } }); } diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index ea076f13a..d1d5b14f3 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "achievements.h" @@ -18,6 +18,7 @@ #include "util/input_manager.h" #include "util/postprocessing.h" +#include "common/error.h" #include "common/file_system.h" #include "IconsFontAwesome5.h" @@ -67,7 +68,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) 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); return; } @@ -77,12 +78,19 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) if (!FileSystem::FileExists(path.c_str())) { 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); 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) @@ -92,14 +100,21 @@ static void HotkeySaveStateSlot(bool global, s32 slot) 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); return; } std::string path(global ? System::GetGlobalSaveStateFileName(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__ diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 3961081c7..c5d0d1625 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #define IMGUI_DEFINE_MATH_OPERATORS @@ -26,6 +26,7 @@ #include "common/align.h" #include "common/assert.h" #include "common/easing.h" +#include "common/error.h" #include "common/file_system.h" #include "common/intrin.h" #include "common/log.h" @@ -1114,7 +1115,14 @@ void SaveStateSelectorUI::LoadCurrentSlot() { 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 { @@ -1133,7 +1141,16 @@ void SaveStateSelectorUI::LoadCurrentSlot() void SaveStateSelectorUI::SaveCurrentSlot() { 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(); } diff --git a/src/core/system.cpp b/src/core/system.cpp index 26977fbe3..30a5663b2 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // 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 stream = ByteStream::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED); + std::unique_ptr 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 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: "); + Log_InfoPrint("Boot Filename: "); 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 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 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 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 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 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; } diff --git a/src/core/system.h b/src/core/system.h index 46574d962..02ee40ab3 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -241,14 +241,14 @@ void ApplySettings(bool display_osd_messages); /// Reloads game specific settings, and applys any changes present. bool ReloadGameSettings(bool display_osd_messages); -bool BootSystem(SystemBootParameters parameters); +bool BootSystem(SystemBootParameters parameters, Error* error); void PauseSystem(bool paused); void ResetSystem(); /// Loads state from the specified filename. -bool LoadState(const char* filename); -bool SaveState(const char* filename, bool backup_existing_save); -bool SaveResumeState(); +bool LoadState(const char* filename, Error* error); +bool SaveState(const char* filename, Error* error, bool backup_existing_save); +bool SaveResumeState(Error* error); /// Memory save states - only for internal use. struct MemorySaveState @@ -258,8 +258,8 @@ struct MemorySaveState }; bool SaveMemoryState(MemorySaveState* mss); bool LoadMemoryState(const MemorySaveState& mss); -bool LoadStateFromStream(ByteStream* stream, bool update_display, bool ignore_media = false); -bool SaveStateToStream(ByteStream* state, u32 screenshot_size = 256, u32 compression_method = 0, +bool LoadStateFromStream(ByteStream* stream, Error* error, bool update_display, bool ignore_media = false); +bool SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_size = 256, u32 compression_method = 0, bool ignore_media = false); /// Runs the VM until the CPU execution is canceled. diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index ef2e346a6..6d3a4e07b 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -722,7 +722,12 @@ void EmuThread::bootSystem(std::shared_ptr params) 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) @@ -731,7 +736,12 @@ void EmuThread::bootOrLoadState(std::string path) 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 { @@ -1241,7 +1251,9 @@ void EmuThread::saveState(const QString& filename, bool block_until_done /* = fa if (!System::IsValid()) 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 */) @@ -1256,10 +1268,14 @@ void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = f if (!global && System::GetGameSerial().empty()) return; - System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetGameSerial(), slot)) - .c_str(), - g_settings.create_save_state_backups); + Error error; + if (!System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) : + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)) + .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() diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index daa290bef..e1a65220f 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -662,11 +662,12 @@ int main(int argc, char* argv[]) RegTestHost::HookSignals(); + Error error; int result = -1; 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; } diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index f3f6a10be..339df124c 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -71,7 +71,7 @@ std::unique_ptr CDImage::Open(const char* filename, bool allow_patches, } else { - Log_ErrorPrintf("Invalid filename: '%s'", filename); + Error::SetStringFmt(error, "Invalid filename: '{}'", Path::GetFileName(filename)); return nullptr; } } @@ -110,7 +110,7 @@ std::unique_ptr CDImage::Open(const char* filename, bool allow_patches, } else { - Log_ErrorPrintf("Unknown extension '%s' from filename '%s'", extension, filename); + Error::SetStringFmt(error, "Unknown extension '{}' from filename '{}'", extension, Path::GetFileName(filename)); return nullptr; }