diff --git a/src/Nyanbyte.Sysnya/Binary/BinaryEnumerator.cs b/src/Nyanbyte.Sysnya/Binary/BinaryEnumerator.cs index 7648489..125d9bb 100644 --- a/src/Nyanbyte.Sysnya/Binary/BinaryEnumerator.cs +++ b/src/Nyanbyte.Sysnya/Binary/BinaryEnumerator.cs @@ -1,4 +1,5 @@ using System.Buffers.Binary; +using System.Data; namespace Nyanbyte.Sysnya.Binary; @@ -15,6 +16,10 @@ public class BinaryEnumerator private readonly ReadOnlyMemory _fullData; private int _position = 0; + public int Position => _position; + public int Remaining => _fullData.Length - _position; + public int Size => _fullData.Length; + public BinaryEnumerator Skip(int length) { _position += length; @@ -34,6 +39,19 @@ public class BinaryEnumerator return this; } + public uint ReadUInt32() + { + uint value = _bigEndian ? BinaryPrimitives.ReadUInt32BigEndian(_fullData.Span[_position..(_position + 4)]) : BinaryPrimitives.ReadUInt32LittleEndian(_fullData.Span[_position..(_position + 4)]); + _position += 4; + return value; + } + + public BinaryEnumerator ReadUInt32(out uint value) + { + value = ReadUInt32(); + return this; + } + public long ReadInt64() { long value = _bigEndian ? BinaryPrimitives.ReadInt64BigEndian(_fullData.Span[_position..(_position + 8)]) : BinaryPrimitives.ReadInt64LittleEndian(_fullData.Span[_position..(_position + 8)]); @@ -46,4 +64,87 @@ public class BinaryEnumerator value = ReadInt64(); return this; } + + public ulong ReadUInt64() + { + ulong value = _bigEndian ? BinaryPrimitives.ReadUInt64BigEndian(_fullData.Span[_position..(_position + 8)]) : BinaryPrimitives.ReadUInt64LittleEndian(_fullData.Span[_position..(_position + 8)]); + _position += 8; + return value; + } + + public BinaryEnumerator ReadUInt64(out ulong value) + { + value = ReadUInt64(); + return this; + } + + public short ReadInt16() + { + short value = _bigEndian ? BinaryPrimitives.ReadInt16BigEndian(_fullData.Span[_position..(_position + 2)]) : BinaryPrimitives.ReadInt16LittleEndian(_fullData.Span[_position..(_position + 2)]); + _position += 2; + return value; + } + + public BinaryEnumerator ReadInt16(out short value) + { + value = ReadInt16(); + return this; + } + + public ushort ReadUInt16() + { + ushort value = _bigEndian ? BinaryPrimitives.ReadUInt16BigEndian(_fullData.Span[_position..(_position + 2)]) : BinaryPrimitives.ReadUInt16LittleEndian(_fullData.Span[_position..(_position + 2)]); + _position += 2; + return value; + } + + public BinaryEnumerator ReadUInt16(out ushort value) + { + value = ReadUInt16(); + return this; + } + + public float ReadF32() + { + float value = _bigEndian ? BinaryPrimitives.ReadSingleBigEndian(_fullData.Span.Slice(_position)) : BinaryPrimitives.ReadSingleLittleEndian(_fullData.Span.Slice(_position)); + _position += 4; + return value; + } + + public BinaryEnumerator ReadF32(out float value) + { + value = ReadF32(); + return this; + } + + public double ReadF64() + { + double value = _bigEndian ? BinaryPrimitives.ReadDoubleBigEndian(_fullData.Span.Slice(_position)) : BinaryPrimitives.ReadDoubleLittleEndian(_fullData.Span.Slice(_position)); + _position += 8; + return value; + } + + public BinaryEnumerator ReadF64(out double value) + { + value = ReadF64(); + return this; + } + + public TNumeric ReadNumeric() where TNumeric : struct, IConvertible + { + //TODO Refactor + object val = typeof(TNumeric).Name switch + { + "Int16" => ReadInt16(), + "UInt16" => ReadUInt16(), + "Int32" => ReadInt32(), + "UInt32" => ReadUInt32(), + "Int64" => ReadInt64(), + "UInt64" => ReadUInt64(), + + _ => throw new DataException($"Invalid type: {typeof(TNumeric).FullName}") + }; + + return (TNumeric)val; + } } diff --git a/src/Nyanbyte.Sysnya/Binary/BinaryStructMapper.cs b/src/Nyanbyte.Sysnya/Binary/BinaryStructMapper.cs index d3273fb..5f208c8 100644 --- a/src/Nyanbyte.Sysnya/Binary/BinaryStructMapper.cs +++ b/src/Nyanbyte.Sysnya/Binary/BinaryStructMapper.cs @@ -3,23 +3,105 @@ using System.Linq.Expressions; namespace Nyanbyte.Sysnya.Binary; -public class BinaryStructMapperBuilder +public class BinaryStructMapperBuilder where T : notnull, new() { - public BinaryStructMapperBuilder(T obj) + public BinaryStructMapperBuilder(int minMemoryLen = 0, bool bigEndian = true) { - ArgumentNullException.ThrowIfNull(obj); - _obj = obj; + _minMemoryLen = minMemoryLen; + _bigEndian = bigEndian; } + private readonly int _minMemoryLen; + private readonly bool _bigEndian; + private readonly LinkedList> _funcPipeline = new(); - [NotNull] - private readonly T _obj; - private readonly Stack>> _funcStack = new(); - - public BinaryStructMapperBuilder BindU32(Expression> property) + public BinaryStructMapperBuilder BindI16(Expression> property) { - var member = property.Body as MemberExpression; - ArgumentNullException.ThrowIfNull(member); - _funcStack.Push(m => _obj.GetType().GetProperty(member.Member.Name)!.SetValue(_obj, m)); + Bind(property); return this; } + + public BinaryStructMapperBuilder BindU16(Expression> property) + { + Bind(property); + return this; + } + + public BinaryStructMapperBuilder BindI32(Expression> property) + { + Bind(property); + return this; + } + + public BinaryStructMapperBuilder BindU32(Expression> property) + { + Bind(property); + return this; + } + + public BinaryStructMapperBuilder BindI64(Expression> property) + { + Bind(property); + return this; + } + + public BinaryStructMapperBuilder BindU64(Expression> property) + { + Bind(property); + return this; + } + + private void Bind(Expression> p) where TNumeric : struct, IConvertible + { + var member = p.Body as MemberExpression; + ArgumentNullException.ThrowIfNull(member); + var prop = typeof(T).GetProperty(member.Member.Name)!; + _funcPipeline.AddLast((obj, enu) => + { + TNumeric value = enu.ReadNumeric(); + prop.SetValue(obj, value); + }); + } + + public BinaryStructMapper Build() + { + return new BinaryStructMapper(_funcPipeline, _minMemoryLen, _bigEndian); + } +} + +public class BinaryStructMapper where T : notnull, new() +{ + internal BinaryStructMapper(LinkedList> funcPipeline, int minMemoryLen, bool bigEndian) + { + _funcPipeline = funcPipeline; + _minMemoryLen = minMemoryLen; + _bigEndian = bigEndian; + } + + private readonly int _minMemoryLen; + private readonly bool _bigEndian; + private readonly LinkedList> _funcPipeline; + + public T Map(byte[] bytes) + { + return Map(bytes.AsMemory()); + } + + public T Map(Memory bytes) + { + BinaryEnumerator enu = new(bytes, _bigEndian); + if (bytes.Length < _minMemoryLen) + { + throw new ArgumentException($"Data shorter than minimum expected: expected {_minMemoryLen} bytes; got: {bytes.Length} bytes"); + } + + T obj = Activator.CreateInstance(); + var enumerator = _funcPipeline.GetEnumerator(); + while (enumerator.MoveNext()) + { + var func = enumerator.Current; + func.Invoke(obj, enu); + } + + return obj; + } } diff --git a/test/Nyanbyte.Sysnya.CoreTests/Binary/BinaryMapperTests.cs b/test/Nyanbyte.Sysnya.CoreTests/Binary/BinaryMapperTests.cs new file mode 100644 index 0000000..b2752c7 --- /dev/null +++ b/test/Nyanbyte.Sysnya.CoreTests/Binary/BinaryMapperTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Threading.Tasks; +using Nyanbyte.Sysnya.Binary; + +namespace Nyanbyte.Sysnya.CoreTests.Binary; + +public class BinaryMapperTests +{ + + private class Dummy + { + public short Int16 { get; set; } + public ushort UInt16 { get; set; } + public int Int32 { get; set; } + public uint UInt32 { get; set; } + public long Int64 { get; set; } + public ulong UInt64 { get; set; } + } + + [Fact] + public void Map32Bit() + { + // Given + Span bytes = stackalloc byte[8]; + BinaryPrimitives.WriteUInt32BigEndian(bytes, 0xfeedbeef); + BinaryPrimitives.WriteInt32BigEndian(bytes[4..], 0xbeef); + + BinaryStructMapperBuilder b = new(0, true); + + // When + var mapper = b.BindU32(p => p.UInt32).BindI32(p => p.Int32).Build(); + var dummy = mapper.Map(bytes.ToArray()); + + // Then + Assert.Equal(0xfeedbeef, dummy.UInt32); + Assert.Equal(0xbeef, dummy.Int32); + } + + [Fact] + public void MapAll() + { + // Given + Span bytes = stackalloc byte[28]; + BinaryPrimitives.WriteUInt16BigEndian(bytes[0..], 0xbabe); + BinaryPrimitives.WriteInt16BigEndian(bytes[2..], 0xbf); + BinaryPrimitives.WriteUInt32BigEndian(bytes[4..], 0xfeedbeef); + BinaryPrimitives.WriteInt32BigEndian(bytes[8..], 0xbeef); + BinaryPrimitives.WriteUInt64BigEndian(bytes[12..], 0xbad1dea0babebebe); + BinaryPrimitives.WriteInt64BigEndian(bytes[20..], 0x1dea0babebebe); + + BinaryStructMapperBuilder b = new(0, true); + + // When + var mapper = b + .BindU16(p => p.UInt16).BindI16(p => p.Int16) + .BindU32(p => p.UInt32).BindI32(p => p.Int32) + .BindU64(p => p.UInt64).BindI64(p => p.Int64) + .Build(); + var dummy = mapper.Map(bytes.ToArray()); + + // Then + Assert.Equal(0xbabe, dummy.UInt16); + Assert.Equal(0xbf, dummy.Int16); + Assert.Equal(0xfeedbeef, dummy.UInt32); + Assert.Equal(0xbeef, dummy.Int32); + Assert.Equal(0xbad1dea0babebebe, dummy.UInt64); + Assert.Equal(0x1dea0babebebe, dummy.Int64); + } +}