using System; using System.Buffers; using System.Buffers.Text; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using Dalamud.Bootstrap.Crypto; namespace Dalamud.Bootstrap.SqexArg { internal sealed class EncryptedArgument { private static char[] ChecksumTable = new char[] { 'f', 'X', '1', 'p', 'G', 't', 'd', 'S', '5', 'C', 'A', 'P', '4', '_', 'V', 'L' }; /// /// Denotes that no checksum is encoded. /// private const char NoChecksumMarker = '!'; private IMemoryOwner m_encryptedData; /// /// Encrypts the argument with given key. /// /// /// public EncryptedArgument(string argument, uint key) { } /// /// Extracts the payload and checksum from the encrypted argument. /// /// /// An encrypted payload extracted. The value is undefined if the function fails. /// A checksum of the key extracted. The value is undefined if the function fails. /// Returns true on success, false otherwise. public static bool Extract(string argument, out byte[] payload, out char checksum) { // must start with //**sqex0003, some characters, one checksum character and end with **// var regex = new Regex(@"^\/\/\*\*sqex0003(?.+)(?.)\*\*\/\/$"); var match = regex.Match(argument); if (!match.Success) { payload = null!; checksum = '\0'; return false; } // Extract checksum checksum = match.Groups["checksum"].Value[0]; // Extract payload var payloadStr = match.Groups["payload"].Value; payload = DecodeUrlSafeBase64(payloadStr); return true; } public override string ToString() { var checksum = GetChecksumFromKey(); } private static char GetChecksumFromKey(uint key) { var index = (key & 0x000F_0000) >> 16; return ChecksumTable[index]; } /// /// /// /// Indicates that returned buffer must be larger than `minimumSize` bytes. /// /// A buffer aligned to next multiple of block size. /// Dispose() must be called when it's not used anymore. /// private static IMemoryOwner CreateAlignedBuffer(int minimumSize) { // align to next multiple of block size. var alignedSize = (minimumSize + Blowfish.BlockSize - 1) & (~(-Blowfish.BlockSize)); return MemoryPool.Shared.Rent(alignedSize); } /// /// Formats a key. /// /// A secret key. /// A buffer where formatted key will be stored. This must be larger than 8 bytes. private static void CreateKey(uint key, Span destination) { if (!Utf8Formatter.TryFormat(key, destination, out var _, new StandardFormat('x', 8))) { throw new InvalidOperationException("BUG: Could not create a key"); } } /// /// Converts the url-safe variant of base64 string to bytes. /// /// A url-safe variant of base64 string. private static byte[] DecodeUrlSafeBase64(string payload) { var base64Str = payload .Replace('-', '+') .Replace('_', '/'); try { return Convert.FromBase64String(base64Str); } catch (FormatException ex) { // This is expected to happen if the argument is ill-formed throw new SqexArgException($"A payload {payload} does not look like a valid encrypted argument.", ex); } } private static string EncodeUrlSafeBase64(byte[] payload) { var payloadStr = Convert.ToBase64String(payload); return payloadStr .Replace('+', '-') .Replace('/', '_'); } } }