Stop using LINQ entirely, it's horribly slow.

This commit is contained in:
BtbN
2019-04-21 18:18:23 +02:00
parent 558c6cb360
commit 05ad0d0b80
6 changed files with 504 additions and 416 deletions

View File

@@ -0,0 +1,311 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace eAmuseCore.KBinXML
{
public class ByteBuffer
{
private List<byte> 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<byte>();
}
public ByteBuffer(IEnumerable<byte> data)
{
this.data = new List<byte>(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<byte> 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);
}
}
}

View File

@@ -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<ulong> TakeU64(this IEnumerable<byte> 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<long> TakeS64(this IEnumerable<byte> 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<uint> TakeU32(this IEnumerable<byte> 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<int> TakeS32(this IEnumerable<byte> 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<ushort> TakeU16(this IEnumerable<byte> 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<short> TakeS16(this IEnumerable<byte> 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<byte> TakeU8(this IEnumerable<byte> input, int count)
{
return input.Take(count);
}
public static IEnumerable<sbyte> TakeS8(this IEnumerable<byte> input, int count)
{
return input.Take(count).Select(b => unchecked((sbyte)b));
}
public static IEnumerable<float> TakeF(this IEnumerable<byte> 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<double> TakeD(this IEnumerable<byte> 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<byte> input)
{
return input.TakeU64(1).First();
}
public static long FirstS64(this IEnumerable<byte> input)
{
return input.TakeS64(1).First();
}
public static uint FirstU32(this IEnumerable<byte> input)
{
return input.TakeU32(1).First();
}
public static int FirstS32(this IEnumerable<byte> input)
{
return input.TakeS32(1).First();
}
public static ushort FirstU16(this IEnumerable<byte> input)
{
return input.TakeU16(1).First();
}
public static short FirstS16(this IEnumerable<byte> input)
{
return input.TakeS16(1).First();
}
public static byte FirstU8(this IEnumerable<byte> input)
{
return input.TakeU8(1).First();
}
public static sbyte FirstS8(this IEnumerable<byte> input)
{
return input.TakeS8(1).First();
}
public static float FirstF(this IEnumerable<byte> input)
{
return input.TakeF(1).First();
}
public static double FirstD(this IEnumerable<byte> input)
{
return input.TakeD(1).First();
}
public static void AddU8(this List<byte> list, byte data)
{
list.Add(data);
}
public static void AddS8(this List<byte> list, sbyte data)
{
list.Add(unchecked((byte)data));
}
public static void AddRangeAligned(this List<byte> list, IEnumerable<byte> data, int alignment = 4)
{
list.AddRange(data);
list.Realign(alignment);
}
public static IEnumerable<byte> TakeBytesAligned(ref IEnumerable<byte> 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<byte> 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<byte> list, uint data)
{
IEnumerable<byte> 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<byte> list, int data)
public static long GetS64(this byte[] input, int offset = 0)
{
IEnumerable<byte> 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<byte> 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);
}
}

View File

@@ -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<byte> 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<byte> input)
public KBinXML(byte[] input)
{
Bytes = input;
@@ -86,7 +85,7 @@ namespace eAmuseCore.KBinXML
private void Generate()
{
List<byte> header = new List<byte>(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<byte>();
dataList = new List<byte>();
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<byte> 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<string> 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<byte> res = Enumerable.Empty<byte>();
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<byte> 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<byte>();
nodeBuf = input.MakeSub(0, nodesSize);
dataBuf = input.MakeSub(nodesSize);
ParseNodes();
nodeBuf = dataBuf = null;
dataByteBuf = dataWordBuf = null;
Bytes = Bytes.ToArray();
}
private IEnumerable<byte> nodeBuf = Enumerable.Empty<byte>(), dataBuf = Enumerable.Empty<byte>();
private IEnumerable<byte> dataByteBuf = Enumerable.Empty<byte>(), dataWordBuf = Enumerable.Empty<byte>();
bool compressed = false;
IEnumerable<byte> TakeDataAligned(int size, bool isArray)
{
if (size <= 0)
{
return Enumerable.Empty<byte>();
}
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<byte> 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:

View File

@@ -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<byte> nodeBuf = data.AsEnumerable();
return Unpack(ref nodeBuf);
return Unpack(new ByteBuffer(data));
}
public static string Unpack(ref IEnumerable<byte> 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];

View File

@@ -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<byte> input);
public delegate IEnumerable<byte> 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<byte> 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<byte> input)
public string KToString(byte[] input, int offset)
{
input = input.Take(Size);
ICollection<byte> col = input as ICollection<byte>;
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<byte> res = Enumerable.Empty<byte>();
foreach (string elem in elems)
res = res.Concat(Converter.KFromString(elem));
return res;
};
KBinToString toString = b =>
KBinToString toString = (b, o) =>
{
IEnumerable<byte> data = b.Take(size);
ICollection<byte> col = data as ICollection<byte>;
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);
};

View File

@@ -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);
}