diff --git a/eAmuseCore/KBinXML/ByteBuffer.cs b/eAmuseCore/KBinXML/ByteBuffer.cs new file mode 100644 index 0000000..ae78ebb --- /dev/null +++ b/eAmuseCore/KBinXML/ByteBuffer.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace eAmuseCore.KBinXML +{ + public class ByteBuffer + { + private List data; + private int wordWriteOffset = 0, byteWriteOffset = 0; + private int wordReadOffset = 0, byteReadOffset = 0; + private int limit = -1; + private readonly int align = 4; + + public int Offset { get; set; } = 0; + + public int Length + { + get => (limit >= 0) ? limit : data.Count; + } + + public int Remaining + { + get => Length - Offset; + } + + public bool AtEnd + { + get => Offset >= Length; + } + + public ByteBuffer() + { + data = new List(); + } + + public ByteBuffer(IEnumerable data) + { + this.data = new List(data); + } + + public ByteBuffer(ByteBuffer other) + { + data = other.data; + Offset = other.Offset; + } + + public byte this[int idx] + { + get => data[idx]; + set => data[idx] = value; + } + + public void CopyTo(int idx, byte[] arr, int offset, int count) + { + data.CopyTo(idx, arr, offset, count); + } + + public ByteBuffer MakeSub(int offset, int length = -1) + { + ByteBuffer res = new ByteBuffer(this); + res.Offset += offset; + if (length >= 0 && offset + length <= Length) + res.limit = offset + length; + return res; + } + + public void RealignReads() + { + int realign = align - (Offset % align); + if (realign == align) + return; + Offset += realign; + } + + public void RealignWrites() + { + int realign = align - (data.Count % align); + if (realign == align) + return; + while (realign-- > 0) + data.Add(0); + } + + public byte[] TakeBytes(int count) + { + byte[] res = new byte[count]; + data.CopyTo(Offset, res, 0, count); + Offset += count; + return res; + } + + public byte[] TakeBytesSubAligned(int count) + { + byte[] res = new byte[count]; + + if (count == 1) + { + if (byteReadOffset % align == 0) + { + byteReadOffset = Offset; + Offset += align; + } + data.CopyTo(byteReadOffset, res, 0, 1); + byteReadOffset += 1; + } + else if (count == 2) + { + if (wordReadOffset % align == 0) + { + wordReadOffset = Offset; + Offset += align; + } + data.CopyTo(wordReadOffset, res, 0, 2); + wordReadOffset += 2; + } + else if (count >= 3) + { + data.CopyTo(Offset, res, 0, count); + Offset += count; + RealignReads(); + } + + return res; + } + + public byte[] TakeBytesAligned(int count) + { + byte[] res = TakeBytes(count); + RealignReads(); + return res; + } + + public byte[] TakeBytesEndian(int count) + { + byte[] res = TakeBytes(count); + if (BitConverter.IsLittleEndian) + Array.Reverse(res); + return res; + } + + public byte TakeU8() + { + return data[Offset++]; + } + + public sbyte TakeS8() + { + return unchecked((sbyte)data[Offset++]); + } + + public ushort TakeU16() + { + byte[] bytes = TakeBytesEndian(2); + return BitConverter.ToUInt16(bytes, 0); + } + + public short TakeS16() + { + byte[] bytes = TakeBytesEndian(2); + return BitConverter.ToInt16(bytes, 0); + } + + public uint TakeU32() + { + byte[] bytes = TakeBytesEndian(4); + return BitConverter.ToUInt32(bytes, 0); + } + + public int TakeS32() + { + byte[] bytes = TakeBytesEndian(4); + return BitConverter.ToInt32(bytes, 0); + } + + public ulong TakeU64() + { + byte[] bytes = TakeBytesEndian(8); + return BitConverter.ToUInt64(bytes, 0); + } + + public long TakeS64() + { + byte[] bytes = TakeBytesEndian(8); + return BitConverter.ToInt64(bytes, 0); + } + + public float TakeFloat() + { + byte[] bytes = TakeBytesEndian(4); + return BitConverter.ToSingle(bytes, 0); + } + + public double TakeDouble() + { + byte[] bytes = TakeBytesEndian(8); + return BitConverter.ToDouble(bytes, 0); + } + + public string TakeString(Encoding encoding) + { + int length = TakeS32(); + byte[] bytes = TakeBytes(length); + return encoding.GetString(bytes, 0, length - 1); + } + + public void AddBytes(IEnumerable bytes) + { + data.AddRange(bytes); + } + + public void AddBytesSubAligned(byte[] bytes) + { + if (bytes.Length == 1) + { + if (byteWriteOffset % align == 0) + { + byteWriteOffset = data.Count; + data.AddRange(new byte[align]); + } + data[byteWriteOffset++] = bytes[0]; + } + else if (bytes.Length == 2) + { + if (wordWriteOffset % align == 0) + { + wordWriteOffset = data.Count; + data.AddRange(new byte[align]); + } + data[wordWriteOffset++] = bytes[0]; + data[wordWriteOffset++] = bytes[1]; + } + else if(bytes.Length >= 3) + { + data.AddRange(bytes); + RealignWrites(); + } + } + + public void AddBytesAligned(byte[] bytes) + { + data.AddRange(bytes); + RealignWrites(); + } + + public void AddBytesEndian(byte[] bytes) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes); + data.AddRange(bytes); + } + + public void AddU8(byte input) + { + data.Add(input); + } + + public void AddS8(sbyte input) + { + data.Add(unchecked((byte)input)); + } + + public void AddU16(ushort input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddS16(short input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddU32(uint input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddS32(int input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddU64(ulong input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddS64(long input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddFloat(float input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddDouble(double input) + { + AddBytesEndian(BitConverter.GetBytes(input)); + } + + public void AddString(string input, Encoding encoding) + { + byte[] bytes = encoding.GetBytes(input); + AddS32(bytes.Length + 1); + AddBytes(bytes); + AddU8(0); + } + } +} diff --git a/eAmuseCore/KBinXML/Helpers.cs b/eAmuseCore/KBinXML/Helpers.cs index f15c281..c9924a2 100644 --- a/eAmuseCore/KBinXML/Helpers.cs +++ b/eAmuseCore/KBinXML/Helpers.cs @@ -1,222 +1,73 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace eAmuseCore.KBinXML.Helpers { - static class EnumHelpers + static class ByteArrayHelpers { - public static IEnumerable TakeU64(this IEnumerable input, int count) + public static ulong GetU64(this byte[] input, int offset = 0) { - for (int i = 0; i < count; ++i) - { - input = input.Take(8); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToUInt64(input.ToArray(), 0); - } - } - - public static IEnumerable TakeS64(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(8); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToInt64(input.ToArray(), 0); - } - } - - public static IEnumerable TakeU32(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(4); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToUInt32(input.ToArray(), 0); - } - } - - public static IEnumerable TakeS32(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(4); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToInt32(input.ToArray(), 0); - } - } - - public static IEnumerable TakeU16(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(2); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToUInt16(input.ToArray(), 0); - } - } - - public static IEnumerable TakeS16(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(2); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToInt16(input.ToArray(), 0); - } - } - - public static IEnumerable TakeU8(this IEnumerable input, int count) - { - return input.Take(count); - } - - public static IEnumerable TakeS8(this IEnumerable input, int count) - { - return input.Take(count).Select(b => unchecked((sbyte)b)); - } - - public static IEnumerable TakeF(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(4); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToSingle(input.ToArray(), 0); - } - } - - public static IEnumerable TakeD(this IEnumerable input, int count) - { - for (int i = 0; i < count; ++i) - { - input = input.Take(8); - if (BitConverter.IsLittleEndian) - input = input.Reverse(); - yield return BitConverter.ToDouble(input.ToArray(), 0); - } - } - - public static ulong FirstU64(this IEnumerable input) - { - return input.TakeU64(1).First(); - } - - public static long FirstS64(this IEnumerable input) - { - return input.TakeS64(1).First(); - } - - public static uint FirstU32(this IEnumerable input) - { - return input.TakeU32(1).First(); - } - - public static int FirstS32(this IEnumerable input) - { - return input.TakeS32(1).First(); - } - - public static ushort FirstU16(this IEnumerable input) - { - return input.TakeU16(1).First(); - } - - public static short FirstS16(this IEnumerable input) - { - return input.TakeS16(1).First(); - } - - public static byte FirstU8(this IEnumerable input) - { - return input.TakeU8(1).First(); - } - - public static sbyte FirstS8(this IEnumerable input) - { - return input.TakeS8(1).First(); - } - - public static float FirstF(this IEnumerable input) - { - return input.TakeF(1).First(); - } - - public static double FirstD(this IEnumerable input) - { - return input.TakeD(1).First(); - } - - public static void AddU8(this List list, byte data) - { - list.Add(data); - } - - public static void AddS8(this List list, sbyte data) - { - list.Add(unchecked((byte)data)); - } - - public static void AddRangeAligned(this List list, IEnumerable data, int alignment = 4) - { - list.AddRange(data); - list.Realign(alignment); - } - - public static IEnumerable TakeBytesAligned(ref IEnumerable input, int size, int alignment = 4) - { - var res = input.Take(size); - input = input.Skip(size); - - int align = alignment - (size % alignment); - if (align != alignment) - input = input.Skip(align); - - return res; - } - - public static string TakeStringAligned(ref IEnumerable input, Encoding encoding, int alignment = 4) - { - int size = input.FirstS32(); - input = input.Skip(4); - byte[] data = TakeBytesAligned(ref input, size, alignment).ToArray(); - return encoding.GetString(data, 0, data.Length - 1); // drop final null byte - } - } - - public static class ListHelpers - { - public static void AddU32(this List list, uint data) - { - IEnumerable bytes = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) - bytes = bytes.Reverse(); - list.AddRange(bytes); + Array.Reverse(input, offset, 8); + return BitConverter.ToUInt64(input, offset); } - public static void AddS32(this List list, int data) + public static long GetS64(this byte[] input, int offset = 0) { - IEnumerable bytes = BitConverter.GetBytes(data); if (BitConverter.IsLittleEndian) - bytes = bytes.Reverse(); - list.AddRange(bytes); + Array.Reverse(input, offset, 8); + return BitConverter.ToInt64(input, offset); } - public static void Realign(this List list, int alignment = 4) + public static uint GetU32(this byte[] input, int offset = 0) { - int align = alignment - (list.Count % alignment); - if (align == alignment) - return; - while (align-- > 0) - list.Add(0); + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 4); + return BitConverter.ToUInt32(input, offset); + } + + public static int GetS32(this byte[] input, int offset = 0) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 4); + return BitConverter.ToInt32(input, offset); + } + + public static ushort GetU16(this byte[] input, int offset = 0) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 2); + return BitConverter.ToUInt16(input, offset); + } + + public static short GetS16(this byte[] input, int offset = 0) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 2); + return BitConverter.ToInt16(input, offset); + } + + public static byte GetU8(this byte[] input, int offset = 0) + { + return input[offset]; + } + + public static sbyte GetS8(this byte[] input, int offset = 0) + { + return unchecked((sbyte)input[offset]); + } + + public static float GetFloat(this byte[] input, int offset = 0) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 4); + return BitConverter.ToSingle(input, offset); + } + + public static double GetDouble(this byte[] input, int offset = 0) + { + if (BitConverter.IsLittleEndian) + Array.Reverse(input, offset, 8); + return BitConverter.ToDouble(input, offset); } } diff --git a/eAmuseCore/KBinXML/KBinXML.cs b/eAmuseCore/KBinXML/KBinXML.cs index 4d4b0b4..39ce28f 100644 --- a/eAmuseCore/KBinXML/KBinXML.cs +++ b/eAmuseCore/KBinXML/KBinXML.cs @@ -1,10 +1,6 @@ using System; using System.Text; -using System.Linq; using System.Xml.Linq; -using System.Collections.Generic; - -using eAmuseCore.KBinXML.Helpers; namespace eAmuseCore.KBinXML { @@ -56,9 +52,12 @@ namespace eAmuseCore.KBinXML } public XDocument Document { get; private set; } - public IEnumerable Bytes { get; private set; } + public byte[] Bytes { get; private set; } public Encoding BinEncoding { get; private set; } + private ByteBuffer nodeBuf = null, dataBuf = null; + bool compressed = false; + public KBinXML(XDocument doc, Encoding encoding, bool compress = true) { Document = doc; @@ -72,7 +71,7 @@ namespace eAmuseCore.KBinXML :this(doc, Encoding.GetEncoding(932), compress) { } - public KBinXML(IEnumerable input) + public KBinXML(byte[] input) { Bytes = input; @@ -86,7 +85,7 @@ namespace eAmuseCore.KBinXML private void Generate() { - List header = new List(8); + ByteBuffer header = new ByteBuffer(); header.AddU8(SIGNATURE); if (compressed) @@ -98,26 +97,28 @@ namespace eAmuseCore.KBinXML header.AddU8(encodingSig); header.AddU8((byte)(0xFF ^ encodingSig)); - nodeList = new List(); - dataList = new List(); - dataByteOffset = dataWordOffset = 0; + nodeBuf = new ByteBuffer(); + dataBuf = new ByteBuffer(); GenerateNode(Document.Root); - nodeList.AddU8(XmlType.SectionEndType | 64); - nodeList.Realign(); + nodeBuf.AddU8(XmlType.SectionEndType | 64); + nodeBuf.RealignWrites(); - header.AddU32((uint)nodeList.Count); - nodeList.AddU32((uint)dataList.Count); + header.AddU32((uint)nodeBuf.Length); + nodeBuf.AddU32((uint)dataBuf.Length); - Bytes = header.Concat(nodeList).Concat(dataList).ToArray(); + byte[] bytes = new byte[header.Length + nodeBuf.Length + dataBuf.Length]; - nodeList = dataList = null; + header.CopyTo(0, bytes, 0, header.Length); + nodeBuf.CopyTo(0, bytes, header.Length, nodeBuf.Length); + dataBuf.CopyTo(0, bytes, header.Length + nodeBuf.Length, dataBuf.Length); + + Bytes = bytes; + + nodeBuf = dataBuf = null; } - private List nodeList = null, dataList = null; - private int dataByteOffset = 0, dataWordOffset = 0; - private bool NodeIsMixed(XElement element) { bool text = false; @@ -141,52 +142,16 @@ namespace eAmuseCore.KBinXML { if (compressed) { - nodeList.AddRange(SixBit.Pack(name)); + nodeBuf.AddBytes(SixBit.Pack(name)); } else { byte[] bytes = BinEncoding.GetBytes(name); - nodeList.AddU8((byte)((bytes.Length - 1) | 64)); - nodeList.AddRange(bytes); + nodeBuf.AddU8((byte)((bytes.Length - 1) | 64)); + nodeBuf.AddBytes(bytes); } } - private void AddDataAligned(byte[] data) - { - if (data.Length == 1) - { - if (dataByteOffset % 4 == 0) - { - dataByteOffset = dataList.Count; - dataList.AddU32(0); - } - dataList[dataByteOffset++] = data[0]; - } - else if (data.Length == 2) - { - if(dataWordOffset % 4 == 0) - { - dataWordOffset = dataList.Count; - dataList.AddU32(0); - } - dataList[dataWordOffset++] = data[0]; - dataList[dataWordOffset++] = data[1]; - } - else - { - dataList.AddRangeAligned(data); - } - } - - private void AddStringAligned(string str) - { - byte[] bytes = BinEncoding.GetBytes(str); - dataList.AddS32(bytes.Length + 1); - dataList.AddRange(bytes); - dataList.AddU8(0); - dataList.Realign(); - } - private byte[] GetNodeData(XElement node, byte nodeType, XmlType xmlType, int count) { if (nodeType == XmlType.StrType) @@ -194,7 +159,10 @@ namespace eAmuseCore.KBinXML if (count != 1) throw new FormatException("String value cannot have a count != 1."); - return BinEncoding.GetBytes(node.Value).Concat(new byte[] { 0 }).ToArray(); + byte[] res = BinEncoding.GetBytes(node.Value); + Array.Resize(ref res, res.Length + 1); + res[res.Length - 1] = 0; + return res; } else if (nodeType == XmlType.BinType) { @@ -210,20 +178,17 @@ namespace eAmuseCore.KBinXML } else { - IEnumerable parts = node.Value.Split(' '); - if (parts.Count() != count * xmlType.Count) + string[] parts = node.Value.Split(' '); + if (parts.Length != count * xmlType.Count) throw new ArgumentException("Node value does not have required amount of fields.", "node"); - IEnumerable res = Enumerable.Empty(); + byte[] res = new byte[xmlType.Size * count]; for (int i = 0; i < count; ++i) { - res = res.Concat( - xmlType.KFromString( - string.Join(" ", parts.Take(xmlType.Count)))); - parts = parts.Skip(xmlType.Count); + Buffer.BlockCopy(xmlType.KFromString(parts, i * xmlType.Count), 0, res, xmlType.Size * i, xmlType.Size); } - return res.ToArray(); + return res; } } @@ -257,7 +222,7 @@ namespace eAmuseCore.KBinXML isArray = true; } - nodeList.AddU8((byte)(nodeType | (isArray ? 64 : 0))); + nodeBuf.AddU8((byte)(nodeType | (isArray ? 64 : 0))); AddNodeName(node.Name.LocalName); if (nodeType != XmlType.VoidType) @@ -266,42 +231,42 @@ namespace eAmuseCore.KBinXML if (isArray || xmlType.Count < 0) { - dataList.AddU32((uint)data.Length); - dataList.AddRangeAligned(data); + dataBuf.AddS32(data.Length); + dataBuf.AddBytesAligned(data); } else { - AddDataAligned(data.ToArray()); + dataBuf.AddBytesSubAligned(data); } } foreach (XAttribute attr in node.Attributes()) { - if (new[] { "__type", "__size", "__count" }.Contains(attr.Name.LocalName)) + if (attr.Name.LocalName == "__type" || attr.Name.LocalName == "__size" || attr.Name.LocalName == "__count") continue; - nodeList.AddU8(XmlType.AttrType); + nodeBuf.AddU8(XmlType.AttrType); AddNodeName(attr.Name.LocalName); - AddStringAligned(attr.Value); + dataBuf.AddString(attr.Value, BinEncoding); + dataBuf.RealignWrites(); } foreach (XElement child in node.Elements()) GenerateNode(child); - nodeList.AddU8(XmlType.NodeEndType | 64); + nodeBuf.AddU8(XmlType.NodeEndType | 64); } private void Parse() { - IEnumerable input = Bytes; + ByteBuffer input = new ByteBuffer(Bytes); Document = new XDocument(); - if (input.FirstU8() != SIGNATURE) + if (input.TakeU8() != SIGNATURE) throw new ArgumentException("Invalid signature", "input"); - input = input.Skip(1); - switch (input.FirstU8()) + switch (input.TakeU8()) { case SIG_COMPRESSED: compressed = true; @@ -312,67 +277,23 @@ namespace eAmuseCore.KBinXML default: throw new ArgumentException("Invalud compression info", "input"); } - input = input.Skip(1); - byte encodingSig = input.FirstU8(); - input = input.Skip(1); + byte encodingSig = input.TakeU8(); - if (input.FirstU8() != (0xFF ^ encodingSig)) + if (input.TakeU8() != (0xFF ^ encodingSig)) throw new ArgumentException("Encoding signature failed to verify", "input"); - input = input.Skip(1); + BinEncoding = GetEncoding(encodingSig); - uint nodesSize = input.FirstU32(); - input = input.Skip(4); + int nodesSize = input.TakeS32(); - nodeBuf = input.Take((int)nodesSize); - dataBuf = input.Skip((int)nodesSize); - dataByteBuf = dataWordBuf = Enumerable.Empty(); + nodeBuf = input.MakeSub(0, nodesSize); + dataBuf = input.MakeSub(nodesSize); ParseNodes(); nodeBuf = dataBuf = null; - dataByteBuf = dataWordBuf = null; - Bytes = Bytes.ToArray(); - } - - private IEnumerable nodeBuf = Enumerable.Empty(), dataBuf = Enumerable.Empty(); - private IEnumerable dataByteBuf = Enumerable.Empty(), dataWordBuf = Enumerable.Empty(); - bool compressed = false; - - IEnumerable TakeDataAligned(int size, bool isArray) - { - if (size <= 0) - { - return Enumerable.Empty(); - } - else if (size == 1 && !isArray) - { - if (!dataByteBuf.Any()) - { - dataByteBuf = dataBuf.Take(4); - dataBuf = dataBuf.Skip(4); - } - var res = dataByteBuf.Take(1); - dataByteBuf = dataByteBuf.Skip(1); - return res; - } - else if (size == 2 && !isArray) - { - if (!dataWordBuf.Any()) - { - dataWordBuf = dataBuf.Take(4); - dataBuf = dataBuf.Skip(4); - } - var res = dataWordBuf.Take(2); - dataWordBuf = dataWordBuf.Skip(2); - return res; - } - else - { - return EnumHelpers.TakeBytesAligned(ref dataBuf, size); - } } void SetNodeValue(XElement node, byte nodeType, XmlType xmlType, bool isArray) @@ -381,38 +302,43 @@ namespace eAmuseCore.KBinXML int arrCount = 1; if (varCount < 0) { - varCount = dataBuf.FirstS32(); - dataBuf = dataBuf.Skip(4); + varCount = dataBuf.TakeS32(); isArray = true; } else if (isArray) { - arrCount = dataBuf.FirstS32() / (xmlType.Size * xmlType.Count); - dataBuf = dataBuf.Skip(4); + arrCount = dataBuf.TakeS32() / (xmlType.Size * xmlType.Count); node.SetAttributeValue("__count", arrCount); } int totCount = arrCount * varCount; int totSize = totCount * xmlType.Size; - IEnumerable data = TakeDataAligned(totSize, isArray); + byte[] data; + if (isArray) + data = dataBuf.TakeBytesAligned(totSize); + else + data = dataBuf.TakeBytesSubAligned(totSize); if (nodeType == XmlType.BinType) { - node.SetAttributeValue("__size", varCount); - node.SetValue(string.Join("", data.Select(b => Convert.ToString(b, 16).PadLeft(2, '0')))); + node.SetAttributeValue("__size", data.Length); + + StringBuilder sb = new StringBuilder(data.Length * 2); + for (int i = 0; i < data.Length; ++i) + sb.Append(Convert.ToString(data[i], 16).PadLeft(2, '0')); + + node.SetValue(sb.ToString()); } else if (nodeType == XmlType.StrType) { - byte[] strData = data.ToArray(); - node.SetValue(BinEncoding.GetString(strData, 0, strData.Length - 1)); + node.SetValue(BinEncoding.GetString(data, 0, data.Length - 1)); } else { string[] parts = new string[arrCount]; for (int i = 0; i < arrCount; ++i) { - parts[i] = xmlType.KToString(data.Take(xmlType.Size)); - data = data.Skip(xmlType.Size); + parts[i] = xmlType.KToString(data, i * xmlType.Size); } node.SetValue(string.Join(" ", parts)); } @@ -424,15 +350,12 @@ namespace eAmuseCore.KBinXML { if (compressed) { - return SixBit.Unpack(ref nodeBuf); + return SixBit.Unpack(nodeBuf); } else { - int length = (nodeBuf.First() & ~64) + 1; - nodeBuf = nodeBuf.Skip(1); - - byte[] nameBytes = nodeBuf.Take(length).ToArray(); - nodeBuf = nodeBuf.Skip(length); + int length = (nodeBuf.TakeU8() & ~64) + 1; + byte[] nameBytes = nodeBuf.TakeBytes(length); return BinEncoding.GetString(nameBytes); } @@ -445,19 +368,18 @@ namespace eAmuseCore.KBinXML private void ParseNodes() { - uint dataSize = dataBuf.FirstU32(); - dataBuf = dataBuf.Skip(4); + uint dataSize = dataBuf.TakeU32(); XElement fakeroot = new XElement("fakeroot"); XElement node = fakeroot; bool nodesLeft = true; - while (nodesLeft && nodeBuf.Any()) + while (nodesLeft && !nodeBuf.AtEnd) { - nodeBuf = nodeBuf.SkipWhile(b => b == 0); + while (nodeBuf[nodeBuf.Offset] == 0) + nodeBuf.Offset += 1; - byte nodeType = nodeBuf.FirstU8(); - nodeBuf = nodeBuf.Skip(1); + byte nodeType = nodeBuf.TakeU8(); bool isArray = (nodeType & 64) != 0; nodeType = (byte)(nodeType & ~64); @@ -470,7 +392,8 @@ namespace eAmuseCore.KBinXML switch (nodeType) { case XmlType.AttrType: - string attrVal = EnumHelpers.TakeStringAligned(ref dataBuf, BinEncoding); + string attrVal = dataBuf.TakeString(BinEncoding); + dataBuf.RealignReads(); node.SetAttributeValue(name, attrVal); break; case XmlType.NodeEndType: diff --git a/eAmuseCore/KBinXML/SixBit.cs b/eAmuseCore/KBinXML/SixBit.cs index 338b0ae..cebdd82 100644 --- a/eAmuseCore/KBinXML/SixBit.cs +++ b/eAmuseCore/KBinXML/SixBit.cs @@ -1,11 +1,7 @@ using System.Collections.Generic; using System.Numerics; -using System.Linq; using System; -using eAmuseCore.KBinXML.Helpers; -using System.Text; - namespace eAmuseCore.KBinXML { public static class SixBit @@ -32,10 +28,10 @@ namespace eAmuseCore.KBinXML try { - foreach (byte i in input.Select(c => bytemap[c])) + foreach (char c in input) { bits <<= 6; - bits |= i; + bits |= bytemap[c]; } } catch (KeyNotFoundException) @@ -45,27 +41,31 @@ namespace eAmuseCore.KBinXML bits <<= padding; - return bits.ToByteArray().Take(length_bytes).Append((byte)input.Length).Reverse().ToArray(); + byte[] res = new byte[length_bytes + 1]; + Buffer.BlockCopy(bits.ToByteArray(), 0, res, 1, length_bytes); + Array.Reverse(res, 1, length_bytes); + res[0] = (byte)input.Length; + + return res; } public static string Unpack(byte[] data) { - IEnumerable nodeBuf = data.AsEnumerable(); - return Unpack(ref nodeBuf); + return Unpack(new ByteBuffer(data)); } - public static string Unpack(ref IEnumerable nodeBuf) + public static string Unpack(ByteBuffer nodeBuf) { - int length = nodeBuf.FirstU8(); - nodeBuf = nodeBuf.Skip(1); + int length = nodeBuf.TakeU8(); int length_bits = length * 6; int length_bytes = (length_bits + 7) / 8; int padding = (8 - (length_bits % 8)) % 8; - // bytes are in big endian order, BigInteger expects little endian, hence .Reverse() it. - BigInteger bits = new BigInteger(nodeBuf.TakeU8(length_bytes).Reverse().ToArray()); - nodeBuf = nodeBuf.Skip(length_bytes); + byte[] bytes = nodeBuf.TakeBytes(length_bytes); + Array.Reverse(bytes); + BigInteger bits = new BigInteger(bytes); + bits >>= padding; char[] res = new char[length]; diff --git a/eAmuseCore/KBinXML/XmlTypes.cs b/eAmuseCore/KBinXML/XmlTypes.cs index d0ac3d1..8d31a5a 100644 --- a/eAmuseCore/KBinXML/XmlTypes.cs +++ b/eAmuseCore/KBinXML/XmlTypes.cs @@ -1,17 +1,14 @@ using System; +using System.Net; using System.Linq; using System.Collections.Generic; -using System.Reflection; -using System.Net; -using System.Text; -using System.Globalization; using eAmuseCore.KBinXML.Helpers; namespace eAmuseCore.KBinXML { - public delegate string KBinToString(IEnumerable input); - public delegate IEnumerable KBinFromString(string input); + public delegate string KBinToString(byte[] input, int offset); + public delegate byte[] KBinFromString(string[] input, int offset); public class KBinConverter { @@ -24,22 +21,32 @@ namespace eAmuseCore.KBinXML public KBinFromString KFromString { get; } public KBinToString KToString { get; } - public static KBinConverter S8 = new KBinConverter(s => Convert.ToSByte(s).ToBytes(), b => b.FirstS8().ToString()); - public static KBinConverter U8 = new KBinConverter(s => Convert.ToByte(s).ToBytes(), b => b.FirstU8().ToString()); - public static KBinConverter S16 = new KBinConverter(s => Convert.ToInt16(s).ToBytes(), b => b.FirstS16().ToString()); - public static KBinConverter U16 = new KBinConverter(s => Convert.ToUInt16(s).ToBytes(), b => b.FirstU16().ToString()); - public static KBinConverter S32 = new KBinConverter(s => Convert.ToInt32(s).ToBytes(), b => b.FirstS32().ToString()); - public static KBinConverter U32 = new KBinConverter(s => Convert.ToUInt32(s).ToBytes(), b => b.FirstU32().ToString()); - public static KBinConverter S64 = new KBinConverter(s => Convert.ToInt64(s).ToBytes(), b => b.FirstS64().ToString()); - public static KBinConverter U64 = new KBinConverter(s => Convert.ToUInt64(s).ToBytes(), b => b.FirstU64().ToString()); + public static KBinConverter S8 = new KBinConverter((s, o) => Convert.ToSByte(s[o]).ToBytes(), (b, o) => b.GetS8(o).ToString()); + public static KBinConverter U8 = new KBinConverter((s, o) => Convert.ToByte(s[o]).ToBytes(), (b, o) => b.GetU8(o).ToString()); + public static KBinConverter S16 = new KBinConverter((s, o) => Convert.ToInt16(s[o]).ToBytes(), (b, o) => b.GetS16(o).ToString()); + public static KBinConverter U16 = new KBinConverter((s, o) => Convert.ToUInt16(s[o]).ToBytes(), (b, o) => b.GetU16(o).ToString()); + public static KBinConverter S32 = new KBinConverter((s, o) => Convert.ToInt32(s[o]).ToBytes(), (b, o) => b.GetS32(o).ToString()); + public static KBinConverter U32 = new KBinConverter((s, o) => Convert.ToUInt32(s[o]).ToBytes(), (b, o) => b.GetU32(o).ToString()); + public static KBinConverter S64 = new KBinConverter((s, o) => Convert.ToInt64(s[o]).ToBytes(), (b, o) => b.GetS64(o).ToString()); + public static KBinConverter U64 = new KBinConverter((s, o) => Convert.ToUInt64(s[o]).ToBytes(), (b, o) => b.GetU64(o).ToString()); - public static KBinConverter KFloat = new KBinConverter(s => Convert.ToSingle(s).ToBytes(), b => b.FirstF().ToString()); - public static KBinConverter KDouble = new KBinConverter(s => Convert.ToDouble(s).ToBytes(), b => b.FirstD().ToString()); + public static KBinConverter KFloat = new KBinConverter((s, o) => Convert.ToSingle(s[o]).ToBytes(), (b, o) => b.GetFloat(o).ToString()); + public static KBinConverter KDouble = new KBinConverter((s, o) => Convert.ToDouble(s[o]).ToBytes(), (b, o) => b.GetDouble(o).ToString()); - public static KBinConverter IP4 = new KBinConverter(s => IPAddress.Parse(s).GetAddressBytes(), b => new IPAddress(b.Take(4).ToArray()).ToString()); - public static KBinConverter Bool = new KBinConverter(s => new byte[] { s.ToBool() ? (byte)1 : (byte)0 }, b => (b.FirstU8() != 0) ? "1" : "0"); + public static KBinConverter IP4 = new KBinConverter((s, o) => IPAddress.Parse(s[o]).GetAddressBytes(), (b, o) => + { + byte[] buf = b; + if (buf.Length != 4) + { + buf = new byte[4]; + Buffer.BlockCopy(b, o, buf, 0, 4); + } + return new IPAddress(buf).ToString(); + }); - public static KBinConverter Invalid = new KBinConverter(s => throw new InvalidOperationException(), b => throw new InvalidOperationException()); + public static KBinConverter Bool = new KBinConverter((s, o) => new byte[] { s[o].ToBool() ? (byte)1 : (byte)0 }, (b, o) => (b.GetU8(o) != 0) ? "1" : "0"); + + public static KBinConverter Invalid = new KBinConverter((s, o) => throw new InvalidOperationException(), (b, o) => throw new InvalidOperationException()); } public class XmlType @@ -82,21 +89,19 @@ namespace eAmuseCore.KBinXML return this; } - public IEnumerable KFromString(string input) + public byte[] KFromString(string[] input, int offset) { if (input.Length == 0) return new byte[0]; - return Converter.KFromString(input); + return Converter.KFromString(input, offset); } - public string KToString(IEnumerable input) + public string KToString(byte[] input, int offset) { - input = input.Take(Size); - ICollection col = input as ICollection; - if (col != null && col.Count != Size) + if (input.Length - offset < Size) throw new ArgumentException("input does not provide enough data", "input"); - return Converter.KToString(input); + return Converter.KToString(input, offset); } private XmlType Times(int count) @@ -106,30 +111,24 @@ namespace eAmuseCore.KBinXML string[] names = Names.Select(name => count.ToString() + name).ToArray(); int size = Size * count; - KBinFromString fromString = s => + KBinFromString fromString = (s, o) => { - string[] elems = s.Split(' '); - if (elems.Length != count) - throw new ArgumentException("input does not split into correct element count", "s"); + if (s.Length - o < count) + throw new ArgumentException("input does not contain enough elements"); + + byte[] res = new byte[size]; + + for (int i = 0; i < count; ++i) + Buffer.BlockCopy(Converter.KFromString(s, o + i), 0, res, i * Size, Size); - IEnumerable res = Enumerable.Empty(); - foreach (string elem in elems) - res = res.Concat(Converter.KFromString(elem)); return res; }; - KBinToString toString = b => + KBinToString toString = (b, o) => { - IEnumerable data = b.Take(size); - ICollection col = data as ICollection; - if (col != null && col.Count != size) - throw new ArgumentException("input does not provide enough data for all elements", "b"); - string[] res = new string[count]; for (int i = 0; i < count; ++i) - { - res[i] = Converter.KToString(data.Take(Size)); - data = data.Skip(Size); - } + res[i] = Converter.KToString(b, i * Size + o); + return string.Join(" ", res); }; diff --git a/eAmuseTest/Program.cs b/eAmuseTest/Program.cs index 186d1fe..1de9dda 100644 --- a/eAmuseTest/Program.cs +++ b/eAmuseTest/Program.cs @@ -32,6 +32,10 @@ namespace eAmuseTest KBinXML kbinxml = new KBinXML(rawData.ToArray()); + kbinxml = new KBinXML(kbinxml.Document); + + kbinxml = new KBinXML(kbinxml.Bytes); + Console.WriteLine(kbinxml); }