Skip to content

binary

stable

A cursor-based binary file reader with typed reads (integers, floats, strings), array reads, format-string unpacking, and a free function for parsing CSV text files.

use plugin binary::{BinaryReader.new, BinaryReader.from_bytes, parse_csv_text, …}
40 functions Data Formats
/ filter jk navigate Esc clear
Functions (40)
  1. BinaryReader.new Opens a binary file at path, loads its contents into memory, and returns a reader handle positioned at byte 0.
  2. BinaryReader.from_bytes Creates a reader from an existing byte buffer (or a string, treated as its UTF-8 bytes).
  3. parse_csv_text Splits a delimited text blob into a 1-indexed table of rows, where each row is a 1-indexed table of trimmed field strings.
  4. pos Returns the current cursor position, in bytes from the start of the reader's window.
  5. set_pos Seeks the cursor to an absolute byte offset.
  6. skip Moves the cursor by delta bytes relative to its current position.
  7. len Returns the total length of the reader's window in bytes.
  8. remaining Returns how many bytes are left between the cursor and the end of the window.
  9. eof Returns true when the cursor has reached or passed the end of the window.
  10. align Advances the cursor forward to the next multiple of boundary (no-op if already aligned).
  11. read_u8 Reads a single unsigned byte (0–255) and advances the cursor by 1.
  12. read_i8 Reads a single signed byte (-128–127) and advances the cursor by 1.
  13. read_u16_le Reads an unsigned 16-bit little-endian integer and advances the cursor by 2.
  14. read_u16_be Reads an unsigned 16-bit big-endian integer and advances the cursor by 2.
  15. read_i16_le Reads a signed 16-bit little-endian integer and advances the cursor by 2.
  16. read_i16_be Reads a signed 16-bit big-endian integer and advances the cursor by 2.
  17. read_u32_le Reads an unsigned 32-bit little-endian integer and advances the cursor by 4.
  18. read_u32_be Reads an unsigned 32-bit big-endian integer and advances the cursor by 4.
  19. read_i32_le Reads a signed 32-bit little-endian integer and advances the cursor by 4.
  20. read_i32_be Reads a signed 32-bit big-endian integer and advances the cursor by 4.
  21. read_u64_le Reads an unsigned 64-bit little-endian integer and advances the cursor by 8.
  22. read_u64_be Reads an unsigned 64-bit big-endian integer and advances the cursor by 8.
  23. read_i64_le Reads a signed 64-bit little-endian integer and advances the cursor by 8.
  24. read_i64_be Reads a signed 64-bit big-endian integer and advances the cursor by 8.
  25. read_f32_le Reads a 32-bit IEEE-754 little-endian float and advances the cursor by 4.
  26. read_f32_be Reads a 32-bit IEEE-754 big-endian float and advances the cursor by 4.
  27. read_f64_le Reads a 64-bit IEEE-754 little-endian double and advances the cursor by 8.
  28. read_f64_be Reads a 64-bit IEEE-754 big-endian double and advances the cursor by 8.
  29. read_string Reads exactly len bytes and decodes them as a UTF-8 string.
  30. read_cstring Reads bytes until a null terminator or up to max bytes, whichever comes first, returning the string without the trailing null.
  31. read_fixed_string Reads a fixed-width field of exactly len bytes and trims it at the first null byte.
  32. read_bytes Reads len raw bytes and returns them as a byte buffer, advancing the cursor by len.
  33. read_u8_array Reads count unsigned bytes and returns them as a 1-indexed table of integers in a single crossing.
  34. read_u16_le_array Reads count unsigned 16-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 2 bytes.
  35. read_u32_le_array Reads count unsigned 32-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 4 bytes.
  36. read_f32_le_array Reads count 32-bit little-endian floats into a 1-indexed table, advancing the cursor by count * 4 bytes.
  37. unpack Reads one record described by a compact format string and returns its fields as a 1-indexed table.
  38. unpack_array Reads count consecutive records using the format string and returns a 1-indexed table of records, each itself a 1-indexed table of fields.
  39. view Creates a sub-view of the buffer covering len bytes from absolute offset start.
  40. read_view Carves the next len bytes from the current cursor into a fresh sub-reader and advances the parent cursor past them.

Overview

The binary plugin reads structured binary data through a stateful BinaryReader handle. A reader wraps an in-memory byte buffer (loaded from a file with BinaryReader.new or from existing bytes with BinaryReader.from_bytes) and keeps an internal cursor that advances with every read. All typed reads are explicit about width and endianness — read_u32_le, read_i16_be, read_f32_le — so the same code parses little- and big-endian formats without surprises.

