using System; using System.Linq; namespace PKHeX.Core; /// /// QR Message reading & writing logic /// public static class QRMessageUtil { private const string QR6PathBad = "null/#"; private const string QR6Path = "http://lunarcookies.github.io/b1s1.html#"; private const string QR6PathWC = "http://lunarcookies.github.io/wc.html#"; private static string GetExploitURLPrefixPKM(int format) => format == 6 ? QR6Path : QR6PathBad; private static string GetExploitURLPrefixWC(int format) => format == 6 ? QR6PathWC : QR6PathBad; /// /// Gets the data from the message that is encoded in a QR. /// /// QR Message /// Preferred to expect. /// Decoded object, null if invalid. public static PKM? GetPKM(string message, EntityContext context) { var data = DecodeMessagePKM(message); if (data == null) return null; return EntityFormat.GetFromBytes(data, context); } /// /// Gets a QR Message from the input data. /// /// Pokémon to encode /// QR Message public static string GetMessage(PKM pk) { if (pk is PK7 pk7) { byte[] payload = QR7.GenerateQRData(pk7); return GetMessage(payload); } var server = GetExploitURLPrefixPKM(pk.Format); var data = pk.EncryptedBoxData; return GetMessageBase64(data, server); } /// /// Gets a QR Message from the input data. /// /// Data to encode /// QR Message public static string GetMessage(byte[] payload) => string.Concat(payload.Select(z => (char) z)); /// /// Gets a QR Message from the input data. /// /// Gift data to encode /// QR Message public static string GetMessage(DataMysteryGift mg) { var server = GetExploitURLPrefixWC(mg.Generation); var data = mg.Write(); return GetMessageBase64(data, server); } public static string GetMessageBase64(byte[] data, string server) { string payload = Convert.ToBase64String(data); return server + payload; } private static byte[]? DecodeMessagePKM(string message) { if (message.Length < 32) // arbitrary length check; everything should be greater than this return null; if (message.StartsWith(QR6PathBad, StringComparison.Ordinal)) // fake url return DecodeMessageDataBase64(message); if (message.StartsWith("http", StringComparison.Ordinal)) // inject url return DecodeMessageDataBase64(message); if (message.StartsWith("POKE", StringComparison.Ordinal) && message.Length > 0x30 + 0xE8) // G7 data return GetBytesFromMessage(message, 0x30, 0xE8); return null; } private static byte[]? DecodeMessageDataBase64(string url) { if (url.Length == 0 || url[^1] == '#') return null; try { int payloadBegin = url.IndexOf('#'); if (payloadBegin < 0) // bad URL, need the payload separator return null; url = url[(payloadBegin + 1)..]; // Trim URL to right after # return Convert.FromBase64String(url); } catch (FormatException) { return null; } } private static byte[] GetBytesFromMessage(string seed, int skip, int take) { byte[] data = new byte[take]; for (int i = 0; i < take; i++) data[i] = (byte)seed[i + skip]; return data; } }