diff --git a/PKHeX.Core/Game/GameInfo.cs b/PKHeX.Core/Game/GameInfo.cs index c99141d59..bff8301e3 100644 --- a/PKHeX.Core/Game/GameInfo.cs +++ b/PKHeX.Core/Game/GameInfo.cs @@ -567,7 +567,7 @@ private static string GetRegionString(int country, int region, int language) /// Generation to get location names for. /// BankID used to choose the text bank. /// List of location names. - public static string[] GetLocationNames(int gen, int bankID) + private static string[] GetLocationNames(int gen, int bankID) { switch (gen) { @@ -611,5 +611,51 @@ public static string[] GetLocationNames(int gen, int bankID) return null; } } + + /// + /// Gets the location name for the specified parameters. + /// + /// Location is from the + /// Location value + /// Current + /// of origin + /// Location name + public static string GetLocationName(bool eggmet, int locval, int format, int generation) + { + int gen = -1; + int bankID = 0; + + if (format == 2) + gen = 2; + else if (format == 3) + gen = 3; + else if (generation == 4 && (eggmet || format == 4)) // 4 + { + const int size = 1000; + bankID = locval / size; + gen = 4; + locval %= size; + } + else // 5-7+ + { + const int size = 10000; + bankID = locval / size; + + int g = generation; + if (g >= 5) + gen = g; + else if (format >= 5) + gen = format; + + locval %= size; + if (bankID >= 3) + locval -= 1; + } + + var bank = GetLocationNames(gen, bankID); + if (bank == null || bank.Length <= locval) + return string.Empty; + return bank[locval]; + } } } diff --git a/PKHeX.Core/PKM/PKX.cs b/PKHeX.Core/PKM/PKX.cs index fc5cf84bd..5e8b1a559 100644 --- a/PKHeX.Core/PKM/PKX.cs +++ b/PKHeX.Core/PKM/PKX.cs @@ -168,15 +168,18 @@ public static bool IsPKM(long len) }; #endregion + /// + /// Species name lists indexed by the value. + /// public static readonly string[][] SpeciesLang = { - Util.GetSpeciesList("ja"), // none + Util.GetSpeciesList("ja"), // 0 (unused, invalid) Util.GetSpeciesList("ja"), // 1 Util.GetSpeciesList("en"), // 2 Util.GetSpeciesList("fr"), // 3 Util.GetSpeciesList("it"), // 4 Util.GetSpeciesList("de"), // 5 - Util.GetSpeciesList("es"), // none + Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused) Util.GetSpeciesList("es"), // 7 Util.GetSpeciesList("ko"), // 8 Util.GetSpeciesList("zh"), // 9 Simplified @@ -570,6 +573,14 @@ public static int[] SetHPIVs(int type, int[] ivs) ivs[i] = (ivs[i] & 0x1E) + hpivs[type, i]; return ivs; } + + /// + /// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type + /// + /// + /// There are other IV combinations to achieve the same Hidden Power Type. + /// These are just precomputed for fast modification. + /// public static readonly int[,] hpivs = { { 1, 1, 0, 0, 0, 0 }, // Fighting { 0, 0, 0, 0, 0, 1 }, // Flying @@ -801,46 +812,19 @@ public static string[] GetPKMExtensions(int MaxGeneration = Generation) } // Extensions + /// + /// Gets the Location Name for the + /// + /// PKM to fetch data for + /// Location requested is the egg obtained location, not met location. + /// Location string public static string GetLocationString(this PKM pk, bool eggmet) { if (pk.Format < 2) return ""; - int gen = -1; - int bankID = 0; int locval = eggmet ? pk.Egg_Location : pk.Met_Location; - - if (pk.Format == 2) - gen = 2; - else if (pk.Format == 3) - gen = 3; - else if (pk.Gen4 && (eggmet || pk.Format == 4)) // 4 - { - const int size = 1000; - bankID = locval/size; - gen = 4; - locval %= size; - } - else // 5-7+ - { - const int size = 10000; - bankID = locval/size; - - int g = pk.GenNumber; - if (g >= 5) - gen = g; - else if (pk.Format >= 5) - gen = pk.Format; - - locval %= size; - if (bankID >= 3) - locval -= 1; - } - - var bank = GameInfo.GetLocationNames(gen, bankID); - if (bank == null || bank.Length <= locval) - return ""; - return bank[locval]; + return GameInfo.GetLocationName(eggmet, locval, pk.Format, pk.GenNumber); } public static string[] GetQRLines(this PKM pkm) { diff --git a/PKHeX.Core/Saves/SAV7.cs b/PKHeX.Core/Saves/SAV7.cs index e617b4b6a..517c6366c 100644 --- a/PKHeX.Core/Saves/SAV7.cs +++ b/PKHeX.Core/Saves/SAV7.cs @@ -1250,14 +1250,6 @@ public override string MiscSaveChecks() { var r = new StringBuilder(); - // MemeCrypto check - if (RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) - { - r.AppendLine("Platform does not support required cryptography providers."); - r.AppendLine("Checksum will be broken until the file is saved using an OS without FIPS compliance enabled or a newer OS."); - r.AppendLine(); - } - // FFFF checks byte[] FFFF = Enumerable.Repeat((byte)0xFF, 0x200).ToArray(); for (int i = 0; i < Data.Length / 0x200; i++) diff --git a/PKHeX.Core/Saves/SaveUtil.cs b/PKHeX.Core/Saves/SaveUtil.cs index fc50dd8fa..7c73e9023 100644 --- a/PKHeX.Core/Saves/SaveUtil.cs +++ b/PKHeX.Core/Saves/SaveUtil.cs @@ -10,6 +10,7 @@ public static class SaveUtil { public const int BEEF = 0x42454546; + public const int SIZE_G7USUM = -1; public const int SIZE_G7SM = 0x6BE00; public const int SIZE_G6XY = 0x65600; public const int SIZE_G6ORAS = 0x76000; @@ -47,10 +48,10 @@ public static class SaveUtil SIZE_G1RAW, SIZE_G1BAT }; - public static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); - public static readonly string[] HEADER_COLO = { "GC6J","GC6E","GC6P" }; // NTSC-J, NTSC-U, PAL - public static readonly string[] HEADER_XD = { "GXXJ","GXXE","GXXP" }; // NTSC-J, NTSC-U, PAL - public static readonly string[] HEADER_RSBOX = { "GPXJ","GPXE","GPXP" }; // NTSC-J, NTSC-U, PAL + private static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); + internal static readonly string[] HEADER_COLO = { "GC6J","GC6E","GC6P" }; // NTSC-J, NTSC-U, PAL + internal static readonly string[] HEADER_XD = { "GXXJ","GXXE","GXXP" }; // NTSC-J, NTSC-U, PAL + internal static readonly string[] HEADER_RSBOX = { "GPXJ","GPXE","GPXP" }; // NTSC-J, NTSC-U, PAL /// Determines the generation of the given save data. /// Save data of which to determine the generation @@ -562,11 +563,11 @@ public static SaveFile GetBlankSAV(int generation, string OT) } /// - /// Retrieves the full path of the most recent file based on LastWriteTime. + /// Retrieves the full path of the most recent file based on . /// /// Folder to look within /// Search all subfolders - /// If this function returns true, full path of all save files that match criteria. If this function returns false, the error message, or null if the directory could not be found + /// If this function returns true, full path of all that match criteria. If this function returns false, the error message, or null if the directory could not be found /// Boolean indicating whether or not operation was successful. public static bool GetSavesFromFolder(string folderPath, bool deep, out IEnumerable result) { @@ -684,7 +685,7 @@ public static ushort CRC16_7(byte[] data, int blockID, ushort initial = 0) } public static byte[] Resign7(byte[] sav7) { - return MemeCrypto.Resign(sav7, false); + return MemeCrypto.Resign(sav7); } /// Calculates the 32bit checksum over an input byte array. Used in GBA save files. /// Input byte array diff --git a/PKHeX.Core/Saves/Substructures/MemeCrypto.cs b/PKHeX.Core/Saves/Substructures/MemeCrypto.cs index 135fc3708..58b2bb4ae 100644 --- a/PKHeX.Core/Saves/Substructures/MemeCrypto.cs +++ b/PKHeX.Core/Saves/Substructures/MemeCrypto.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Numerics; using System.Security.Cryptography; -using System.Text; namespace PKHeX.Core { @@ -12,19 +11,17 @@ public static class MemeCrypto private static byte[] AESECBEncrypt(byte[] key, byte[] data) { using (var ms = new MemoryStream()) + using (var aes = Aes.Create()) { - using (var aes = Util.GetAesProvider()) + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + using (var cs = new CryptoStream(ms, aes.CreateEncryptor(key, new byte[0x10]), CryptoStreamMode.Write)) { - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); - using (var cs = new CryptoStream(ms, aes.CreateEncryptor(key, new byte[0x10]), CryptoStreamMode.Write)) - { - cs.Write(data, 0, data.Length); - cs.FlushFinalBlock(); - - return ms.ToArray(); - } + return ms.ToArray(); } } } @@ -32,19 +29,17 @@ private static byte[] AESECBEncrypt(byte[] key, byte[] data) private static byte[] AESECBDecrypt(byte[] key, byte[] data) { using (var ms = new MemoryStream()) + using (var aes = Aes.Create()) { - using (var aes = Util.GetAesProvider()) + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + using (var cs = new CryptoStream(ms, aes.CreateDecryptor(key, new byte[0x10]), CryptoStreamMode.Write)) { - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); - using (var cs = new CryptoStream(ms, aes.CreateDecryptor(key, new byte[0x10]), CryptoStreamMode.Write)) - { - cs.Write(data, 0, data.Length); - cs.FlushFinalBlock(); - - return ms.ToArray(); - } + return ms.ToArray(); } } } @@ -171,10 +166,9 @@ private static byte[] RSAEncrypt(byte[] data, int index) private static byte[] ReverseCrypt(byte[] input, int meme_ofs, int memeindex) { var output = (byte[])input.Clone(); - var memekey = MemeKeys[memeindex]; - using (var sha1 = Util.GetSHA1Provider()) + using (var sha1 = SHA1.Create()) { var enc = new byte[0x60]; Array.Copy(input, meme_ofs, enc, 0, 0x60); @@ -202,6 +196,7 @@ private static byte[] ReverseCrypt(byte[] input, int meme_ofs, int memeindex) return null; } + private const uint POKE = 0x454B4F50; public static byte[] VerifyMemeData(byte[] input) { if (input.Length < 0x60) @@ -211,7 +206,7 @@ public static byte[] VerifyMemeData(byte[] input) for (var i = input.Length - 8; i >= 0; i--) { - if (BitConverter.ToUInt32(input, i) != 0x454B4F50 || + if (BitConverter.ToUInt32(input, i) != POKE || BitConverter.ToUInt32(input, i + 4) >= MemeKeys.Length) continue; meme_ofs = i - 0x60; @@ -236,7 +231,7 @@ public static byte[] SignMemeData(byte[] input) if (input.Length < 0x60) throw new ArgumentException("Bad Meme input!"); const int memeindex = 3; - using (var sha1 = Util.GetSHA1Provider()) + using (var sha1 = SHA1.Create()) { var key = sha1.ComputeHash(MemeKeys[memeindex].DER.Concat(input.Take(input.Length - 0x60)).ToArray()).Take(0x10).ToArray(); @@ -251,66 +246,40 @@ public static byte[] SignMemeData(byte[] input) } /// - /// Resigns save data. + /// Resigns save data. /// - /// The save data to resign. - /// - /// If true, throw an if MemeCrypto is - /// unsupported. If false, calling this function will have no effect. - /// - /// - /// Thrown if the current platform has FIPS mode enabled on a platform that - /// does not support the required crypto service providers. - /// /// The resigned save data. - public static byte[] Resign(byte[] sav7, bool throwIfUnsupported = true) + public static byte[] Resign(byte[] sav7) { - if (sav7 == null || sav7.Length != 0x6BE00) + if (sav7 == null || sav7.Length != SaveUtil.SIZE_G7SM && sav7.Length != SaveUtil.SIZE_G7USUM) return null; - try + // Save Chunks are 0x200 bytes each; Memecrypto signature is 0x100 bytes into the 2nd to last chunk. + int ChecksumTableOffset = sav7.Length - 0x200; + int MemeCryptoOffset = ChecksumTableOffset - 0x100; + const int ChecksumSignatureLength = 0x140; + const int MemeCryptoSignatureLength = 0x80; + + var outSav = (byte[])sav7.Clone(); + + using (var sha256 = SHA256.Create()) { - var outSav = (byte[])sav7.Clone(); + // Store current signature + var CurSig = new byte[MemeCryptoSignatureLength]; + Buffer.BlockCopy(sav7, MemeCryptoOffset, CurSig, 0, MemeCryptoSignatureLength); - using (var sha256 = Util.GetSHA256Provider()) - { - var CurSig = new byte[0x80]; - Array.Copy(sav7, 0x6BB00, CurSig, 0, 0x80); + var ChecksumTableSignature = new byte[ChecksumSignatureLength]; + Buffer.BlockCopy(sav7, ChecksumTableOffset, ChecksumTableSignature, 0, ChecksumSignatureLength); - var ChecksumTable = new byte[0x140]; - Array.Copy(sav7, 0x6BC00, ChecksumTable, 0, 0x140); + var newSig = new byte[MemeCryptoSignatureLength]; + sha256.ComputeHash(ChecksumTableSignature).CopyTo(newSig, 0); + var memeSig = VerifyMemeData(CurSig); + if (memeSig != null) + Buffer.BlockCopy(memeSig, 0x20, newSig, 0x20, 0x60); - var newSig = new byte[0x80]; - sha256.ComputeHash(ChecksumTable).CopyTo(newSig, 0); - var memeSig = VerifyMemeData(CurSig); - if (memeSig != null) - Array.Copy(memeSig, 0x20, newSig, 0x20, 0x60); - - SignMemeData(newSig).CopyTo(outSav, 0x6BB00); - } - return outSav; + SignMemeData(newSig).CopyTo(outSav, MemeCryptoOffset); } - catch (InvalidOperationException) - { - if (throwIfUnsupported) - { - throw; - } - return (byte[])sav7.Clone(); - } - } - - public static bool CanUseMemeCrypto() - { - try - { - Util.GetSHA256Provider(); - } - catch (InvalidOperationException) - { - return false; - } - return true; + return outSav; } #region Meme Key Data @@ -400,13 +369,5 @@ public static byte[] Xor(this byte[] b1, byte[] b2) x[i] = (byte)(b1[i] ^ b2[i]); return x; } - - public static string ToHexString(this byte[] ba) - { - var hex = new StringBuilder(ba.Length * 2); - foreach (var b in ba) - hex.AppendFormat("{0:X2}", b); - return hex.ToString(); - } } } \ No newline at end of file diff --git a/PKHeX.Core/Util/CryptoUtil.cs b/PKHeX.Core/Util/CryptoUtil.cs deleted file mode 100644 index 77da136da..000000000 --- a/PKHeX.Core/Util/CryptoUtil.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Security.Cryptography; - -namespace PKHeX.Core -{ - public partial class Util - { - - /// - /// Creates a new instance of , or if not supported by the current platform. - /// - /// Thrown if FIPS mode is enabled on a platform that does not support . - public static SHA1 GetSHA1Provider() - { - return SHA1.Create(); - } - - /// - /// Creates a new instance of , or if not supported by the current platform. - /// - /// Thrown if FIPS mode is enabled on a platform that does not support . - public static SHA256 GetSHA256Provider() - { - return SHA256.Create(); - } - - /// - /// Creates a new instance of , or if not supported by the current platform. - /// - /// Thrown if FIPS mode is enabled on a platform that does not support . - public static Aes GetAesProvider() - { - return Aes.Create(); - } - } -} diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 44fa13f48..a2398b4b2 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -743,7 +743,7 @@ private void OpenSAV(SaveFile sav, string path) if (sav == null || sav.Version == GameVersion.Invalid) { WinFormsUtil.Error("Invalid save file loaded. Aborting.", path); return; } - if (!SanityCheckSAV(ref sav, path)) + if (!SanityCheckSAV(ref sav)) return; StoreLegalSaveGameData(sav); PKMUtil.Initialize(sav); // refresh sprite generator @@ -834,17 +834,8 @@ private static bool TryBackupExportCheck(SaveFile sav, string path) "If the path is a removable disk (SD card), please ensure the write protection switch is not set."); return false; } - private static bool SanityCheckSAV(ref SaveFile sav, string path) + private static bool SanityCheckSAV(ref SaveFile sav) { - if (!string.IsNullOrWhiteSpace(path)) // If path is null, this is the default save - { - if (sav.RequiresMemeCrypto && !MemeCrypto.CanUseMemeCrypto()) - { - WinFormsUtil.Error("Your platform does not support the required cryptography components.", - "In order to be able to save your changes, you must either upgrade to a newer version of Windows or disable FIPS compliance mode."); - // Don't abort loading; user can still view save and fix checksum on another platform. - } - } // Finish setting up the save file. if (sav.Generation == 1) { diff --git a/PKHeX.WinForms/Util/PathUtilWindows.cs b/PKHeX.WinForms/Util/PathUtilWindows.cs index 03e8d2e78..0b67beea8 100644 --- a/PKHeX.WinForms/Util/PathUtilWindows.cs +++ b/PKHeX.WinForms/Util/PathUtilWindows.cs @@ -1,21 +1,33 @@ -using PKHeX.Core; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using PKHeX.Core; + namespace PKHeX.WinForms { public static class PathUtilWindows { - public static string Get3DSLocation() + /// + /// Gets the 3DS's root folder, usually from an inserted SD card. + /// + /// Optional parameter to skip the first drive. + /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data). + /// Folder path pointing to the Nintendo 3DS folder. + public static string Get3DSLocation(bool skipFirstDrive = true) { try { - string[] DriveList = Environment.GetLogicalDrives(); - for (int i = 1; i < DriveList.Length; i++) // Skip first drive (some users still have floppy drives and would chew up time!) + IEnumerable DriveList = Environment.GetLogicalDrives(); + + // Skip first drive (some users still have floppy drives and would chew up time!) + if (skipFirstDrive) + DriveList = DriveList.Skip(1); + + foreach (var drive in DriveList) { - string potentialPath = Path.Combine(DriveList[i], "Nintendo 3DS"); + string potentialPath = Path.Combine(drive, "Nintendo 3DS"); if (Directory.Exists(potentialPath)) return potentialPath; } @@ -29,41 +41,37 @@ public static string Get3DSLocation() /// /// Root location of device /// List of possible 3DS save backup paths. - public static string[] Get3DSBackupPaths(string root) + public static IEnumerable Get3DSBackupPaths(string root) { - return new[] - { - Path.Combine(root, "saveDataBackup"), - Path.Combine(root, "filer", "UserSaveData"), - Path.Combine(root, "JKSV", "Saves"), - Path.Combine(root, "TWLSaveTool"), - Path.Combine(root, "fbi", "save"), - Path.Combine(root, "gm9", "out"), - Path.Combine(root, "3ds", "data", "JKSM", "Saves"), - }; + yield return Path.Combine(root, "saveDataBackup"); + yield return Path.Combine(root, "filer", "UserSaveData"); + yield return Path.Combine(root, "JKSV", "Saves"); + yield return Path.Combine(root, "TWLSaveTool"); + yield return Path.Combine(root, "fbi", "save"); + yield return Path.Combine(root, "gm9", "out"); } /// - /// Detects a save file. + /// Finds a compatible save file that was most recently saved (by file write time). /// /// If this function returns true, full path of a save file or null if no path could be found. If this function returns false, this parameter will be set to the error message. /// Paths to check in addition to the default paths /// A boolean indicating whether or not a file was detected public static bool DetectSaveFile(out string path, params string[] extra) { + var foldersToCheck = extra.Where(f => f?.Length > 0); + string path3DS = Path.GetPathRoot(Get3DSLocation()); - List possiblePaths = new List(); - List foldersToCheck = new List(extra.Where(f => f?.Length > 0)); - path = null; - if (path3DS != null) // check for Homebrew/CFW backups - foldersToCheck.AddRange(Get3DSBackupPaths(path3DS)); + foldersToCheck = foldersToCheck.Concat(Get3DSBackupPaths(path3DS)); - foreach (var p in foldersToCheck) + path = null; + List possiblePaths = new List(); + foreach (var folder in foldersToCheck) { - if (!SaveUtil.GetSavesFromFolder(p, true, out IEnumerable files)) + if (!SaveUtil.GetSavesFromFolder(folder, true, out IEnumerable files)) { - if (files != null) // Could be null if `p` doesn't exist + if (files != null) // can be null if folder doesn't exist { path = string.Join(Environment.NewLine, files); // `files` contains the error message return false; diff --git a/Tests/PKHeX.Tests/PKHeX.Tests.csproj b/Tests/PKHeX.Tests/PKHeX.Tests.csproj index 0ddb23e35..7664b59db 100644 --- a/Tests/PKHeX.Tests/PKHeX.Tests.csproj +++ b/Tests/PKHeX.Tests/PKHeX.Tests.csproj @@ -77,7 +77,6 @@ - @@ -90,7 +89,9 @@ PKHeX.WinForms - + + + diff --git a/Tests/PKHeX.Tests/Saves/Substructures/MemeCryptoTests.cs b/Tests/PKHeX.Tests/Saves/Substructures/MemeCryptoTests.cs deleted file mode 100644 index f76c18e4d..000000000 --- a/Tests/PKHeX.Tests/Saves/Substructures/MemeCryptoTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PKHeX.Core; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PKHeX.Tests.Saves.Substructures -{ - [TestClass] - public class MemeCryptoTests - { - [TestMethod] - public void CanUseMemeCrypto() - { - Assert.IsTrue(MemeCrypto.CanUseMemeCrypto()); - } - } -}