Beyond field-at-a-time reads, the plugin offers higher-level tools for parsing whole records: unpack/unpack_array decode structs from a compact format string in a single crossing, the *_array reads pull homogeneous sequences into a table, and view/read_view carve out independent sub-readers for nested chunks. The free parse_csv_text function handles the text side, splitting a delimited file into trimmed rows. Reach for this plugin whenever you need to parse a binary container, a game asset, a network packet, or a flat text table.

Common patterns

Read a fixed file header field by field, watching the cursor as you go:

use plugin binary::{BinaryReader, read_u32_be, read_u16_le, read_cstring, pos}

let r = BinaryReader.new("/data/asset.bin")
let magic = read_u32_be(r)
let version = read_u16_le(r)
let name = read_cstring(r, 32)
print("magic={magic} version={version} name={name} at byte {pos(r)}")

Decode a table of fixed-size records in one batch with unpack_array, then iterate the rows:

use plugin binary::{BinaryReader, read_u32_le, unpack_array}

let r = BinaryReader.new("/data/vertices.bin")
let count = read_u32_le(r)
let verts = unpack_array(r, "fff", count)
for v in verts {
  print("x={v[1]} y={v[2]} z={v[3]}")
}

Isolate a nested chunk as its own reader so its offsets start at zero:

use plugin binary::{BinaryReader, read_u32_le, read_view, remaining}

let r = BinaryReader.new("/data/archive.bin")
let chunk_len = read_u32_le(r)
let chunk = read_view(r, chunk_len)
print("chunk has {remaining(chunk)} bytes; parent advanced past it")

Opens a binary file at path, loads its contents into memory, and returns a reader handle positioned at byte 0.

Opens a binary file at path, loads its contents into memory, and returns a reader handle positioned at byte 0. The path is resolved relative to the plugin search roots.

use plugin binary::{BinaryReader, pos, len, read_u32_le}

let r = BinaryReader.new("/data/model.bin")
print("file size: {len(r)}")
let magic = read_u32_le(r)
print("magic: {magic} cursor: {pos(r)}")

Creates a reader from an existing byte buffer (or a string, treated as its UTF-8 bytes).

Creates a reader from an existing byte buffer (or a string, treated as its UTF-8 bytes). Useful when the data was already loaded or produced in memory rather than read from a file.

use plugin binary::{BinaryReader, read_u8, eof}

let data = [0x42, 0x4D, 0x1E, 0x00]
let r = BinaryReader.from_bytes(data)
let b1 = read_u8(r)
let b2 = read_u8(r)
print("header: {b1} {b2}")

Splits a delimited text blob into a 1-indexed table of rows, where each row is a 1-indexed table of trimmed field strings.

Splits a delimited text blob into a 1-indexed table of rows, where each row is a 1-indexed table of trimmed field strings. Empty lines and lines beginning with comment_prefix are dropped; pass "" for comment_prefix to keep every line. Single-token lines (no separator) yield a one-element row.

use plugin binary::{parse_csv_text}

let text = "# header comment\nname,age\nAlice,30\nBob,25"
let rows = parse_csv_text(text, ",", "#")
print(rows[1][1])
print(rows[2][2])

It also handles section-marker style files where some lines are plain tokens:

use plugin binary::{parse_csv_text}

let ide = "objs\n1234, model, txd\nend"
let rows = parse_csv_text(ide, ",", "")
print("section: {rows[1][1]}")
print("entry id: {rows[2][1]}")

Returns the current cursor position, in bytes from the start of the reader's window.

Returns the current cursor position, in bytes from the start of the reader's window.

use plugin binary::{BinaryReader, read_u32_le, pos}

let r = BinaryReader.new("/data/file.bin")
read_u32_le(r)
print("now at byte {pos(r)}")

Seeks the cursor to an absolute byte offset.

Seeks the cursor to an absolute byte offset. Errors if pos is negative or beyond the buffer length.

use plugin binary::{BinaryReader, set_pos, read_u16_le}

let r = BinaryReader.new("/data/file.bin")
set_pos(r, 16)
let flags = read_u16_le(r)
print("flags at offset 16: {flags}")

Moves the cursor by delta bytes relative to its current position.

Moves the cursor by delta bytes relative to its current position. delta may be negative to move backward. Errors if the result falls outside the buffer.

use plugin binary::{BinaryReader, read_u32_le, skip}

let r = BinaryReader.new("/data/file.bin")
let size = read_u32_le(r)
skip(r, size)
print("skipped past a {size}-byte block")

