diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs index 0dad51832..dee212adf 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs @@ -58,6 +58,8 @@ private static void AddBoxData(SaveFile sav, ConcurrentBag db) private static void AddPartyData(SaveFile sav, ConcurrentBag db) { var count = sav.PartyCount; + if ((uint)count > 6) + count = 6; for (var index = 0; index < count; index++) { var pk = sav.GetPartySlotAtIndex(index); diff --git a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs index 6e257e434..cbf6138d4 100644 --- a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs +++ b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs @@ -47,7 +47,7 @@ public sealed class SAV3GCMemoryCard 0x4000000, // 128MB }; - public static bool IsMemoryCardSize(long Size) => ValidMemoryCardSizes.Contains((int)Size); + public static bool IsMemoryCardSize(long size) => ValidMemoryCardSizes.Contains((int)size); public static bool IsMemoryCardSize(ReadOnlySpan Data) { diff --git a/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs b/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs index 03260bbcb..b22bf44f3 100644 --- a/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs +++ b/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs @@ -12,7 +12,7 @@ public interface ISaveHandler /// /// File size /// True if recognized, false if not recognized. - bool IsRecognized(int size); + bool IsRecognized(long size); /// /// Tries splitting up the into header/footer/data components. Returns null if not a valid save file for this handler. diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs index ed0bf5aa1..696ec0328 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs @@ -10,7 +10,7 @@ public sealed class SaveHandlerARDS : ISaveHandler private const int sizeHeader = 0xA4; private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeHeader; // 0x800A4 - public bool IsRecognized(int size) => size is ExpectedSize; + public bool IsRecognized(long size) => size is ExpectedSize; public SaveHandlerSplitResult TrySplit(ReadOnlySpan input) { diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs index 6b36d0dae..d9636723b 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs @@ -19,7 +19,7 @@ private static bool GetHasFooter(ReadOnlySpan input) return _0x0b == _0x14; } - public bool IsRecognized(int size) => SaveUtil.IsSizeValidNoHandler(size - sizeFooter); + public bool IsRecognized(long size) => SaveUtil.IsSizeValidNoHandler(size - sizeFooter); public SaveHandlerSplitResult? TrySplit(ReadOnlySpan input) { diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs index 4b8c1d383..10c3c30d5 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs @@ -26,7 +26,7 @@ private static bool GetHasFooter(ReadOnlySpan input) return true; } - public bool IsRecognized(int size) => size is ExpectedSize; + public bool IsRecognized(long size) => size is ExpectedSize; public SaveHandlerSplitResult? TrySplit(ReadOnlySpan input) { diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs index 23e6f1265..a1b9cbc7e 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs @@ -38,7 +38,7 @@ private static bool IsGameMatchHeader(ReadOnlySpan data, ReadOnlySpan size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI; + public bool IsRecognized(long size) => size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI; public SaveHandlerSplitResult? TrySplit(ReadOnlySpan input) { diff --git a/PKHeX.Core/Saves/Util/SaveFinder.cs b/PKHeX.Core/Saves/Util/SaveFinder.cs index c45271f6f..213450cd7 100644 --- a/PKHeX.Core/Saves/Util/SaveFinder.cs +++ b/PKHeX.Core/Saves/Util/SaveFinder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -164,7 +164,7 @@ private static bool GetSaveFilePathsFromFolders(IEnumerable foldersToChe /// public static IEnumerable DetectSaveFiles() => GetSaveFiles(Environment.GetLogicalDrives(), true, CustomBackupPaths, true); - /// + /// public static bool TryDetectSaveFile([NotNullWhen(true)] out SaveFile? sav) => TryDetectSaveFile(Environment.GetLogicalDrives(), out sav); public static bool TryDetectSaveFile(IReadOnlyList drives, [NotNullWhen(true)] out SaveFile? sav) diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index 062a1df0f..eb9b47ccf 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -115,7 +115,7 @@ public static class SaveUtil }; #endif - private static readonly HashSet SizesSV = new() + private static readonly HashSet SizesSV = new() { SIZE_G9_0, SIZE_G9_0a, SIZE_G9_1, SIZE_G9_1a, @@ -129,17 +129,17 @@ public static class SaveUtil SIZE_G9_3P0, SIZE_G9_3P1, }; - private static readonly HashSet SizesSWSH = new() + private static readonly HashSet SizesSWSH = new() { SIZE_G8SWSH, SIZE_G8SWSH_1, SIZE_G8SWSH_2, SIZE_G8SWSH_2B, SIZE_G8SWSH_3, SIZE_G8SWSH_3A, SIZE_G8SWSH_3B, SIZE_G8SWSH_3C, }; - private static readonly HashSet SizesGen2 = new() + private static readonly HashSet SizesGen2 = new() { SIZE_G2RAW_U, SIZE_G2VC_U, SIZE_G2BAT_U, SIZE_G2EMU_U, SIZE_G2RAW_J, SIZE_G2BAT_J, SIZE_G2EMU_J, SIZE_G2VC_J, }; - private static readonly HashSet Sizes = new(SizesGen2.Concat(SizesSWSH).Concat(SizesSV)) + private static readonly HashSet Sizes = new(SizesGen2.Concat(SizesSWSH).Concat(SizesSV)) { SIZE_G8LA, SIZE_G8LA_1, SIZE_G8BDSP, SIZE_G8BDSP_1, SIZE_G8BDSP_2, SIZE_G8BDSP_3, // SizesSWSH covers gen8 sizes since there's so many @@ -858,10 +858,9 @@ public static bool GetSavesFromFolder(string folderPath, bool deep, out IEnumera try { var searchOption = deep ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - // force evaluation so that an invalid path will throw before we return true/false. - // EnumerateFiles throws an exception while iterating, which won't be caught by the try-catch here. - var files = Directory.GetFiles(folderPath, "*", searchOption); - result = files.Where(f => !(ignoreBackups && IsBackup(f)) && IsSizeValid(FileUtil.GetFileSize(f))); + var files = Directory.EnumerateFiles(folderPath, "*", searchOption) + .IterateSafe(log: z => System.Diagnostics.Debug.WriteLine(z)); + result = FilterSaveFiles(ignoreBackups, files); return true; } catch (Exception ex) @@ -876,24 +875,47 @@ public static bool GetSavesFromFolder(string folderPath, bool deep, out IEnumera } } - public static bool IsBackup(string path) => Path.GetFileNameWithoutExtension(path).Equals("backup", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path) is ".bak"; + private static IEnumerable FilterSaveFiles(bool ignoreBackups, IEnumerable files) + { + foreach (string file in files) + { + if (ignoreBackups && IsBackup(file)) + continue; + + var size = FileUtil.GetFileSize(file); + if (!IsSizeValid(size)) + continue; + + yield return file; + } + } + + public static bool IsBackup(string path) + { + var fn = Path.GetFileNameWithoutExtension(path); + if (fn == "backup") + return true; + + var ext = Path.GetExtension(path); + return ext == ".bak"; + } /// /// Determines whether the save data size is valid for automatically detecting saves. /// /// Size in bytes of the save data /// A boolean indicating whether or not the save data size is valid. - public static bool IsSizeValid(int size) => IsSizeValidNoHandler(size) || IsSizeValidHandler(size); + public static bool IsSizeValid(long size) => IsSizeValidNoHandler(size) || IsSizeValidHandler(size) || SAV3GCMemoryCard.IsMemoryCardSize(size); /// /// Determines whether the save data size is valid for automatically detecting saves. /// /// Only checks the list. - public static bool IsSizeValidHandler(int size) => Handlers.Any(z => z.IsRecognized(size)); + public static bool IsSizeValidHandler(long size) => Handlers.Any(z => z.IsRecognized(size)); /// /// Determines whether the save data size is valid for automatically detecting saves. /// /// Does not check the list. - public static bool IsSizeValidNoHandler(int size) => Sizes.Contains(size); + public static bool IsSizeValidNoHandler(long size) => Sizes.Contains(size); } diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs index e9d95e61e..a2eb53089 100644 --- a/PKHeX.Core/Util/FileUtil.cs +++ b/PKHeX.Core/Util/FileUtil.cs @@ -73,18 +73,43 @@ public static bool IsFileLocked(string path) catch { return true; } } - public static int GetFileSize(string path) + public static long GetFileSize(string path) { try { - var size = new FileInfo(path).Length; + var fi = new FileInfo(path); + var size = fi.Length; if (size > int.MaxValue) return -1; - return (int)size; + return size; } catch { return -1; } // Bad File / Locked } + public static IEnumerable IterateSafe(this IEnumerable source, int failOut = 10, Action? log = null) + { + using var enumerator = source.GetEnumerator(); + int ctr = 0; + while (true) + { + try + { + var next = enumerator.MoveNext(); + if (!next) + yield break; + } + catch (Exception ex) + { + log?.Invoke(ex); + if (++ctr >= failOut) + yield break; + continue; + } + ctr = 0; + yield return enumerator.Current; + } + } + private static bool TryGetGP1(byte[] data, [NotNullWhen(true)] out GP1? gp1) { gp1 = null; @@ -167,6 +192,16 @@ public static bool TryGetMemoryCard(byte[] data, [NotNullWhen(true)] out SAV3GCM return true; } + /// + public static bool TryGetMemoryCard(string file, [NotNullWhen(true)] out SAV3GCMemoryCard? memcard) + { + memcard = null; + if (!File.Exists(file)) + return false; + var data = File.ReadAllBytes(file); + return TryGetMemoryCard(data, out memcard); + } + /// /// Tries to get an object from the input parameters. /// diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 6f9ad512c..564188641 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -593,6 +593,7 @@ private bool LoadFile(object? input, string path) var mcsav = SaveUtil.GetVariantSAV(gc); if (mcsav is null) return false; + mcsav.Metadata.SetExtraInfo(path); return OpenSAV(mcsav, path); } return false; diff --git a/PKHeX.WinForms/Subforms/SAV_Database.cs b/PKHeX.WinForms/Subforms/SAV_Database.cs index 76b5c6313..21edac5af 100644 --- a/PKHeX.WinForms/Subforms/SAV_Database.cs +++ b/PKHeX.WinForms/Subforms/SAV_Database.cs @@ -433,13 +433,40 @@ private static void TryAddPKMsFromSaveFilePath(ConcurrentBag dbTemp, var sav = SaveUtil.GetVariantSAV(file); if (sav == null) { - Debug.WriteLine("Unable to load SaveFile: " + file); + if (FileUtil.TryGetMemoryCard(file, out var mc)) + TryAddPKMsFromMemoryCard(dbTemp, mc, file); + else + Debug.WriteLine($"Unable to load SaveFile: {file}"); return; } SlotInfoLoader.AddFromSaveFile(sav, dbTemp); } + private static void TryAddPKMsFromMemoryCard(ConcurrentBag dbTemp, SAV3GCMemoryCard mc, string file) + { + var state = mc.GetMemoryCardState(); + if (state == GCMemoryCardState.Invalid) + return; + + if (mc.HasCOLO) + TryAdd(dbTemp, mc, file, GameVersion.COLO); + if (mc.HasXD) + TryAdd(dbTemp, mc, file, GameVersion.XD); + if (mc.HasRSBOX) + TryAdd(dbTemp, mc, file, GameVersion.RSBOX); + + static void TryAdd(ConcurrentBag dbTemp, SAV3GCMemoryCard mc, string path, GameVersion game) + { + mc.SelectSaveGame(game); + var sav = SaveUtil.GetVariantSAV(mc); + if (sav is null) + return; + sav.Metadata.SetExtraInfo(path); + SlotInfoLoader.AddFromSaveFile(sav, dbTemp); + } + } + // IO Usage private void OpenDB(object sender, EventArgs e) {