dissect.cstruct 5.0.dev0__py3-none-any.whl → 5.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect/cstruct/__init__.py +2 -2
- dissect/cstruct/bitbuffer.py +3 -3
- dissect/cstruct/compiler.py +7 -7
- dissect/cstruct/cstruct.py +192 -65
- dissect/cstruct/expression.py +8 -9
- dissect/cstruct/lexer.py +17 -7
- dissect/cstruct/parser.py +31 -13
- dissect/cstruct/tools/stubgen.py +18 -22
- dissect/cstruct/types/base.py +78 -33
- dissect/cstruct/types/char.py +21 -11
- dissect/cstruct/types/enum.py +33 -13
- dissect/cstruct/types/int.py +9 -7
- dissect/cstruct/types/leb128.py +6 -4
- dissect/cstruct/types/packed.py +19 -10
- dissect/cstruct/types/pointer.py +74 -56
- dissect/cstruct/types/structure.py +130 -30
- dissect/cstruct/types/void.py +6 -4
- dissect/cstruct/types/wchar.py +26 -14
- dissect/cstruct/{utils.py → util.py} +36 -25
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev7.dist-info/RECORD +32 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/scm_file_list.json +2 -2
- dissect_cstruct-5.0.dev7.dist-info/scm_version.json +8 -0
- dissect_cstruct-5.0.dev0.dist-info/RECORD +0 -32
- dissect_cstruct-5.0.dev0.dist-info/scm_version.json +0 -8
- /dissect/cstruct/{exceptions.py → exception.py} +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/top_level.txt +0 -0
dissect/cstruct/types/leb128.py
CHANGED
|
@@ -7,6 +7,8 @@ from dissect.cstruct.types.base import BaseType
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
|
+
from dissect.cstruct.cstruct import Endianness
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class LEB128(int, BaseType):
|
|
12
14
|
"""Variable-length code compression to store an arbitrarily large integer in a small number of bytes.
|
|
@@ -17,7 +19,7 @@ class LEB128(int, BaseType):
|
|
|
17
19
|
signed: bool
|
|
18
20
|
|
|
19
21
|
@classmethod
|
|
20
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
22
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
21
23
|
result = 0
|
|
22
24
|
shift = 0
|
|
23
25
|
while True:
|
|
@@ -37,11 +39,11 @@ class LEB128(int, BaseType):
|
|
|
37
39
|
return cls.__new__(cls, result)
|
|
38
40
|
|
|
39
41
|
@classmethod
|
|
40
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]:
|
|
42
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]:
|
|
41
43
|
result = []
|
|
42
44
|
|
|
43
45
|
while True:
|
|
44
|
-
if (value := cls._read(stream, context)) == 0:
|
|
46
|
+
if (value := cls._read(stream, context=context, endian=endian)) == 0:
|
|
45
47
|
break
|
|
46
48
|
|
|
47
49
|
result.append(value)
|
|
@@ -49,7 +51,7 @@ class LEB128(int, BaseType):
|
|
|
49
51
|
return result
|
|
50
52
|
|
|
51
53
|
@classmethod
|
|
52
|
-
def _write(cls, stream: BinaryIO, data: int) -> int:
|
|
54
|
+
def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
|
|
53
55
|
# only write negative numbers when in signed mode
|
|
54
56
|
if data < 0 and not cls.signed:
|
|
55
57
|
raise ValueError("Attempt to encode a negative integer using unsigned LEB128 encoding")
|
dissect/cstruct/types/packed.py
CHANGED
|
@@ -9,6 +9,8 @@ from dissect.cstruct.types.base import EOF, BaseType
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from typing_extensions import Self
|
|
11
11
|
|
|
12
|
+
from dissect.cstruct.cstruct import Endianness
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
@lru_cache(1024)
|
|
14
16
|
def _struct(endian: str, packchar: str) -> Struct:
|
|
@@ -24,11 +26,18 @@ class Packed(BaseType, Generic[T]):
|
|
|
24
26
|
packchar: str
|
|
25
27
|
|
|
26
28
|
@classmethod
|
|
27
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
28
|
-
|
|
29
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
30
|
+
data = stream.read(cls.size)
|
|
31
|
+
|
|
32
|
+
if len(data) != cls.size:
|
|
33
|
+
raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}")
|
|
34
|
+
|
|
35
|
+
return cls.__new__(cls, _struct(endian, cls.packchar).unpack(data)[0])
|
|
29
36
|
|
|
30
37
|
@classmethod
|
|
31
|
-
def _read_array(
|
|
38
|
+
def _read_array(
|
|
39
|
+
cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
40
|
+
) -> list[Self]:
|
|
32
41
|
if count == EOF:
|
|
33
42
|
data = stream.read()
|
|
34
43
|
length = len(data)
|
|
@@ -37,7 +46,7 @@ class Packed(BaseType, Generic[T]):
|
|
|
37
46
|
length = cls.size * count
|
|
38
47
|
data = stream.read(length)
|
|
39
48
|
|
|
40
|
-
fmt = _struct(
|
|
49
|
+
fmt = _struct(endian, f"{count}{cls.packchar}")
|
|
41
50
|
|
|
42
51
|
if len(data) != length:
|
|
43
52
|
raise EOFError(f"Read {len(data)} bytes, but expected {length}")
|
|
@@ -45,10 +54,10 @@ class Packed(BaseType, Generic[T]):
|
|
|
45
54
|
return [cls.__new__(cls, value) for value in fmt.unpack(data)]
|
|
46
55
|
|
|
47
56
|
@classmethod
|
|
48
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
57
|
+
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None, *, endian: Endianness) -> Self:
|
|
49
58
|
result = []
|
|
50
59
|
|
|
51
|
-
fmt = _struct(
|
|
60
|
+
fmt = _struct(endian, cls.packchar)
|
|
52
61
|
while True:
|
|
53
62
|
data = stream.read(cls.size)
|
|
54
63
|
|
|
@@ -63,9 +72,9 @@ class Packed(BaseType, Generic[T]):
|
|
|
63
72
|
return result
|
|
64
73
|
|
|
65
74
|
@classmethod
|
|
66
|
-
def _write(cls, stream: BinaryIO, data: Packed[T]) -> int:
|
|
67
|
-
return stream.write(_struct(
|
|
75
|
+
def _write(cls, stream: BinaryIO, data: Packed[T], *, endian: Endianness) -> int:
|
|
76
|
+
return stream.write(_struct(endian, cls.packchar).pack(data))
|
|
68
77
|
|
|
69
78
|
@classmethod
|
|
70
|
-
def _write_array(cls, stream: BinaryIO, data: list[Packed[T]]) -> int:
|
|
71
|
-
return stream.write(_struct(
|
|
79
|
+
def _write_array(cls, stream: BinaryIO, data: list[Packed[T]], *, endian: Endianness) -> int:
|
|
80
|
+
return stream.write(_struct(endian, f"{len(data)}{cls.packchar}").pack(*data))
|
dissect/cstruct/types/pointer.py
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from functools import cache
|
|
3
4
|
from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
|
|
4
5
|
|
|
5
|
-
from dissect.cstruct.
|
|
6
|
-
from dissect.cstruct.types.base import BaseType
|
|
6
|
+
from dissect.cstruct.exception import NullPointerDereference
|
|
7
|
+
from dissect.cstruct.types.base import BaseType, normalize_endianness
|
|
7
8
|
from dissect.cstruct.types.char import Char
|
|
8
9
|
from dissect.cstruct.types.void import Void
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
|
|
11
14
|
from typing_extensions import Self
|
|
12
15
|
|
|
16
|
+
from dissect.cstruct.cstruct import AllowedEndianness, Endianness
|
|
17
|
+
|
|
13
18
|
T = TypeVar("T", bound=BaseType)
|
|
14
19
|
|
|
15
20
|
|
|
@@ -19,13 +24,16 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
19
24
|
type: type[T]
|
|
20
25
|
_stream: BinaryIO | None
|
|
21
26
|
_context: dict[str, Any] | None
|
|
22
|
-
|
|
27
|
+
_endian: Endianness
|
|
23
28
|
|
|
24
|
-
def __new__(
|
|
29
|
+
def __new__(
|
|
30
|
+
cls, value: int, stream: BinaryIO | None, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
31
|
+
) -> Self:
|
|
25
32
|
obj = super().__new__(cls, value)
|
|
26
33
|
obj._stream = stream
|
|
27
34
|
obj._context = context
|
|
28
|
-
obj.
|
|
35
|
+
obj._endian = endian
|
|
36
|
+
obj.dereference = cache(obj.dereference)
|
|
29
37
|
return obj
|
|
30
38
|
|
|
31
39
|
def __repr__(self) -> str:
|
|
@@ -37,68 +45,78 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
37
45
|
def __getattr__(self, attr: str) -> Any:
|
|
38
46
|
return getattr(self.dereference(), attr)
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def __and__(self, other: int) -> Self:
|
|
65
|
-
return type.__call__(self.__class__, int.__and__(self, other), self._stream, self._context)
|
|
66
|
-
|
|
67
|
-
def __xor__(self, other: int) -> Self:
|
|
68
|
-
return type.__call__(self.__class__, int.__xor__(self, other), self._stream, self._context)
|
|
69
|
-
|
|
70
|
-
def __or__(self, other: int) -> Self:
|
|
71
|
-
return type.__call__(self.__class__, int.__or__(self, other), self._stream, self._context)
|
|
48
|
+
@staticmethod
|
|
49
|
+
def __op(op: Callable[[int, int], int]) -> Callable[[Self, int], Self]:
|
|
50
|
+
def method(self: Self, other: int) -> Self:
|
|
51
|
+
return type.__call__(
|
|
52
|
+
self.__class__,
|
|
53
|
+
op(self, other),
|
|
54
|
+
self._stream,
|
|
55
|
+
context=self._context,
|
|
56
|
+
endian=self._endian,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return method
|
|
60
|
+
|
|
61
|
+
__add__ = __op(int.__add__)
|
|
62
|
+
__sub__ = __op(int.__sub__)
|
|
63
|
+
__mul__ = __op(int.__mul__)
|
|
64
|
+
__floordiv__ = __op(int.__floordiv__)
|
|
65
|
+
__mod__ = __op(int.__mod__)
|
|
66
|
+
__pow__ = __op(int.__pow__)
|
|
67
|
+
__lshift__ = __op(int.__lshift__)
|
|
68
|
+
__rshift__ = __op(int.__rshift__)
|
|
69
|
+
__and__ = __op(int.__and__)
|
|
70
|
+
__xor__ = __op(int.__xor__)
|
|
71
|
+
__or__ = __op(int.__or__)
|
|
72
72
|
|
|
73
73
|
@classmethod
|
|
74
74
|
def __default__(cls) -> Self:
|
|
75
|
-
return cls.__new__(
|
|
75
|
+
return cls.__new__(
|
|
76
|
+
cls,
|
|
77
|
+
cls.cs.pointer.__default__(),
|
|
78
|
+
None,
|
|
79
|
+
context=None,
|
|
80
|
+
endian=cls.cs.endian,
|
|
81
|
+
)
|
|
76
82
|
|
|
77
83
|
@classmethod
|
|
78
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
79
|
-
return cls.__new__(
|
|
84
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
85
|
+
return cls.__new__(
|
|
86
|
+
cls,
|
|
87
|
+
cls.cs.pointer._read(stream, context=context, endian=endian),
|
|
88
|
+
stream,
|
|
89
|
+
context=context,
|
|
90
|
+
endian=endian,
|
|
91
|
+
)
|
|
80
92
|
|
|
81
93
|
@classmethod
|
|
82
|
-
def _write(cls, stream: BinaryIO, data: int) -> int:
|
|
83
|
-
return cls.cs.pointer._write(stream, data)
|
|
94
|
+
def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
|
|
95
|
+
return cls.cs.pointer._write(stream, data, endian=endian)
|
|
96
|
+
|
|
97
|
+
def dereference(self, *, endian: AllowedEndianness | None = None) -> T | None:
|
|
98
|
+
"""Dereference the pointer and read the value it points to.
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
Args:
|
|
101
|
+
endian: Optional endianness to use when reading the value.
|
|
102
|
+
If not provided, the endianness used when reading the pointer itself will be used.
|
|
103
|
+
"""
|
|
86
104
|
if self == 0 or self._stream is None:
|
|
87
105
|
raise NullPointerDereference
|
|
88
106
|
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Reposition the file read/write pointer
|
|
93
|
-
self._stream.seek(self)
|
|
107
|
+
endian = normalize_endianness(endian) if endian is not None else self._endian
|
|
108
|
+
if issubclass(self.type, Void):
|
|
109
|
+
return None
|
|
94
110
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
value = self.type._read_0(self._stream, self._context)
|
|
98
|
-
else:
|
|
99
|
-
value = self.type._read(self._stream, self._context)
|
|
111
|
+
position = self._stream.tell()
|
|
112
|
+
self._stream.seek(self)
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
if issubclass(self.type, Char):
|
|
115
|
+
# This makes the assumption that a char pointer is a null-terminated string
|
|
116
|
+
value = self.type._read_0(self._stream, context=self._context, endian=endian)
|
|
117
|
+
else:
|
|
118
|
+
value = self.type._read(self._stream, context=self._context, endian=endian)
|
|
103
119
|
|
|
104
|
-
|
|
120
|
+
# Restore the stream position after reading the value
|
|
121
|
+
self._stream.seek(position)
|
|
122
|
+
return value
|
|
@@ -13,7 +13,10 @@ from types import FunctionType
|
|
|
13
13
|
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
14
14
|
|
|
15
15
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
16
|
+
from dissect.cstruct.expression import Expression
|
|
17
|
+
from dissect.cstruct.lexer import IDENTIFIER_TYPES
|
|
16
18
|
from dissect.cstruct.types.base import (
|
|
19
|
+
BaseArray,
|
|
17
20
|
BaseType,
|
|
18
21
|
MetaType,
|
|
19
22
|
_is_buffer_type,
|
|
@@ -28,6 +31,8 @@ if TYPE_CHECKING:
|
|
|
28
31
|
|
|
29
32
|
from typing_extensions import Self
|
|
30
33
|
|
|
34
|
+
from dissect.cstruct.cstruct import Endianness
|
|
35
|
+
|
|
31
36
|
|
|
32
37
|
class Field:
|
|
33
38
|
"""Structure field."""
|
|
@@ -71,15 +76,6 @@ class StructureMetaType(MetaType):
|
|
|
71
76
|
return super().__new__(metacls, name, bases, classdict)
|
|
72
77
|
|
|
73
78
|
def __call__(cls, *args, **kwargs) -> Self: # type: ignore
|
|
74
|
-
if (
|
|
75
|
-
cls.__fields__
|
|
76
|
-
and len(args) == len(cls.__fields__) == 1
|
|
77
|
-
and isinstance(args[0], bytes)
|
|
78
|
-
and issubclass(cls.__fields__[0].type, bytes)
|
|
79
|
-
and len(args[0]) == cls.__fields__[0].type.size
|
|
80
|
-
):
|
|
81
|
-
# Shortcut for single char/bytes type
|
|
82
|
-
return type.__call__(cls, *args, **kwargs)
|
|
83
79
|
if not args and not kwargs:
|
|
84
80
|
obj = type.__call__(cls)
|
|
85
81
|
object.__setattr__(obj, "__dynamic_sizes__", {})
|
|
@@ -246,8 +242,8 @@ class StructureMetaType(MetaType):
|
|
|
246
242
|
# The structure size is whatever the currently calculated offset is
|
|
247
243
|
return offset, alignment
|
|
248
244
|
|
|
249
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self: # type: ignore
|
|
250
|
-
bit_buffer = BitBuffer(stream,
|
|
245
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: # type: ignore
|
|
246
|
+
bit_buffer = BitBuffer(stream, endian=endian)
|
|
251
247
|
struct_start = stream.tell()
|
|
252
248
|
|
|
253
249
|
result = {}
|
|
@@ -256,7 +252,7 @@ class StructureMetaType(MetaType):
|
|
|
256
252
|
offset = stream.tell()
|
|
257
253
|
|
|
258
254
|
if field.offset is not None and offset != struct_start + field.offset:
|
|
259
|
-
# Field is at a specific offset, either
|
|
255
|
+
# Field is at a specific offset, either aligned or added that way
|
|
260
256
|
offset = struct_start + field.offset
|
|
261
257
|
stream.seek(offset)
|
|
262
258
|
|
|
@@ -276,7 +272,7 @@ class StructureMetaType(MetaType):
|
|
|
276
272
|
|
|
277
273
|
bit_buffer.reset()
|
|
278
274
|
|
|
279
|
-
value = field.type._read(stream, result)
|
|
275
|
+
value = field.type._read(stream, context=result, endian=endian)
|
|
280
276
|
|
|
281
277
|
result[field._name] = value
|
|
282
278
|
if field.type.dynamic:
|
|
@@ -292,21 +288,21 @@ class StructureMetaType(MetaType):
|
|
|
292
288
|
obj.__dynamic_sizes__ = sizes
|
|
293
289
|
return obj
|
|
294
290
|
|
|
295
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]: # type: ignore
|
|
291
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]: # type: ignore
|
|
296
292
|
result = []
|
|
297
293
|
|
|
298
|
-
while obj := cls._read(stream, context):
|
|
294
|
+
while obj := cls._read(stream, context=context, endian=endian):
|
|
299
295
|
result.append(obj)
|
|
300
296
|
|
|
301
297
|
return result
|
|
302
298
|
|
|
303
|
-
def _write(cls, stream: BinaryIO, data: Structure) -> int:
|
|
304
|
-
bit_buffer = BitBuffer(stream,
|
|
299
|
+
def _write(cls, stream: BinaryIO, data: Structure, *, endian: Endianness) -> int:
|
|
300
|
+
bit_buffer = BitBuffer(stream, endian=endian)
|
|
305
301
|
struct_start = stream.tell()
|
|
306
302
|
num = 0
|
|
307
303
|
|
|
308
304
|
for field in cls.__fields__:
|
|
309
|
-
field_type =
|
|
305
|
+
field_type = field.type
|
|
310
306
|
|
|
311
307
|
bit_field_type = (
|
|
312
308
|
(field_type.type if isinstance(field_type, EnumMetaType) else field_type) if field.bits else None
|
|
@@ -322,7 +318,7 @@ class StructureMetaType(MetaType):
|
|
|
322
318
|
offset = stream.tell()
|
|
323
319
|
|
|
324
320
|
if field.offset is not None and offset < struct_start + field.offset:
|
|
325
|
-
# Field is at a specific offset, either
|
|
321
|
+
# Field is at a specific offset, either aligned or added that way
|
|
326
322
|
stream.write(b"\x00" * (struct_start + field.offset - offset))
|
|
327
323
|
offset = struct_start + field.offset
|
|
328
324
|
|
|
@@ -346,7 +342,7 @@ class StructureMetaType(MetaType):
|
|
|
346
342
|
else:
|
|
347
343
|
bit_buffer.write(field_type, value, field.bits)
|
|
348
344
|
else:
|
|
349
|
-
field_type._write(stream, value)
|
|
345
|
+
field_type._write(stream, value, endian=endian)
|
|
350
346
|
num += stream.tell() - offset
|
|
351
347
|
|
|
352
348
|
if bit_buffer._type is not None:
|
|
@@ -380,6 +376,102 @@ class StructureMetaType(MetaType):
|
|
|
380
376
|
for key, value in classdict.items():
|
|
381
377
|
setattr(cls, key, value)
|
|
382
378
|
|
|
379
|
+
def cdef(cls, *, recursive: bool = False) -> str:
|
|
380
|
+
"""Render this structure (or union) back to its C-style definition.
|
|
381
|
+
|
|
382
|
+
When ``recursive=True``, any ``#define`` constants referenced in array sizes are emitted as well, so that the
|
|
383
|
+
result can be parsed on its own. Note that the result is semantically equivalent to the original definition,
|
|
384
|
+
but not necessarily identical.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
recursive: Also emit the definitions of any types referenced by this structure.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
The C-style definition as a string.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
def _decompose(type_: MetaType) -> tuple[MetaType, int, list[int | Expression | None]]:
|
|
394
|
+
dimensions = []
|
|
395
|
+
while issubclass(type_, BaseArray):
|
|
396
|
+
dimensions.append(type_.num_entries)
|
|
397
|
+
type_ = type_.type
|
|
398
|
+
|
|
399
|
+
stars = 0
|
|
400
|
+
while issubclass(type_, Pointer):
|
|
401
|
+
stars += 1
|
|
402
|
+
type_ = type_.type
|
|
403
|
+
|
|
404
|
+
return type_, stars, dimensions
|
|
405
|
+
|
|
406
|
+
def _deps(type_: StructureMetaType, seen: set[MetaType]) -> list[MetaType | str]:
|
|
407
|
+
deps = []
|
|
408
|
+
for field in type_.__fields__:
|
|
409
|
+
base, _, dimensions = _decompose(field.type)
|
|
410
|
+
|
|
411
|
+
for dim in dimensions:
|
|
412
|
+
if isinstance(dim, Expression):
|
|
413
|
+
for token in dim._tokens:
|
|
414
|
+
if (
|
|
415
|
+
token.type in IDENTIFIER_TYPES
|
|
416
|
+
and token.value in cls.cs.consts
|
|
417
|
+
and token.value not in seen
|
|
418
|
+
):
|
|
419
|
+
deps.append(token.value)
|
|
420
|
+
seen.add(token.value)
|
|
421
|
+
|
|
422
|
+
if isinstance(base, StructureMetaType) and base not in seen:
|
|
423
|
+
if base.__anonymous__:
|
|
424
|
+
# Anonymous structures are not emitted, but their dependencies are
|
|
425
|
+
deps.extend(_deps(base, seen))
|
|
426
|
+
else:
|
|
427
|
+
seen.add(base)
|
|
428
|
+
deps.extend(_deps(base, seen))
|
|
429
|
+
deps.append(base)
|
|
430
|
+
|
|
431
|
+
elif isinstance(base, EnumMetaType) and base not in seen:
|
|
432
|
+
seen.add(base)
|
|
433
|
+
deps.append(base)
|
|
434
|
+
|
|
435
|
+
return deps
|
|
436
|
+
|
|
437
|
+
def _render_struct(type_: StructureMetaType, name: str) -> list[str]:
|
|
438
|
+
keyword = "union" if isinstance(type_, UnionMetaType) else "struct"
|
|
439
|
+
title = f"{keyword} {name}" if name else keyword
|
|
440
|
+
|
|
441
|
+
lines = [f"{title} {{"]
|
|
442
|
+
for field in type_.__fields__:
|
|
443
|
+
base, stars, dimensions = _decompose(field.type)
|
|
444
|
+
|
|
445
|
+
declarator = "*" * stars + (field.name or "") + "".join(f"[{dim or ''}]" for dim in dimensions)
|
|
446
|
+
if field.bits is not None:
|
|
447
|
+
declarator = f"{declarator} : {field.bits}" if declarator else f": {field.bits}"
|
|
448
|
+
|
|
449
|
+
if isinstance(base, StructureMetaType) and base.__anonymous__:
|
|
450
|
+
# Anonymous struct/union: inline the full definition
|
|
451
|
+
lines.extend(f" {line}" for line in _render_struct(base, ""))
|
|
452
|
+
lines[-1] = f"{lines[-1]} {declarator};" if declarator else f"{lines[-1]};"
|
|
453
|
+
else:
|
|
454
|
+
prefix = f"{base.__name__} " if declarator else base.__name__
|
|
455
|
+
lines.append(f" {prefix}{declarator};")
|
|
456
|
+
|
|
457
|
+
lines.append("}")
|
|
458
|
+
return lines
|
|
459
|
+
|
|
460
|
+
deps = _deps(cls, {cls}) if recursive else []
|
|
461
|
+
deps.append(cls)
|
|
462
|
+
|
|
463
|
+
consts = []
|
|
464
|
+
blocks = []
|
|
465
|
+
for type_ in deps:
|
|
466
|
+
if isinstance(type_, str):
|
|
467
|
+
consts.append(f"#define {type_} {cls.cs.consts[type_]!r}")
|
|
468
|
+
elif isinstance(type_, EnumMetaType):
|
|
469
|
+
blocks.append(type_.cdef())
|
|
470
|
+
else:
|
|
471
|
+
blocks.append("\n".join(_render_struct(type_, type_.__name__)) + ";")
|
|
472
|
+
|
|
473
|
+
return ("\n".join(consts) + "\n\n" if consts else "") + ("\n\n".join(blocks) if blocks else "")
|
|
474
|
+
|
|
383
475
|
|
|
384
476
|
class Structure(BaseType, metaclass=StructureMetaType):
|
|
385
477
|
"""Base class for cstruct structure type classes.
|
|
@@ -459,6 +551,12 @@ class UnionMetaType(StructureMetaType):
|
|
|
459
551
|
def __call__(cls, *args, **kwargs) -> Self: # type: ignore
|
|
460
552
|
obj: Union = super().__call__(*args, **kwargs)
|
|
461
553
|
|
|
554
|
+
if not hasattr(obj, "_buf"):
|
|
555
|
+
# If we don't have a _buf attribute, we haven't read from a stream and are initializing with values
|
|
556
|
+
# Set default internal attributes
|
|
557
|
+
object.__setattr__(obj, "_buf", None)
|
|
558
|
+
object.__setattr__(obj, "_endian", cls.cs.endian)
|
|
559
|
+
|
|
462
560
|
# Calling with non-stream args or kwargs means we are initializing with values
|
|
463
561
|
if (args and not (len(args) == 1 and (_is_readable_type(args[0]) or _is_buffer_type(args[0])))) or kwargs:
|
|
464
562
|
# We don't support user initialization of dynamic unions yet
|
|
@@ -502,7 +600,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
502
600
|
return size, alignment
|
|
503
601
|
|
|
504
602
|
def _read_fields(
|
|
505
|
-
cls, stream: BinaryIO, context: dict[str, Any] | None = None
|
|
603
|
+
cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
506
604
|
) -> tuple[dict[str, Any], dict[str, int]]:
|
|
507
605
|
result = {}
|
|
508
606
|
sizes = {}
|
|
@@ -515,14 +613,14 @@ class UnionMetaType(StructureMetaType):
|
|
|
515
613
|
buf = io.BytesIO(stream.read(cls.size))
|
|
516
614
|
|
|
517
615
|
for field in cls.__fields__:
|
|
518
|
-
field_type =
|
|
616
|
+
field_type = field.type
|
|
519
617
|
|
|
520
618
|
start = 0
|
|
521
619
|
if field.offset is not None:
|
|
522
620
|
start = field.offset
|
|
523
621
|
|
|
524
622
|
buf.seek(offset + start)
|
|
525
|
-
value = field_type._read(buf, result)
|
|
623
|
+
value = field_type._read(buf, context=result, endian=endian)
|
|
526
624
|
|
|
527
625
|
result[field._name] = value
|
|
528
626
|
if field.type.dynamic:
|
|
@@ -530,10 +628,10 @@ class UnionMetaType(StructureMetaType):
|
|
|
530
628
|
|
|
531
629
|
return result, sizes
|
|
532
630
|
|
|
533
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self: # type: ignore
|
|
631
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: # type: ignore
|
|
534
632
|
if cls.size is None:
|
|
535
633
|
start = stream.tell()
|
|
536
|
-
result, sizes = cls._read_fields(stream, context)
|
|
634
|
+
result, sizes = cls._read_fields(stream, context=context, endian=endian)
|
|
537
635
|
size = stream.tell() - start
|
|
538
636
|
stream.seek(start)
|
|
539
637
|
buf = stream.read(size)
|
|
@@ -550,6 +648,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
550
648
|
obj: Union = type.__call__(cls, **result)
|
|
551
649
|
object.__setattr__(obj, "__dynamic_sizes__", sizes)
|
|
552
650
|
object.__setattr__(obj, "_buf", buf)
|
|
651
|
+
object.__setattr__(obj, "_endian", endian)
|
|
553
652
|
|
|
554
653
|
if cls.size is not None:
|
|
555
654
|
obj._update()
|
|
@@ -559,7 +658,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
559
658
|
|
|
560
659
|
return obj
|
|
561
660
|
|
|
562
|
-
def _write(cls, stream: BinaryIO, data: Union) -> int:
|
|
661
|
+
def _write(cls, stream: BinaryIO, data: Union, *, endian: Endianness) -> int:
|
|
563
662
|
if cls.dynamic:
|
|
564
663
|
raise NotImplementedError("Writing dynamic unions is not yet supported")
|
|
565
664
|
|
|
@@ -578,12 +677,12 @@ class UnionMetaType(StructureMetaType):
|
|
|
578
677
|
continue
|
|
579
678
|
|
|
580
679
|
# Write the value
|
|
581
|
-
field.type._write(stream, getattr(data, field._name))
|
|
680
|
+
field.type._write(stream, getattr(data, field._name), endian=endian)
|
|
582
681
|
break
|
|
583
682
|
|
|
584
683
|
# If we haven't written anything yet and we initially skipped an anonymous struct, write it now
|
|
585
684
|
if stream.tell() == offset and anonymous_struct:
|
|
586
|
-
anonymous_struct._write(stream, data)
|
|
685
|
+
anonymous_struct._write(stream, data, endian=endian)
|
|
587
686
|
|
|
588
687
|
# If we haven't filled the union size yet, pad it
|
|
589
688
|
if remaining := expected_offset - stream.tell():
|
|
@@ -596,6 +695,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
596
695
|
"""Base class for cstruct union type classes."""
|
|
597
696
|
|
|
598
697
|
_buf: bytes
|
|
698
|
+
_endian: Endianness
|
|
599
699
|
|
|
600
700
|
def __eq__(self, other: object) -> bool:
|
|
601
701
|
return self.__class__ is other.__class__ and bytes(self) == bytes(other)
|
|
@@ -619,7 +719,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
619
719
|
if (value := getattr(self, attr)) is None:
|
|
620
720
|
value = field.type.__default__()
|
|
621
721
|
|
|
622
|
-
field.type._write(buf, value)
|
|
722
|
+
field.type._write(buf, value, endian=self._endian)
|
|
623
723
|
|
|
624
724
|
object.__setattr__(self, "_buf", buf.getvalue())
|
|
625
725
|
self._update()
|
|
@@ -628,7 +728,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
628
728
|
self._proxify()
|
|
629
729
|
|
|
630
730
|
def _update(self) -> None:
|
|
631
|
-
result, sizes = self.__class__._read_fields(io.BytesIO(self._buf))
|
|
731
|
+
result, sizes = self.__class__._read_fields(io.BytesIO(self._buf), endian=self._endian)
|
|
632
732
|
self.__dict__.update(result)
|
|
633
733
|
object.__setattr__(self, "__dynamic_sizes__", sizes)
|
|
634
734
|
|
dissect/cstruct/types/void.py
CHANGED
|
@@ -7,6 +7,8 @@ from dissect.cstruct.types.base import BaseArray, BaseType
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
|
+
from dissect.cstruct.cstruct import Endianness
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class VoidArray(list, BaseArray):
|
|
12
14
|
"""Array type representing void elements, primarily used for no-op reading and writing operations."""
|
|
@@ -16,11 +18,11 @@ class VoidArray(list, BaseArray):
|
|
|
16
18
|
return cls()
|
|
17
19
|
|
|
18
20
|
@classmethod
|
|
19
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
21
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
20
22
|
return cls()
|
|
21
23
|
|
|
22
24
|
@classmethod
|
|
23
|
-
def _write(cls, stream: BinaryIO, data: bytes) -> int:
|
|
25
|
+
def _write(cls, stream: BinaryIO, data: bytes, *, endian: Endianness) -> int:
|
|
24
26
|
return 0
|
|
25
27
|
|
|
26
28
|
|
|
@@ -36,9 +38,9 @@ class Void(BaseType):
|
|
|
36
38
|
return isinstance(value, Void)
|
|
37
39
|
|
|
38
40
|
@classmethod
|
|
39
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
41
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
40
42
|
return cls.__new__(cls)
|
|
41
43
|
|
|
42
44
|
@classmethod
|
|
43
|
-
def _write(cls, stream: BinaryIO, data: Void) -> int:
|
|
45
|
+
def _write(cls, stream: BinaryIO, data: Void, *, endian: Endianness) -> int:
|
|
44
46
|
return 0
|