Returns the total length of the reader's window in bytes.

Returns the total length of the reader's window in bytes. For a sub-view this is the view length, not the parent file size.

use plugin binary::{BinaryReader, len}

let r = BinaryReader.new("/data/file.bin")
print("total bytes: {len(r)}")

Returns how many bytes are left between the cursor and the end of the window.

Returns how many bytes are left between the cursor and the end of the window.

use plugin binary::{BinaryReader, read_u32_le, remaining}

let r = BinaryReader.new("/data/file.bin")
read_u32_le(r)
print("still {remaining(r)} bytes to read")

Returns true when the cursor has reached or passed the end of the window.

Returns true when the cursor has reached or passed the end of the window. Handy as a loop condition when the record count is implicit.

use plugin binary::{BinaryReader, read_u16_le, eof}

let r = BinaryReader.new("/data/records.bin")
while !eof(r) {
  print("value: {read_u16_le(r)}")
}

Advances the cursor forward to the next multiple of boundary (no-op if already aligned).

Advances the cursor forward to the next multiple of boundary (no-op if already aligned). Useful for formats that pad records to 2-, 4-, or 16-byte boundaries. Errors if aligning would overshoot the buffer.

use plugin binary::{BinaryReader, read_u8, align, pos}

let r = BinaryReader.new("/data/file.bin")
read_u8(r)
align(r, 4)
print("aligned to byte {pos(r)}")

Reads a single unsigned byte (0–255) and advances the cursor by 1.

Reads a single unsigned byte (0–255) and advances the cursor by 1.

use plugin binary::{BinaryReader, read_u8}

let r = BinaryReader.new("/data/file.bin")
print("byte: {read_u8(r)}")

Reads a single signed byte (-128–127) and advances the cursor by 1.

Reads a single signed byte (-128–127) and advances the cursor by 1.

use plugin binary::{BinaryReader, read_i8}

let r = BinaryReader.new("/data/file.bin")
print("signed byte: {read_i8(r)}")

Reads an unsigned 16-bit little-endian integer and advances the cursor by 2.

Reads an unsigned 16-bit little-endian integer and advances the cursor by 2.

use plugin binary::{BinaryReader, read_u16_le}

let r = BinaryReader.new("/data/file.bin")
print("u16le: {read_u16_le(r)}")

Reads an unsigned 16-bit big-endian integer and advances the cursor by 2.

Reads an unsigned 16-bit big-endian integer and advances the cursor by 2.

use plugin binary::{BinaryReader, read_u16_be}

let r = BinaryReader.new("/data/file.bin")
print("u16be: {read_u16_be(r)}")

Reads a signed 16-bit little-endian integer and advances the cursor by 2.

Reads a signed 16-bit little-endian integer and advances the cursor by 2.

use plugin binary::{BinaryReader, read_i16_le}

let r = BinaryReader.new("/data/file.bin")
print("i16le: {read_i16_le(r)}")

Reads a signed 16-bit big-endian integer and advances the cursor by 2.

Reads a signed 16-bit big-endian integer and advances the cursor by 2.

use plugin binary::{BinaryReader, read_i16_be}

let r = BinaryReader.new("/data/file.bin")
print("i16be: {read_i16_be(r)}")

Reads an unsigned 32-bit little-endian integer and advances the cursor by 4.

Reads an unsigned 32-bit little-endian integer and advances the cursor by 4.

use plugin binary::{BinaryReader, read_u32_le}

let r = BinaryReader.new("/data/file.bin")
print("u32le: {read_u32_le(r)}")

Reads an unsigned 32-bit big-endian integer and advances the cursor by 4.

Reads an unsigned 32-bit big-endian integer and advances the cursor by 4. Common for magic numbers stored in network byte order.

use plugin binary::{BinaryReader, read_u32_be}

let r = BinaryReader.new("/data/file.bin")
let magic = read_u32_be(r)
print("magic: {magic}")

Reads a signed 32-bit little-endian integer and advances the cursor by 4.

Reads a signed 32-bit little-endian integer and advances the cursor by 4.

use plugin binary::{BinaryReader, read_i32_le}

let r = BinaryReader.new("/data/file.bin")
print("i32le: {read_i32_le(r)}")

Reads a signed 32-bit big-endian integer and advances the cursor by 4.

Reads a signed 32-bit big-endian integer and advances the cursor by 4.

use plugin binary::{BinaryReader, read_i32_be}

let r = BinaryReader.new("/data/file.bin")
print("i32be: {read_i32_be(r)}")

