Skip to content

Commit 7af03fe

Browse files
committed
feat: add big-endian support in the resolver
This propagates down the chain, and everything works big-endian
1 parent 0c501ee commit 7af03fe

10 files changed

Lines changed: 309 additions & 20 deletions

File tree

libdestruct/backing/fake_resolver.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
class FakeResolver(Resolver):
1313
"""A class that can resolve elements in a simulated memory storage."""
1414

15-
def __init__(self: FakeResolver, memory: dict | None = None, address: int | None = 0) -> None:
15+
def __init__(self: FakeResolver, memory: dict | None = None, address: int | None = 0, endianness: str = "little") -> None:
1616
"""Initializes a basic fake resolver."""
1717
self.memory = memory if memory is not None else {}
1818
self.address = address
1919
self.parent = None
2020
self.offset = None
21+
self.endianness = endianness
2122

2223
def resolve_address(self: FakeResolver) -> int:
2324
"""Resolves self's address, mainly used by children to determine their own address."""
@@ -28,14 +29,14 @@ def resolve_address(self: FakeResolver) -> int:
2829

2930
def relative_from_own(self: FakeResolver, address_offset: int, _: int) -> FakeResolver:
3031
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
31-
new_resolver = FakeResolver(self.memory, None)
32+
new_resolver = FakeResolver(self.memory, None, self.endianness)
3233
new_resolver.parent = self
3334
new_resolver.offset = address_offset
3435
return new_resolver
3536

3637
def absolute_from_own(self: FakeResolver, address: int) -> FakeResolver:
3738
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
38-
return FakeResolver(self.memory, address)
39+
return FakeResolver(self.memory, address, self.endianness)
3940

4041
def resolve(self: FakeResolver, size: int, _: int) -> bytes:
4142
"""Resolves itself, providing the bytes it references for the specified size and index."""

libdestruct/backing/memory_resolver.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
class MemoryResolver(Resolver):
1818
"""A class that can resolve itself to a value in a referenced memory storage."""
1919

20-
def __init__(self: MemoryResolver, memory: MutableSequence, address: int | None) -> None:
20+
def __init__(self: MemoryResolver, memory: MutableSequence, address: int | None, endianness: str = "little") -> None:
2121
"""Initializes a basic memory resolver."""
2222
self.memory = memory
2323
self.address = address
2424
self.parent = None
2525
self.offset = None
26+
self.endianness = endianness
2627

2728
def resolve_address(self: MemoryResolver) -> int:
2829
"""Resolves self's address, mainly used by childs to determine their own address."""
@@ -33,14 +34,14 @@ def resolve_address(self: MemoryResolver) -> int:
3334

3435
def relative_from_own(self: MemoryResolver, address_offset: int, _: int) -> MemoryResolver:
3536
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
36-
new_resolver = MemoryResolver(self.memory, None)
37+
new_resolver = MemoryResolver(self.memory, None, self.endianness)
3738
new_resolver.parent = self
3839
new_resolver.offset = address_offset
3940
return new_resolver
4041

4142
def absolute_from_own(self: MemoryResolver, address: int) -> MemoryResolver:
4243
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
43-
return MemoryResolver(self.memory, address)
44+
return MemoryResolver(self.memory, address, self.endianness)
4445

4546
def resolve(self: MemoryResolver, size: int, _: int) -> bytes:
4647
"""Resolves itself, providing the bytes it references for the specified size and index."""

libdestruct/backing/resolver.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ class Resolver(ABC):
1616

1717
parent: Self
1818

19+
endianness: str = "little"
20+
"""The endianness of the data this resolver accesses."""
21+
1922
@abstractmethod
2023
def relative_from_own(self: Resolver, address_offset: int, index_offset: int) -> Self:
2124
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""

libdestruct/common/inflater.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
class Inflater:
2222
"""The memory manager, which inflates any memory-referencing type."""
2323

24-
def __init__(self: Inflater, memory: MutableSequence) -> None:
24+
def __init__(self: Inflater, memory: MutableSequence, endianness: str = "little") -> None:
2525
"""Initialize the memory manager."""
2626
self.memory = memory
27+
self.endianness = endianness
2728
self.type_registry = TypeRegistry()
2829

