libdestruct Skills
libdestruct is a Python library for destructuring binary data into typed objects. It maps raw bytes to C-like types (integers, floats, strings, structs, pointers, arrays, enums, bitfields) with read/write support.
Installation
pip install git+https://github.com/mrindeciso/libdestruct.git
Core Concepts
All types inherit from obj. Every obj has:
.valueproperty to read/write the underlying data.addressproperty for the memory offset.to_bytes()to serialize back to bytes.freeze()/.diff()/.reset()for snapshotting.hexdump()for a hex dump of the object's bytes.from_bytes(data)class method to create a read-only instance from raw bytes
Memory is accessed through an inflater, which wraps a bytes, bytearray, or mmap.mmap buffer. Use bytearray or writable mmap for read/write access. For file-backed memory, use inflater_from_file().
Quick Reference
Imports
from typing import Annotated
from libdestruct import (
inflater, # memory wrapper (bytearray / mmap)
inflater_from_file, # file-backed inflater (convenience)
FileInflater, # file-backed inflater class
struct, # struct base class
c_int, c_uint, # 32-bit integers (signed/unsigned)
c_long, c_ulong, # 64-bit integers (signed/unsigned)
c_short, c_ushort, # 16-bit integers (signed/unsigned)
c_char, c_uchar, # 8-bit integers (signed/unsigned)
c_float, c_double, # IEEE 754 floats (32/64-bit)
c_str, # null-terminated C string
ptr, # 8-byte pointer
ptr_to, # typed pointer field descriptor (legacy)
ptr_to_self, # self-referential pointer field descriptor (legacy)
array, array_of, # array type + field descriptor
vla_of, # variable-length array field descriptor
enum, enum_of, # enum type + field descriptor
flags, flags_of, # bit flags type + field descriptor
bitfield_of, # bitfield descriptor
union, # union annotation type
union_of, # plain union field descriptor
tagged_union, # tagged union field descriptor
offset, # explicit field offset
size_of, # get size in bytes of any type/instance/field
alignment_of, # get natural alignment of any type/instance
)
Type Sizes
| Type | Size (bytes) |
|---|---|
c_int / c_uint | 4 |
c_long / c_ulong | 8 |
c_float | 4 |
c_double | 8 |
ptr | 8 |
c_str | variable (reads until null) |
Reading Primitives from a Buffer
memory = bytearray(b"\x2a\x00\x00\x00\x00\x00\x00\x00")
lib = inflater(memory)
x = lib.inflate(c_int, 0) # inflate c_int at offset 0
print(x.value) # 42
y = lib.inflate(c_long, 0) # inflate c_long at offset 0
print(y.value)
Reading Primitives from Raw Bytes
x = c_int.from_bytes(b"\x2a\x00\x00\x00")
print(x.value) # 42
# Note: from_bytes returns a frozen (read-only) object
Writing Primitives
memory = bytearray(4)
lib = inflater(memory)
x = lib.inflate(c_int, 0)
x.value = -1
print(memory) # bytearray(b'\xff\xff\xff\xff')
Defining Structs
class player_t(struct):
health: c_int
score: c_uint
position_x: c_float
position_y: c_float
Struct fields are laid out sequentially. Access members as attributes; each returns a typed obj (use .value to get the Python value).
Inflating Structs
import struct as pystruct
memory = bytearray(16)
memory[0:4] = pystruct.pack("<i", 100)
memory[4:8] = pystruct.pack("<I", 5000)
memory[8:12] = pystruct.pack("<f", 1.5)
memory[12:16] = pystruct.pack("<f", -3.0)
lib = inflater(memory)
player = lib.inflate(player_t, 0)
print(player.health.value) # 100
print(player.score.value) # 5000
print(player.position_x.value) # 1.5
Or from raw bytes (read-only):
player = player_t.from_bytes(memory)
Pointers
class node_t(struct):
value: c_int
next: ptr["node_t"] # pointer to own type (forward ref)
# Typed pointer to another type:
class container_t(struct):
data: c_int
ref: ptr[c_long] # subscript syntax (preferred)
Legacy syntax with ptr_to() and ptr_to_self() is still supported:
class node_t(struct):
value: c_int
next: ptr = ptr_to_self()
class container_t(struct):
data: c_int
ref: ptr = ptr_to(c_long)
Dereference with .unwrap() or safe .try_unwrap() (returns None if invalid):
node = lib.inflate(node_t, 0)
print(node.value.value)
next_node = node.next.unwrap() # follow pointer
maybe_node = node.next.try_unwrap() # None if invalid
Pointer arithmetic (C-style, scaled by element size):
p = lib.inflate(ptr, 0)
p.wrapper = c_int
print(p[0].value) # element at index 0
print(p[1].value) # element at index 1
print((p + 2).unwrap().value) # element at index 2
Pointer results are cached; call .invalidate() after memory changes.
Forward References
For mutually referential structs, use ptr["TypeName"]:
class tree_t(struct):
value: c_int
left: ptr["tree_t"]
right: ptr["tree_t"]
Arrays
class packet_t(struct):
length: c_int
data: array[c_int, 8] # subscript syntax (preferred)
Legacy syntax with array_of() is still supported:
class packet_t(struct):
length: c_int
data: array = array_of(c_int, 8)
Access array elements:
pkt = lib.inflate(packet_t, 0)
print(pkt.data[0].value) # first element
print(pkt.data.count()) # 8
for element in pkt.data:
print(element.value)
Variable-Length Arrays
VLAs model C flexible array members: the count is read from a sibling field at inflation time.
class packet_t(struct):
length: c_int
data: array[c_int, "length"] # subscript syntax (string = VLA)
Or with the descriptor:
class packet_t(struct):
length: c_int
data: array = vla_of(c_int, "length")
pkt = lib.inflate(packet_t, 0)
print(len(pkt.data)) # reads from pkt.length.value
print(pkt.data[0].value) # first element
Size semantics: size_of(packet_t) returns the fixed part only (excludes VLA). size_of(instance) includes VLA data. VLA must be the last field in the struct. VLA elements can be structs.
Enums
from enum import IntEnum
class Color(IntEnum):
RED = 0
GREEN = 1
BLUE = 2
class pixel_t(struct):
color: enum[Color] # subscript syntax (preferred, defaults to c_int backing)
alpha: c_int
# With a custom backing type:
class pixel_t(struct):
color: enum[Color, c_short] # 2-byte backing type
alpha: c_int
Legacy syntax with enum_of() is still supported:
class pixel_t(struct):
color: enum = enum_of(Color)
alpha: c_int
pixel = lib.inflate(pixel_t, 0)
print(pixel.color.value) # Color.RED
Bit Flags
Use Python's IntFlag for bitmask fields:
from enum import IntFlag
class Perms(IntFlag):
READ = 1
WRITE = 2
EXEC = 4
class file_t(struct):
mode: flags[Perms] # subscript syntax (defaults to c_int backing)
size: c_int
# With a custom backing type:
class file_t(struct):
mode: flags[Perms, c_short] # 2-byte backing
size: c_int
Legacy syntax with flags_of():
class file_t(struct):
mode: flags = flags_of(Perms)
size: c_int
f = lib.inflate(file_t, 0)
print(f.mode.value) # Perms.READ|Perms.WRITE
print(Perms.READ in f.mode.value) # True
By default flags are lenient (unknown bits produce raw int). Use flags_of(Perms, lenient=False) for strict mode that raises ValueError