diff --git a/src/core/memory_card_image.cpp b/src/core/memory_card_image.cpp index ed27affae..635b71075 100644 --- a/src/core/memory_card_image.cpp +++ b/src/core/memory_card_image.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 "memory_card_image.h" @@ -10,6 +10,7 @@ #include "common/bitutils.h" #include "common/byte_stream.h" +#include "common/error.h" #include "common/file_system.h" #include "common/log.h" #include "common/path.h" @@ -87,12 +88,13 @@ const T* GetFramePtr(const DataArray& data, u32 block, u32 frame) } static std::optional GetNextFreeBlock(const DataArray& data); -static bool ImportCardMCD(DataArray* data, const char* filename, std::vector file_data); -static bool ImportCardGME(DataArray* data, const char* filename, std::vector file_data); -static bool ImportCardVGS(DataArray* data, const char* filename, std::vector file_data); -static bool ImportCardPSX(DataArray* data, const char* filename, std::vector file_data); -static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd); -static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd); +static bool ImportCardMCD(DataArray* data, const char* filename, std::vector file_data, Error* error); +static bool ImportCardGME(DataArray* data, const char* filename, std::vector file_data, Error* error); +static bool ImportCardVGS(DataArray* data, const char* filename, std::vector file_data, Error* error); +static bool ImportCardPSX(DataArray* data, const char* filename, std::vector file_data, Error* error); +static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd, + Error* error); +static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd, Error* error); } // namespace MemoryCardImage bool MemoryCardImage::LoadFromFile(DataArray* data, const char* filename) @@ -310,7 +312,7 @@ std::vector MemoryCardImage::EnumerateFiles(const Dat return files; } -bool MemoryCardImage::ReadFile(const DataArray& data, const FileInfo& fi, std::vector* buffer) +bool MemoryCardImage::ReadFile(const DataArray& data, const FileInfo& fi, std::vector* buffer, Error* error) { buffer->resize(fi.num_blocks * BLOCK_SIZE); @@ -327,18 +329,21 @@ bool MemoryCardImage::ReadFile(const DataArray& data, const FileInfo& fi, std::v return true; } -bool MemoryCardImage::WriteFile(DataArray* data, const std::string_view& filename, const std::vector& buffer) +bool MemoryCardImage::WriteFile(DataArray* data, const std::string_view& filename, const std::vector& buffer, + Error* error) { if (buffer.empty()) { - Log_ErrorPrint("Failed to write file to memory card: buffer is empty"); + Error::SetStringView(error, "Buffer is empty."); return false; } + const u32 free_block_count = GetFreeBlockCount(*data); const u32 num_blocks = (static_cast(buffer.size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE; - if (GetFreeBlockCount(*data) < num_blocks) + if (free_block_count < num_blocks) { - Log_ErrorPrint("Failed to write file to memory card: insufficient free blocks"); + Error::SetStringFmt(error, "Insufficient free blocks, {} blocks are needed, but only have {}.", num_blocks, + free_block_count); return false; } @@ -482,11 +487,12 @@ bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi) return true; } -bool MemoryCardImage::ImportCardMCD(DataArray* data, const char* filename, std::vector file_data) +bool MemoryCardImage::ImportCardMCD(DataArray* data, const char* filename, std::vector file_data, Error* error) { if (file_data.size() != DATA_SIZE) { - Log_ErrorFmt("Failed to import memory card from '{}': file is incorrect size.", filename); + Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", static_cast(DATA_SIZE), + file_data.size()); return false; } @@ -494,7 +500,7 @@ bool MemoryCardImage::ImportCardMCD(DataArray* data, const char* filename, std:: return true; } -bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std::vector file_data) +bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std::vector file_data, Error* error) { #pragma pack(push, 1) struct GMEHeader @@ -510,9 +516,12 @@ bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std:: static_assert(sizeof(GMEHeader) == 0xF40); #pragma pack(pop) - if (file_data.size() < (sizeof(GMEHeader) + BLOCK_SIZE)) + constexpr u32 MIN_SIZE = sizeof(GMEHeader) + BLOCK_SIZE; + + if (file_data.size() < MIN_SIZE) { - Log_ErrorFmt("Failed to import GME memory card from '{}': file is incorrect size.", filename); + Error::SetStringFmt(error, "File is incorrect size, expected at least {} bytes, got {} bytes.", MIN_SIZE, + file_data.size()); return false; } @@ -530,20 +539,22 @@ bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std:: return true; } -bool MemoryCardImage::ImportCardVGS(DataArray* data, const char* filename, std::vector file_data) +bool MemoryCardImage::ImportCardVGS(DataArray* data, const char* filename, std::vector file_data, Error* error) { constexpr u32 HEADER_SIZE = 64; + constexpr u32 EXPECTED_SIZE = HEADER_SIZE + DATA_SIZE; - if (file_data.size() != (HEADER_SIZE + DATA_SIZE)) + if (file_data.size() != EXPECTED_SIZE) { - Log_ErrorFmt("Failed to import memory card from '{}': file is incorrect size.", filename); + Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", EXPECTED_SIZE, + file_data.size()); return false; } // Connectix Virtual Game Station format (.MEM): "VgsM", 64 bytes if (file_data[0] != 'V' || file_data[1] != 'g' || file_data[2] != 's' || file_data[3] != 'M') { - Log_ErrorFmt("Failed to import memory card from '{}': incorrect header.", filename); + Error::SetStringView(error, "Incorrect header."); return false; } @@ -551,20 +562,22 @@ bool MemoryCardImage::ImportCardVGS(DataArray* data, const char* filename, std:: return true; } -bool MemoryCardImage::ImportCardPSX(DataArray* data, const char* filename, std::vector file_data) +bool MemoryCardImage::ImportCardPSX(DataArray* data, const char* filename, std::vector file_data, Error* error) { constexpr u32 HEADER_SIZE = 256; + constexpr u32 EXPECTED_SIZE = HEADER_SIZE + DATA_SIZE; - if (file_data.size() != (HEADER_SIZE + DATA_SIZE)) + if (file_data.size() != EXPECTED_SIZE) { - Log_ErrorFmt("Failed to import memory card from '{}': file is incorrect size.", filename); + Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", EXPECTED_SIZE, + file_data.size()); return false; } // Connectix Virtual Game Station format (.MEM): "VgsM", 64 bytes if (file_data[0] != 'P' || file_data[1] != 'S' || file_data[2] != 'V') { - Log_ErrorFmt("Failed to import memory card from '{}': incorrect header.", filename); + Error::SetStringView(error, "Incorrect header."); return false; } @@ -572,76 +585,72 @@ bool MemoryCardImage::ImportCardPSX(DataArray* data, const char* filename, std:: return true; } -bool MemoryCardImage::ImportCard(DataArray* data, const char* filename, std::vector file_data) +bool MemoryCardImage::ImportCard(DataArray* data, const char* filename, std::vector file_data, Error* error) { - const char* extension = std::strrchr(filename, '.'); - if (!extension) + const std::string_view extension = Path::GetExtension(filename); + if (extension.empty()) { - Log_ErrorFmt("Failed to import memory card from '{}': missing extension?", filename); + Error::SetStringFmt(error, "File must have an extension."); return false; } - if (StringUtil::Strcasecmp(extension, ".mcd") == 0 || StringUtil::Strcasecmp(extension, ".mcr") == 0 || - StringUtil::Strcasecmp(extension, ".mc") == 0 || StringUtil::Strcasecmp(extension, ".srm") == 0 || - StringUtil::Strcasecmp(extension, ".psm") == 0 || StringUtil::Strcasecmp(extension, ".ps") == 0 || - StringUtil::Strcasecmp(extension, ".ddf") == 0) + if (StringUtil::EqualNoCase(extension, "mcd") || StringUtil::EqualNoCase(extension, "mcr") || + StringUtil::EqualNoCase(extension, "mc") || StringUtil::EqualNoCase(extension, "srm") || + StringUtil::EqualNoCase(extension, "psm") || StringUtil::EqualNoCase(extension, "ps") || + StringUtil::EqualNoCase(extension, "ddf")) { - return ImportCardMCD(data, filename, std::move(file_data)); + return ImportCardMCD(data, filename, std::move(file_data), error); } - else if (StringUtil::Strcasecmp(extension, ".gme") == 0) + else if (StringUtil::EqualNoCase(extension, "gme")) { - return ImportCardGME(data, filename, std::move(file_data)); + return ImportCardGME(data, filename, std::move(file_data), error); } - else if (StringUtil::Strcasecmp(extension, ".mem") == 0 || StringUtil::Strcasecmp(extension, ".vgs") == 0) + else if (StringUtil::EqualNoCase(extension, "mem") || StringUtil::EqualNoCase(extension, "vgs")) { - return ImportCardVGS(data, filename, std::move(file_data)); + return ImportCardVGS(data, filename, std::move(file_data), error); } - else if (StringUtil::Strcasecmp(extension, ".psx") == 0) + else if (StringUtil::EqualNoCase(extension, "psx")) { - return ImportCardPSX(data, filename, std::move(file_data)); + return ImportCardPSX(data, filename, std::move(file_data), error); } else { - Log_ErrorFmt("Failed to import memory card from '{}': unknown extension?", filename); + Error::SetStringFmt(error, "Unknown extension '{}'.", extension); return false; } } -bool MemoryCardImage::ImportCard(DataArray* data, const char* filename) +bool MemoryCardImage::ImportCard(DataArray* data, const char* filename, Error* error) { - std::optional> file_data = FileSystem::ReadBinaryFile(filename); + std::optional> file_data = FileSystem::ReadBinaryFile(filename, error); if (!file_data.has_value()) return false; - return ImportCard(data, filename, std::move(file_data.value())); + return ImportCard(data, filename, std::move(file_data.value()), error); } -bool MemoryCardImage::ExportSave(DataArray* data, const FileInfo& fi, const char* filename) +bool MemoryCardImage::ExportSave(DataArray* data, const FileInfo& fi, const char* filename, Error* error) { std::unique_ptr stream = - ByteStream::OpenFile(filename, BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_TRUNCATE | BYTESTREAM_OPEN_WRITE | - BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED); + ByteStream::OpenFile(filename, + BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_TRUNCATE | BYTESTREAM_OPEN_WRITE | + BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED, + error); if (!stream) - { - Log_ErrorFmt("Failed to open '{}' for writing.", filename); return false; - } DirectoryFrame* df_ptr = GetFramePtr(data, 0, fi.first_block); std::vector header = std::vector(static_cast(FRAME_SIZE)); std::memcpy(header.data(), df_ptr, sizeof(*df_ptr)); std::vector blocks; - if (!ReadFile(*data, fi, &blocks)) - { - Log_ErrorPrint("Failed to read save blocks from memory card data"); + if (!ReadFile(*data, fi, &blocks, error)) return false; - } if (!stream->Write(header.data(), static_cast(header.size())) || !stream->Write(blocks.data(), static_cast(blocks.size())) || !stream->Commit()) { - Log_ErrorFmt("Failed to write exported save to '{}'", filename); + Error::SetStringView(error, "Failed to write exported save."); stream->Discard(); return false; } @@ -650,47 +659,45 @@ bool MemoryCardImage::ExportSave(DataArray* data, const FileInfo& fi, const char } bool MemoryCardImage::ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, - const FILESYSTEM_STAT_DATA& sd) + const FILESYSTEM_STAT_DATA& sd, Error* error) { // Make sure the size of the actual file is valid if (sd.Size <= FRAME_SIZE || (sd.Size - FRAME_SIZE) % BLOCK_SIZE != 0u || (sd.Size - FRAME_SIZE) / BLOCK_SIZE > 15u) { - Log_ErrorFmt("Invalid size for save file '{}'", filename); + Error::SetStringView(error, "Invalid size for save file."); return false; } - 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) - { - Log_ErrorFmt("Failed to open '{}' for reading", filename); return false; - } DirectoryFrame df; if (stream->Read(&df, FRAME_SIZE) != FRAME_SIZE) { - Log_ErrorFmt("Failed to read directory frame from '{}'", filename); + Error::SetStringView(error, "Failed to read directory frame."); return false; } // Make sure the size reported by the directory frame is valid if (df.file_size < BLOCK_SIZE || df.file_size % BLOCK_SIZE != 0 || df.file_size / BLOCK_SIZE > 15u) { - Log_ErrorFmt("Invalid size ({} bytes) reported by directory frame", df.file_size); + Error::SetStringFmt(error, "Invalid size ({} bytes) reported by directory frame.", df.file_size); return false; } std::vector blocks = std::vector(static_cast(df.file_size)); if (stream->Read(blocks.data(), df.file_size) != df.file_size) { - Log_ErrorFmt("Failed to read block bytes from '{}'", filename); + Error::SetStringView(error, "Failed to read block bytes."); return false; } const u32 num_blocks = (static_cast(blocks.size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE; if (GetFreeBlockCount(*data) < num_blocks) { - Log_ErrorPrint("Failed to write file to memory card: insufficient free blocks"); + Error::SetStringView(error, "Insufficient free blocks."); return false; } @@ -702,7 +709,7 @@ bool MemoryCardImage::ImportSaveWithDirectoryFrame(DataArray* data, const char* { if (!fi.deleted) { - Log_ErrorFmt("Save file with the same name '{}' already exists in memory card", fi.filename.c_str()); + Error::SetStringFmt(error, "Save file with the same name '{}' already exists in memory card", fi.filename); return false; } @@ -710,33 +717,32 @@ bool MemoryCardImage::ImportSaveWithDirectoryFrame(DataArray* data, const char* } } - return WriteFile(data, df.filename, blocks); + return WriteFile(data, df.filename, blocks, error); } -bool MemoryCardImage::ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd) +bool MemoryCardImage::ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd, Error* error) { - const std::string display_name(FileSystem::GetDisplayNameFromPath(filename)); + const std::string display_name = FileSystem::GetDisplayNameFromPath(filename); std::string save_name(Path::GetFileTitle(filename)); if (save_name.length() == 0) { - Log_ErrorFmt("Invalid filename: '{}'", filename); + Error::SetStringView(error, "Invalid filename."); return false; } if (save_name.length() > DirectoryFrame::FILE_NAME_LENGTH) save_name.erase(DirectoryFrame::FILE_NAME_LENGTH); - std::optional> blocks = FileSystem::ReadBinaryFile(filename); + std::optional> blocks = FileSystem::ReadBinaryFile(filename, error); if (!blocks.has_value()) - { - Log_ErrorFmt("Failed to read '{}'", filename); return false; - } + const u32 free_block_count = GetFreeBlockCount(*data); const u32 num_blocks = (static_cast(blocks->size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE; - if (GetFreeBlockCount(*data) < num_blocks) + if (free_block_count < num_blocks) { - Log_ErrorPrint("Failed to write file to memory card: insufficient free blocks"); + Error::SetStringFmt(error, "Insufficient free blocks, needs {} blocks, but only have {}.", num_blocks, + free_block_count); return false; } @@ -748,7 +754,7 @@ bool MemoryCardImage::ImportRawSave(DataArray* data, const char* filename, const { if (!fi.deleted) { - Log_ErrorFmt("Save file with the same name '{}' already exists in memory card", fi.filename); + Error::SetStringFmt(error, "Save file with the same name '{}' already exists in memory card.", fi.filename); return false; } @@ -756,36 +762,30 @@ bool MemoryCardImage::ImportRawSave(DataArray* data, const char* filename, const } } - return WriteFile(data, save_name, blocks.value()); + return WriteFile(data, save_name, blocks.value(), error); } -bool MemoryCardImage::ImportSave(DataArray* data, const char* filename) +bool MemoryCardImage::ImportSave(DataArray* data, const char* filename, Error* error) { - FILESYSTEM_STAT_DATA sd; - if (!FileSystem::StatFile(filename, &sd)) - { - Log_ErrorFmt("Failed to stat file '{}'", filename); - return false; - } - // Make sure the size of the actual file is valid - if (sd.Size == 0) + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(filename, &sd) || sd.Size == 0) { - Log_ErrorFmt("Invalid size for save file '{}'", filename); + Error::SetStringView(error, "File does not exist, or is empty."); return false; } - if (std::string_view(filename).ends_with(".mcs")) + if (StringUtil::EqualNoCase(Path::GetExtension(filename), "mcs")) { - return ImportSaveWithDirectoryFrame(data, filename, sd); + return ImportSaveWithDirectoryFrame(data, filename, sd, error); } else if (sd.Size > 0 && sd.Size < DATA_SIZE && (sd.Size % BLOCK_SIZE) == 0) { - return ImportRawSave(data, filename, sd); + return ImportRawSave(data, filename, sd, error); } else { - Log_ErrorFmt("Unknown save format for '{}'", filename); + Error::SetStringView(error, "Unknown save format."); return false; } } diff --git a/src/core/memory_card_image.h b/src/core/memory_card_image.h index 69683371d..44315986d 100644 --- a/src/core/memory_card_image.h +++ b/src/core/memory_card_image.h @@ -1,15 +1,15 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once -#include "common/bitfield.h" -#include "controller.h" + #include -#include #include #include #include +class Error; + namespace MemoryCardImage { enum : u32 { @@ -50,12 +50,12 @@ struct FileInfo bool IsValid(const DataArray& data); u32 GetFreeBlockCount(const DataArray& data); std::vector EnumerateFiles(const DataArray& data, bool include_deleted); -bool ReadFile(const DataArray& data, const FileInfo& fi, std::vector* buffer); -bool WriteFile(DataArray* data, const std::string_view& filename, const std::vector& buffer); +bool ReadFile(const DataArray& data, const FileInfo& fi, std::vector* buffer, Error* error); +bool WriteFile(DataArray* data, const std::string_view& filename, const std::vector& buffer, Error* error); bool DeleteFile(DataArray* data, const FileInfo& fi, bool clear_sectors); bool UndeleteFile(DataArray* data, const FileInfo& fi); -bool ImportCard(DataArray* data, const char* filename); -bool ImportCard(DataArray* data, const char* filename, std::vector file_data); -bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename); -bool ImportSave(DataArray* data, const char* filename); +bool ImportCard(DataArray* data, const char* filename, Error* error); +bool ImportCard(DataArray* data, const char* filename, std::vector file_data, Error* error); +bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename, Error* error); +bool ImportSave(DataArray* data, const char* filename, Error* error); } // namespace MemoryCardImage diff --git a/src/duckstation-qt/memorycardeditorwindow.cpp b/src/duckstation-qt/memorycardeditorwindow.cpp index f33cabab7..4ef231212 100644 --- a/src/duckstation-qt/memorycardeditorwindow.cpp +++ b/src/duckstation-qt/memorycardeditorwindow.cpp @@ -8,6 +8,7 @@ #include "core/settings.h" #include "common/assert.h" +#include "common/error.h" #include "common/file_system.h" #include "common/path.h" #include "common/string_util.h" @@ -424,16 +425,23 @@ void MemoryCardEditorWindow::doCopyFile() return; } + Error error; std::vector buffer; - if (!MemoryCardImage::ReadFile(src->data, *fi, &buffer)) + if (!MemoryCardImage::ReadFile(src->data, *fi, &buffer, &error)) { - QMessageBox::critical(this, tr("Error"), tr("Failed to read file %1").arg(QString::fromStdString(fi->filename))); + QMessageBox::critical(this, tr("Error"), + tr("Failed to read file %1:\n%2") + .arg(QString::fromStdString(fi->filename)) + .arg(QString::fromStdString(error.GetDescription()))); return; } - if (!MemoryCardImage::WriteFile(&dst->data, fi->filename, buffer)) + if (!MemoryCardImage::WriteFile(&dst->data, fi->filename, buffer, &error)) { - QMessageBox::critical(this, tr("Error"), tr("Failed to write file %1").arg(QString::fromStdString(fi->filename))); + QMessageBox::critical(this, tr("Error"), + tr("Failed to write file %1:\n%2") + .arg(QString::fromStdString(fi->filename)) + .arg(QString::fromStdString(error.GetDescription()))); return; } @@ -497,11 +505,13 @@ void MemoryCardEditorWindow::doExportSaveFile() if (!fi) return; - if (!MemoryCardImage::ExportSave(&card->data, *fi, filename.toStdString().c_str())) + Error error; + if (!MemoryCardImage::ExportSave(&card->data, *fi, filename.toStdString().c_str(), &error)) { - QMessageBox::critical( - this, tr("Error"), - tr("Failed to export save file %1. Check the log for more details.").arg(QString::fromStdString(fi->filename))); + QMessageBox::critical(this, tr("Error"), + tr("Failed to export save file %1:\n%2") + .arg(QString::fromStdString(fi->filename)) + .arg(QString::fromStdString(error.GetDescription()))); return; } } @@ -515,10 +525,14 @@ void MemoryCardEditorWindow::importCard(Card* card) if (filename.isEmpty()) return; + Error error; std::unique_ptr temp = std::make_unique(); - if (!MemoryCardImage::ImportCard(temp.get(), filename.toStdString().c_str())) + if (!MemoryCardImage::ImportCard(temp.get(), filename.toStdString().c_str(), &error)) { - QMessageBox::critical(this, tr("Error"), tr("Failed to import memory card. The log may contain more information.")); + QMessageBox::critical(this, tr("Error"), + tr("Failed to import memory card from %1:\n%2") + .arg(QFileInfo(filename).fileName()) + .arg(QString::fromStdString(error.GetDescription()))); return; } @@ -557,16 +571,18 @@ void MemoryCardEditorWindow::formatCard(Card* card) void MemoryCardEditorWindow::importSaveFile(Card* card) { QString filename = QDir::toNativeSeparators( - QFileDialog::getOpenFileName(this, tr("Select Import Save File"), QString(), tr(SINGLE_SAVEFILE_FILTER))); + QFileDialog::getOpenFileName(this, tr("Select Save File"), QString(), tr(SINGLE_SAVEFILE_FILTER))); if (filename.isEmpty()) return; - if (!MemoryCardImage::ImportSave(&card->data, filename.toStdString().c_str())) + Error error; + if (!MemoryCardImage::ImportSave(&card->data, filename.toStdString().c_str(), &error)) { QMessageBox::critical(this, tr("Error"), - tr("Failed to import save. Check if there is enough room on the memory card or if an " - "existing save with the same name already exists.")); + tr("Failed to import save from %1:\n%2") + .arg(QFileInfo(filename).fileName()) + .arg(QString::fromStdString(error.GetDescription()))); return; }