2930
def inflate(self: Inflater, item: type, address: int | Resolver) -> obj:
@@ -38,6 +39,6 @@ def inflate(self: Inflater, item: type, address: int | Resolver) -> obj:
3839
"""
3940
if isinstance(address, int):
4041
# Create a memory resolver from the address
41-
address = MemoryResolver(self.memory, address)
42+
address = MemoryResolver(self.memory, address, self.endianness)
4243

4344
return self.type_registry.inflater_for(item)(address)

libdestruct/common/obj.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def __init__(self: obj, resolver: Resolver) -> None:
3838
resolver: The resolver for the value of this object.
3939
"""
4040
self.resolver = resolver
41+
if resolver is not None:
42+
self.endianness = resolver.endianness
4143

4244
@property
4345
def address(self: obj) -> int:
@@ -57,11 +59,11 @@ def to_bytes(self: obj) -> bytes:
5759
"""Serialize the object to bytes."""
5860

5961
@classmethod
60-
def from_bytes(cls: type[obj], data: bytes) -> obj:
62+
def from_bytes(cls: type[obj], data: bytes, endianness: str = "little") -> obj:
6163
"""Deserialize the object from bytes."""
6264
from libdestruct.libdestruct import inflater
6365

64-
lib = inflater(data)
66+
lib = inflater(data, endianness=endianness)
6567
item = lib.inflate(cls, 0)
6668
item.freeze()
6769
return item

libdestruct/common/ptr/ptr.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ class _ArithmeticResolver(Resolver):
2525
def __init__(self: _ArithmeticResolver, original: Resolver, address: int) -> None:
2626
self._original = original
2727
self._address = address
28+
self.endianness = original.endianness
2829

2930
def resolve_address(self: _ArithmeticResolver) -> int:
3031
return self._address
3132

3233
def resolve(self: _ArithmeticResolver, size: int, _: int) -> bytes:
33-
return self._address.to_bytes(size, "little")
34+
return self._address.to_bytes(size, self.endianness)
3435

3536
def modify(self: _ArithmeticResolver, _size: int, _index: int, _value: bytes) -> None:
3637
raise RuntimeError("Cannot modify a synthetic pointer.")
@@ -98,10 +99,9 @@ def unwrap(self: ptr, length: int | None = None) -> obj:
9899
raise ValueError("Length is not supported when unwrapping a pointer to a wrapper object.")
99100

100101
result = self.wrapper(self.resolver.absolute_from_own(address))
101-
elif not length:
102-
result = self.resolver.resolve(1, 0)
103102
else:
104-
result = self.resolver.resolve(length, 0)
103+
target_resolver = self.resolver.absolute_from_own(address)
104+
result = target_resolver.resolve(length or 1, 0)
105105

106106
self._cached_unwrap = result
107107
self._cache_valid = True

libdestruct/common/struct/struct.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def __new__(cls: type[struct], *args: ..., **kwargs: ...) -> struct: # noqa: PY
3030
return type_impl(*args, **kwargs)
3131

3232
@classmethod
33-
def from_bytes(cls: type[struct], data: bytes) -> struct_impl:
33+
def from_bytes(cls: type[struct], data: bytes, endianness: str = "little") -> struct_impl:
3434
"""Create a struct from a serialized representation."""
35-
type_inflater = inflater(data)
35+
type_inflater = inflater(data, endianness=endianness)
3636

3737
return type_inflater.inflate(cls, 0)

libdestruct/libdestruct.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,27 @@
1616
from libdestruct.common.obj import obj
1717

1818

19-
def inflater(memory: Sequence) -> Inflater:
19+
def inflater(memory: Sequence, endianness: str = "little") -> Inflater:
2020
"""Return a TypeInflater instance."""
2121
if not isinstance(memory, Sequence):
2222
raise TypeError(f"memory must be a MutableSequence, not {type(memory).__name__}")
2323

24-
return Inflater(memory)
24+
return Inflater(memory, endianness=endianness)
2525

2626

27-
def inflate(item: type, memory: Sequence, address: int | Resolver) -> obj:
27+
def inflate(item: type, memory: Sequence, address: int | Resolver, endianness: str = "little") -> obj:
2828
"""Inflate a memory-referencing type.
2929
3030
Args:
3131
item: The type to inflate.
3232
memory: The memory view, which can be mutable or immutable.
3333
address: The address of the object in the memory view.
34+
endianness: The byte order ("little" or "big").
3435
3536
Returns:
3637
The inflated object.
3738
"""
3839
if not isinstance(address, int) and not isinstance(address, Resolver):
3940
raise TypeError(f"address must be an int or a Resolver, not {type(address).__name__}")
4041

41-
return inflater(memory).inflate(item, address)
42+
return inflater(memory, endianness=endianness).inflate(item, address)

0 commit comments

Comments
 (0)