diff --git a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs index 17b89e35a..9ce86589c 100644 --- a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs +++ b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs @@ -65,6 +65,8 @@ public sealed class SAV3GCMemoryCard private static int BlockAlloc => BLOCK_SIZE * BlockAlloc_Block; private static int BlockAllocBAK => BLOCK_SIZE * BlockAlloc_Block; + public SAV3GCMemoryCard(byte[] data) => Data = data; + // Checksums private void GetChecksum(int block, int offset, int length, out ushort csum, out ushort inv_csum) { @@ -178,9 +180,8 @@ private void RestoreBackup() Array.Copy(Data, BlockAllocBackup_Block*BLOCK_SIZE, Data, BlockAlloc_Block*BLOCK_SIZE, BLOCK_SIZE); } - public GCMemoryCardState LoadMemoryCardFile(byte[] data) + public GCMemoryCardState GetMemoryCardState() { - Data = data; if (!IsMemoryCardSize(Data)) return GCMemoryCardState.Invalid; // Invalid size diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs new file mode 100644 index 000000000..580247bc0 --- /dev/null +++ b/PKHeX.Core/Util/FileUtil.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace PKHeX.Core +{ + /// + /// Logic for detecting supported binary object formats. + /// + public static class FileUtil + { + /// + /// Attempts to get a binary object from the provided path. + /// + /// + /// Reference savefile used for PC Binary compatibility checks. + /// Supported file object reference, null if none found. + public static object GetSupportedFile(string path, SaveFile reference = null) + { + try + { + var fi = new FileInfo(path); + if (IsFileTooBig(fi.Length) || IsFileTooSmall(fi.Length)) + return null; + + var data = File.ReadAllBytes(path); + var ext = Path.GetExtension(path); + return GetSupportedFile(data, ext, reference); + } + catch (Exception e) + { + Debug.WriteLine(MessageStrings.MsgFileInUse); + Debug.WriteLine(e.Message); + return null; + } + } + + /// + /// Attempts to get a binary object from the provided inputs. + /// + /// Binary data for the file. + /// File extension used as a hint. + /// Reference savefile used for PC Binary compatibility checks. + /// Supported file object reference, null if none found. + public static object GetSupportedFile(byte[] data, string ext, SaveFile reference = null) + { + if (TryGetSAV(data, out var sav)) + return sav; + if (TryGetMemoryCard(data, out var mc)) + return mc; + if (TryGetPKM(data, out var pk, ext)) + return pk; + if (TryGetPCBoxBin(data, out IEnumerable pks, reference)) + return pks; + if (TryGetBattleVideo(data, out BattleVideo bv)) + return bv; + if (TryGetMysteryGift(data, out MysteryGift g, ext)) + return g; + return null; + } + + /// + /// Checks if the length is too big to be a detectable file. + /// + /// File size + public static bool IsFileTooBig(long length) + { + if (length <= 0x100000) + return false; + if (length == SaveUtil.SIZE_G4BR) + return false; + if (SAV3GCMemoryCard.IsMemoryCardSize(length)) + return false; // pbr/GC have size > 1MB + return true; + } + + /// + /// Checks if the length is too small to be a detectable file. + /// + /// File size + public static bool IsFileTooSmall(long length) => length < 0x20; + + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetSAV(byte[] data, out SaveFile sav) + { + sav = SaveUtil.GetVariantSAV(data); + return sav != null; + } + + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetMemoryCard(byte[] data, out SAV3GCMemoryCard memcard) + { + if (!SAV3GCMemoryCard.IsMemoryCardSize(data)) + { + memcard = null; + return false; + } + memcard = new SAV3GCMemoryCard(data); + return true; + } + + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// Format hint + /// Reference savefile used for PC Binary compatibility checks. + /// True if file object reference is valid, false if none found. + public static bool TryGetPKM(byte[] data, out PKM pk, string ext, ITrainerInfo sav = null) + { + if (ext == ".pgt") // size collision with pk6 + { + pk = default(PKM); + return false; + } + var format = PKX.GetPKMFormatFromExtension(ext, sav?.Generation ?? 6); + pk = PKMConverter.GetPKMfromBytes(data, prefer: format); + return pk != null; + } + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// Reference savefile used for PC Binary compatibility checks. + /// True if file object reference is valid, false if none found. + public static bool TryGetPCBoxBin(byte[] data, out IEnumerable pkms, SaveFile SAV) + { + if (SAV == null) + { + pkms = Enumerable.Empty(); + return false; + } + var length = data.Length; + if (PKX.IsPKM(length / SAV.SlotCount) || PKX.IsPKM(length / SAV.BoxSlotCount)) + { + pkms = PKX.GetPKMDataFromConcatenatedBinary(data, length); + return true; + } + pkms = Enumerable.Empty(); + return false; + } + + /// + /// Tries to get a object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetBattleVideo(byte[] data, out BattleVideo bv) + { + bv = BattleVideo.GetVariantBattleVideo(data); + return bv != null; + } + + /// + /// Tries to get a object from the input parameters. + /// + /// Binary data + /// Output result + /// Format hint + /// True if file object reference is valid, false if none found. + public static bool TryGetMysteryGift(byte[] data, out MysteryGift mg, string ext) + { + mg = MysteryGift.GetMysteryGift(data, ext); + return mg != null; + } + } +} diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 13a23ee43..25f35057c 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -502,12 +502,12 @@ private void OpenQuick(string path, bool force = false) return; string ext = Path.GetExtension(path); - if (fi.Length > 0x10009C && fi.Length != SaveUtil.SIZE_G4BR && !SAV3GCMemoryCard.IsMemoryCardSize(fi.Length)) // pbr/GC have size > 1MB + if (FileUtil.IsFileTooBig(fi.Length)) { WinFormsUtil.Error(MsgFileSizeLarge + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); return; } - if (fi.Length < 32) + if (FileUtil.IsFileTooSmall(fi.Length)) { WinFormsUtil.Error(MsgFileSizeSmall + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); return; @@ -525,19 +525,8 @@ private void OpenQuick(string path, bool force = false) private void OpenFile(byte[] input, string path, string ext) { - if (TryLoadXorpadSAV(input, path)) - return; - if (TryLoadSAV(input, path)) - return; - if (TryLoadMemoryCard(input, path)) - return; - if (TryLoadPKM(input, ext)) - return; - if (TryLoadPCBoxBin(input)) - return; - if (TryLoadBattleVideo(input)) - return; - if (TryLoadMysteryGift(input, path, ext)) + var obj = FileUtil.GetSupportedFile(input, ext, C_SAV.SAV); + if (obj != null && LoadFile(obj, path)) return; WinFormsUtil.Error(MsgFileUnsupported, @@ -545,103 +534,46 @@ private void OpenFile(byte[] input, string path, string ext) $"{string.Format(MsgFileSize, input.Length)}{Environment.NewLine}{input.Length} bytes (0x{input.Length:X4})"); } - private bool TryLoadXorpadSAV(byte[] input, string path) + private bool LoadFile(object input, string path) { - if (input.Length == 0x10009C) // Resize to 1MB + if (input == null) + return false; + + switch (input) { - Array.Copy(input, 0x9C, input, 0, 0x100000); - Array.Resize(ref input, 0x100000); + case PKM pk: return OpenPKM(pk); + case SaveFile s: return OpenSAV(s, path); + case BattleVideo b: return OpenBattleVideo(b); + case MysteryGift g: return OpenMysteryGift(g, path); + case IEnumerable pkms: return OpenPCBoxBin(pkms); + + case SAV3GCMemoryCard gc: + if (!CheckGCMemoryCard(gc, path)) + return true; + var mcsav = SaveUtil.GetVariantSAV(gc); + return OpenSAV(mcsav, path); } - if (input.Length != 0x100000) - return false; - if (OpenXOR(input, path)) // Check if we can load the save via xorpad - return true; - - if (BitConverter.ToUInt64(input, 0x10) != 0) // encrypted save - { - WinFormsUtil.Error(MsgFileLoadEncrypted + Environment.NewLine + MsgFileLoadEncryptedFail, path); - return true; - } - - DialogResult sdr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, MsgFileLoadXorpad1, MsgFileLoadXorpad2); - if (sdr == DialogResult.Cancel) - return true; - - int savshift = sdr == DialogResult.Yes ? 0 : 0x7F000; - var psdata = SaveUtil.GetMainFromSaveContainer(input, savshift); - if (psdata != null) - return TryLoadSAV(psdata, path); - - WinFormsUtil.Error(MsgFileLoadSaveFail, path); - return true; + return false; } - private bool TryLoadSAV(byte[] input, string path) + private bool OpenPKM(PKM pk) { - var sav = SaveUtil.GetVariantSAV(input); - if (sav == null) - return false; - OpenSAV(sav, path); - return true; - } - - private bool TryLoadMemoryCard(byte[] input, string path) - { - if (!SAV3GCMemoryCard.IsMemoryCardSize(input)) - return false; - SAV3GCMemoryCard MC = CheckGCMemoryCard(input, path); - if (MC == null) - return false; - var sav = SaveUtil.GetVariantSAV(MC); - if (sav == null) - return false; - OpenSAV(sav, path); - return true; - } - - private bool TryLoadPKM(byte[] input, string ext) - { - if (ext == ".pgt") // size collision with pk6 - return false; - var pk = PKMConverter.GetPKMfromBytes(input, prefer: PKX.GetPKMFormatFromExtension(ext, C_SAV.SAV.Generation)); - if (pk == null) - return false; - + pk = PKMConverter.ConvertToType(pk, C_SAV.SAV.PKMType, out string c); + Debug.WriteLine(c); PKME_Tabs.PopulateFields(pk); return true; } - private bool TryLoadPCBoxBin(byte[] input) + private bool OpenBattleVideo(BattleVideo b) { - if (!C_SAV.IsPCBoxBin(input.Length)) - return false; - if (!C_SAV.OpenPCBoxBin(input, out string c)) - { - WinFormsUtil.Alert(MsgFileLoadIncompatible, c); - return true; - } - - WinFormsUtil.Alert(c); - return true; - } - - private bool TryLoadBattleVideo(byte[] input) - { - if (!BattleVideo.IsValid(input)) - return false; - - BattleVideo b = BattleVideo.GetVariantBattleVideo(input); bool result = C_SAV.OpenBattleVideo(b, out string c); WinFormsUtil.Alert(c); Debug.WriteLine(c); return result; } - private bool TryLoadMysteryGift(byte[] input, string path, string ext) + private bool OpenMysteryGift(MysteryGift tg, string path) { - var tg = MysteryGift.GetMysteryGift(input, ext); - if (tg == null) - return false; if (!tg.IsPokémon) { WinFormsUtil.Alert(MsgPKMMysteryGiftFail, path); @@ -662,24 +594,15 @@ private bool TryLoadMysteryGift(byte[] input, string path, string ext) return true; } - private bool OpenXOR(byte[] input, string path) + private bool OpenPCBoxBin(IEnumerable pkms) { - // try to get a save file via xorpad in same folder - var folder = new DirectoryInfo(path).Parent.FullName; - var pads = Directory.EnumerateFiles(folder); - var s = SaveUtil.GetSAVfromXORpads(input, pads); - - if (s == null) // failed to find xorpad in path folder + if (!C_SAV.OpenPCBoxBin(pkms.SelectMany(z => z).ToArray(), out string c)) { - // try again - pads = Directory.EnumerateFiles(WorkingDirectory); - s = SaveUtil.GetSAVfromXORpads(input, pads); + WinFormsUtil.Alert(MsgFileLoadIncompatible, c); + return true; } - if (s == null) - return false; // failed - - OpenSAV(s, s.FileName); + WinFormsUtil.Alert(c); return true; } @@ -699,30 +622,29 @@ private static GameVersion SelectMemoryCardSaveGame(SAV3GCMemoryCard MC) return dialog.Result; } - private static SAV3GCMemoryCard CheckGCMemoryCard(byte[] Data, string path) + private static bool CheckGCMemoryCard(SAV3GCMemoryCard MC, string path) { - var MC = new SAV3GCMemoryCard(); - var MCState = MC.LoadMemoryCardFile(Data); - switch (MCState) + var state = MC.GetMemoryCardState(); + switch (state) { - default: { WinFormsUtil.Error(MsgFileGameCubeBad, path); return null; } - case GCMemoryCardState.NoPkmSaveGame: { WinFormsUtil.Error(MsgFileGameCubeNoGames, path); return null; } + default: { WinFormsUtil.Error(MsgFileGameCubeBad, path); return false; } + case GCMemoryCardState.NoPkmSaveGame: { WinFormsUtil.Error(MsgFileGameCubeNoGames, path); return false; } case GCMemoryCardState.DuplicateCOLO: case GCMemoryCardState.DuplicateXD: - case GCMemoryCardState.DuplicateRSBOX: { WinFormsUtil.Error(MsgFileGameCubeDuplicate, path); return null; } + case GCMemoryCardState.DuplicateRSBOX: { WinFormsUtil.Error(MsgFileGameCubeDuplicate, path); return false; } case GCMemoryCardState.MultipleSaveGame: { GameVersion Game = SelectMemoryCardSaveGame(MC); if (Game == GameVersion.Invalid) //Cancel - return null; + return false; MC.SelectSaveGame(Game); break; } - case GCMemoryCardState.SaveGameCOLO: MC.SelectSaveGame(GameVersion.COLO); break; - case GCMemoryCardState.SaveGameXD: MC.SelectSaveGame(GameVersion.XD); break; - case GCMemoryCardState.SaveGameRSBOX: MC.SelectSaveGame(GameVersion.RSBOX); break; + case GCMemoryCardState.SaveGameCOLO: MC.SelectSaveGame(GameVersion.COLO); break; + case GCMemoryCardState.SaveGameXD: MC.SelectSaveGame(GameVersion.XD); break; + case GCMemoryCardState.SaveGameRSBOX: MC.SelectSaveGame(GameVersion.RSBOX); break; } - return MC; + return true; } private static void StoreLegalSaveGameData(SaveFile sav) @@ -748,14 +670,14 @@ private static PKM LoadTemplate(SaveFile sav) return PKMConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out path); // no sneaky plz; reuse string } - private void OpenSAV(SaveFile sav, string path) + private bool OpenSAV(SaveFile sav, string path) { if (sav == null || sav.Version == GameVersion.Invalid) - { WinFormsUtil.Error(MsgFileLoadSaveLoadFail, path); return; } + { WinFormsUtil.Error(MsgFileLoadSaveLoadFail, path); return true; } sav.SetFileInfo(path); if (!SanityCheckSAV(ref sav)) - return; + return true; StoreLegalSaveGameData(sav); PKMConverter.Trainer = sav; SpriteUtil.Spriter.Initialize(sav); // refresh sprite generator @@ -778,6 +700,7 @@ private void OpenSAV(SaveFile sav, string path) Menu_ShowdownExportCurrentBox.Visible = sav.HasBox; SystemSounds.Beep.Play(); + return true; } private void ResetSAVPKMEditors(SaveFile sav) @@ -1010,10 +933,16 @@ private void ImportQRToTabs(string url) return; var sav = C_SAV.SAV; - if (TryLoadPKM(input, sav.Generation.ToString())) + if (FileUtil.TryGetPKM(input, out var pk, sav.Generation.ToString(), sav)) + { + OpenPKM(pk); return; - if (TryLoadMysteryGift(input, url, null)) + } + if (FileUtil.TryGetMysteryGift(input, out var mg, url)) + { + OpenMysteryGift(mg, url); return; + } WinFormsUtil.Alert(MsgQRDecodeFail, string.Format(MsgQRDecodeSize, input.Length)); }