diff --git a/Dalamud.Bootstrap/Bootstrapper.cs b/Dalamud.Bootstrap/Bootstrapper.cs
index f6eec5abc..f39959123 100644
--- a/Dalamud.Bootstrap/Bootstrapper.cs
+++ b/Dalamud.Bootstrap/Bootstrapper.cs
@@ -56,26 +56,19 @@ namespace Dalamud.Bootstrap
var argument = ReadArgumentFromProcess(process);
- var newTick = Environment.TickCount;
+ var newTick = (uint)Environment.TickCount;
var newKey = newTick & 0xFFFF_0000; // only the high nibble is used
- var newArgument = argument.Remove("T")
+ var newArgument = argument
+ .Remove("T")
.Add("T", $"{newTick}")
.ToString();
- // TODO: encode as a encrypted?
- // TODO: launch it
+ var encryptedArgument = new EncryptedArgument(newArgument, newKey);
+
+
+ // TODO: launch new exe with the argument from encryptedArgument.ToString()
- //var newCommandLine =
- // TODO:
- // .... if arg1 exists
- // DecodeSqexArg(arguments[1]);
- // args = ParseArgument()
- // FindArguments(args, "T")
- // RemoveArgs(args, "T")
- // AddArgs(args, "T", newTick)
- // str = ToString()
- // EncodeSqexArg(str, newKey)
process.Terminate();
}
@@ -111,7 +104,7 @@ namespace Dalamud.Bootstrap
argument = encryptedArgument.Decrypt(key);
}
- return ArgumentBuilder.TryParse(argument);
+ return ArgumentBuilder.Parse(argument);
}
///
@@ -143,11 +136,11 @@ namespace Dalamud.Bootstrap
}
catch (Exception ex)
{
- const string message = "Failed to inject Dalamud library into the process.";
+ var exMessage = $"Failed to inject Dalamud library into the process id {pid}.";
// Could not inject Dalamud for whatever reason; it could be process is not actually running, insufficient os privilege, or whatever the thing SE put in their game;
// Therefore there's not much we can do on this side; You have to trobleshoot by yourself somehow.
- throw new ProcessException(message, pid, ex);
+ throw new BootstrapException(exMessage, ex);
}
}
}
diff --git a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs
index 954858ecc..fa5193064 100644
--- a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs
+++ b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs
@@ -12,6 +12,11 @@ namespace Dalamud.Bootstrap.SqexArg
m_dict = new Dictionary();
}
+ public static ArgumentBuilder Parse(string argument)
+ {
+
+ }
+
public ArgumentBuilder Add(string key, string value)
{
m_dict.Add(key, value);
diff --git a/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs b/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs
index d77948826..36706d4e0 100644
--- a/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs
+++ b/Dalamud.Bootstrap/SqexArg/EncryptedArgument.cs
@@ -1,9 +1,20 @@
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Text;
using System.Text.RegularExpressions;
+using Dalamud.Bootstrap.Crypto;
namespace Dalamud.Bootstrap.SqexArg
{
internal sealed class EncryptedArgument
{
+ private static readonly char[] ChecksumTable =
+ {
+ 'f', 'X', '1', 'p', 'G', 't', 'd', 'S',
+ '5', 'C', 'A', 'P', '4', '_', 'V', 'L'
+ };
+
///
/// A data that is encrypted and encoded in url-safe variant of base64.
///
@@ -14,12 +25,127 @@ namespace Dalamud.Bootstrap.SqexArg
///
public char Checksum { get; }
- public EncryptedArgument(string data, char checksum)
+ ///
+ /// Creates an encrypted argument.
+ /// Unlike other constructors, this does not encrypt the data passed to encdoedData parameter and assume that process is already done.
+ ///
+ /// A string that is already encrypted and encoded to url-safe variant of base64.
+ /// A checksum that is used to validate the encryption key.
+ private EncryptedArgument(string encodedData, char checksum)
{
- Data = data;
+ Data = encodedData;
Checksum = checksum;
}
+ ///
+ /// Encrypts a string with given key.
+ ///
+ /// A data that is not encrypted.
+ /// A key that is used to encrypt the data.
+ public EncryptedArgument(string plainText, uint key)
+ {
+ Span keyBytes = stackalloc byte[8];
+ CreateKey(key, keyBytes);
+
+ var blowfish = new Blowfish(keyBytes);
+
+ Data = EncodeString(plainText, blowfish);
+ Checksum = GetChecksum(key);
+ }
+
+ private static char GetChecksum(uint key)
+ {
+ // There's no OoB since ChecksumTable has 16 elements and we mask the key with 0xF
+ var index = (key >> 16) & 0x0000_000F;
+
+ return ChecksumTable[index];
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the data property does not have a valid base64 string.
+ public string Decrypt(uint key)
+ {
+ Span keyBytes = stackalloc byte[8];
+ CreateKey(key, keyBytes);
+
+ var blowfish = new Blowfish(keyBytes);
+
+ return DecodeString(Data, blowfish);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Thrown when the data property does not have a valid base64 string.
+ private static string DecodeString(string payload, Blowfish blowfish)
+ {
+ // plainText <- utf8Bytes <- encryptedBytes(payload) <- base64(payloadStr)
+
+ // base64: 3 bytes per 4 characters
+ // We also want the size to be aligned with the block size.
+ var dataLength = (payload.Length / 4) * 3;
+ var alignedDataLength = AlignBufferLength(dataLength);
+ var encryptedData = new byte[alignedDataLength];
+ var decryptedData = new byte[alignedDataLength];
+
+ // Converts to standard base64 string so that we can feed it to stdlib
+ var base64Str = payload
+ .Replace('-', '+')
+ .Replace('_', '/');
+
+ // base64 -> encryptedBytes
+ if (!Convert.TryFromBase64String(base64Str, encryptedData, out var _))
+ {
+ // We don't care about bytesWritten because we can't handle failure anyway
+ throw new SqexArgException($"A payload {payload} does not look like a valid encrypted argument.");
+ }
+
+ // encryptedBytes -> utf8Bytes (decrypted)
+ blowfish.Decrypt(encryptedData, decryptedData);
+
+ // utf8Bytes -> C# string
+ var plainText = Encoding.UTF8.GetString(decryptedData[..dataLength]);
+
+ return plainText;
+ }
+
+ ///
+ /// Converts plain text string to url-safe variant of base64 string.
+ ///
+ private static string EncodeString(string plainText, Blowfish blowfish)
+ {
+ // plainText -> utf8Bytes -> encryptedBytes(payload) -> base64(payloadStr)
+
+ // This is needed because we want the size to be aligned with the block size. We'll also need to pad them zero.
+ var utf8BytesLength = AlignBufferLength(Encoding.UTF8.GetByteCount(plainText));
+ var utf8Bytes = new byte[utf8BytesLength];
+
+ // We also need the buffer to store encrypted bytes
+ var encryptedBytes = new byte[utf8Bytes.Length];
+
+ // Now we can the string to UTF8
+ // NOTE: This should fail as GetByteCount returns the exact size required to encode utf8 string, but if this assumption is wrong, please make an issue.
+ Encoding.UTF8.GetBytes(plainText, utf8Bytes);
+
+ // Encrypt it
+ blowfish.Encrypt(utf8Bytes, encryptedBytes);
+
+ // Convert to url-safe variant of base64
+ var base64Str = Convert.ToBase64String(encryptedBytes, Base64FormattingOptions.None)
+ .Replace('+', '-')
+ .Replace('/', '_');
+
+ return base64Str;
+ }
+
///
///
///
@@ -65,5 +191,23 @@ namespace Dalamud.Bootstrap.SqexArg
}
public override string ToString() => $"//**sqex0003{Data}{Checksum}**//";
+
+ ///
+ /// Formats a key.
+ ///
+ /// A secret key.
+ /// A buffer where formatted key will be stored. This must be larger than 8 bytes.
+ internal 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");
+ }
+ }
+
+ ///
+ /// Rounds to next mutliple of block size (i.e. 8 bytes) to satisfy with Blowfish requirements.
+ ///
+ internal static int AlignBufferLength(int length) => (length + (Blowfish.BlockSize - 1)) & (-Blowfish.BlockSize);
}
}
diff --git a/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs b/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs
deleted file mode 100644
index 8b580c7fc..000000000
--- a/Dalamud.Bootstrap/SqexArg/EncryptedArgumentExtensions.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Text;
-using Dalamud.Bootstrap.Crypto;
-
-namespace Dalamud.Bootstrap.SqexArg
-{
- internal static class EncryptedArgumentExtensions
- {
- ///
- ///
- ///
- ///
- ///
- ///
- /// Thrown when the data property does not have a valid base64 string.
- public static string Decrypt(this EncryptedArgument argument, uint key)
- {
- Span keyBytes = stackalloc byte[8];
- CreateKey(key, keyBytes);
-
- var encryptedData = DecodeDataString(argument.Data, out var encryptedDataLength);
- var decryptedData = new byte[encryptedData.Length];
-
- var blowfish = new Blowfish(keyBytes);
- blowfish.Decrypt(encryptedData, decryptedData);
-
- return Encoding.UTF8.GetString(decryptedData[..encryptedDataLength]);
- }
-
- ///
- /// 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 data string to bytes. It also allocates more bytes than actual data contained in base64 string for Blowfish.
- ///
- /// A url-safe variant of base64 string.
- /// A data length that is actually written to the buffer.
- private static byte[] DecodeDataString(string payload, out int dataLength)
- {
- var base64Str = payload
- .Replace('-', '+')
- .Replace('_', '/');
-
- // base64: 3 bytes per 4 characters
- dataLength = (payload.Length / 4) * 3;
-
- // round to next mutliple of block size which is what Blowfish can process. (i.e. 8 bytes)
- var alignedLength = (dataLength + (Blowfish.BlockSize - 1)) & (-Blowfish.BlockSize);
-
- var buffer = new byte[alignedLength];
-
- if (!Convert.TryFromBase64String(base64Str, buffer, out var _))
- {
- throw new SqexArgException($"A payload {payload} does not look like a valid encrypted argument.");
- }
-
- return buffer;
- }
-
- private static string EncodeUrlSafeBase64(byte[] payload)
- {
- var payloadStr = Convert.ToBase64String(payload);
-
- return payloadStr
- .Replace('+', '-')
- .Replace('/', '_');
- }
- }
-}