Skip to content

Commit 697c29c

Browse files
committed
feat: make structs instantiatable, to generate their in-memory representation from the initializers
1 parent 00bb181 commit 697c29c

3 files changed

Lines changed: 104 additions & 6 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#
2+
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3+
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
4+
# Licensed under the MIT license. See LICENSE file in the project root for details.
5+
#
6+
7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING
10+
11+
from libdestruct.backing.resolver import Resolver
12+
13+
14+
class FakeResolver(Resolver):
15+
"""A class that can resolve elements in a simulated memory storage."""
16+
17+
def __init__(self: FakeResolver, memory: dict | None = None, address: int | None = 0) -> FakeResolver:
18+
"""Initializes a basic fake resolver."""
19+
self.memory = memory if memory is not None else {}
20+
self.address = address
21+
self.parent = None
22+
self.offset = None
23+
24+
def resolve_address(self: FakeResolver) -> int:
25+
"""Resolves self's address, mainly used by children to determine their own address."""
26+
if self.address is not None:
27+
return self.address
28+
29+
return self.parent.resolve_address() + self.offset
30+
31+
def relative_from_own(self: FakeResolver, address_offset: int, _: int) -> FakeResolver:
32+
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
33+
new_resolver = FakeResolver(self.memory, None)
34+
new_resolver.parent = self
35+
new_resolver.offset = address_offset
36+
return new_resolver
37+
38+
def absolute_from_own(self: FakeResolver, address: int) -> FakeResolver:
39+
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
40+
return FakeResolver(self.memory, address)
41+
42+
def resolve(self: FakeResolver, size: int, _: int) -> bytes:
43+
"""Resolves itself, providing the bytes it references for the specified size and index."""
44+
address = self.resolve_address()
45+
# We store data in the dictionary as 4K pages
46+
page_address = address & ~0xFFF
47+
page_offset = address & 0xFFF
48+
49+
result = b""
50+
51+
while size:
52+
page = self.memory.get(page_address, b"\x00" * (0x1000 - page_offset))
53+
page_size = min(size, 0x1000 - page_offset)
54+
result += page[page_offset : page_offset + page_size]
55+
size -= page_size
56+
page_address += 0x1000
57+
page_offset = 0
58+
59+
return result
60+
61+
def modify(self: FakeResolver, size: int, _: int, value: bytes) -> None:
62+
"""Modifies itself in memory."""
63+
address = self.resolve_address()
64+
# We store data in the dictionary as 4K pages
65+
page_address = address & ~0xFFF
66+
page_offset = address & 0xFFF
67+
68+
while size:
69+
page = self.memory.get(page_address, b"\x00" * 0x1000)
70+
page_size = min(size, 0x1000 - page_offset)
71+
page = page[:page_offset] + value[:page_size] + page[page_offset + page_size :]
72+
self.memory[page_address] = page
73+
size -= page_size
74+
value = value[page_size:]
75+
page_address += 0x1000
76+
page_offset = 0

libdestruct/common/struct/struct.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import TYPE_CHECKING
1010

1111
from libdestruct.common.obj import obj
12+
from libdestruct.common.type_registry import TypeRegistry
1213
from libdestruct.libdestruct import inflater
1314

1415
if TYPE_CHECKING: # pragma: no cover
@@ -22,6 +23,12 @@ def __init__(self: struct) -> None:
2223
"""Initialize the struct."""
2324
raise RuntimeError("This type should not be directly instantiated.")
2425

26+
def __new__(cls: type[struct], *args: ..., **kwargs: ...) -> struct: # noqa: PYI034
27+
"""Create a new struct."""
28+
# Look for an inflater for this struct
29+
inflater = TypeRegistry().inflater_for(cls)
30+
return inflater(*args, **kwargs)
31+
2532
@classmethod
2633
def from_bytes(cls: type[struct], data: bytes) -> struct_impl:
2734
"""Create a struct from a serialized representation."""

libdestruct/common/struct/struct_impl.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66

77
from __future__ import annotations
88

9-
from typing import TYPE_CHECKING
9+
from typing import Self
1010

11+
from libdestruct.backing.fake_resolver import FakeResolver
12+
from libdestruct.backing.resolver import Resolver
1113
from libdestruct.common.attributes.offset_attribute import OffsetAttribute
1214
from libdestruct.common.field import Field
1315
from libdestruct.common.obj import obj
1416
from libdestruct.common.struct import struct
1517
from libdestruct.common.type_registry import TypeRegistry
1618
from libdestruct.common.utils import iterate_annotation_chain
1719

18-
if TYPE_CHECKING: # pragma: no cover
19-
from libdestruct.backing.resolver import Resolver
20-
2120

2221
class struct_impl(struct):
2322
"""The implementation for the C struct type."""
@@ -34,9 +33,16 @@ class struct_impl(struct):
3433
_inflater: TypeRegistry = TypeRegistry()
3534
"""The type registry, used for inflating the attributes."""
3635

37-
def __init__(self: struct_impl, resolver: Resolver) -> None:
36+
def __init__(self: struct_impl, resolver: Resolver | None = None, **kwargs: ...) -> None:
3837
"""Initialize the struct implementation."""
39-
# array overrides the __init__ method, so we need to call the parent class __init__ method
38+
# If we have kwargs and the resolver is None, we provide a fake resolver
39+
if kwargs and resolver is None:
40+
resolver = FakeResolver()
41+
42+
if not isinstance(resolver, Resolver):
43+
raise TypeError("The resolver must be a Resolver instance.")
44+
45+
# struct overrides the __init__ method, so we need to call the parent class __init__ method
4046
obj.__init__(self, resolver)
4147

4248
self.name = self.__class__.__name__
@@ -45,6 +51,15 @@ def __init__(self: struct_impl, resolver: Resolver) -> None:
4551
reference_type = self._reference_struct
4652
self._inflate_struct_attributes(self._inflater, resolver, reference_type)
4753

54+
for name, value in kwargs.items():
55+
getattr(self, name).value = value
56+
57+
def __new__(cls: struct_impl, *args: ..., **kwargs: ...) -> Self:
58+
"""Create a new struct."""
59+
# Skip the __new__ method of the parent class
60+
# struct_impl -> struct -> obj becomes struct_impl -> obj
61+
return obj.__new__(cls)
62+
4863
def _inflate_struct_attributes(
4964
self: struct_impl,
5065
inflater: TypeRegistry,

0 commit comments

Comments
 (0)