diff --git a/SabreTools.IO.Test/Extensions/ByteArrayExtensionsTests.cs b/SabreTools.IO.Test/Extensions/ByteArrayExtensionsTests.cs
index b742d9a..d7c5f0c 100644
--- a/SabreTools.IO.Test/Extensions/ByteArrayExtensionsTests.cs
+++ b/SabreTools.IO.Test/Extensions/ByteArrayExtensionsTests.cs
@@ -424,6 +424,86 @@ namespace SabreTools.IO.Test.Extensions
#endregion
+ #region Add
+
+ [Theory]
+ [InlineData(new byte[0], 0, new byte[0])]
+ [InlineData(new byte[0], 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD2 }, 1234, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xA4 })]
+ public void Add_NumericInput(byte[] self, uint add, byte[] expected)
+ {
+ byte[] actual = self.Add(add);
+
+ Assert.Equal(expected.Length, actual.Length);
+ if (actual.Length > 0)
+ Assert.True(actual.EqualsExactly(expected));
+ }
+
+ [Theory]
+ [InlineData(new byte[0], new byte[0], new byte[0])]
+ [InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x09, 0xA4 })]
+ [InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x09, 0xA4 })]
+ public void Add_ArrayInput(byte[] self, byte[] add, byte[] expected)
+ {
+ byte[] actual = self.Add(add);
+
+ Assert.Equal(expected.Length, actual.Length);
+ if (actual.Length > 0)
+ Assert.True(actual.EqualsExactly(expected));
+ }
+
+ #endregion
+
+ #region RotateLeft
+
+ [Theory]
+ [InlineData(new byte[0], 0, new byte[0])]
+ [InlineData(new byte[] { 0x01 }, 0, new byte[] { 0x01 })]
+ [InlineData(new byte[] { 0x01 }, 1, new byte[] { 0x02 })]
+ [InlineData(new byte[] { 0x80 }, 1, new byte[] { 0x01 })]
+ [InlineData(new byte[] { 0x00, 0x01 }, 0, new byte[] { 0x00, 0x01 })]
+ [InlineData(new byte[] { 0x00, 0x01 }, 1, new byte[] { 0x00, 0x02 })]
+ [InlineData(new byte[] { 0x00, 0x80 }, 1, new byte[] { 0x01, 0x00 })]
+ [InlineData(new byte[] { 0x80, 0x00 }, 1, new byte[] { 0x00, 0x01 })]
+ public void RotateLeftTest(byte[] self, int numBits, byte[] expected)
+ {
+ byte[] actual = self.RotateLeft(numBits);
+
+ Assert.Equal(expected.Length, actual.Length);
+ if (actual.Length > 0)
+ Assert.True(actual.EqualsExactly(expected));
+ }
+
+ #endregion
+
+ #region Xor
+
+ [Theory]
+ [InlineData(new byte[0], new byte[0], new byte[0])]
+ [InlineData(new byte[0], new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[0], new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x00, 0x00 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0x00, 0x00 })]
+ [InlineData(new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
+ [InlineData(new byte[] { 0x04, 0xD2 }, new byte[] { 0xAB, 0x04, 0xD2 }, new byte[] { 0xAB, 0x00, 0x00 })]
+ public void XorTest(byte[] self, byte[] add, byte[] expected)
+ {
+ byte[] actual = self.Xor(add);
+
+ Assert.Equal(expected.Length, actual.Length);
+ if (actual.Length > 0)
+ Assert.True(actual.EqualsExactly(expected));
+ }
+
+ #endregion
+
#region ToHexString
[Fact]
diff --git a/SabreTools.IO/Extensions/ByteArrayExtensions.cs b/SabreTools.IO/Extensions/ByteArrayExtensions.cs
index 8de4f2a..3b3d89e 100644
--- a/SabreTools.IO/Extensions/ByteArrayExtensions.cs
+++ b/SabreTools.IO/Extensions/ByteArrayExtensions.cs
@@ -237,6 +237,176 @@ namespace SabreTools.IO.Extensions
#endregion
+ #region Math
+
+ ///
+ /// Add an integer value to a number represented by a byte array
+ ///
+ /// Byte array to add to
+ /// Amount to add
+ /// Byte array representing the new value
+ /// Assumes array values are in big-endian format
+ public static byte[] Add(this byte[] self, uint add)
+ {
+ // If nothing is being added, just return
+ if (add == 0)
+ return self;
+
+ // Get the big-endian representation of the value
+ byte[] addBytes = BitConverter.GetBytes(add);
+ Array.Reverse(addBytes);
+
+ // Pad the array out to 16 bytes
+ byte[] paddedBytes = new byte[16];
+ Array.Copy(addBytes, 0, paddedBytes, 12, 4);
+
+ // If the input is empty, just return the added value
+ if (self.Length == 0)
+ return paddedBytes;
+
+ return self.Add(paddedBytes);
+ }
+
+ ///
+ /// Add two numbers represented by byte arrays
+ ///
+ /// Byte array to add to
+ /// Amount to add
+ /// Byte array representing the new value
+ /// Assumes array values are in big-endian format
+ public static byte[] Add(this byte[] self, byte[] add)
+ {
+ // If either input is empty
+ if (self.Length == 0 && add.Length == 0)
+ return [];
+ else if (self.Length > 0 && add.Length == 0)
+ return self;
+ else if (self.Length == 0 && add.Length > 0)
+ return add;
+
+ // Setup the output array
+ int outLength = Math.Max(self.Length, add.Length);
+ byte[] output = new byte[outLength];
+
+ // Loop adding with carry
+ uint carry = 0;
+ for (int i = 0; i < outLength; i++)
+ {
+ int selfIndex = self.Length - i - 1;
+ uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
+
+ int addIndex = add.Length - i - 1;
+ uint addValue = addIndex >= 0 ? add[addIndex] : 0u;
+
+ uint next = selfValue + addValue + carry;
+ carry = next >> 8;
+
+ int outputIndex = output.Length - i - 1;
+ output[outputIndex] = (byte)(next & 0xFF);
+ }
+
+ return output;
+ }
+
+ ///
+ /// Perform a rotate left on a byte array
+ ///
+ /// Byte array value to rotate
+ /// Number of bits to rotate
+ /// Rotated byte array value
+ /// Assumes array values are in big-endian format
+ public static byte[] RotateLeft(this byte[] self, int numBits)
+ {
+ // If either input is empty
+ if (self.Length == 0)
+ return [];
+ else if (numBits == 0)
+ return self;
+
+ byte[] output = new byte[self.Length];
+ Array.Copy(self, output, output.Length);
+
+ // Shift by bytes
+ while (numBits >= 8)
+ {
+ byte temp = output[0];
+ for (int i = 0; i < output.Length - 1; i++)
+ {
+ output[i] = output[i + 1];
+ }
+
+ output[output.Length - 1] = temp;
+ numBits -= 8;
+ }
+
+ // Shift by bits
+ if (numBits > 0)
+ {
+ byte bitMask = (byte)(8 - numBits), carry, wrap = 0;
+ for (int i = 0; i < output.Length; i++)
+ {
+ carry = (byte)((255 << bitMask & output[i]) >> bitMask);
+
+ // Make sure the first byte carries to the end
+ if (i == 0)
+ wrap = carry;
+
+ // Otherwise, move to the last byte
+ else
+ output[i - 1] |= carry;
+
+ // Shift the current bits
+ output[i] <<= numBits;
+ }
+
+ // Make sure the wrap happens
+ output[output.Length - 1] |= wrap;
+ }
+
+ return output;
+ }
+
+ ///
+ /// XOR two numbers represented by byte arrays
+ ///
+ /// Byte array to XOR to
+ /// Amount to XOR
+ /// Byte array representing the new value
+ /// Assumes array values are in big-endian format
+ public static byte[] Xor(this byte[] self, byte[] xor)
+ {
+ // If either input is empty
+ if (self.Length == 0 && xor.Length == 0)
+ return [];
+ else if (self.Length > 0 && xor.Length == 0)
+ return self;
+ else if (self.Length == 0 && xor.Length > 0)
+ return xor;
+
+ // Setup the output array
+ int outLength = Math.Max(self.Length, xor.Length);
+ byte[] output = new byte[outLength];
+
+ // Loop XOR
+ for (int i = 0; i < outLength; i++)
+ {
+ int selfIndex = self.Length - i - 1;
+ uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u;
+
+ int xorIndex = xor.Length - i - 1;
+ uint xorValue = xorIndex >= 0 ? xor[xorIndex] : 0u;
+
+ uint next = selfValue ^ xorValue;
+
+ int outputIndex = output.Length - i - 1;
+ output[outputIndex] = (byte)(next & 0xFF);
+ }
+
+ return output;
+ }
+
+ #endregion
+
#region Strings
///