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 }