binutils64
v0.3.0
Published
A .NET-like BinaryReader and BinaryWriter with endianness support.
Downloads
6,145
Maintainers
Readme
binutils64
A .NET-style
BinaryReaderandBinaryWriterfor Node.js, with selectable endianness.
binutils64 provides two small classes — BinaryReader and BinaryWriter — that
make it easy to parse and produce binary data sequentially, with an API modelled on
the corresponding .NET classes. Both classes let you choose the byte order
(big- or little-endian) and support 8-, 16-, 32- and 64-bit integers, floats,
doubles, raw byte runs, and encoded strings.
Table of contents
- Features
- Installation
- Quick start
- API reference
- Behavior and best practices
- Examples
- Requirements and compatibility
- Testing
- Contributing
- License
Features
- Sequential
Read*/Write*methods for every common fixed-width type. - Per-instance endianness (
bigby default, orlittle). - 64-bit integers via JavaScript
BigInt. - Signed and unsigned integers, IEEE-754
floatanddouble, raw byte runs, and strings in anyBufferencoding. - Zero runtime dependencies.
- Bundled TypeScript type definitions.
Installation
npm install binutils64Then require it:
const binutils = require('binutils64');
// const { BinaryReader, BinaryWriter } = require('binutils64');Quick start
const { BinaryReader, BinaryWriter } = require('binutils64');
// --- Writing ---
const writer = new BinaryWriter(); // big-endian by default
writer.WriteUInt16(65535);
writer.WriteUInt32(0);
writer.WriteInt32(-1);
writer.WriteBytes([5, 4, 3, 2, 1]);
console.log(writer.ByteBuffer);
// <Buffer ff ff 00 00 00 00 ff ff ff ff 05 04 03 02 01>
console.log(writer.Length); // 15
// --- Reading ---
const reader = new BinaryReader(writer.ByteBuffer);
console.log(reader.ReadUInt16()); // 65535
console.log(reader.ReadUInt32()); // 0
console.log(reader.ReadInt32()); // -1
console.log(reader.ReadBytes(5)); // <Buffer 05 04 03 02 01>API reference
BinaryReader
A reader wraps an immutable copy of the input data and consumes it from the front as you read.
new BinaryReader(input, [endianness], [encoding])
| Parameter | Type | Default | Description |
| ------------ | ----------------------------- | ------- | ------------------------------------------------------------------ |
| input | Buffer | number[] | string | — | The data to read. A Buffer is copied so the source is never mutated. |
| endianness | 'big' | 'little' | 'big' | Byte order used by all multi-byte reads. |
| encoding | string | 'ascii' | Used to turn a string input into bytes, and by ReadString. |
Throws Error if input is not a Buffer, array, or string.
Read methods
| Method | Bytes | Returns | Notes |
| ----------------- | ----- | --------- | ---------------------------------------------- |
| ReadUInt8() | 1 | number | Unsigned. |
| ReadInt8() | 1 | number | Signed. |
| ReadUInt16() | 2 | number | Honors endianness. |
| ReadInt16() | 2 | number | Honors endianness. |
| ReadUInt32() | 4 | number | Honors endianness. |
| ReadInt32() | 4 | number | Honors endianness. |
| ReadUInt64() | 8 | BigInt | Honors endianness. Returns a BigInt. |
| ReadInt64() | 8 | BigInt | Honors endianness. Returns a BigInt. |
| ReadFloat() | 4 | number | IEEE-754 single precision. |
| ReadDouble() | 8 | number | IEEE-754 double precision. |
| ReadBytes(count)| count | Buffer| Copies count bytes into a new Buffer. |
| ReadString(count)| count | string| Decodes count bytes using the instance's Encoding. |
If fewer than the required number of bytes remain, integer/float reads return 0
(or 0.0), and ReadBytes/ReadString return an empty Buffer/string — without
throwing or advancing the position. See Behavior and best practices.
Reader properties
| Property | Type | Description |
| ------------ | -------- | ------------------------------------------------------------------- |
| ByteBuffer | Buffer | The remaining unread data. Shrinks as you read. |
| Position | number | Number of bytes consumed so far (starts at 0). |
| Length | number | The length of the original input. Does not change as you read. |
| Endianness | string | 'big' or 'little'. |
| Encoding | string | The encoding passed to the constructor. |
BinaryWriter
A writer accumulates bytes in an internal buffer that grows with every write.
new BinaryWriter([endianness], [encoding])
| Parameter | Type | Default | Description |
| ------------ | --------------------- | --------- | ---------------------------------------- |
| endianness | 'big' | 'little' | 'big' | Byte order used by all multi-byte writes.|
| encoding | string | 'ascii' | Used by WriteString to encode strings. |
Write methods
| Method | Bytes | Accepts | Notes |
| --------------------- | ----- | ------------------- | -------------------------------------------------- |
| WriteUInt8(value) | 1 | number | Unsigned. |
| WriteInt8(value) | 1 | number | Signed. |
| WriteUInt16(value) | 2 | number | Honors endianness. |
| WriteInt16(value) | 2 | number | Honors endianness. |
| WriteUInt32(value) | 4 | number | Honors endianness. |
| WriteInt32(value) | 4 | number | Honors endianness. |
| WriteUInt64(value) | 8 | number | BigInt| Coerced with BigInt(value); honors endianness. |
| WriteInt64(value) | 8 | number | BigInt| Coerced with BigInt(value); honors endianness. |
| WriteFloat(value) | 4 | number | IEEE-754 single precision. |
| WriteDouble(value) | 8 | number | IEEE-754 double precision. |
| WriteBytes(value) | varies| Buffer | number[] | string | Strings are written as one byte per character code. Throws on any other type. |
| WriteString(value) | varies| string | Encodes the string using the instance's Encoding. Throws on any other type. |
Writing a value outside the target type's range throws a RangeError (the standard
Node.js Buffer write behavior) — e.g. WriteUInt8(256).
Writer properties
| Property | Type | Description |
| ------------ | -------- | -------------------------------------------- |
| ByteBuffer | Buffer | All bytes written so far. |
| Length | number | The current length of ByteBuffer. |
| Endianness | string | 'big' or 'little'. |
| Encoding | string | The encoding passed to the constructor. |
Behavior and best practices
Reads are destructive. Each
Read*call consumes bytes from the front ofByteBufferand advancesPosition. If you need the original bytes again, keep your own copy before reading. Create a freshBinaryReaderto start over.Lengthvs.Position. On a reader,Lengthis the original size and never changes;Positiontracks how much you have consumed. The bytes left to read areLength - Position(alsoByteBuffer.length).Out-of-range reads do not throw. Reading past the end returns
0/0.0/ an emptyBufferand leavesPositionunchanged. Check the remaining length yourself if a short buffer should be treated as an error:if (reader.ByteBuffer.length < 4) { throw new Error('truncated record'); } const value = reader.ReadUInt32();64-bit values are
BigInts.ReadUInt64/ReadInt64always return aBigInt. When writing, pass aBigIntfor any value aboveNumber.MAX_SAFE_INTEGER(2^53 - 1) to avoid silent precision loss; smallernumbers are accepted and coerced automatically.Set endianness once, at construction. All multi-byte methods follow the instance's
Endianness; reader and writer must agree to round-trip correctly.Constructing a reader from a string? Pass the encoding explicitly (e.g.
new BinaryReader(text, 'big', 'utf8')) so the bytes are interpreted the way you expect.The default
'ascii'encoding is 7-bit only. Node.js clears the high bit of every byte when decoding'ascii', so text containing characters above0x7Fdoes not round-trip throughWriteString/ReadStringwith the default encoding. Pass'utf8'(or'latin1') to both constructors when handling non-ASCII text.
Examples
Round-tripping a 64-bit integer
const { BinaryReader, BinaryWriter } = require('binutils64');
const writer = new BinaryWriter(); // big-endian
writer.WriteUInt64(0x1234567890ABCDEFn); // pass a BigInt for large values
console.log(writer.ByteBuffer); // <Buffer 12 34 56 78 90 ab cd ef>
const reader = new BinaryReader(writer.ByteBuffer);
console.log(reader.ReadUInt64()); // 1311768467294899695nLittle-endian
const { BinaryReader, BinaryWriter } = require('binutils64');
const writer = new BinaryWriter('little');
writer.WriteUInt32(0x01020304);
console.log(writer.ByteBuffer); // <Buffer 04 03 02 01>
const reader = new BinaryReader(writer.ByteBuffer, 'little');
console.log(reader.ReadUInt32().toString(16)); // "1020304"Writing and reading strings
const { BinaryReader, BinaryWriter } = require('binutils64');
const text = 'héllo';
const writer = new BinaryWriter('big', 'utf8');
writer.WriteUInt8(Buffer.byteLength(text, 'utf8')); // length prefix: 6
writer.WriteString(text);
const reader = new BinaryReader(writer.ByteBuffer, 'big', 'utf8');
const length = reader.ReadUInt8(); // 6
console.log(reader.ReadString(length)); // "héllo"Parsing a structured record
const { BinaryReader } = require('binutils64');
// type (u8), id (u32, big-endian), payload (6 bytes)
const reader = new BinaryReader(Buffer.from([1, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6]));
const record = {
type: reader.ReadUInt8(), // 1
id: reader.ReadUInt32(), // 3
payload: reader.ReadBytes(6), // <Buffer 01 02 03 04 05 06>
};
console.log(record, 'read', reader.Position, 'of', reader.Length, 'bytes');Requirements and compatibility
- Node.js 12 or newer (declared in
package.jsonengines). The 64-bit methods (ReadUInt64,ReadInt64,WriteUInt64,WriteInt64) rely onBigIntand theBufferbig-integer methods introduced in Node.js 12. - TypeScript typings are bundled (
binutils.d.ts) — no separate@typespackage is needed. - Running the test suite uses the built-in
node:testrunner, which requires Node.js 18 or newer.
Testing
npm test # runs `node --test`The suite covers every reader/writer method, round-trips across both endiannesses, edge cases (out-of-range reads, constructor input types, buffer-copy isolation), and the documented examples. Continuous integration runs it on Node.js 20, 22 and 24.
Contributing
Issues and pull requests are welcome. Please add or update tests for any behavioral
change, note it in CHANGELOG.md under Unreleased, and make sure npm test and
npm run lint pass before opening a pull request (run npm ci once to install the
linter).