Reads an unsigned 64-bit little-endian integer and advances the cursor by 8.

Reads an unsigned 64-bit little-endian integer and advances the cursor by 8.

use plugin binary::{BinaryReader, read_u64_le}

let r = BinaryReader.new("/data/file.bin")
print("u64le: {read_u64_le(r)}")

Reads an unsigned 64-bit big-endian integer and advances the cursor by 8.

Reads an unsigned 64-bit big-endian integer and advances the cursor by 8.

use plugin binary::{BinaryReader, read_u64_be}

let r = BinaryReader.new("/data/file.bin")
print("u64be: {read_u64_be(r)}")

Reads a signed 64-bit little-endian integer and advances the cursor by 8.

Reads a signed 64-bit little-endian integer and advances the cursor by 8.

use plugin binary::{BinaryReader, read_i64_le}

let r = BinaryReader.new("/data/file.bin")
print("i64le: {read_i64_le(r)}")

Reads a signed 64-bit big-endian integer and advances the cursor by 8.

Reads a signed 64-bit big-endian integer and advances the cursor by 8.

use plugin binary::{BinaryReader, read_i64_be}

let r = BinaryReader.new("/data/file.bin")
print("i64be: {read_i64_be(r)}")

Reads a 32-bit IEEE-754 little-endian float and advances the cursor by 4.

Reads a 32-bit IEEE-754 little-endian float and advances the cursor by 4.

use plugin binary::{BinaryReader, read_f32_le}

let r = BinaryReader.new("/data/file.bin")
print("f32le: {read_f32_le(r)}")

Reads a 32-bit IEEE-754 big-endian float and advances the cursor by 4.

Reads a 32-bit IEEE-754 big-endian float and advances the cursor by 4.

use plugin binary::{BinaryReader, read_f32_be}

let r = BinaryReader.new("/data/file.bin")
print("f32be: {read_f32_be(r)}")

Reads a 64-bit IEEE-754 little-endian double and advances the cursor by 8.

Reads a 64-bit IEEE-754 little-endian double and advances the cursor by 8.

use plugin binary::{BinaryReader, read_f64_le}

let r = BinaryReader.new("/data/file.bin")
print("f64le: {read_f64_le(r)}")

Reads a 64-bit IEEE-754 big-endian double and advances the cursor by 8.

Reads a 64-bit IEEE-754 big-endian double and advances the cursor by 8.

use plugin binary::{BinaryReader, read_f64_be}

let r = BinaryReader.new("/data/file.bin")
print("f64be: {read_f64_be(r)}")

Reads exactly len bytes and decodes them as a UTF-8 string.

Reads exactly len bytes and decodes them as a UTF-8 string. Errors if the bytes are not valid UTF-8.

use plugin binary::{BinaryReader, read_string}

let r = BinaryReader.new("/data/file.bin")
let tag = read_string(r, 4)
print("tag: {tag}")

Reads bytes until a null terminator or up to max bytes, whichever comes first, returning the string without the trailing null.

Reads bytes until a null terminator or up to max bytes, whichever comes first, returning the string without the trailing null. Use for C-style strings embedded in binary formats.

use plugin binary::{BinaryReader, read_cstring, read_u32_le}

let r = BinaryReader.new("/data/header.bin")
let version = read_u32_le(r)
let name = read_cstring(r, 64)
print("version {version}, name: {name}")

It also stops cleanly at the end of the buffer, so it is safe even when no null terminator is present:

use plugin binary::{BinaryReader, read_cstring}

let r = BinaryReader.from_bytes("hello")
print(read_cstring(r, 32))

Reads a fixed-width field of exactly len bytes and trims it at the first null byte.

Reads a fixed-width field of exactly len bytes and trims it at the first null byte. The cursor always advances by len, even when the text is shorter — ideal for null-padded name fields.

use plugin binary::{BinaryReader, read_fixed_string}

let r = BinaryReader.new("/data/file.bin")
let model = read_fixed_string(r, 24)
print("model name: {model}")

Reads len raw bytes and returns them as a byte buffer, advancing the cursor by len.

Reads len raw bytes and returns them as a byte buffer, advancing the cursor by len. Pass the result back into BinaryReader.from_bytes to parse it as its own stream.

use plugin binary::{BinaryReader, read_u32_le, read_bytes, read_u16_le}

let r = BinaryReader.new("/data/file.bin")
let blob_len = read_u32_le(r)
let blob = read_bytes(r, blob_len)
let sub = BinaryReader.from_bytes(blob)
print("first u16 of blob: {read_u16_le(sub)}")

