1 // Copyright 2014 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/HW/GCMemcard/GCMemcardDirectory.h"
6 
7 #include <algorithm>
8 #include <chrono>
9 #include <cinttypes>
10 #include <cstring>
11 #include <memory>
12 #include <mutex>
13 #include <string>
14 #include <vector>
15 
16 #include <fmt/format.h>
17 
18 #include "Common/Assert.h"
19 #include "Common/ChunkFile.h"
20 #include "Common/CommonTypes.h"
21 #include "Common/Config/Config.h"
22 #include "Common/File.h"
23 #include "Common/FileSearch.h"
24 #include "Common/FileUtil.h"
25 #include "Common/Logging/Log.h"
26 #include "Common/MsgHandler.h"
27 #include "Common/StringUtil.h"
28 #include "Common/Thread.h"
29 #include "Common/Timer.h"
30 
31 #include "Core/Config/MainSettings.h"
32 #include "Core/ConfigManager.h"
33 #include "Core/Core.h"
34 #include "Core/HW/EXI/EXI_DeviceIPL.h"
35 #include "Core/HW/Sram.h"
36 #include "Core/NetPlayProto.h"
37 
38 static const char* MC_HDR = "MC_SYSTEM_AREA";
39 
LoadGCI(Memcard::GCIFile gci)40 bool GCMemcardDirectory::LoadGCI(Memcard::GCIFile gci)
41 {
42   // check if any already loaded file has the same internal name as the new file
43   for (const Memcard::GCIFile& already_loaded_gci : m_saves)
44   {
45     if (gci.m_gci_header.GCI_FileName() == already_loaded_gci.m_gci_header.GCI_FileName())
46     {
47       ERROR_LOG(EXPANSIONINTERFACE,
48                 "%s\nwas not loaded because it has the same internal filename as previously "
49                 "loaded save\n%s",
50                 gci.m_filename.c_str(), already_loaded_gci.m_filename.c_str());
51       return false;
52     }
53   }
54 
55   // check if this file has a valid block size
56   // 2043 is the largest number of free blocks on a memory card
57   // in reality, there are not likely any valid gci files > 251 blocks
58   const u16 num_blocks = gci.m_gci_header.m_block_count;
59   if (num_blocks > 2043)
60   {
61     ERROR_LOG(EXPANSIONINTERFACE,
62               "%s\nwas not loaded because it is an invalid GCI.\nNumber of blocks claimed to be %u",
63               gci.m_filename.c_str(), num_blocks);
64     return false;
65   }
66 
67   if (!gci.LoadSaveBlocks())
68   {
69     ERROR_LOG(EXPANSIONINTERFACE, "Failed to load data of %s", gci.m_filename.c_str());
70     return false;
71   }
72 
73   // reserve storage for the save file in the BAT
74   u16 first_block = m_bat1.AssignBlocksContiguous(num_blocks);
75   if (first_block == 0xFFFF)
76   {
77     ERROR_LOG(
78         EXPANSIONINTERFACE,
79         "%s\nwas not loaded because there are not enough free blocks on the virtual memory card",
80         gci.m_filename.c_str());
81     return false;
82   }
83   gci.m_gci_header.m_first_block = first_block;
84 
85   if (gci.HasCopyProtection())
86   {
87     Memcard::GCMemcard::PSO_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data);
88     Memcard::GCMemcard::FZEROGX_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data);
89   }
90 
91   // actually load save file into memory card
92   int idx = (int)m_saves.size();
93   m_dir1.Replace(gci.m_gci_header, idx);
94   m_saves.push_back(std::move(gci));
95   SetUsedBlocks(idx);
96 
97   return true;
98 }
99 
100 // This is only used by NetPlay but it made sense to put it here to keep the relevant code together
GetFileNamesForGameID(const std::string & directory,const std::string & game_id)101 std::vector<std::string> GCMemcardDirectory::GetFileNamesForGameID(const std::string& directory,
102                                                                    const std::string& game_id)
103 {
104   std::vector<std::string> filenames;
105 
106   u32 game_code = 0;
107   if (game_id.length() >= 4 && game_id != "00000000")
108     game_code = Common::swap32(reinterpret_cast<const u8*>(game_id.c_str()));
109 
110   std::vector<std::string> loaded_saves;
111   for (const std::string& file_name : Common::DoFileSearch({directory}, {".gci"}))
112   {
113     File::IOFile gci_file(file_name, "rb");
114     if (!gci_file)
115       continue;
116 
117     Memcard::GCIFile gci;
118     gci.m_filename = file_name;
119     gci.m_dirty = false;
120     if (!gci_file.ReadBytes(&gci.m_gci_header, Memcard::DENTRY_SIZE))
121       continue;
122 
123     const std::string gci_filename = gci.m_gci_header.GCI_FileName();
124     if (std::find(loaded_saves.begin(), loaded_saves.end(), gci_filename) != loaded_saves.end())
125       continue;
126 
127     const u16 num_blocks = gci.m_gci_header.m_block_count;
128     // largest number of free blocks on a memory card
129     // in reality, there are not likely any valid gci files > 251 blocks
130     if (num_blocks > 2043)
131       continue;
132 
133     const u32 size = num_blocks * Memcard::BLOCK_SIZE;
134     const u64 file_size = gci_file.GetSize();
135     if (file_size != size + Memcard::DENTRY_SIZE)
136       continue;
137 
138     // There's technically other available block checks to prevent overfilling the virtual memory
139     // card (see above method), but since we're only loading the saves for one GameID here, we're
140     // definitely not going to run out of space.
141 
142     if (game_code == Common::swap32(gci.m_gci_header.m_gamecode.data()))
143     {
144       loaded_saves.push_back(gci_filename);
145       filenames.push_back(file_name);
146     }
147   }
148 
149   return filenames;
150 }
151 
GCMemcardDirectory(const std::string & directory,int slot,const Memcard::HeaderData & header_data,u32 game_id)152 GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot,
153                                        const Memcard::HeaderData& header_data, u32 game_id)
154     : MemoryCardBase(slot, header_data.m_size_mb), m_game_id(game_id), m_last_block(-1),
155       m_hdr(header_data), m_bat1(header_data.m_size_mb), m_saves(0), m_save_directory(directory),
156       m_exiting(false)
157 {
158   // Use existing header data if available
159   {
160     File::IOFile((m_save_directory + MC_HDR), "rb").ReadBytes(&m_hdr, Memcard::BLOCK_SIZE);
161   }
162 
163   const bool current_game_only = Config::Get(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY);
164   std::vector<std::string> filenames = Common::DoFileSearch({m_save_directory}, {".gci"});
165 
166   // split up into files for current games we should definitely load,
167   // and files for other games that we don't care too much about
168   std::vector<Memcard::GCIFile> gci_current_game;
169   std::vector<Memcard::GCIFile> gci_other_games;
170   for (const std::string& filename : filenames)
171   {
172     Memcard::GCIFile gci;
173     gci.m_filename = filename;
174     gci.m_dirty = false;
175     if (!gci.LoadHeader())
176     {
177       ERROR_LOG(EXPANSIONINTERFACE, "Failed to load header of %s", filename.c_str());
178       continue;
179     }
180 
181     if (m_game_id == Common::swap32(gci.m_gci_header.m_gamecode.data()))
182       gci_current_game.emplace_back(std::move(gci));
183     else if (!current_game_only)
184       gci_other_games.emplace_back(std::move(gci));
185   }
186 
187   m_saves.reserve(Memcard::DIRLEN);
188 
189   // load files for current game
190   size_t failed_loads_current_game = 0;
191   for (Memcard::GCIFile& gci : gci_current_game)
192   {
193     if (!LoadGCI(std::move(gci)))
194     {
195       // keep track of how many files failed to load for the current game so we can display a
196       // message to the user informing them why some of their saves may not be loaded
197       ++failed_loads_current_game;
198     }
199   }
200 
201   // leave about 10% of free space on the card if possible
202   const int total_blocks =
203       m_hdr.m_data.m_size_mb * Memcard::MBIT_TO_BLOCKS - Memcard::MC_FST_BLOCKS;
204   const int reserved_blocks = total_blocks / 10;
205 
206   // load files for other games
207   for (Memcard::GCIFile& gci : gci_other_games)
208   {
209     // leave some free file entries for new saves that might be created
210     if (m_saves.size() > 112)
211       break;
212 
213     // leave some free blocks for new saves that might be created
214     const int free_blocks = m_bat1.m_free_blocks;
215     const int gci_blocks = gci.m_gci_header.m_block_count;
216     if (free_blocks - gci_blocks < reserved_blocks)
217       continue;
218 
219     LoadGCI(std::move(gci));
220   }
221 
222   if (failed_loads_current_game > 0)
223   {
224     Core::DisplayMessage("Warning: Save file(s) of the current game failed to load.", 10000);
225   }
226 
227   m_dir1.FixChecksums();
228   m_dir2 = m_dir1;
229   m_bat2 = m_bat1;
230 
231   m_flush_thread = std::thread(&GCMemcardDirectory::FlushThread, this);
232 }
233 
FlushThread()234 void GCMemcardDirectory::FlushThread()
235 {
236   if (!SConfig::GetInstance().bEnableMemcardSdWriting)
237   {
238     return;
239   }
240 
241   Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_index).c_str());
242 
243   constexpr std::chrono::seconds flush_interval{1};
244   while (true)
245   {
246     // no-op until signalled
247     m_flush_trigger.Wait();
248 
249     if (m_exiting.TestAndClear())
250       return;
251     // no-op as long as signalled within flush_interval
252     while (m_flush_trigger.WaitFor(flush_interval))
253     {
254       if (m_exiting.TestAndClear())
255         return;
256     }
257 
258     FlushToFile();
259   }
260 }
261 
~GCMemcardDirectory()262 GCMemcardDirectory::~GCMemcardDirectory()
263 {
264   m_exiting.Set();
265   m_flush_trigger.Set();
266   m_flush_thread.join();
267 
268   FlushToFile();
269 }
270 
Read(u32 src_address,s32 length,u8 * dest_address)271 s32 GCMemcardDirectory::Read(u32 src_address, s32 length, u8* dest_address)
272 {
273   s32 block = src_address / Memcard::BLOCK_SIZE;
274   u32 offset = src_address % Memcard::BLOCK_SIZE;
275   s32 extra = 0;  // used for read calls that are across multiple blocks
276 
277   if (offset + length > Memcard::BLOCK_SIZE)
278   {
279     extra = length + offset - Memcard::BLOCK_SIZE;
280     length -= extra;
281 
282     // verify that we haven't calculated a length beyond BLOCK_SIZE
283     DEBUG_ASSERT_MSG(EXPANSIONINTERFACE, (src_address + length) % Memcard::BLOCK_SIZE == 0,
284                      "Memcard directory Read Logic Error");
285   }
286 
287   if (m_last_block != block)
288   {
289     switch (block)
290     {
291     case 0:
292       m_last_block = block;
293       m_last_block_address = (u8*)&m_hdr;
294       break;
295     case 1:
296       m_last_block = -1;
297       m_last_block_address = (u8*)&m_dir1;
298       break;
299     case 2:
300       m_last_block = -1;
301       m_last_block_address = (u8*)&m_dir2;
302       break;
303     case 3:
304       m_last_block = block;
305       m_last_block_address = (u8*)&m_bat1;
306       break;
307     case 4:
308       m_last_block = block;
309       m_last_block_address = (u8*)&m_bat2;
310       break;
311     default:
312       m_last_block = SaveAreaRW(block);
313 
314       if (m_last_block == -1)
315       {
316         memset(dest_address, 0xFF, length);
317         return 0;
318       }
319     }
320   }
321 
322   memcpy(dest_address, m_last_block_address + offset, length);
323   if (extra)
324     extra = Read(src_address + length, extra, dest_address + length);
325   return length + extra;
326 }
327 
Write(u32 dest_address,s32 length,const u8 * src_address)328 s32 GCMemcardDirectory::Write(u32 dest_address, s32 length, const u8* src_address)
329 {
330   std::unique_lock<std::mutex> l(m_write_mutex);
331   if (length != 0x80)
332     INFO_LOG(EXPANSIONINTERFACE, "Writing to 0x%x. Length: 0x%x", dest_address, length);
333   s32 block = dest_address / Memcard::BLOCK_SIZE;
334   u32 offset = dest_address % Memcard::BLOCK_SIZE;
335   s32 extra = 0;  // used for write calls that are across multiple blocks
336 
337   if (offset + length > Memcard::BLOCK_SIZE)
338   {
339     extra = length + offset - Memcard::BLOCK_SIZE;
340     length -= extra;
341 
342     // verify that we haven't calculated a length beyond BLOCK_SIZE
343     DEBUG_ASSERT_MSG(EXPANSIONINTERFACE, (dest_address + length) % Memcard::BLOCK_SIZE == 0,
344                      "Memcard directory Write Logic Error");
345   }
346   if (m_last_block != block)
347   {
348     switch (block)
349     {
350     case 0:
351       m_last_block = block;
352       m_last_block_address = (u8*)&m_hdr;
353       break;
354     case 1:
355     case 2:
356     {
357       m_last_block = -1;
358       s32 bytes_written = 0;
359       while (length > 0)
360       {
361         s32 to_write = std::min<s32>(Memcard::DENTRY_SIZE, length);
362         bytes_written +=
363             DirectoryWrite(dest_address + bytes_written, to_write, src_address + bytes_written);
364         length -= to_write;
365       }
366       return bytes_written;
367     }
368     case 3:
369       m_last_block = block;
370       m_last_block_address = (u8*)&m_bat1;
371       break;
372     case 4:
373       m_last_block = block;
374       m_last_block_address = (u8*)&m_bat2;
375       break;
376     default:
377       m_last_block = SaveAreaRW(block, true);
378       if (m_last_block == -1)
379       {
380         PanicAlertT("Report: GCIFolder Writing to unallocated block 0x%x", block);
381         exit(0);
382       }
383     }
384   }
385 
386   memcpy(m_last_block_address + offset, src_address, length);
387 
388   l.unlock();
389   if (extra)
390     extra = Write(dest_address + length, extra, src_address + length);
391   if (offset + length == Memcard::BLOCK_SIZE)
392     m_flush_trigger.Set();
393   return length + extra;
394 }
395 
ClearBlock(u32 address)396 void GCMemcardDirectory::ClearBlock(u32 address)
397 {
398   if (address % Memcard::BLOCK_SIZE)
399   {
400     PanicAlertT("GCMemcardDirectory: ClearBlock called with invalid block address");
401     return;
402   }
403 
404   u32 block = address / Memcard::BLOCK_SIZE;
405   INFO_LOG(EXPANSIONINTERFACE, "Clearing block %u", block);
406   switch (block)
407   {
408   case 0:
409     m_last_block = block;
410     m_last_block_address = (u8*)&m_hdr;
411     break;
412   case 1:
413     m_last_block = -1;
414     m_last_block_address = (u8*)&m_dir1;
415     break;
416   case 2:
417     m_last_block = -1;
418     m_last_block_address = (u8*)&m_dir2;
419     break;
420   case 3:
421     m_last_block = block;
422     m_last_block_address = (u8*)&m_bat1;
423     break;
424   case 4:
425     m_last_block = block;
426     m_last_block_address = (u8*)&m_bat2;
427     break;
428   default:
429     m_last_block = SaveAreaRW(block, true);
430     if (m_last_block == -1)
431       return;
432   }
433   ((Memcard::GCMBlock*)m_last_block_address)->Erase();
434 }
435 
SyncSaves()436 inline void GCMemcardDirectory::SyncSaves()
437 {
438   Memcard::Directory* current = &m_dir2;
439 
440   if (m_dir1.m_update_counter > m_dir2.m_update_counter)
441   {
442     current = &m_dir1;
443   }
444 
445   for (u32 i = 0; i < Memcard::DIRLEN; ++i)
446   {
447     if (current->m_dir_entries[i].m_gamecode != Memcard::DEntry::UNINITIALIZED_GAMECODE)
448     {
449       INFO_LOG(EXPANSIONINTERFACE, "Syncing save 0x%x",
450                Common::swap32(current->m_dir_entries[i].m_gamecode.data()));
451       bool added = false;
452       while (i >= m_saves.size())
453       {
454         Memcard::GCIFile temp;
455         m_saves.push_back(temp);
456         added = true;
457       }
458 
459       if (added || memcmp((u8*)&(m_saves[i].m_gci_header), (u8*)&(current->m_dir_entries[i]),
460                           Memcard::DENTRY_SIZE))
461       {
462         m_saves[i].m_dirty = true;
463         const u32 gamecode = Common::swap32(m_saves[i].m_gci_header.m_gamecode.data());
464         const u32 new_gamecode = Common::swap32(current->m_dir_entries[i].m_gamecode.data());
465         const u32 old_start = m_saves[i].m_gci_header.m_first_block;
466         const u32 new_start = current->m_dir_entries[i].m_first_block;
467 
468         if ((gamecode != 0xFFFFFFFF) && (gamecode != new_gamecode))
469         {
470           PanicAlertT("Game overwrote with another games save. Data corruption ahead 0x%x, 0x%x",
471                       Common::swap32(m_saves[i].m_gci_header.m_gamecode.data()),
472                       Common::swap32(current->m_dir_entries[i].m_gamecode.data()));
473         }
474         memcpy((u8*)&(m_saves[i].m_gci_header), (u8*)&(current->m_dir_entries[i]),
475                Memcard::DENTRY_SIZE);
476         if (old_start != new_start)
477         {
478           INFO_LOG(EXPANSIONINTERFACE, "Save moved from 0x%x to 0x%x", old_start, new_start);
479           m_saves[i].m_used_blocks.clear();
480           m_saves[i].m_save_data.clear();
481         }
482         if (m_saves[i].m_used_blocks.empty())
483         {
484           SetUsedBlocks(i);
485         }
486       }
487     }
488     else if ((i < m_saves.size()) && (*(u32*)&(m_saves[i].m_gci_header) != 0xFFFFFFFF))
489     {
490       INFO_LOG(EXPANSIONINTERFACE, "Clearing and/or deleting save 0x%x",
491                Common::swap32(m_saves[i].m_gci_header.m_gamecode.data()));
492       m_saves[i].m_gci_header.m_gamecode = Memcard::DEntry::UNINITIALIZED_GAMECODE;
493       m_saves[i].m_save_data.clear();
494       m_saves[i].m_used_blocks.clear();
495       m_saves[i].m_dirty = true;
496     }
497   }
498 }
SaveAreaRW(u32 block,bool writing)499 inline s32 GCMemcardDirectory::SaveAreaRW(u32 block, bool writing)
500 {
501   for (u16 i = 0; i < m_saves.size(); ++i)
502   {
503     if (m_saves[i].m_gci_header.m_gamecode != Memcard::DEntry::UNINITIALIZED_GAMECODE)
504     {
505       if (m_saves[i].m_used_blocks.empty())
506       {
507         SetUsedBlocks(i);
508       }
509 
510       int idx = m_saves[i].UsesBlock(block);
511       if (idx != -1)
512       {
513         if (!m_saves[i].LoadSaveBlocks())
514         {
515           int num_blocks = m_saves[i].m_gci_header.m_block_count;
516           while (num_blocks)
517           {
518             m_saves[i].m_save_data.emplace_back();
519             num_blocks--;
520           }
521         }
522 
523         if (writing)
524         {
525           m_saves[i].m_dirty = true;
526         }
527 
528         m_last_block = block;
529         m_last_block_address = m_saves[i].m_save_data[idx].m_block.data();
530         return m_last_block;
531       }
532     }
533   }
534   return -1;
535 }
536 
DirectoryWrite(u32 dest_address,u32 length,const u8 * src_address)537 s32 GCMemcardDirectory::DirectoryWrite(u32 dest_address, u32 length, const u8* src_address)
538 {
539   u32 block = dest_address / Memcard::BLOCK_SIZE;
540   u32 offset = dest_address % Memcard::BLOCK_SIZE;
541   Memcard::Directory* dest = (block == 1) ? &m_dir1 : &m_dir2;
542   u16 Dnum = offset / Memcard::DENTRY_SIZE;
543 
544   if (Dnum == Memcard::DIRLEN)
545   {
546     // first 58 bytes should always be 0xff
547     // needed to update the update ctr, checksums
548     // could check for writes to the 6 important bytes but doubtful that it improves performance
549     // noticably
550     memcpy((u8*)(dest) + offset, src_address, length);
551     SyncSaves();
552   }
553   else
554     memcpy((u8*)(dest) + offset, src_address, length);
555   return length;
556 }
557 
SetUsedBlocks(int save_index)558 bool GCMemcardDirectory::SetUsedBlocks(int save_index)
559 {
560   Memcard::BlockAlloc* current_bat;
561   if (m_bat2.m_update_counter > m_bat1.m_update_counter)
562     current_bat = &m_bat2;
563   else
564     current_bat = &m_bat1;
565 
566   u16 block = m_saves[save_index].m_gci_header.m_first_block;
567   while (block != 0xFFFF)
568   {
569     m_saves[save_index].m_used_blocks.push_back(block);
570     block = current_bat->GetNextBlock(block);
571     if (block == 0)
572     {
573       PanicAlertT("BAT incorrect. Dolphin will now exit");
574       exit(0);
575     }
576   }
577 
578   u16 num_blocks = m_saves[save_index].m_gci_header.m_block_count;
579   u16 blocks_from_bat = (u16)m_saves[save_index].m_used_blocks.size();
580   if (blocks_from_bat != num_blocks)
581   {
582     PanicAlertT("Warning: Number of blocks indicated by the BAT (%u) does not match that of the "
583                 "loaded file header (%u)",
584                 blocks_from_bat, num_blocks);
585     return false;
586   }
587 
588   return true;
589 }
590 
FlushToFile()591 void GCMemcardDirectory::FlushToFile()
592 {
593   std::unique_lock<std::mutex> l(m_write_mutex);
594   int errors = 0;
595   Memcard::DEntry invalid;
596   for (Memcard::GCIFile& save : m_saves)
597   {
598     if (save.m_dirty)
599     {
600       if (save.m_gci_header.m_gamecode != Memcard::DEntry::UNINITIALIZED_GAMECODE)
601       {
602         save.m_dirty = false;
603         if (save.m_save_data.empty())
604         {
605           // The save's header has been changed but the actual save blocks haven't been read/written
606           // to
607           // skip flushing this file until actual save data is modified
608           ERROR_LOG(EXPANSIONINTERFACE,
609                     "GCI header modified without corresponding save data changes");
610           continue;
611         }
612         if (save.m_filename.empty())
613         {
614           std::string default_save_name = m_save_directory + save.m_gci_header.GCI_FileName();
615 
616           // Check to see if another file is using the same name
617           // This seems unlikely except in the case of file corruption
618           // otherwise what user would name another file this way?
619           for (int j = 0; File::Exists(default_save_name) && j < 10; ++j)
620           {
621             default_save_name.insert(default_save_name.end() - 4, '0');
622           }
623           if (File::Exists(default_save_name))
624             PanicAlertT("Failed to find new filename.\n%s\n will be overwritten",
625                         default_save_name.c_str());
626           save.m_filename = default_save_name;
627         }
628         File::IOFile gci(save.m_filename, "wb");
629         if (gci)
630         {
631           gci.WriteBytes(&save.m_gci_header, Memcard::DENTRY_SIZE);
632           gci.WriteBytes(save.m_save_data.data(), Memcard::BLOCK_SIZE * save.m_save_data.size());
633 
634           if (gci.IsGood())
635           {
636             Core::DisplayMessage(fmt::format("Wrote save contents to {}", save.m_filename), 4000);
637           }
638           else
639           {
640             ++errors;
641             Core::DisplayMessage(
642                 fmt::format("Failed to write save contents to {}", save.m_filename), 4000);
643             ERROR_LOG(EXPANSIONINTERFACE, "Failed to save data to %s", save.m_filename.c_str());
644           }
645         }
646       }
647       else if (save.m_filename.length() != 0)
648       {
649         save.m_dirty = false;
650         std::string& old_name = save.m_filename;
651         std::string deleted_name = old_name + ".deleted";
652         if (File::Exists(deleted_name))
653           File::Delete(deleted_name);
654         File::Rename(old_name, deleted_name);
655         save.m_filename.clear();
656         save.m_save_data.clear();
657         save.m_used_blocks.clear();
658       }
659     }
660 
661     // Unload the save data for any game that is not running
662     // we could use !m_dirty, but some games have multiple gci files and may not write to them
663     // simultaneously
664     // this ensures that the save data for all of the current games gci files are stored in the
665     // savestate
666     const u32 gamecode = Common::swap32(save.m_gci_header.m_gamecode.data());
667     if (gamecode != m_game_id && gamecode != 0xFFFFFFFF && !save.m_save_data.empty())
668     {
669       INFO_LOG(EXPANSIONINTERFACE, "Flushing savedata to disk for %s", save.m_filename.c_str());
670       save.m_save_data.clear();
671     }
672   }
673 #if _WRITE_MC_HEADER
674   u8 mc[BLOCK_SIZE * MC_FST_BLOCKS];
675   Read(0, BLOCK_SIZE * MC_FST_BLOCKS, mc);
676   File::IOFile hdrfile(m_save_directory + MC_HDR, "wb");
677   hdrfile.WriteBytes(mc, BLOCK_SIZE * MC_FST_BLOCKS);
678 #endif
679 }
680 
DoState(PointerWrap & p)681 void GCMemcardDirectory::DoState(PointerWrap& p)
682 {
683   std::unique_lock<std::mutex> l(m_write_mutex);
684   m_last_block = -1;
685   m_last_block_address = nullptr;
686   p.Do(m_save_directory);
687   p.DoPOD<Memcard::Header>(m_hdr);
688   p.DoPOD<Memcard::Directory>(m_dir1);
689   p.DoPOD<Memcard::Directory>(m_dir2);
690   p.DoPOD<Memcard::BlockAlloc>(m_bat1);
691   p.DoPOD<Memcard::BlockAlloc>(m_bat2);
692   int num_saves = (int)m_saves.size();
693   p.Do(num_saves);
694   m_saves.resize(num_saves);
695   for (Memcard::GCIFile& save : m_saves)
696   {
697     save.DoState(p);
698   }
699 }
700 
MigrateFromMemcardFile(const std::string & directory_name,int card_index)701 void MigrateFromMemcardFile(const std::string& directory_name, int card_index)
702 {
703   File::CreateFullPath(directory_name);
704   std::string ini_memcard = (card_index == 0) ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
705                                                 Config::Get(Config::MAIN_MEMCARD_B_PATH);
706   if (File::Exists(ini_memcard))
707   {
708     auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str());
709     if (!error_code.HasCriticalErrors() && memcard && memcard->IsValid())
710     {
711       for (u8 i = 0; i < Memcard::DIRLEN; i++)
712       {
713         memcard->ExportGci(i, "", directory_name);
714       }
715     }
716   }
717 }
718