From 884af05589ff456258429cf50b1d5d24e11ecee1 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 17 May 2019 20:30:08 +0200 Subject: [PATCH 1/3] GCMemcardDirectory: Move GCIFile class to its own file. --- Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/Core.vcxproj | 2 + Source/Core/Core/Core.vcxproj.filters | 6 ++ Source/Core/Core/HW/GCMemcard/GCIFile.cpp | 70 +++++++++++++++++++ Source/Core/Core/HW/GCMemcard/GCIFile.h | 28 ++++++++ Source/Core/Core/HW/GCMemcard/GCMemcard.h | 24 ------- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 50 ------------- .../Core/HW/GCMemcard/GCMemcardDirectory.h | 1 + 8 files changed, 108 insertions(+), 74 deletions(-) create mode 100644 Source/Core/Core/HW/GCMemcard/GCIFile.cpp create mode 100644 Source/Core/Core/HW/GCMemcard/GCIFile.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f72a43861d..498b430d71 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -107,6 +107,7 @@ add_library(core HW/EXI/EXI_DeviceMic.cpp HW/GCKeyboard.cpp HW/GCKeyboardEmu.cpp + HW/GCMemcard/GCIFile.cpp HW/GCMemcard/GCMemcard.cpp HW/GCMemcard/GCMemcardDirectory.cpp HW/GCMemcard/GCMemcardRaw.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 82709080f4..62b00d0f70 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -146,6 +146,7 @@ + @@ -414,6 +415,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 62091ccacd..68a8141f87 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -462,6 +462,9 @@ HW %28Flipper/Hollywood%29\EXI - Expansion Interface + + HW %28Flipper/Hollywood%29\GCMemcard + HW %28Flipper/Hollywood%29\GCMemcard @@ -1173,6 +1176,9 @@ HW %28Flipper/Hollywood%29\EXI - Expansion Interface + + HW %28Flipper/Hollywood%29\GCMemcard + HW %28Flipper/Hollywood%29\GCMemcard diff --git a/Source/Core/Core/HW/GCMemcard/GCIFile.cpp b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp new file mode 100644 index 0000000000..77ddad34d4 --- /dev/null +++ b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp @@ -0,0 +1,70 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/HW/GCMemcard/GCIFile.h" + +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/File.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" + +bool GCIFile::LoadSaveBlocks() +{ + if (m_save_data.empty()) + { + if (m_filename.empty()) + return false; + + File::IOFile save_file(m_filename, "rb"); + if (!save_file) + return false; + + INFO_LOG(EXPANSIONINTERFACE, "Reading savedata from disk for %s", m_filename.c_str()); + save_file.Seek(DENTRY_SIZE, SEEK_SET); + u16 num_blocks = m_gci_header.m_block_count; + m_save_data.resize(num_blocks); + if (!save_file.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE)) + { + PanicAlertT("Failed to read data from GCI file %s", m_filename.c_str()); + m_save_data.clear(); + return false; + } + } + return true; +} + +bool GCIFile::HasCopyProtection() const +{ + if ((strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "PSO_SYSTEM") == 0) || + (strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "PSO3_SYSTEM") == 0) || + (strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "f_zero.dat") == 0)) + return true; + return false; +} + +int GCIFile::UsesBlock(u16 block_num) +{ + for (u16 i = 0; i < m_used_blocks.size(); ++i) + { + if (m_used_blocks[i] == block_num) + return i; + } + return -1; +} + +void GCIFile::DoState(PointerWrap& p) +{ + p.DoPOD(m_gci_header); + p.Do(m_dirty); + p.Do(m_filename); + int num_blocks = (int)m_save_data.size(); + p.Do(num_blocks); + m_save_data.resize(num_blocks); + for (auto itr = m_save_data.begin(); itr != m_save_data.end(); ++itr) + { + p.DoPOD(*itr); + } + p.Do(m_used_blocks); +} diff --git a/Source/Core/Core/HW/GCMemcard/GCIFile.h b/Source/Core/Core/HW/GCMemcard/GCIFile.h new file mode 100644 index 0000000000..b118330d34 --- /dev/null +++ b/Source/Core/Core/HW/GCMemcard/GCIFile.h @@ -0,0 +1,28 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/HW/GCMemcard/GCMemcard.h" + +class PointerWrap; + +class GCIFile +{ +public: + bool LoadSaveBlocks(); + bool HasCopyProtection() const; + void DoState(PointerWrap& p); + int UsesBlock(u16 blocknum); + + DEntry m_gci_header; + std::vector m_save_data; + std::vector m_used_blocks; + bool m_dirty; + std::string m_filename; +}; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.h b/Source/Core/Core/HW/GCMemcard/GCMemcard.h index 75017fe28b..8d735ced1f 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.h @@ -337,30 +337,6 @@ struct BlockAlloc static_assert(sizeof(BlockAlloc) == BLOCK_SIZE); #pragma pack(pop) -class GCIFile -{ -public: - bool LoadSaveBlocks(); - bool HasCopyProtection() const - { - if ((strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "PSO_SYSTEM") == - 0) || - (strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "PSO3_SYSTEM") == - 0) || - (strcmp(reinterpret_cast(m_gci_header.m_filename.data()), "f_zero.dat") == 0)) - return true; - return false; - } - - void DoState(PointerWrap& p); - DEntry m_gci_header; - std::vector m_save_data; - std::vector m_used_blocks; - int UsesBlock(u16 blocknum); - bool m_dirty; - std::string m_filename; -}; - class GCMemcard { private: diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 0353e9e4b8..f99012aa0f 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -693,56 +693,6 @@ void GCMemcardDirectory::DoState(PointerWrap& p) } } -bool GCIFile::LoadSaveBlocks() -{ - if (m_save_data.empty()) - { - if (m_filename.empty()) - return false; - - File::IOFile save_file(m_filename, "rb"); - if (!save_file) - return false; - - INFO_LOG(EXPANSIONINTERFACE, "Reading savedata from disk for %s", m_filename.c_str()); - save_file.Seek(DENTRY_SIZE, SEEK_SET); - u16 num_blocks = m_gci_header.m_block_count; - m_save_data.resize(num_blocks); - if (!save_file.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE)) - { - PanicAlertT("Failed to read data from GCI file %s", m_filename.c_str()); - m_save_data.clear(); - return false; - } - } - return true; -} - -int GCIFile::UsesBlock(u16 block_num) -{ - for (u16 i = 0; i < m_used_blocks.size(); ++i) - { - if (m_used_blocks[i] == block_num) - return i; - } - return -1; -} - -void GCIFile::DoState(PointerWrap& p) -{ - p.DoPOD(m_gci_header); - p.Do(m_dirty); - p.Do(m_filename); - int num_blocks = (int)m_save_data.size(); - p.Do(num_blocks); - m_save_data.resize(num_blocks); - for (auto itr = m_save_data.begin(); itr != m_save_data.end(); ++itr) - { - p.DoPOD(*itr); - } - p.Do(m_used_blocks); -} - void MigrateFromMemcardFile(const std::string& directory_name, int card_index) { File::CreateFullPath(directory_name); diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index ce784b8101..04e5e908ef 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -10,6 +10,7 @@ #include #include "Common/Event.h" +#include "Core/HW/GCMemcard/GCIFile.h" #include "Core/HW/GCMemcard/GCMemcard.h" // Uncomment this to write the system data of the memorycard from directory to disc From 5af05f671446e65f2e10bad09d2ca94bf633fcf3 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 17 May 2019 21:08:21 +0200 Subject: [PATCH 2/3] GCMemcard/GCIFile: Implement LoadHeader(). --- Source/Core/Core/HW/GCMemcard/GCIFile.cpp | 19 +++++++++++++++++++ Source/Core/Core/HW/GCMemcard/GCIFile.h | 1 + 2 files changed, 20 insertions(+) diff --git a/Source/Core/Core/HW/GCMemcard/GCIFile.cpp b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp index 77ddad34d4..b82aa6f52b 100644 --- a/Source/Core/Core/HW/GCMemcard/GCIFile.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp @@ -10,6 +10,25 @@ #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +bool GCIFile::LoadHeader() +{ + if (m_filename.empty()) + return false; + + File::IOFile save_file(m_filename, "rb"); + if (!save_file) + return false; + + INFO_LOG(EXPANSIONINTERFACE, "Reading header from disk for %s", m_filename.c_str()); + if (!save_file.ReadBytes(&m_gci_header, sizeof(m_gci_header))) + { + ERROR_LOG(EXPANSIONINTERFACE, "Failed to read header for %s", m_filename.c_str()); + return false; + } + + return true; +} + bool GCIFile::LoadSaveBlocks() { if (m_save_data.empty()) diff --git a/Source/Core/Core/HW/GCMemcard/GCIFile.h b/Source/Core/Core/HW/GCMemcard/GCIFile.h index b118330d34..330c519612 100644 --- a/Source/Core/Core/HW/GCMemcard/GCIFile.h +++ b/Source/Core/Core/HW/GCMemcard/GCIFile.h @@ -15,6 +15,7 @@ class PointerWrap; class GCIFile { public: + bool LoadHeader(); bool LoadSaveBlocks(); bool HasCopyProtection() const; void DoState(PointerWrap& p); From 15abb1c92d1972fcecdcec9bb5edb590c2450009 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 13 May 2019 23:37:50 +0200 Subject: [PATCH 3/3] GCMemcardDirectory: Improve logic for which files are loaded into the virtual memory card. - Files for the current game are now guaranteed to be loaded, space and validity permitting. - Avoid showing PanicAlerts for every problem encountered, most of them aren't really important enough and will probably just annoy the user. - And for the only error the user will definitely care about, when the save of the game they're trying to play fails to load, show an imgui message instead. --- Source/Core/Core/HW/GCMemcard/GCIFile.cpp | 23 +- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 209 +++++++++--------- .../Core/HW/GCMemcard/GCMemcardDirectory.h | 3 +- 3 files changed, 125 insertions(+), 110 deletions(-) diff --git a/Source/Core/Core/HW/GCMemcard/GCIFile.cpp b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp index b82aa6f52b..b173417b8a 100644 --- a/Source/Core/Core/HW/GCMemcard/GCIFile.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCIFile.cpp @@ -4,11 +4,12 @@ #include "Core/HW/GCMemcard/GCIFile.h" +#include + #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/File.h" #include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" bool GCIFile::LoadHeader() { @@ -41,12 +42,24 @@ bool GCIFile::LoadSaveBlocks() return false; INFO_LOG(EXPANSIONINTERFACE, "Reading savedata from disk for %s", m_filename.c_str()); - save_file.Seek(DENTRY_SIZE, SEEK_SET); u16 num_blocks = m_gci_header.m_block_count; - m_save_data.resize(num_blocks); - if (!save_file.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE)) + + const u32 size = num_blocks * BLOCK_SIZE; + u64 file_size = save_file.GetSize(); + if (file_size != size + DENTRY_SIZE) { - PanicAlertT("Failed to read data from GCI file %s", m_filename.c_str()); + ERROR_LOG(EXPANSIONINTERFACE, + "%s\nwas not loaded because it is an invalid GCI.\n File size (0x%" PRIx64 + ") does not match the size recorded in the header (0x%x)", + m_filename.c_str(), file_size, size + DENTRY_SIZE); + return false; + } + + m_save_data.resize(num_blocks); + save_file.Seek(DENTRY_SIZE, SEEK_SET); + if (!save_file.ReadBytes(m_save_data.data(), size)) + { + ERROR_LOG(EXPANSIONINTERFACE, "Failed to read data from GCI file %s", m_filename.c_str()); m_save_data.clear(); return false; } diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index f99012aa0f..54fae94d24 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -32,96 +32,64 @@ const int NO_INDEX = -1; static const char* MC_HDR = "MC_SYSTEM_AREA"; -int GCMemcardDirectory::LoadGCI(const std::string& file_name, bool current_game_only) +bool GCMemcardDirectory::LoadGCI(GCIFile gci) { - File::IOFile gci_file(file_name, "rb"); - if (gci_file) + // check if any already loaded file has the same internal name as the new file + for (const GCIFile& already_loaded_gci : m_saves) { - GCIFile gci; - gci.m_filename = file_name; - gci.m_dirty = false; - if (!gci_file.ReadBytes(&(gci.m_gci_header), DENTRY_SIZE)) + if (gci.m_gci_header.GCI_FileName() == already_loaded_gci.m_gci_header.GCI_FileName()) { - ERROR_LOG(EXPANSIONINTERFACE, "%s failed to read header", file_name.c_str()); - return NO_INDEX; + ERROR_LOG(EXPANSIONINTERFACE, + "%s\nwas not loaded because it has the same internal filename as previously " + "loaded save\n%s", + gci.m_filename.c_str(), already_loaded_gci.m_filename.c_str()); + return false; } - - std::string gci_filename = gci.m_gci_header.GCI_FileName(); - for (u16 i = 0; i < m_loaded_saves.size(); ++i) - { - if (m_loaded_saves[i] == gci_filename) - { - PanicAlertT("%s\nwas not loaded because it has the same internal filename as previously " - "loaded save\n%s", - gci.m_filename.c_str(), m_saves[i].m_filename.c_str()); - return NO_INDEX; - } - } - - u16 num_blocks = gci.m_gci_header.m_block_count; - // largest number of free blocks on a memory card - // in reality, there are not likely any valid gci files > 251 blocks - if (num_blocks > 2043) - { - PanicAlertT( - "%s\nwas not loaded because it is an invalid GCI.\n Number of blocks claimed to be %u", - gci.m_filename.c_str(), num_blocks); - return NO_INDEX; - } - - u32 size = num_blocks * BLOCK_SIZE; - u64 file_size = gci_file.GetSize(); - if (file_size != size + DENTRY_SIZE) - { - PanicAlertT("%s\nwas not loaded because it is an invalid GCI.\n File size (0x%" PRIx64 - ") does not match the size recorded in the header (0x%x)", - gci.m_filename.c_str(), file_size, size + DENTRY_SIZE); - return NO_INDEX; - } - - if (m_game_id == BE32(gci.m_gci_header.m_gamecode.data())) - { - gci.LoadSaveBlocks(); - } - else - { - if (current_game_only) - { - return NO_INDEX; - } - int total_blocks = m_hdr.m_size_mb * MBIT_TO_BLOCKS - MC_FST_BLOCKS; - int free_blocks = m_bat1.m_free_blocks; - if (total_blocks > free_blocks * 10) - { - PanicAlertT("%s\nwas not loaded because there is less than 10%% free blocks available on " - "the memory card\n" - "Total Blocks: %d; Free Blocks: %d", - gci.m_filename.c_str(), total_blocks, free_blocks); - return NO_INDEX; - } - } - u16 first_block = m_bat1.AssignBlocksContiguous(num_blocks); - if (first_block == 0xFFFF) - { - PanicAlertT( - "%s\nwas not loaded because there are not enough free blocks on the virtual memory card", - file_name.c_str()); - return NO_INDEX; - } - gci.m_gci_header.m_first_block = first_block; - if (gci.HasCopyProtection() && gci.LoadSaveBlocks()) - { - GCMemcard::PSO_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data); - GCMemcard::FZEROGX_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data); - } - int idx = (int)m_saves.size(); - m_dir1.Replace(gci.m_gci_header, idx); - m_saves.push_back(std::move(gci)); - SetUsedBlocks(idx); - - return idx; } - return NO_INDEX; + + // check if this file has a valid block size + // 2043 is the largest number of free blocks on a memory card + // in reality, there are not likely any valid gci files > 251 blocks + const u16 num_blocks = gci.m_gci_header.m_block_count; + if (num_blocks > 2043) + { + ERROR_LOG(EXPANSIONINTERFACE, + "%s\nwas not loaded because it is an invalid GCI.\nNumber of blocks claimed to be %u", + gci.m_filename.c_str(), num_blocks); + return false; + } + + if (!gci.LoadSaveBlocks()) + { + ERROR_LOG(EXPANSIONINTERFACE, "Failed to load data of %s", gci.m_filename.c_str()); + return false; + } + + // reserve storage for the save file in the BAT + u16 first_block = m_bat1.AssignBlocksContiguous(num_blocks); + if (first_block == 0xFFFF) + { + ERROR_LOG( + EXPANSIONINTERFACE, + "%s\nwas not loaded because there are not enough free blocks on the virtual memory card", + gci.m_filename.c_str()); + return false; + } + gci.m_gci_header.m_first_block = first_block; + + if (gci.HasCopyProtection()) + { + GCMemcard::PSO_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data); + GCMemcard::FZEROGX_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data); + } + + // actually load save file into memory card + int idx = (int)m_saves.size(); + m_dir1.Replace(gci.m_gci_header, idx); + m_saves.push_back(std::move(gci)); + SetUsedBlocks(idx); + + return true; } // This is only used by NetPlay but it made sense to put it here to keep the relevant code together @@ -187,34 +155,69 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u File::IOFile((m_save_directory + MC_HDR), "rb").ReadBytes(&m_hdr, BLOCK_SIZE); } + const bool current_game_only = Config::Get(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY); std::vector filenames = Common::DoFileSearch({m_save_directory}, {".gci"}); - if (filenames.size() > 112) + // split up into files for current games we should definitely load, + // and files for other games that we don't care too much about + std::vector gci_current_game; + std::vector gci_other_games; + for (const std::string& filename : filenames) { - Core::DisplayMessage("Warning: There are more than 112 save files on this memory card.\n" - " Only loading the first 112 in the folder, unless the game ID is the " - "same as the current game's ID", - 4000); + GCIFile gci; + gci.m_filename = filename; + gci.m_dirty = false; + if (!gci.LoadHeader()) + { + ERROR_LOG(EXPANSIONINTERFACE, "Failed to load header of %s", filename.c_str()); + continue; + } + + if (m_game_id == BE32(gci.m_gci_header.m_gamecode.data())) + gci_current_game.emplace_back(std::move(gci)); + else if (!current_game_only) + gci_other_games.emplace_back(std::move(gci)); } - for (const std::string& gci_file : filenames) + m_saves.reserve(DIRLEN); + + // load files for current game + size_t failed_loads_current_game = 0; + for (GCIFile& gci : gci_current_game) { - if (m_saves.size() == DIRLEN) + if (!LoadGCI(std::move(gci))) { - PanicAlertT( - "There are too many GCI files in the folder\n%s.\nOnly the first 127 will be available", - m_save_directory.c_str()); + // keep track of how many files failed to load for the current game so we can display a + // message to the user informing them why some of their saves may not be loaded + ++failed_loads_current_game; + } + } + + // leave about 10% of free space on the card if possible + const int total_blocks = m_hdr.m_size_mb * MBIT_TO_BLOCKS - MC_FST_BLOCKS; + const int reserved_blocks = total_blocks / 10; + + // load files for other games + for (GCIFile& gci : gci_other_games) + { + // leave some free file entries for new saves that might be created + if (m_saves.size() > 112) break; - } - int index = LoadGCI(gci_file, m_saves.size() > 112 || - Config::Get(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY)); - if (index != NO_INDEX) - { - m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName()); - } + + // leave some free blocks for new saves that might be created + const int free_blocks = m_bat1.m_free_blocks; + const int gci_blocks = gci.m_gci_header.m_block_count; + if (free_blocks - gci_blocks < reserved_blocks) + continue; + + LoadGCI(std::move(gci)); + } + + if (failed_loads_current_game > 0) + { + Core::DisplayMessage("Warning: Save file(s) of the current game failed to load.", 10000); } - m_loaded_saves.clear(); m_dir1.FixChecksums(); m_dir2 = m_dir1; m_bat2 = m_bat1; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index 04e5e908ef..473c1b713c 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -40,7 +40,7 @@ public: void DoState(PointerWrap& p) override; private: - int LoadGCI(const std::string& file_name, bool current_game_only); + bool LoadGCI(GCIFile gci); inline s32 SaveAreaRW(u32 block, bool writing = false); // s32 DirectoryRead(u32 offset, u32 length, u8* dest_address); s32 DirectoryWrite(u32 dest_address, u32 length, const u8* src_address); @@ -56,7 +56,6 @@ private: BlockAlloc m_bat1, m_bat2; std::vector m_saves; - std::vector m_loaded_saves; std::string m_save_directory; Common::Event m_flush_trigger; std::mutex m_write_mutex;