An endian-aware binary reader and writer for .NET. Snowberry.IO extends BinaryReader and BinaryWriter and reads and writes primitives, strings and Guid values in little- or big-endian order, plus SHA-1 values, from any Stream. It also restricts reads to a region of the stream, scans for byte signatures and can read the memory of another Windows process. It targets .NET 10 plus .NET Standard 2.1 and 2.0, and builds on Snowberry.IO.Common.
- Installation
- Quick Start
- Top Features at a Glance
- How It Works
- Core Types
- Region Views
- Endian Converter
- Custom Analyzer
- Process Memory
- Supported Data Types
- Pitfalls
- Related Packages
dotnet add package Snowberry.IO- Write values with an explicit byte order.
- Rewind and read them back in the same order.
using Snowberry.IO.Common;
using Snowberry.IO.Reader;
using Snowberry.IO.Writer;
var stream = new MemoryStream();
using (var writer = new EndianStreamWriter(stream, keepStreamOpen: true))
{
writer.Write(10, EndianType.BIG)
.Write(20L, EndianType.BIG)
.Write(30F, EndianType.BIG)
.WriteCString("hello");
}
stream.Position = 0;
using var reader = new EndianStreamReader(stream);
int a = reader.ReadInt32(EndianType.BIG);
long b = reader.ReadInt64(EndianType.BIG);
float c = reader.ReadSingle(EndianType.BIG);
string text = reader.ReadCString();- Both byte orders for every primitive. The readers are
BinaryReaders, soReadInt32()and the other inherited reads stay little-endian; the overloads such asReadInt32(EndianType.BIG)andWrite(value, EndianType.BIG)pick the byte order for 16-, 32- and 64-bit integers,float,double,Guid, andHalfon the .NET 10 build. The endian-awareWriteoverloads return the writer, so calls chain. - Strings in every common layout. Zero-terminated (
ReadCString/WriteCString), fixed-size zero-terminated (ReadSizedCString/WriteSizedCString), 7-bit length-prefixed (ReadString), lines (ReadLine/WriteLine) and raw characters without a prefix or terminator (WriteStringCharacters), for UTF-8, UTF-16 and UTF-32. - Non-throwing reads.
TryReadInt32,TryReadGuid,TryReadSha1and the otherTryRead*methods returnfalseinstead of throwing at the end of the data, and restore the position when the stream can seek.ReadByteSafereturns-1at the end instead of throwing. - Region views.
EnableRegionView(20..80)makes a slice of the stream look like the whole stream:PositionandLengthbecome relative, and reads stop at the region end. - Bulk reads.
ReadUntilEndreturns the remaining bytes,CopyTo(destination)andCopyTo(destination, length)copy them to anotherStream, andReadAlignment/WritePaddingskip or write padding to a power-of-two boundary. - Signature scanning. With
using Snowberry.IO.Extensions;,reader.ScanSignatures("44 ?? 44 ?? 44", maxCount: 10, maxAddress: reader.Length)returns the positions of a byte pattern with??wildcards. - Buffer helpers.
ReadInInternalBufferfills the reader'sBuffer, andReadInt64At,ReadGuidAt,ReadSha1Atand friends decode values from it at an offset. The buffer holds 20 bytes unless a largerbufferSizeis passed to the constructor; callEnsureBufferSizebefore reading more into it. - Process memory.
Win32ProcessReaderreads another process's memory on Windows through the sameIEndianReaderAPI, withPositionas the virtual address. See Process Memory.
Every reader reads through an internal read-only view stream that sits between the BinaryReader base and the data source. The view applies the region view and reports every block of bytes to the Analyzer, so the inherited BinaryReader methods honor both, just like the endian-aware reads. BaseStream returns this view, so seeking or reading it moves the reader.
EndianStreamReader / Win32ProcessReader
inherited reads endian-aware, string and TryRead* reads
\ /
v v
view stream (BaseStream)
- Position and Length relative to the region view
- Analyzer.AnalyzeReadBytes on every block read
|
v
source: any Stream (EndianStreamReader.Stream)
or process memory (ReadProcessMemory)
The writer has no such layer: EndianStreamWriter derives from BinaryWriter and writes to the Stream directly.
| Type | Purpose |
|---|---|
IEndianReader |
The reader contract, defined in Snowberry.IO.Common. |
BaseEndianReader |
Derives from BinaryReader and implements IEndianReader with the string, region view and try-read logic. |
EndianStreamReader |
Reads from any Stream, including non-seekable ones; disposes it unless KeepStreamOpen is set. |
Win32ProcessReader |
Reads the memory of a Windows process. |
IEndianWriter |
The writer contract, defined in Snowberry.IO.Common. |
EndianStreamWriter |
Writes to a Stream; derives from BinaryWriter and implements IEndianWriter. |
BinaryEndianConverter |
Converts between spans of bytes and values, see Endian Converter. |
BinaryUtils |
ApplyAlignment and CalculatePadding compute the aligned position or the padding for a power-of-two alignment. |
RegionRange |
A region given as StartPosition and Size, accepted by EnableRegionView(RegionRange). |
Sha1 |
A 20-byte SHA-1 hash value type; built from bytes or a 40-character hex string, written out with TryWriteBytes. |
EndianStreamReader(stream, analyzer, bufferSize, encoding) and EndianStreamWriter(stream, keepStreamOpen, encoding) take an explicit Encoding; the shorter constructors use UTF-8 without a byte order mark.
A region view restricts a reader to part of its data. Indexes relative to the end are resolved against the full length, which ActualLength keeps reporting while the view is active, as ActualPosition does for the position:
using var reader = new EndianStreamReader(File.OpenRead("archive.bin"));
reader.EnableRegionView(0x200..0x400); // 512 bytes starting at 0x200
long length = reader.Length; // 512
reader.Position = 0; // the first byte of the region
reader.EnableRegionView(^16..); // the last 16 bytes
reader.DisableRegionView();BinaryEndianConverter decodes values from a ReadOnlySpan<byte> with the To… methods and encodes them into a Span<byte> with the Write… methods, optionally at an offset. Every overload checks that enough bytes are available and throws ArgumentOutOfRangeException otherwise, without writing anything. The single-precision methods are ToFloat and WriteFloat, and ToGuid and WriteGuid are only available with an offset.
byte[] buffer = [0x00, 0x00, 0x00, 0x2A, 0x01, 0x02];
int value = BinaryEndianConverter.ToInt32(buffer, EndianType.BIG); // 42
ushort tail = BinaryEndianConverter.ToUInt16(buffer, offset: 4, EndianType.BIG); // 0x0102
BinaryEndianConverter.WriteUInt16(buffer, 4, 0x0304, EndianType.LITTLE); // buffer ends with 04 03Derive from Analyzer to observe or rewrite every block of bytes right after it is read, including the bytes of the inherited BinaryReader methods, for example to decrypt content on the fly:
using Snowberry.IO.Common.Reader;
using Snowberry.IO.Common.Reader.Interfaces;
using Snowberry.IO.Reader;
using var stream = File.OpenRead("encrypted.bin");
using var reader = new EndianStreamReader(stream, new XorAnalyzer(0x5A));
public sealed class XorAnalyzer(byte key) : Analyzer
{
public override void Initialize(IEndianReader reader)
{
}
public override void AnalyzeReadBytes(IEndianReader reader, Span<byte> buffer, int amount)
{
for (int i = 0; i < amount; i++)
buffer[i] ^= key;
}
}Note: An analyzer that only observes the bytes can override
ModifiesBytesto returnfalse.ReadCString,ReadSizedCStringandReadLinethen search the bytes before they report them, and report the consumed bytes in larger pieces instead of one code unit at a time.
Win32ProcessReader(pid, startPosition) opens a process with PROCESS_VM_READ and PROCESS_QUERY_LIMITED_INFORMATION access (c_DefaultAccessFlags); pass other accessFlags for more rights. Win32ProcessReader(handle, startPosition) wraps a handle you already hold; set CloseHandleOnDispose to false when someone else owns it, for example Process.Handle.
ReadCStringfollows a pointer. It reads a 4- or 8-byte pointer atPosition, depending on the bitness of the target, and returns the string it points to, up toc_MaxCStringByteLengthbytes.CopyToneeds a length.CopyTo(destination)always throwsNotSupportedException; useCopyTo(destination, length).InjectDLLloads a library.InjectDLL(filePath)runsLoadLibraryWon a remote thread. It needsPROCESS_CREATE_THREAD,PROCESS_VM_OPERATIONandPROCESS_VM_WRITEaccess and a target with the same bitness.
| Type | Endian type(s) |
|---|---|
| Boolean | - |
| Char | - |
| Int8 | - |
| UInt8 | - |
| Int16 | Little, Big |
| UInt16 | Little, Big |
| Int32 | Little, Big |
| UInt32 | Little, Big |
| Int64 | Little, Big |
| UInt64 | Little, Big |
| Half | Little, Big (.NET 10 build) |
| Single | Little, Big |
| Double | Little, Big |
| Decimal | Little (inherited) |
| Guid | Little, Big |
| Sha1 | - |
- Writer and reader must agree on the encoding.
EndianStreamReaderandEndianStreamWriterdefault to UTF-8 without BOM on all targets. Pass the same explicitEncodingto both when the data uses another encoding.Win32ProcessReaderalways decodes UTF-8. - Alignment must be a power of two.
ReadAlignmentandWritePaddingthrowArgumentOutOfRangeExceptionfor0or values such as3or6. LengthandPositionneed a seekable stream. Plain reads and theTryRead*methods work on non-seekable streams, butLength,Position, region views,ReadCString,ReadSizedCString,ReadLine,ReadUntilEnd,ReadAlignmentandCopyTorequireStream.CanSeek.- A failed fixed-size read consumes the available bytes. Like
BinaryReader,ReadInt32()and the other fixed-size reads throwEndOfStreamExceptionat the end of the data and leavePositionafter the bytes that were there. Use theTryRead*methods orReadExactly(byte[], int, int)to keep the position on a seekable stream. ReadCStringneeds a terminator. A string that runs to the end of the data throwsEndOfStreamExceptioninstead of returning a partial value.WriteLinewritesEnvironment.NewLine. The line ending differs between Windows and other platforms;ReadLineaccepts CR LF, LF and CR.Win32ProcessReaderhas no length. Without a region view itsLengthis0, so useReadCString(which follows a pointer) andCopyTo(destination, length)rather than length-based methods.- Readers are not thread-safe. Use one reader per thread, or synchronize access.
| Package | Purpose |
|---|---|
| Snowberry.IO.Common | Reader and writer contracts, BinaryEndianConverter, Sha1 and EndianType. |
| Snowberry.IO.SingleFile | Reads, modifies and creates bundles of single-file published .NET applications. |