mirror of
https://github.com/BtbN/ClanServer.git
synced 2026-09-06 15:44:38 -05:00
Add XML to Binary mode
This commit is contained in:
@@ -189,27 +189,38 @@ namespace eAmuseCore.KBinXML.Helpers
|
||||
list.AddRange(bytes);
|
||||
}
|
||||
|
||||
public static IEnumerable<byte> TakeBytesAligned(ref IEnumerable<byte> input, int size = -1)
|
||||
public static void Realign(this List<byte> list, int alignment = 4)
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
size = input.FirstS32();
|
||||
input = input.Skip(4);
|
||||
}
|
||||
int align = alignment - (list.Count % alignment);
|
||||
if (align == alignment)
|
||||
return;
|
||||
while (align-- > 0)
|
||||
list.Add(0);
|
||||
}
|
||||
|
||||
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 = 4 - (size % 4);
|
||||
if (align != 4)
|
||||
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 size = -1)
|
||||
public static string TakeStringAligned(ref IEnumerable<byte> input, Encoding encoding, int alignment = 4)
|
||||
{
|
||||
byte[] data = TakeBytesAligned(ref input, size).ToArray();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,187 @@ namespace eAmuseCore.KBinXML
|
||||
private void Generate()
|
||||
{
|
||||
List<byte> header = new List<byte>(8);
|
||||
header.AddU8(SIGNATURE);
|
||||
|
||||
if (compressed)
|
||||
header.AddU8(SIG_COMPRESSED);
|
||||
else
|
||||
header.AddU8(SIG_UNCOMPRESSED);
|
||||
|
||||
byte encodingSig = GetEncodingSig(BinEncoding);
|
||||
header.AddU8(encodingSig);
|
||||
header.AddU8((byte)(0xFF ^ encodingSig));
|
||||
|
||||
nodeList = new List<byte>();
|
||||
dataList = new List<byte>();
|
||||
dataByteOffset = dataWordOffset = 0;
|
||||
|
||||
GenerateNode(Document.Root);
|
||||
|
||||
nodeList.AddU8(XmlTypes.XmlTypes.SectionEndType | 64);
|
||||
nodeList.Realign();
|
||||
|
||||
header.AddU32((uint)nodeList.Count);
|
||||
nodeList.AddU32((uint)dataList.Count);
|
||||
|
||||
Bytes = header.Concat(nodeList).Concat(dataList).ToArray();
|
||||
|
||||
nodeList = dataList = null;
|
||||
}
|
||||
|
||||
private List<byte> nodeList = null, dataList = null;
|
||||
private int dataByteOffset = 0, dataWordOffset = 0;
|
||||
|
||||
private bool NodeIsMixed(XElement element)
|
||||
{
|
||||
bool text = false;
|
||||
bool nontext = false;
|
||||
|
||||
foreach (XNode node in element.Nodes())
|
||||
{
|
||||
if (node.NodeType == System.Xml.XmlNodeType.Text)
|
||||
text = true;
|
||||
else
|
||||
nontext = true;
|
||||
|
||||
if (text && nontext)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddNodeName(string name)
|
||||
{
|
||||
if (compressed)
|
||||
{
|
||||
nodeList.AddRange(SixBit.Pack(name));
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bytes = BinEncoding.GetBytes(name);
|
||||
nodeList.AddU8((byte)((bytes.Length - 1) | 64));
|
||||
nodeList.AddRange(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 void GenerateNode(XElement node)
|
||||
{
|
||||
if (NodeIsMixed(node))
|
||||
throw new ArgumentException("Nodes with mixed elements/text are not supported.", "node");
|
||||
|
||||
XAttribute nodeTypeXAttr = node.Attribute("__type");
|
||||
KValueAttribute nodeTypeAttrs;
|
||||
if (nodeTypeXAttr != null)
|
||||
{
|
||||
nodeTypeAttrs = KValueAttribute.GetAttrByName(nodeTypeXAttr.Value.ToLower());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node.IsEmpty || node.HasElements)
|
||||
nodeTypeAttrs = KValueAttribute.GetAttrByType(XmlTypes.XmlTypes.VoidType);
|
||||
else
|
||||
nodeTypeAttrs = KValueAttribute.GetAttrByType(XmlTypes.XmlTypes.StrType);
|
||||
}
|
||||
|
||||
bool isArray = false;
|
||||
int count = 1;
|
||||
XAttribute countXAttr = node.Attribute("__count");
|
||||
if (countXAttr != null)
|
||||
{
|
||||
count = Convert.ToInt32(countXAttr.Value);
|
||||
isArray = true;
|
||||
}
|
||||
|
||||
nodeList.AddU8((byte)(nodeTypeAttrs.NodeType | (isArray ? 64 : 0)));
|
||||
AddNodeName(node.Name.LocalName);
|
||||
|
||||
if (nodeTypeAttrs.NodeType != XmlTypes.XmlTypes.VoidType)
|
||||
{
|
||||
Type valueType = XmlTypes.XmlTypes.GetByType(nodeTypeAttrs.NodeType);
|
||||
|
||||
IEnumerable<byte> data;
|
||||
if (nodeTypeAttrs.NodeType == XmlTypes.XmlTypes.StrType)
|
||||
{
|
||||
if (count != 1)
|
||||
throw new FormatException("String value cannot have a count != 1.");
|
||||
|
||||
data = BinEncoding.GetBytes(node.Value).Concat(new byte[] { 0 });
|
||||
}
|
||||
else if (nodeTypeAttrs.NodeType == XmlTypes.XmlTypes.BinType)
|
||||
{
|
||||
data = XmlTypes.Bin.FromString(node.Value).ToBytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
IKValue kValue = XmlTypes.XmlTypes.KValueFromString(valueType, node.Value, count);
|
||||
data = kValue.ToBytes();
|
||||
}
|
||||
|
||||
if (isArray || nodeTypeAttrs.Count < 0)
|
||||
{
|
||||
dataList.AddU32((uint)data.Count());
|
||||
dataList.AddRangeAligned(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddDataAligned(data.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XAttribute attr in node.Attributes())
|
||||
{
|
||||
if (new[] { "__type", "__size", "__count" }.Contains(attr.Name.LocalName))
|
||||
continue;
|
||||
|
||||
nodeList.AddU8(XmlTypes.XmlTypes.AttrType);
|
||||
AddNodeName(attr.Name.LocalName);
|
||||
|
||||
AddStringAligned(attr.Value);
|
||||
}
|
||||
|
||||
foreach (XElement child in node.Elements())
|
||||
GenerateNode(child);
|
||||
|
||||
nodeList.AddU8(XmlTypes.XmlTypes.NodeEndType | 64);
|
||||
}
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
IEnumerable<byte> input = Bytes;
|
||||
@@ -254,7 +430,7 @@ namespace eAmuseCore.KBinXML
|
||||
case XmlTypes.XmlTypes.SectionEndType:
|
||||
nodesLeft = false;
|
||||
break;
|
||||
case XmlTypes.XmlTypes.NodeStartType:
|
||||
case XmlTypes.XmlTypes.VoidType:
|
||||
startNode = true;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -4,10 +4,11 @@ using System.Linq;
|
||||
using System;
|
||||
|
||||
using eAmuseCore.KBinXML.Helpers;
|
||||
using System.Text;
|
||||
|
||||
namespace eAmuseCore.KBinXML
|
||||
{
|
||||
static class SixBit
|
||||
public static class SixBit
|
||||
{
|
||||
static readonly string charmap = "0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
|
||||
static readonly Dictionary<char, byte> bytemap = new Dictionary<char, byte>();
|
||||
@@ -23,7 +24,10 @@ namespace eAmuseCore.KBinXML
|
||||
if (input.Length > byte.MaxValue)
|
||||
throw new ArgumentException("input string is too long", "input");
|
||||
|
||||
int padding = (8 - (input.Length * 6)) % 8;
|
||||
int length_bits = input.Length * 6;
|
||||
int length_bytes = (length_bits + 7) / 8;
|
||||
int padding = (8 - (length_bits % 8)) % 8;
|
||||
|
||||
BigInteger bits = new BigInteger(0);
|
||||
|
||||
try
|
||||
@@ -41,7 +45,7 @@ namespace eAmuseCore.KBinXML
|
||||
|
||||
bits <<= padding;
|
||||
|
||||
return bits.ToByteArray().Append((byte)input.Length).Reverse().ToArray();
|
||||
return bits.ToByteArray().Take(length_bytes).Append((byte)input.Length).Reverse().ToArray();
|
||||
}
|
||||
|
||||
public static string Unpack(byte[] data)
|
||||
|
||||
@@ -113,12 +113,18 @@ namespace eAmuseCore.KBinXML.Helpers
|
||||
if (Value == null)
|
||||
return Enumerable.Empty<byte>();
|
||||
|
||||
IEnumerable<byte> res = BitConverter.GetBytes(Convert.ToUInt64(Value));
|
||||
dynamic v = Value;
|
||||
Type t = v.GetType();
|
||||
|
||||
if (t == typeof(byte) || t == typeof(sbyte))
|
||||
return new byte[] { unchecked((byte)v) };
|
||||
|
||||
IEnumerable<byte> res = BitConverter.GetBytes(v);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
res = res.Reverse();
|
||||
|
||||
return res.Skip(8 - Size);
|
||||
return res;
|
||||
}
|
||||
|
||||
protected KValueAttribute KValAttr
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace eAmuseCore.KBinXML.XmlTypes
|
||||
|
||||
static public Bin FromString(string input)
|
||||
{
|
||||
if ((input.Length % 2) != 2)
|
||||
if ((input.Length % 2) != 0)
|
||||
throw new ArgumentException("Hex string needs to consist of pairs of two chars.", "input");
|
||||
byte[] res = new byte[input.Length / 2];
|
||||
for (int i = 0, j = 0; i < input.Length; i += 2, ++j)
|
||||
@@ -119,6 +119,8 @@ namespace eAmuseCore.KBinXML.XmlTypes
|
||||
|
||||
public override IEnumerable<byte> ToBytes() => Encoding.UTF8.GetBytes(Value);
|
||||
|
||||
public IEnumerable<byte> ToBytes(Encoding encoding) => encoding.GetBytes(Value);
|
||||
|
||||
static public Str FromString(string input) => new Str(input);
|
||||
|
||||
static public Str FromBytes(IEnumerable<byte> input, Encoding encoding)
|
||||
@@ -527,7 +529,7 @@ namespace eAmuseCore.KBinXML.XmlTypes
|
||||
|
||||
public static class XmlTypes
|
||||
{
|
||||
public const int NodeStartType = 1;
|
||||
public const int VoidType = 1;
|
||||
public const int BinType = 10;
|
||||
public const int StrType = 11;
|
||||
public const int AttrType = 46;
|
||||
@@ -583,7 +585,7 @@ namespace eAmuseCore.KBinXML.XmlTypes
|
||||
}
|
||||
}
|
||||
|
||||
public static IKValue ValueListFromString(Type type, string input, int count)
|
||||
public static IKValue KValueFromString(Type type, string input, int count)
|
||||
{
|
||||
Type listType = typeof(KValueArray<>).MakeGenericType(type);
|
||||
KValueAttribute attr = type.GetCustomAttribute<KValueAttribute>(false);
|
||||
@@ -595,14 +597,17 @@ namespace eAmuseCore.KBinXML.XmlTypes
|
||||
if (vals.Length != attr.Count * count)
|
||||
throw new ArgumentException("input string had invalid field count", "input");
|
||||
|
||||
object[] mainParams = new object[count];
|
||||
IKValue[] mainParams = new IKValue[count];
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
mainParams[i] = fromString.Invoke(null, new object[] { string.Join(" ", strings.Take(attr.Count)) });
|
||||
mainParams[i] = (IKValue)fromString.Invoke(null, new object[] { string.Join(" ", strings.Take(attr.Count)) });
|
||||
strings = strings.Skip(attr.Count);
|
||||
}
|
||||
|
||||
if (count == 1)
|
||||
return mainParams[0];
|
||||
|
||||
return (IKValue)Activator.CreateInstance(listType, mainParams);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Collections.Generic;
|
||||
using eAmuseCore.Crypto;
|
||||
using eAmuseCore.Compression;
|
||||
using eAmuseCore.KBinXML;
|
||||
using eAmuseCore.KBinXML;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace eAmuseTest
|
||||
{
|
||||
@@ -47,9 +49,20 @@ namespace eAmuseTest
|
||||
throw new ArgumentException("Unsupported compression algorithm");
|
||||
|
||||
KBinXML kbinxml = new KBinXML(rawData);
|
||||
KBinXML testDoc = new KBinXML(ExtractResource("eAmuseTest.testcases_out.kbin"));
|
||||
//KBinXML testDoc = new KBinXML(ExtractResource("eAmuseTest.testcases_out.kbin"));
|
||||
|
||||
Console.WriteLine(testDoc.ToString());
|
||||
KBinXML reverseTest = new KBinXML(kbinxml.Document);
|
||||
|
||||
Console.WriteLine(kbinxml);
|
||||
|
||||
Console.WriteLine("Orig: " + BytesToString(rawData));
|
||||
Console.WriteLine("Len: " + rawData.Count());
|
||||
Console.WriteLine("Mine: " + BytesToString(reverseTest.Bytes));
|
||||
Console.WriteLine("Len: " + reverseTest.Bytes.Count());
|
||||
|
||||
KBinXML reReverseText = new KBinXML(reverseTest.Bytes);
|
||||
|
||||
Console.WriteLine(reReverseText);
|
||||
}
|
||||
|
||||
private static string BytesToString(IEnumerable<byte> bytes)
|
||||
|
||||
Reference in New Issue
Block a user