Xe.BinaryMapper 1.2.0

There is a newer version of this package available.
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
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xe.BinaryMapper" Version="1.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Xe.BinaryMapper --version 1.2.0
#r "nuget: Xe.BinaryMapper, 1.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Xe.BinaryMapper as a Cake Addin
#addin nuget:?package=Xe.BinaryMapper&version=1.2.0

// Install Xe.BinaryMapper as a Cake Tool
#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.

NuGet Last commit

Build status Test status

Downloads Issues

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:

  • offset where 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.
  • count how many times the item should be de/serialized. This is only useful for byte[] or List<T> types.
  • stride how long is the actual data to de/serialize. This is very useful to skip some data when de/serializing List<T> data.
  • bitIndex A 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.Boolean 1 bit long.
  • byte / System.Byte 1 byte long.
  • sbyte / System.SByte 1 byte long.
  • short / System.Int16 2 bytes long.
  • ushort / System.UInt16 2 bytes long.
  • int / System.Int32 4 bytes long.
  • uint / System.UInt32 4 bytes long.
  • long / System.Int64 8 bytes long.
  • ulong / System.UInt64 8 bytes long.
  • float / System.Single 4 bytes long.
  • double / System.Double 8 bytes long.
  • Enum variable length.
  • TimeSpan 8 bytes long.
  • DateTime 8 bytes long. Ignores the Kind property.
  • Enum customizable size based on inherted type.
  • string fixed size based from count parameter.
  • byte[] fixed array of bytes based from count parameter.
  • List<> fixed list based from count parameter 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 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. 
.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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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
Version Downloads Last updated
1.5.2 7,408 12/17/2020
1.5.1 1,132 6/27/2020
1.5.0 677 4/17/2020
1.4.1 1,306 8/28/2019
1.4.0 816 6/2/2019
1.3.0 579 4/3/2019
1.2.0 735 3/30/2019
1.1.1 524 3/27/2019
1.1.0 538 3/16/2019
1.0.0 551 3/16/2019