duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

memory_card_image.cpp (24287B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "memory_card_image.h"
      5 #include "gpu_types.h"
      6 #include "system.h"
      7 
      8 #include "util/shiftjis.h"
      9 #include "util/state_wrapper.h"
     10 
     11 #include "common/bitutils.h"
     12 #include "common/error.h"
     13 #include "common/file_system.h"
     14 #include "common/log.h"
     15 #include "common/path.h"
     16 #include "common/string_util.h"
     17 
     18 #include <algorithm>
     19 #include <cstdio>
     20 #include <optional>
     21 
     22 Log_SetChannel(MemoryCard);
     23 
     24 namespace MemoryCardImage {
     25 namespace {
     26 
     27 #pragma pack(push, 1)
     28 
     29 struct DirectoryFrame
     30 {
     31   enum : u32
     32   {
     33     FILE_NAME_LENGTH = 20
     34   };
     35 
     36   u32 block_allocation_state;
     37   u32 file_size;
     38   u16 next_block_number;
     39   char filename[FILE_NAME_LENGTH + 1];
     40   u8 zero_pad_1;
     41   u8 pad_2[95];
     42   u8 checksum;
     43 };
     44 
     45 static_assert(sizeof(DirectoryFrame) == FRAME_SIZE);
     46 
     47 struct TitleFrame
     48 {
     49   char id[2];
     50   u8 icon_flag;
     51   u8 unk_block_number;
     52   u8 title[64];
     53   u8 pad_1[12];
     54   u8 pad_2[16];
     55   u16 icon_palette[16];
     56 };
     57 
     58 static_assert(sizeof(TitleFrame) == FRAME_SIZE);
     59 
     60 #pragma pack(pop)
     61 
     62 } // namespace
     63 
     64 static u8 GetChecksum(const u8* frame)
     65 {
     66   u8 checksum = frame[0];
     67   for (u32 i = 1; i < FRAME_SIZE - 1; i++)
     68     checksum ^= frame[i];
     69   return checksum;
     70 }
     71 
     72 static void UpdateChecksum(DirectoryFrame* df)
     73 {
     74   df->checksum = GetChecksum(reinterpret_cast<u8*>(df));
     75 }
     76 
     77 template<typename T>
     78 T* GetFramePtr(DataArray* data, u32 block, u32 frame)
     79 {
     80   return reinterpret_cast<T*>(data->data() + (block * BLOCK_SIZE) + (frame * FRAME_SIZE));
     81 }
     82 
     83 template<typename T>
     84 const T* GetFramePtr(const DataArray& data, u32 block, u32 frame)
     85 {
     86   return reinterpret_cast<const T*>(&data[(block * BLOCK_SIZE) + (frame * FRAME_SIZE)]);
     87 }
     88 
     89 static std::optional<u32> GetNextFreeBlock(const DataArray& data);
     90 static bool ImportCardMCD(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error);
     91 static bool ImportCardGME(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error);
     92 static bool ImportCardVGS(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error);
     93 static bool ImportCardPSX(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error);
     94 static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd,
     95                                          Error* error);
     96 static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd, Error* error);
     97 } // namespace MemoryCardImage
     98 
     99 bool MemoryCardImage::LoadFromFile(DataArray* data, const char* filename, Error* error)
    100 {
    101   FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(filename, "rb", error);
    102   if (!fp)
    103     return false;
    104 
    105   const s64 size = FileSystem::FSize64(fp.get());
    106   if (size != static_cast<s64>(DATA_SIZE))
    107   {
    108     ERROR_LOG("Memory card {} is incorrect size (expected {} got {})", Path::GetFileName(filename),
    109               static_cast<u32>(DATA_SIZE), size);
    110     return false;
    111   }
    112 
    113   const size_t num_read = std::fread(data->data(), 1, DATA_SIZE, fp.get());
    114   if (num_read != DATA_SIZE)
    115   {
    116     ERROR_LOG("Only read {} of {} sectors from '{}'", num_read / FRAME_SIZE, static_cast<u32>(NUM_FRAMES), filename);
    117     return false;
    118   }
    119 
    120   VERBOSE_LOG("Loaded memory card from {}", filename);
    121   return true;
    122 }
    123 
    124 bool MemoryCardImage::SaveToFile(const DataArray& data, const char* filename, Error* error)
    125 {
    126   Error local_error;
    127   if (!FileSystem::WriteAtomicRenamedFile(filename, data.data(), data.size(), error ? error : &local_error))
    128     [[unlikely]]
    129   {
    130     ERROR_LOG("Failed to save memory card '{}': {}", Path::GetFileName(filename),
    131               (error ? error : &local_error)->GetDescription());
    132     return false;
    133   }
    134 
    135   return true;
    136 }
    137 
    138 bool MemoryCardImage::IsValid(const DataArray& data)
    139 {
    140   // TODO: Check checksum?
    141   const u8* fptr = GetFramePtr<u8>(data, 0, 0);
    142   return fptr[0] == 'M' && fptr[1] == 'C';
    143 }
    144 
    145 void MemoryCardImage::Format(DataArray* data)
    146 {
    147   // fill everything with FF
    148   data->fill(u8(0xFF));
    149 
    150   // header
    151   {
    152     u8* fptr = GetFramePtr<u8>(data, 0, 0);
    153     std::fill_n(fptr, FRAME_SIZE, u8(0));
    154     fptr[0] = 'M';
    155     fptr[1] = 'C';
    156     fptr[0x7F] = GetChecksum(fptr);
    157   }
    158 
    159   // directory
    160   for (u32 frame = 1; frame < 16; frame++)
    161   {
    162     u8* fptr = GetFramePtr<u8>(data, 0, frame);
    163     std::fill_n(fptr, FRAME_SIZE, u8(0));
    164     fptr[0] = 0xA0;                 // free
    165     fptr[8] = 0xFF;                 // pointer to next file
    166     fptr[9] = 0xFF;                 // pointer to next file
    167     fptr[0x7F] = GetChecksum(fptr); // checksum
    168   }
    169 
    170   // broken sector list
    171   for (u32 frame = 16; frame < 36; frame++)
    172   {
    173     u8* fptr = GetFramePtr<u8>(data, 0, frame);
    174     std::fill_n(fptr, FRAME_SIZE, u8(0));
    175     fptr[0] = 0xFF;
    176     fptr[1] = 0xFF;
    177     fptr[2] = 0xFF;
    178     fptr[3] = 0xFF;
    179     fptr[8] = 0xFF;                 // pointer to next file
    180     fptr[9] = 0xFF;                 // pointer to next file
    181     fptr[0x7F] = GetChecksum(fptr); // checksum
    182   }
    183 
    184   // broken sector replacement data
    185   for (u32 frame = 36; frame < 56; frame++)
    186   {
    187     u8* fptr = GetFramePtr<u8>(data, 0, frame);
    188     std::fill_n(fptr, FRAME_SIZE, u8(0x00));
    189   }
    190 
    191   // unused frames
    192   for (u32 frame = 56; frame < 63; frame++)
    193   {
    194     u8* fptr = GetFramePtr<u8>(data, 0, frame);
    195     std::fill_n(fptr, FRAME_SIZE, u8(0x00));
    196   }
    197 
    198   // write test frame
    199   std::memcpy(GetFramePtr<u8>(data, 0, 63), GetFramePtr<u8>(data, 0, 0), FRAME_SIZE);
    200 }
    201 
    202 std::optional<u32> MemoryCardImage::GetNextFreeBlock(const DataArray& data)
    203 {
    204   for (u32 dir_frame = 1; dir_frame < FRAMES_PER_BLOCK; dir_frame++)
    205   {
    206     const DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, dir_frame);
    207     if ((df->block_allocation_state & 0xF0) == 0xA0)
    208       return dir_frame;
    209   }
    210 
    211   return std::nullopt;
    212 }
    213 
    214 u32 MemoryCardImage::GetFreeBlockCount(const DataArray& data)
    215 {
    216   u32 count = 0;
    217   for (u32 dir_frame = 1; dir_frame < FRAMES_PER_BLOCK; dir_frame++)
    218   {
    219     const DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, dir_frame);
    220     if ((df->block_allocation_state & 0xF0) == 0xA0)
    221       count++;
    222   }
    223 
    224   return count;
    225 }
    226 
    227 std::vector<MemoryCardImage::FileInfo> MemoryCardImage::EnumerateFiles(const DataArray& data, bool include_deleted)
    228 {
    229   // For getting the icon, we only consider binary transparency. Some games set the alpha to 0.
    230   static constexpr auto icon_to_rgba8 = [](u16 col) { return (col == 0) ? 0u : VRAMRGBA5551ToRGBA8888(col | 0x8000); };
    231 
    232   std::vector<FileInfo> files;
    233 
    234   for (u32 dir_frame = 1; dir_frame < FRAMES_PER_BLOCK; dir_frame++)
    235   {
    236     const DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, dir_frame);
    237     if (df->block_allocation_state != 0x51 &&
    238         (!include_deleted || (df->block_allocation_state != 0xA1 && df->block_allocation_state != 0xA2 &&
    239                               df->block_allocation_state != 0xA3)))
    240     {
    241       continue;
    242     }
    243 
    244     u32 filename_length = 0;
    245     while (filename_length < sizeof(df->filename) && df->filename[filename_length] != '\0')
    246       filename_length++;
    247 
    248     FileInfo fi;
    249     fi.filename.append(df->filename, filename_length);
    250     fi.first_block = dir_frame;
    251     fi.size = df->file_size;
    252     fi.num_blocks = 1;
    253     fi.deleted = (df->block_allocation_state != 0x51);
    254 
    255     const DirectoryFrame* next_df = df;
    256     while (next_df->next_block_number < (NUM_BLOCKS - 1) && fi.num_blocks < FRAMES_PER_BLOCK)
    257     {
    258       fi.num_blocks++;
    259       next_df = GetFramePtr<DirectoryFrame>(data, 0, next_df->next_block_number + 1);
    260     }
    261 
    262     if (fi.num_blocks == FRAMES_PER_BLOCK)
    263     {
    264       // invalid
    265       WARNING_LOG("Invalid block chain in block {}", dir_frame);
    266       continue;
    267     }
    268 
    269     const TitleFrame* tf = GetFramePtr<TitleFrame>(data, dir_frame, 0);
    270     u32 num_icon_frames = 0;
    271     if (tf->icon_flag == 0x11)
    272       num_icon_frames = 1;
    273     else if (tf->icon_flag == 0x12)
    274       num_icon_frames = 2;
    275     else if (tf->icon_flag == 0x13)
    276       num_icon_frames = 3;
    277     else
    278     {
    279       WARNING_LOG("Unknown icon flag 0x{:02X}", tf->icon_flag);
    280       continue;
    281     }
    282 
    283     char title_sjis[sizeof(tf->title) + 2];
    284     std::memcpy(title_sjis, tf->title, sizeof(tf->title));
    285     title_sjis[sizeof(tf->title)] = 0;
    286     title_sjis[sizeof(tf->title) + 1] = 0;
    287     char* title_utf8 = sjis2utf8(title_sjis);
    288     fi.title = title_utf8;
    289     std::free(title_utf8);
    290 
    291     fi.icon_frames.resize(num_icon_frames);
    292     for (u32 icon_frame = 0; icon_frame < num_icon_frames; icon_frame++)
    293     {
    294       const u8* indices_ptr = GetFramePtr<u8>(data, dir_frame, 1 + icon_frame);
    295       u32* pixels_ptr = fi.icon_frames[icon_frame].pixels;
    296       for (u32 i = 0; i < ICON_WIDTH * ICON_HEIGHT; i += 2)
    297       {
    298         *(pixels_ptr++) = icon_to_rgba8(tf->icon_palette[*indices_ptr & 0xF]);
    299         *(pixels_ptr++) = icon_to_rgba8(tf->icon_palette[*indices_ptr >> 4]);
    300         indices_ptr++;
    301       }
    302     }
    303 
    304     files.push_back(std::move(fi));
    305   }
    306 
    307   return files;
    308 }
    309 
    310 bool MemoryCardImage::ReadFile(const DataArray& data, const FileInfo& fi, std::vector<u8>* buffer, Error* error)
    311 {
    312   buffer->resize(fi.num_blocks * BLOCK_SIZE);
    313 
    314   u32 block_number = fi.first_block;
    315   for (u32 i = 0; i < fi.num_blocks; i++)
    316   {
    317     Assert(block_number < FRAMES_PER_BLOCK);
    318     std::memcpy(buffer->data() + (i * BLOCK_SIZE), GetFramePtr<u8>(data, block_number, 0), BLOCK_SIZE);
    319 
    320     const DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, block_number);
    321     block_number = df->next_block_number + 1;
    322   }
    323 
    324   return true;
    325 }
    326 
    327 bool MemoryCardImage::WriteFile(DataArray* data, std::string_view filename, const std::span<const u8> buffer, Error* error)
    328 {
    329   if (buffer.empty())
    330   {
    331     Error::SetStringView(error, "Buffer is empty.");
    332     return false;
    333   }
    334 
    335   const u32 free_block_count = GetFreeBlockCount(*data);
    336   const u32 num_blocks = (static_cast<u32>(buffer.size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE;
    337   if (free_block_count < num_blocks)
    338   {
    339     Error::SetStringFmt(error, "Insufficient free blocks, {} blocks are needed, but only have {}.", num_blocks,
    340                         free_block_count);
    341     return false;
    342   }
    343 
    344   DirectoryFrame* last_df = nullptr;
    345   for (u32 i = 0; i < num_blocks; i++)
    346   {
    347     std::optional<u32> block_number = GetNextFreeBlock(*data);
    348     Assert(block_number.has_value());
    349 
    350     DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, block_number.value());
    351     std::memset(df, 0, sizeof(DirectoryFrame));
    352 
    353     if (last_df)
    354     {
    355       // not first sector
    356       last_df->next_block_number = Truncate16(block_number.value() - 1);
    357       UpdateChecksum(last_df);
    358 
    359       // 53 for last otherwise 52
    360       df->block_allocation_state = (i == (num_blocks - 1)) ? 0x53 : 0x52;
    361     }
    362     else
    363     {
    364       // first sector
    365       df->block_allocation_state = 0x51;
    366       df->file_size = static_cast<u32>(buffer.size());
    367       StringUtil::Strlcpy(df->filename, filename, sizeof(df->filename));
    368     }
    369 
    370     df->next_block_number = 0xFFFF;
    371     UpdateChecksum(df);
    372     last_df = df;
    373 
    374     u8* data_block = GetFramePtr<u8>(data, block_number.value(), 0);
    375     const u32 src_offset = i * BLOCK_SIZE;
    376     const u32 size_to_copy = std::min<u32>(BLOCK_SIZE, static_cast<u32>(buffer.size()) - src_offset);
    377     const u32 size_to_zero = BLOCK_SIZE - size_to_copy;
    378     std::memcpy(data_block, buffer.data() + src_offset, size_to_copy);
    379     if (size_to_zero)
    380       std::memset(data_block + size_to_copy, 0, size_to_zero);
    381   }
    382 
    383   INFO_LOG("Wrote {} byte ({} block) file to memory card", buffer.size(), num_blocks);
    384   return true;
    385 }
    386 
    387 bool MemoryCardImage::DeleteFile(DataArray* data, const FileInfo& fi, bool clear_sectors)
    388 {
    389   INFO_LOG("Deleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
    390 
    391   u32 block_number = fi.first_block;
    392   for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++)
    393   {
    394     DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, block_number);
    395     block_number = ZeroExtend32(df->next_block_number) + 1;
    396     if (clear_sectors)
    397     {
    398       std::memset(df, 0, sizeof(DirectoryFrame));
    399       df->block_allocation_state = 0xA0;
    400     }
    401     else
    402     {
    403       if (i == 0)
    404         df->block_allocation_state = 0xA1;
    405       else if (i == (fi.num_blocks - 1))
    406         df->block_allocation_state = 0xA3;
    407       else
    408         df->block_allocation_state = 0xA2;
    409     }
    410 
    411     df->next_block_number = 0xFFFF;
    412     UpdateChecksum(df);
    413   }
    414 
    415   return true;
    416 }
    417 
    418 bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi)
    419 {
    420   if (!fi.deleted)
    421   {
    422     ERROR_LOG("File '{}' is not deleted", fi.filename);
    423     return false;
    424   }
    425 
    426   INFO_LOG("Undeleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
    427 
    428   // check that all blocks are present first
    429   u32 block_number = fi.first_block;
    430   for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++)
    431   {
    432     const u32 this_block_number = block_number;
    433     DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, block_number);
    434     block_number = ZeroExtend32(df->next_block_number) + 1;
    435 
    436     if (i == 0)
    437     {
    438       if (df->block_allocation_state != 0xA1)
    439       {
    440         ERROR_LOG("Incorrect block state for {}, expected 0xA1 got 0x{:02X}", this_block_number,
    441                   df->block_allocation_state);
    442         return false;
    443       }
    444     }
    445     else if (i == (fi.num_blocks - 1))
    446     {
    447       if (df->block_allocation_state != 0xA3)
    448       {
    449         ERROR_LOG("Incorrect block state for {}, expected 0xA3 got 0x{:02X}", this_block_number,
    450                   df->block_allocation_state);
    451         return false;
    452       }
    453     }
    454     else
    455     {
    456       if (df->block_allocation_state != 0xA2)
    457       {
    458         ERROR_LOG("Incorrect block state for {}, expected 0xA2 got 0x{:02X}", this_block_number,
    459                   df->block_allocation_state);
    460         return false;
    461       }
    462     }
    463   }
    464 
    465   block_number = fi.first_block;
    466   for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++)
    467   {
    468     DirectoryFrame* df = GetFramePtr<DirectoryFrame>(data, 0, block_number);
    469     block_number = ZeroExtend32(df->next_block_number) + 1;
    470 
    471     if (i == 0)
    472       df->block_allocation_state = 0x51;
    473     else if (i == (fi.num_blocks - 1))
    474       df->block_allocation_state = 0x53;
    475     else
    476       df->block_allocation_state = 0x52;
    477 
    478     UpdateChecksum(df);
    479   }
    480 
    481   return true;
    482 }
    483 
    484 bool MemoryCardImage::ImportCardMCD(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error)
    485 {
    486   if (file_data.size() != DATA_SIZE)
    487   {
    488     Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", static_cast<u32>(DATA_SIZE),
    489                         file_data.size());
    490     return false;
    491   }
    492 
    493   std::memcpy(data->data(), file_data.data(), DATA_SIZE);
    494   return true;
    495 }
    496 
    497 bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error)
    498 {
    499 #pragma pack(push, 1)
    500   struct GMEHeader
    501   {
    502     char id[12];
    503     u8 unk1[4];
    504     u8 unk2[5];
    505     u8 sector0[16];
    506     u8 sector1[16];
    507     u8 unk3[11];
    508     char descriptions[256][15];
    509   };
    510   static_assert(sizeof(GMEHeader) == 0xF40);
    511 #pragma pack(pop)
    512 
    513   // some gme files are raw files in disguise...
    514   if (file_data.size() == DATA_SIZE)
    515     return ImportCardMCD(data, filename, std::move(file_data), error);
    516 
    517   constexpr u32 MIN_SIZE = sizeof(GMEHeader) + BLOCK_SIZE;
    518 
    519   if (file_data.size() < MIN_SIZE)
    520   {
    521     Error::SetStringFmt(error, "File is incorrect size, expected at least {} bytes, got {} bytes.", MIN_SIZE,
    522                         file_data.size());
    523     return false;
    524   }
    525 
    526   // if it's too small, pad it
    527   const u32 expected_size = sizeof(GMEHeader) + DATA_SIZE;
    528   if (file_data.size() < expected_size)
    529   {
    530     WARNING_LOG("GME memory card '{}' is too small (got {} expected {}), padding with zeroes", filename,
    531                 file_data.size(), expected_size);
    532     if (file_data.size() > sizeof(GMEHeader))
    533     {
    534       const size_t present = file_data.size() - sizeof(GMEHeader);
    535       std::memcpy(data->data(), file_data.data() + sizeof(GMEHeader), present);
    536       std::memset(data->data() + present, 0, DATA_SIZE - present);
    537     }
    538     else
    539     {
    540       std::memset(data->data(), 0, DATA_SIZE);
    541     }
    542   }
    543   else
    544   {
    545     // we don't actually care about the header, just skip over it
    546     std::memcpy(data->data(), file_data.data() + sizeof(GMEHeader), DATA_SIZE);
    547   }
    548 
    549   return true;
    550 }
    551 
    552 bool MemoryCardImage::ImportCardVGS(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error)
    553 {
    554   constexpr u32 HEADER_SIZE = 64;
    555   constexpr u32 EXPECTED_SIZE = HEADER_SIZE + DATA_SIZE;
    556 
    557   if (file_data.size() != EXPECTED_SIZE)
    558   {
    559     Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", EXPECTED_SIZE,
    560                         file_data.size());
    561     return false;
    562   }
    563 
    564   // Connectix Virtual Game Station format (.MEM): "VgsM", 64 bytes
    565   if (file_data[0] != 'V' || file_data[1] != 'g' || file_data[2] != 's' || file_data[3] != 'M')
    566   {
    567     Error::SetStringView(error, "Incorrect header.");
    568     return false;
    569   }
    570 
    571   std::memcpy(data->data(), &file_data[HEADER_SIZE], DATA_SIZE);
    572   return true;
    573 }
    574 
    575 bool MemoryCardImage::ImportCardPSX(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error)
    576 {
    577   constexpr u32 HEADER_SIZE = 256;
    578   constexpr u32 EXPECTED_SIZE = HEADER_SIZE + DATA_SIZE;
    579 
    580   if (file_data.size() != EXPECTED_SIZE)
    581   {
    582     Error::SetStringFmt(error, "File is incorrect size, expected {} bytes, got {} bytes.", EXPECTED_SIZE,
    583                         file_data.size());
    584     return false;
    585   }
    586 
    587   // Connectix Virtual Game Station format (.MEM): "VgsM", 64 bytes
    588   if (file_data[0] != 'P' || file_data[1] != 'S' || file_data[2] != 'V')
    589   {
    590     Error::SetStringView(error, "Incorrect header.");
    591     return false;
    592   }
    593 
    594   std::memcpy(data->data(), &file_data[HEADER_SIZE], DATA_SIZE);
    595   return true;
    596 }
    597 
    598 bool MemoryCardImage::ImportCard(DataArray* data, const char* filename, std::span<const u8> file_data, Error* error)
    599 {
    600   const std::string_view extension = Path::GetExtension(filename);
    601   if (extension.empty())
    602   {
    603     Error::SetStringFmt(error, "File must have an extension.");
    604     return false;
    605   }
    606 
    607   if (StringUtil::EqualNoCase(extension, "mcd") || StringUtil::EqualNoCase(extension, "mcr") ||
    608       StringUtil::EqualNoCase(extension, "mc") || StringUtil::EqualNoCase(extension, "srm") ||
    609       StringUtil::EqualNoCase(extension, "psm") || StringUtil::EqualNoCase(extension, "ps") ||
    610       StringUtil::EqualNoCase(extension, "ddf"))
    611   {
    612     return ImportCardMCD(data, filename, file_data, error);
    613   }
    614   else if (StringUtil::EqualNoCase(extension, "gme"))
    615   {
    616     return ImportCardGME(data, filename, file_data, error);
    617   }
    618   else if (StringUtil::EqualNoCase(extension, "mem") || StringUtil::EqualNoCase(extension, "vgs"))
    619   {
    620     return ImportCardVGS(data, filename, file_data, error);
    621   }
    622   else if (StringUtil::EqualNoCase(extension, "psx"))
    623   {
    624     return ImportCardPSX(data, filename, file_data, error);
    625   }
    626   else
    627   {
    628     Error::SetStringFmt(error, "Unknown extension '{}'.", extension);
    629     return false;
    630   }
    631 }
    632 
    633 bool MemoryCardImage::ImportCard(DataArray* data, const char* filename, Error* error)
    634 {
    635   std::optional<DynamicHeapArray<u8>> file_data = FileSystem::ReadBinaryFile(filename, error);
    636   if (!file_data.has_value())
    637     return false;
    638 
    639   return ImportCard(data, filename, file_data->cspan(), error);
    640 }
    641 
    642 bool MemoryCardImage::ExportSave(DataArray* data, const FileInfo& fi, const char* filename, Error* error)
    643 {
    644   // TODO: This could be span...
    645   std::vector<u8> file_data;
    646   if (!ReadFile(*data, fi, &file_data, error))
    647     return false;
    648 
    649   auto fp = FileSystem::CreateAtomicRenamedFile(filename, "wb", error);
    650   if (!fp)
    651     return false;
    652 
    653   DirectoryFrame* df_ptr = GetFramePtr<DirectoryFrame>(data, 0, fi.first_block);
    654   if (std::fwrite(df_ptr, sizeof(DirectoryFrame), 1, fp.get()) != 1 ||
    655       std::fwrite(file_data.data(), file_data.size(), 1, fp.get()) != 1)
    656   {
    657     Error::SetErrno(error, "fwrite() failed: ", errno);
    658     FileSystem::DiscardAtomicRenamedFile(fp);
    659     return false;
    660   }
    661 
    662   return true;
    663 }
    664 
    665 bool MemoryCardImage::ImportSaveWithDirectoryFrame(DataArray* data, const char* filename,
    666                                                    const FILESYSTEM_STAT_DATA& sd, Error* error)
    667 {
    668   // Make sure the size of the actual file is valid
    669   if (sd.Size <= FRAME_SIZE || (sd.Size - FRAME_SIZE) % BLOCK_SIZE != 0u || (sd.Size - FRAME_SIZE) / BLOCK_SIZE > 15u)
    670   {
    671     Error::SetStringView(error, "Invalid size for save file.");
    672     return false;
    673   }
    674 
    675   auto fp = FileSystem::OpenManagedCFile(filename, "rb", error);
    676   if (!fp)
    677     return false;
    678 
    679   DirectoryFrame df;
    680   if (std::fread(&df, sizeof(df), 1, fp.get()) != 1)
    681   {
    682     Error::SetErrno(error, "Failed to read directory frame: ", errno);
    683     return false;
    684   }
    685 
    686   // Make sure the size reported by the directory frame is valid
    687   if (df.file_size < BLOCK_SIZE || df.file_size % BLOCK_SIZE != 0 || df.file_size / BLOCK_SIZE > 15u)
    688   {
    689     Error::SetStringFmt(error, "Invalid size ({} bytes) reported by directory frame.", df.file_size);
    690     return false;
    691   }
    692 
    693   std::vector<u8> blocks = std::vector<u8>(static_cast<size_t>(df.file_size));
    694   if (std::fread(blocks.data(), df.file_size, 1, fp.get()) != 1)
    695   {
    696     Error::SetErrno(error, "Failed to read block bytes: ", errno);
    697     return false;
    698   }
    699 
    700   const u32 num_blocks = (static_cast<u32>(blocks.size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE;
    701   if (GetFreeBlockCount(*data) < num_blocks)
    702   {
    703     Error::SetStringView(error, "Insufficient free blocks.");
    704     return false;
    705   }
    706 
    707   // Make sure there isn't already a save with the same name
    708   std::vector<FileInfo> fileinfos = EnumerateFiles(*data, true);
    709   for (const FileInfo& fi : fileinfos)
    710   {
    711     if (fi.filename.compare(0, sizeof(df.filename), df.filename) == 0)
    712     {
    713       if (!fi.deleted)
    714       {
    715         Error::SetStringFmt(error, "Save file with the same name '{}' already exists in memory card", fi.filename);
    716         return false;
    717       }
    718 
    719       DeleteFile(data, fi, true);
    720     }
    721   }
    722 
    723   return WriteFile(data, df.filename, blocks, error);
    724 }
    725 
    726 bool MemoryCardImage::ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd, Error* error)
    727 {
    728   const std::string display_name = FileSystem::GetDisplayNameFromPath(filename);
    729   std::string save_name(Path::GetFileTitle(filename));
    730   if (save_name.length() == 0)
    731   {
    732     Error::SetStringView(error, "Invalid filename.");
    733     return false;
    734   }
    735 
    736   if (save_name.length() > DirectoryFrame::FILE_NAME_LENGTH)
    737     save_name.erase(DirectoryFrame::FILE_NAME_LENGTH);
    738 
    739   std::optional<DynamicHeapArray<u8>> blocks = FileSystem::ReadBinaryFile(filename, error);
    740   if (!blocks.has_value())
    741     return false;
    742 
    743   const u32 free_block_count = GetFreeBlockCount(*data);
    744   const u32 num_blocks = (static_cast<u32>(blocks->size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE;
    745   if (free_block_count < num_blocks)
    746   {
    747     Error::SetStringFmt(error, "Insufficient free blocks, needs {} blocks, but only have {}.", num_blocks,
    748                         free_block_count);
    749     return false;
    750   }
    751 
    752   // Make sure there isn't already a save with the same name
    753   std::vector<FileInfo> fileinfos = EnumerateFiles(*data, true);
    754   for (const FileInfo& fi : fileinfos)
    755   {
    756     if (fi.filename.compare(save_name) == 0)
    757     {
    758       if (!fi.deleted)
    759       {
    760         Error::SetStringFmt(error, "Save file with the same name '{}' already exists in memory card.", fi.filename);
    761         return false;
    762       }
    763 
    764       DeleteFile(data, fi, true);
    765     }
    766   }
    767 
    768   return WriteFile(data, save_name, blocks.value(), error);
    769 }
    770 
    771 bool MemoryCardImage::ImportSave(DataArray* data, const char* filename, Error* error)
    772 {
    773   // Make sure the size of the actual file is valid
    774   FILESYSTEM_STAT_DATA sd;
    775   if (!FileSystem::StatFile(filename, &sd) || sd.Size == 0)
    776   {
    777     Error::SetStringView(error, "File does not exist, or is empty.");
    778     return false;
    779   }
    780 
    781   if (StringUtil::EqualNoCase(Path::GetExtension(filename), "mcs"))
    782   {
    783     return ImportSaveWithDirectoryFrame(data, filename, sd, error);
    784   }
    785   else if (sd.Size > 0 && sd.Size < DATA_SIZE && (sd.Size % BLOCK_SIZE) == 0)
    786   {
    787     return ImportRawSave(data, filename, sd, error);
    788   }
    789   else
    790   {
    791     Error::SetStringView(error, "Unknown save format.");
    792     return false;
    793   }
    794 }