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());
- }
- }
-}