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