Reads count unsigned bytes and returns them as a 1-indexed table of integers in a single crossing.

Reads count unsigned bytes and returns them as a 1-indexed table of integers in a single crossing.

use plugin binary::{BinaryReader, read_u8_array}

let r = BinaryReader.new("/data/file.bin")
let palette = read_u8_array(r, 4)
print("rgba: {palette[1]} {palette[2]} {palette[3]} {palette[4]}")

Reads count unsigned 16-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 2 bytes.

Reads count unsigned 16-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 2 bytes.

use plugin binary::{BinaryReader, read_u16_le_array}

let r = BinaryReader.new("/data/indices.bin")
let indices = read_u16_le_array(r, 6)
print("first index: {indices[1]}")

Reads count unsigned 32-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 4 bytes.

Reads count unsigned 32-bit little-endian integers into a 1-indexed table, advancing the cursor by count * 4 bytes.

use plugin binary::{BinaryReader, read_u32_le_array}

let r = BinaryReader.new("/data/offsets.bin")
let offsets = read_u32_le_array(r, 8)
print("offset[1]: {offsets[1]}")

Reads count 32-bit little-endian floats into a 1-indexed table, advancing the cursor by count * 4 bytes.

Reads count 32-bit little-endian floats into a 1-indexed table, advancing the cursor by count * 4 bytes. Convenient for vertex or matrix data.

use plugin binary::{BinaryReader, read_f32_le_array}

let r = BinaryReader.new("/data/transform.bin")
let matrix = read_f32_le_array(r, 16)
print("m11: {matrix[1]}")

Reads one record described by a compact format string and returns its fields as a 1-indexed table.

Reads one record described by a compact format string and returns its fields as a 1-indexed table. Codes: b/B = i8/u8, h/H = i16/u16, i/I = i32/u32, l/L = i64/u64, f/d = f32/f64, s<N> = fixed null-padded string of N bytes, x<N> = skip N bytes (no field emitted). A leading <, >, or = selects little-, big-, or native-endian (default little). Whitespace is ignored.

use plugin binary::{BinaryReader, unpack}

let r = BinaryReader.new("/data/particles.bin")
let rec = unpack(r, "fff")
print("x={rec[1]} y={rec[2]} z={rec[3]}")

Skip fields and fixed strings let you map a real struct in one call — here a 32-bit id, an 8-byte gap, and a 24-byte name:

use plugin binary::{BinaryReader, unpack}

let r = BinaryReader.new("/data/objects.bin")
let obj = unpack(r, "<I x8 s24")
print("id={obj[1]} name={obj[2]}")

Reads count consecutive records using the format string and returns a 1-indexed table of records, each itself a 1-indexed table of fields.

Reads count consecutive records using the format string and returns a 1-indexed table of records, each itself a 1-indexed table of fields. Decodes the whole batch in a single crossing, so it is far faster than calling unpack in a loop.

use plugin binary::{BinaryReader, unpack_array}

let r = BinaryReader.new("/data/verts.bin")
let verts = unpack_array(r, "fff", 1000)
print("first vertex: {verts[1][1]}, {verts[1][2]}, {verts[1][3]}")

It works with mixed-type records too — for example an id plus two floats per row:

use plugin binary::{BinaryReader, read_u32_le, unpack_array}

let r = BinaryReader.new("/data/points.bin")
let n = read_u32_le(r)
let points = unpack_array(r, "<I ff", n)
for p in points {
  print("id={p[1]} at ({p[2]}, {p[3]})")
}

Creates a sub-view of the buffer covering len bytes from absolute offset start.

Creates a sub-view of the buffer covering len bytes from absolute offset start. The sub-view shares the underlying bytes but has its own independent cursor starting at 0, and leaves the parent reader's cursor untouched.

use plugin binary::{BinaryReader, view, read_u32_le}

let r = BinaryReader.new("/data/archive.bin")
let chunk = view(r, 128, 512)
let chunk_magic = read_u32_le(chunk)
print("chunk magic: {chunk_magic}")

Carves the next len bytes from the current cursor into a fresh sub-reader and advances the parent cursor past them.

Carves the next len bytes from the current cursor into a fresh sub-reader and advances the parent cursor past them. Unlike view, it is cursor-relative, which makes sequential chunked formats easy to walk.

use plugin binary::{BinaryReader, read_u32_le, read_view, len}

let r = BinaryReader.new("/data/archive.bin")
let size = read_u32_le(r)
let section = read_view(r, size)
print("section length: {len(section)}; parent moved on")
enespt-br