Xe.BinaryMapper
1.2.0
See the version list below for details.
dotnet add package Xe.BinaryMapper --version 1.2.0
NuGet\Install-Package Xe.BinaryMapper -Version 1.2.0
<PackageReference Include="Xe.BinaryMapper" Version="1.2.0" />
<PackageVersion Include="Xe.BinaryMapper" Version="1.2.0" />
<PackageReference Include="Xe.BinaryMapper" />
paket add Xe.BinaryMapper --version 1.2.0
#r "nuget: Xe.BinaryMapper, 1.2.0"
#:package Xe.BinaryMapper@1.2.0
#addin nuget:?package=Xe.BinaryMapper&version=1.2.0
#tool nuget:?package=Xe.BinaryMapper&version=1.2.0
About
Xe.BinaryMapper is a .Net library that is capable to deserialize and serialize a binary file into a managed object. BinaryMapper aims to be easy to use and to hack, without using additional dependencies.
The library is available on NuGet and a Install-Package Xe.BinaryMapper will make it available in your project in few seconds.
Example
class Sample
{
[Data] public short Foo { get; set; }
[Data(offset: 4, count: 3, stride: 2)] public List<byte> Bar { get; set; }
}
...
var obj = new Sample
{
Foo = 123,
Bar = new List<byte>(){ 22, 44 }
};
BinaryMapping.WriteObject(writer, obj);
will be serialized into 7B 00 00 00 16 00 2C 00 00 00.
How the data is de/serialized under the hood
The binary data serialized few lines ago can be break down in the following flow logic:
[Data] public short Foo { get; set; }
Write a short (or System.Int16), so 2 bytes, of foo that contains the 123 value: 7B 00 is written.
[Data(offset: 4, count: 3, stride: 2)] public List<byte> Bar { get; set; }
Move to offset 4, which is 4 bytes after the initial class definition. But we already written 2 bytes, so just move 2 bytes forward.
We now have a List<> of two System.Byte. The stride between each value is 2 bytes, so write the first element 22 (our 0x16), skip one byte of stride and do the same with the second element 44.
But the count is 3, so we will just write other two bytes of zeroed data.
Can be done more?
Absolutely! Many primitive values are supported and can be customized (like how to de/serialize TimeSpan for example). Plus, nested class definitions can be used.
Usage and documentation
Serialization
The entire serialization happens in BinaryMapping.WriteObject, which accepts a Stream or BinaryWriter to write into a stream and the object to serialize.
When using ReadObject<T>, a new instance of T will be created automatically, only if that T class contains a parameterless constructor. ReadObject, accepta an existing instance instead too, overwriting all the existing properties that has a DataAttribute
The serialization always starts from BinaryWriter.BaseStream.Position or you can specify a base offset where the deserialization starts.
Deserialization
The entire de-serialization happens in BinaryMapping.ReadObject, which accepts a BinaryReader to read from the specified object and an existing object that will be used to store the read data.
The deserialization always starts from BinaryReader.BaseStream.Position.
The Data attribute
The DataAttribute is really important. Every property with this attribute will be evaluated during the de/serialization. It can be used only on a property that has public getter and setter, and has the following three optional parameters:
offsetwhere the data is physically located inside the file; the value is relative to the class definition. If not specified, the offset value is the same as the previous offset + its value size.counthow many times the item should be de/serialized. This is only useful forbyte[]orList<T>types.stridehow long is the actual data to de/serialize. This is very useful to skip some data when de/serializingList<T>data.bitIndexA custom bit index to de/serialize. -1 ignores it, while between 0 and 7 is a valid value.
The type bool and bit fields
By default, boolean types are read bit by bit if they are aligned. Infact, 8 consecutive boolean properties are considered 1 byte long.
[Data] public bool Bit0 { get; set; }
[Data] public bool Bit1 { get; set; }
[Data] public bool Bit2 { get; set; }
[Data] public bool Bit3 { get; set; }
[Data] public byte SomeRandomData { get; set; }
The code snippet above will read a total of 2 bytes and only the first 4 bits of the first byte will be considered.
[Data] public bool Bit0 { get; set; }
[Data] public bool Bit1 { get; set; }
[Data] public byte SomeRandomData { get; set; }
[Data] public bool Bit2 { get; set; }
[Data] public bool Bit3 { get; set; }
The code snippet above will read a total of 3 bytes. The first two bits will be read, then a byte and then the first two bits of the next byte. This is why order is important for alignment.
[Data(0)] public bool Bit0 { get; set; }
[Data] public bool Bit1 { get; set; }
[Data] public byte SomeRandomData { get; set; }
[Data(0, BitIndex = 2)] public bool Bit2 { get; set; }
[Data] public bool Bit3 { get; set; }
The code snippet above will read again only 2 bytes. After reading the 2nd byte, it will return to the position 0 and to the 3rd bit (0 based index), continuing the read from there.
Custom mapping
To customize how the de/serialization works for a specific type, a Mapping object must be passed to BinaryMapping.SetMapping.
A Mapping object is defined by two actions: Writer and Reader. An example on how to customize a mapping can be found here:
BinaryMapping.SetMapping<bool>(new BinaryMapping.Mapping
{
Writer = x => x.Writer.Write((byte)((bool)x.Item ? 1 : 0)),
Reader = x => x.Reader.ReadByte() != 0
});
"But I do not want / I cannot modify the existing classes"
A Data Transfer Object helps a lot. Libraries like Automapper allows you to map to existing classes your custom class that contains the DataAttribute specification for the properties.
Types supported
bool/System.Boolean1 bit long.byte/System.Byte1 byte long.sbyte/System.SByte1 byte long.short/System.Int162 bytes long.ushort/System.UInt162 bytes long.int/System.Int324 bytes long.uint/System.UInt324 bytes long.long/System.Int648 bytes long.ulong/System.UInt648 bytes long.float/System.Single4 bytes long.double/System.Double8 bytes long.Enumvariable length.TimeSpan8 bytes long.DateTime8 bytes long. Ignores the Kind property.Enumcustomizable size based on inherted type.stringfixed size based fromcountparameter.byte[]fixed array of bytes based fromcountparameter.List<>fixed list based fromcountparameter of any object or one of the types specified above.
Future plans
- Improve performance caching types
- Array and IEnumerable support
- BinaryMapping object instances, without relying to a global instance
- Custom object de/serialization
- Support for existing classes without using DataAttribute
Projects that uses BinaryMapper
Kingdom Hearts 3 Save Editor
https://github.com/Xeeynamo/KH3SaveEditor
Written by the author of BinaryMapper. This is a perfect example on a real scenario of how BinaryMapper can be used.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (2)
Showing the top 2 popular GitHub repositories that depend on Xe.BinaryMapper:
| Repository | Stars |
|---|---|
|
OpenKH/OpenKh
Kingdom Hearts libraries, tools, game engine and documentation
|
|
|
Xeeynamo/KingdomSaveEditor
General purpose videogame save editor
|