dissect.cstruct 5.0.dev8__py3-none-any.whl → 5.0.dev9__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/bitbuffer.py +6 -6
- dissect/cstruct/compiler.py +28 -28
- dissect/cstruct/cstruct.py +60 -28
- dissect/cstruct/tools/stubgen.py +5 -5
- dissect/cstruct/types/base.py +121 -38
- dissect/cstruct/types/char.py +5 -5
- dissect/cstruct/types/enum.py +15 -15
- dissect/cstruct/types/int.py +4 -4
- dissect/cstruct/types/packed.py +26 -15
- dissect/cstruct/types/pointer.py +24 -24
- dissect/cstruct/types/structure.py +72 -51
- dissect/cstruct/types/void.py +1 -1
- dissect/cstruct/types/wchar.py +5 -5
- dissect/cstruct/util.py +12 -4
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev9.dist-info/RECORD +30 -0
- dissect_cstruct-5.0.dev8.dist-info/RECORD +0 -32
- dissect_cstruct-5.0.dev8.dist-info/scm_file_list.json +0 -78
- dissect_cstruct-5.0.dev8.dist-info/scm_version.json +0 -8
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev8.dist-info → dissect_cstruct-5.0.dev9.dist-info}/top_level.txt +0 -0
dissect/cstruct/types/base.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
|
+
import warnings
|
|
4
5
|
from io import BytesIO
|
|
5
6
|
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar
|
|
6
7
|
|
|
7
8
|
from dissect.cstruct.exception import ArraySizeError
|
|
8
9
|
from dissect.cstruct.expression import Expression
|
|
9
|
-
from dissect.cstruct.util import normalize_endianness
|
|
10
|
+
from dissect.cstruct.util import classproperty, normalize_endianness
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from collections.abc import Callable
|
|
@@ -22,17 +23,17 @@ EOF = -0xE0F # Negative counts are illegal anyway, so abuse that for our EOF se
|
|
|
22
23
|
class MetaType(type):
|
|
23
24
|
"""Base metaclass for cstruct type classes."""
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
__cs__: cstruct
|
|
26
27
|
"""The cstruct instance this type class belongs to."""
|
|
27
|
-
|
|
28
|
+
__size__: int | None
|
|
28
29
|
"""The size of the type in bytes. Can be ``None`` for dynamic sized types."""
|
|
29
|
-
|
|
30
|
+
__dynamic__: bool
|
|
30
31
|
"""Whether or not the type is dynamically sized."""
|
|
31
|
-
|
|
32
|
+
__alignment__: int | None
|
|
32
33
|
"""The alignment of the type in bytes. A value of ``None`` will be treated as 1-byte aligned."""
|
|
33
34
|
|
|
34
35
|
# This must be the actual type, but since Array is a subclass of BaseType, we correct this at the bottom of the file
|
|
35
|
-
|
|
36
|
+
__ArrayType__: type[BaseArray] = "Array"
|
|
36
37
|
"""The array type for this type class."""
|
|
37
38
|
|
|
38
39
|
def __call__(cls, *args, **kwargs) -> Self: # type: ignore
|
|
@@ -42,10 +43,12 @@ class MetaType(type):
|
|
|
42
43
|
stream = args[0]
|
|
43
44
|
|
|
44
45
|
if _is_readable_type(stream):
|
|
45
|
-
endian =
|
|
46
|
+
endian = (
|
|
47
|
+
normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.__cs__.endian
|
|
48
|
+
)
|
|
46
49
|
return cls._read(stream, endian=endian)
|
|
47
50
|
|
|
48
|
-
if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.
|
|
51
|
+
if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.__size__:
|
|
49
52
|
# Shortcut for char/bytes type
|
|
50
53
|
return type.__call__(cls, *args, **kwargs)
|
|
51
54
|
|
|
@@ -56,7 +59,7 @@ class MetaType(type):
|
|
|
56
59
|
|
|
57
60
|
def __getitem__(cls, num_entries: int | Expression | None) -> type[BaseArray]:
|
|
58
61
|
"""Create a new array with the given number of entries."""
|
|
59
|
-
return cls.
|
|
62
|
+
return cls.__cs__._make_array(cls, num_entries)
|
|
60
63
|
|
|
61
64
|
def __bool__(cls) -> bool:
|
|
62
65
|
"""Type class is always truthy."""
|
|
@@ -64,10 +67,10 @@ class MetaType(type):
|
|
|
64
67
|
|
|
65
68
|
def __len__(cls) -> int:
|
|
66
69
|
"""Return the byte size of the type."""
|
|
67
|
-
if cls.
|
|
70
|
+
if cls.__size__ is None:
|
|
68
71
|
raise TypeError("Dynamic size")
|
|
69
72
|
|
|
70
|
-
return cls.
|
|
73
|
+
return cls.__size__
|
|
71
74
|
|
|
72
75
|
def __default__(cls) -> Self: # type: ignore
|
|
73
76
|
"""Return the default value of this type."""
|
|
@@ -84,7 +87,7 @@ class MetaType(type):
|
|
|
84
87
|
Returns:
|
|
85
88
|
The parsed value of this type.
|
|
86
89
|
"""
|
|
87
|
-
endian = normalize_endianness(endian) if endian is not None else cls.
|
|
90
|
+
endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
|
|
88
91
|
return cls._read(BytesIO(data), endian=endian)
|
|
89
92
|
|
|
90
93
|
def read(cls, obj: BinaryIO | bytes | memoryview | bytearray, *, endian: AllowedEndianness | None = None) -> Self: # type: ignore
|
|
@@ -104,7 +107,7 @@ class MetaType(type):
|
|
|
104
107
|
if not _is_readable_type(obj):
|
|
105
108
|
raise TypeError("Invalid object type")
|
|
106
109
|
|
|
107
|
-
endian = normalize_endianness(endian) if endian is not None else cls.
|
|
110
|
+
endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
|
|
108
111
|
return cls._read(obj, endian=endian)
|
|
109
112
|
|
|
110
113
|
def write(cls, stream: BinaryIO, value: Any, *, endian: AllowedEndianness | None = None) -> int:
|
|
@@ -119,7 +122,7 @@ class MetaType(type):
|
|
|
119
122
|
Returns:
|
|
120
123
|
The amount of bytes written.
|
|
121
124
|
"""
|
|
122
|
-
endian = normalize_endianness(endian) if endian is not None else cls.
|
|
125
|
+
endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
|
|
123
126
|
return cls._write(stream, value, endian=endian)
|
|
124
127
|
|
|
125
128
|
def dumps(cls, value: Any, *, endian: AllowedEndianness | None = None) -> bytes:
|
|
@@ -133,7 +136,7 @@ class MetaType(type):
|
|
|
133
136
|
Returns:
|
|
134
137
|
The raw bytes of this type.
|
|
135
138
|
"""
|
|
136
|
-
endian = normalize_endianness(endian) if endian is not None else cls.
|
|
139
|
+
endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian
|
|
137
140
|
|
|
138
141
|
out = BytesIO()
|
|
139
142
|
cls._write(out, value, endian=endian)
|
|
@@ -211,6 +214,56 @@ class MetaType(type):
|
|
|
211
214
|
"""
|
|
212
215
|
return cls._write_array(stream, [*array, cls.__default__()], endian=endian)
|
|
213
216
|
|
|
217
|
+
@property
|
|
218
|
+
def cs(cls) -> cstruct:
|
|
219
|
+
"""Access the cstruct object for this type."""
|
|
220
|
+
warnings.warn(
|
|
221
|
+
"The 'cs' property is deprecated, use '__cs__' instead.",
|
|
222
|
+
DeprecationWarning,
|
|
223
|
+
stacklevel=2,
|
|
224
|
+
)
|
|
225
|
+
return cls.__cs__
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def size(cls) -> int | None:
|
|
229
|
+
"""Access the size of this type."""
|
|
230
|
+
warnings.warn(
|
|
231
|
+
"The 'size' property is deprecated, use '__size__', len(cls) or sizeof(cls) instead.",
|
|
232
|
+
DeprecationWarning,
|
|
233
|
+
stacklevel=2,
|
|
234
|
+
)
|
|
235
|
+
return cls.__size__
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def dynamic(cls) -> bool:
|
|
239
|
+
"""Access whether this type is dynamically sized."""
|
|
240
|
+
warnings.warn(
|
|
241
|
+
"The 'dynamic' property is deprecated, use '__dynamic__' instead.",
|
|
242
|
+
DeprecationWarning,
|
|
243
|
+
stacklevel=2,
|
|
244
|
+
)
|
|
245
|
+
return cls.__dynamic__
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def alignment(cls) -> int | None:
|
|
249
|
+
"""Access the alignment of this type."""
|
|
250
|
+
warnings.warn(
|
|
251
|
+
"The 'alignment' property is deprecated, use '__alignment__' instead.",
|
|
252
|
+
DeprecationWarning,
|
|
253
|
+
stacklevel=2,
|
|
254
|
+
)
|
|
255
|
+
return cls.__alignment__
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def ArrayType(cls) -> type[BaseArray]:
|
|
259
|
+
"""Access the array type of this type."""
|
|
260
|
+
warnings.warn(
|
|
261
|
+
"The 'ArrayType' property is deprecated, use '__ArrayType__' instead.",
|
|
262
|
+
DeprecationWarning,
|
|
263
|
+
stacklevel=2,
|
|
264
|
+
)
|
|
265
|
+
return cls.__ArrayType__
|
|
266
|
+
|
|
214
267
|
|
|
215
268
|
class _overload:
|
|
216
269
|
"""Descriptor to use on the ``write`` and ``dumps`` methods on cstruct types.
|
|
@@ -241,10 +294,10 @@ class BaseType(metaclass=MetaType):
|
|
|
241
294
|
|
|
242
295
|
def __len__(self) -> int:
|
|
243
296
|
"""Return the byte size of the type."""
|
|
244
|
-
if self.__class__.
|
|
297
|
+
if self.__class__.__size__ is None:
|
|
245
298
|
raise TypeError("Dynamic size")
|
|
246
299
|
|
|
247
|
-
return self.__class__.
|
|
300
|
+
return self.__class__.__size__
|
|
248
301
|
|
|
249
302
|
|
|
250
303
|
T = TypeVar("T", bound=BaseType)
|
|
@@ -261,46 +314,76 @@ class BaseArray(BaseType):
|
|
|
261
314
|
x[expr] -> expr -> dynamic length.
|
|
262
315
|
"""
|
|
263
316
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
317
|
+
__type__: ClassVar[type[BaseType]]
|
|
318
|
+
__num_entries__: ClassVar[int | Expression | None]
|
|
319
|
+
__null_terminated__: ClassVar[bool]
|
|
267
320
|
|
|
268
321
|
@classmethod
|
|
269
322
|
def __default__(cls) -> BaseType:
|
|
270
323
|
return type.__call__(
|
|
271
|
-
cls, [cls.
|
|
324
|
+
cls, [cls.__type__.__default__()] * (cls.__num_entries__ if isinstance(cls.__num_entries__, int) else 0)
|
|
272
325
|
)
|
|
273
326
|
|
|
274
327
|
@classmethod
|
|
275
328
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[BaseType]:
|
|
276
|
-
if cls.
|
|
277
|
-
return cls.
|
|
329
|
+
if cls.__null_terminated__:
|
|
330
|
+
return cls.__type__._read_0(stream, context=context, endian=endian)
|
|
278
331
|
|
|
279
|
-
if isinstance(cls.
|
|
280
|
-
num = max(0, cls.
|
|
281
|
-
elif cls.
|
|
332
|
+
if isinstance(cls.__num_entries__, int):
|
|
333
|
+
num = max(0, cls.__num_entries__)
|
|
334
|
+
elif cls.__num_entries__ is None:
|
|
282
335
|
num = EOF
|
|
283
|
-
elif isinstance(cls.
|
|
336
|
+
elif isinstance(cls.__num_entries__, Expression):
|
|
284
337
|
try:
|
|
285
|
-
num = max(0, cls.
|
|
338
|
+
num = max(0, cls.__num_entries__.evaluate(cls.__cs__, context))
|
|
286
339
|
except Exception:
|
|
287
|
-
if cls.
|
|
340
|
+
if cls.__num_entries__.expression != "EOF":
|
|
288
341
|
raise
|
|
289
342
|
num = EOF
|
|
290
343
|
|
|
291
|
-
return cls.
|
|
344
|
+
return cls.__type__._read_array(stream, num, context=context, endian=endian)
|
|
292
345
|
|
|
293
346
|
@classmethod
|
|
294
347
|
def _write(cls, stream: BinaryIO, data: list[Any], *, endian: Endianness) -> int:
|
|
295
|
-
if cls.
|
|
296
|
-
return cls.
|
|
348
|
+
if cls.__null_terminated__:
|
|
349
|
+
return cls.__type__._write_0(stream, data, endian=endian)
|
|
297
350
|
|
|
298
|
-
if not cls.
|
|
351
|
+
if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(data))):
|
|
299
352
|
if remaining < 0:
|
|
300
|
-
raise ArraySizeError(f"Expected static array size {cls.
|
|
301
|
-
data = data + [cls.
|
|
302
|
-
|
|
303
|
-
return cls.
|
|
353
|
+
raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
|
|
354
|
+
data = data + [cls.__type__.__default__()] * remaining
|
|
355
|
+
|
|
356
|
+
return cls.__type__._write_array(stream, data, endian=endian)
|
|
357
|
+
|
|
358
|
+
@classproperty
|
|
359
|
+
def type(cls) -> type[BaseType]:
|
|
360
|
+
"""Access the array element type."""
|
|
361
|
+
warnings.warn(
|
|
362
|
+
"The 'type' property is deprecated, use '__type__' instead.",
|
|
363
|
+
DeprecationWarning,
|
|
364
|
+
stacklevel=2,
|
|
365
|
+
)
|
|
366
|
+
return cls.__type__
|
|
367
|
+
|
|
368
|
+
@classproperty
|
|
369
|
+
def num_entries(cls) -> int | Expression | None:
|
|
370
|
+
"""Access the number of entries for this array type."""
|
|
371
|
+
warnings.warn(
|
|
372
|
+
"The 'num_entries' property is deprecated, use '__num_entries__' instead.",
|
|
373
|
+
DeprecationWarning,
|
|
374
|
+
stacklevel=2,
|
|
375
|
+
)
|
|
376
|
+
return cls.__num_entries__
|
|
377
|
+
|
|
378
|
+
@classproperty
|
|
379
|
+
def null_terminated(cls) -> bool:
|
|
380
|
+
"""Access whether this array type is null-terminated."""
|
|
381
|
+
warnings.warn(
|
|
382
|
+
"The 'null_terminated' property is deprecated, use '__null_terminated__' instead.",
|
|
383
|
+
DeprecationWarning,
|
|
384
|
+
stacklevel=2,
|
|
385
|
+
)
|
|
386
|
+
return cls.__null_terminated__
|
|
304
387
|
|
|
305
388
|
|
|
306
389
|
class Array(list[T], BaseArray):
|
|
@@ -330,4 +413,4 @@ def _is_eof(stream: BinaryIO) -> bool:
|
|
|
330
413
|
|
|
331
414
|
|
|
332
415
|
# As mentioned in the BaseType class, we correctly set the type here
|
|
333
|
-
MetaType.
|
|
416
|
+
MetaType.__ArrayType__ = Array
|
dissect/cstruct/types/char.py
CHANGED
|
@@ -16,7 +16,7 @@ class CharArray(bytes, BaseArray):
|
|
|
16
16
|
|
|
17
17
|
@classmethod
|
|
18
18
|
def __default__(cls) -> Self:
|
|
19
|
-
return type.__call__(cls, b"\x00" * (0 if cls.
|
|
19
|
+
return type.__call__(cls, b"\x00" * (0 if cls.__dynamic__ or cls.__null_terminated__ else cls.__num_entries__))
|
|
20
20
|
|
|
21
21
|
@classmethod
|
|
22
22
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
@@ -29,12 +29,12 @@ class CharArray(bytes, BaseArray):
|
|
|
29
29
|
elif isinstance(data, str):
|
|
30
30
|
data = data.encode("latin-1")
|
|
31
31
|
|
|
32
|
-
if cls.
|
|
32
|
+
if cls.__null_terminated__:
|
|
33
33
|
data += b"\x00"
|
|
34
34
|
|
|
35
|
-
if not cls.
|
|
35
|
+
if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(data))):
|
|
36
36
|
if remaining < 0:
|
|
37
|
-
raise ArraySizeError(f"Expected static array size {cls.
|
|
37
|
+
raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
|
|
38
38
|
data += b"\x00" * remaining
|
|
39
39
|
|
|
40
40
|
return stream.write(data)
|
|
@@ -43,7 +43,7 @@ class CharArray(bytes, BaseArray):
|
|
|
43
43
|
class Char(bytes, BaseType):
|
|
44
44
|
"""Character type for reading and writing bytes."""
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
__ArrayType__ = CharArray
|
|
47
47
|
|
|
48
48
|
@classmethod
|
|
49
49
|
def __default__(cls) -> Self:
|
dissect/cstruct/types/enum.py
CHANGED
|
@@ -21,7 +21,7 @@ _S = TypeVar("_S")
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class EnumMetaType(EnumMeta, MetaType):
|
|
24
|
-
|
|
24
|
+
__type__: type[BaseType]
|
|
25
25
|
|
|
26
26
|
@overload
|
|
27
27
|
def __call__(cls, value: cstruct, name: str, type_: type[BaseType], *args, **kwargs) -> type[Enum]: ...
|
|
@@ -39,11 +39,11 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
39
39
|
) -> Enum | type[Enum]:
|
|
40
40
|
if name is None:
|
|
41
41
|
if value is None:
|
|
42
|
-
value = cls.
|
|
42
|
+
value = cls.__type__.__default__()
|
|
43
43
|
|
|
44
44
|
if not isinstance(value, int):
|
|
45
45
|
# value is a parsable value
|
|
46
|
-
value = cls.
|
|
46
|
+
value = cls.__type__(value)
|
|
47
47
|
|
|
48
48
|
return super().__call__(value)
|
|
49
49
|
|
|
@@ -54,11 +54,11 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
54
54
|
raise TypeError("Enum can only be created from int type")
|
|
55
55
|
|
|
56
56
|
enum_cls = super().__call__(name, *args, **kwargs)
|
|
57
|
-
enum_cls.
|
|
58
|
-
enum_cls.
|
|
59
|
-
enum_cls.
|
|
60
|
-
enum_cls.
|
|
61
|
-
enum_cls.
|
|
57
|
+
enum_cls.__cs__ = cs
|
|
58
|
+
enum_cls.__type__ = type_
|
|
59
|
+
enum_cls.__size__ = type_.__size__
|
|
60
|
+
enum_cls.__dynamic__ = type_.__dynamic__
|
|
61
|
+
enum_cls.__alignment__ = type_.__alignment__
|
|
62
62
|
|
|
63
63
|
_fix_alias_members(enum_cls)
|
|
64
64
|
|
|
@@ -85,26 +85,26 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
85
85
|
return value in cls._value2member_map_
|
|
86
86
|
|
|
87
87
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
88
|
-
return cls(cls.
|
|
88
|
+
return cls(cls.__type__._read(stream, context=context, endian=endian))
|
|
89
89
|
|
|
90
90
|
def _read_array(
|
|
91
91
|
cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
92
92
|
) -> list[Self]:
|
|
93
|
-
return list(map(cls, cls.
|
|
93
|
+
return list(map(cls, cls.__type__._read_array(stream, count, context=context, endian=endian)))
|
|
94
94
|
|
|
95
95
|
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]:
|
|
96
|
-
return list(map(cls, cls.
|
|
96
|
+
return list(map(cls, cls.__type__._read_0(stream, context=context, endian=endian)))
|
|
97
97
|
|
|
98
98
|
def _write(cls, stream: BinaryIO, data: Enum, *, endian: Endianness) -> int:
|
|
99
|
-
return cls.
|
|
99
|
+
return cls.__type__._write(stream, data.value, endian=endian)
|
|
100
100
|
|
|
101
101
|
def _write_array(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
|
|
102
102
|
data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
|
|
103
|
-
return cls.
|
|
103
|
+
return cls.__type__._write_array(stream, data, endian=endian)
|
|
104
104
|
|
|
105
105
|
def _write_0(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
|
|
106
106
|
data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
|
|
107
|
-
return cls._write_array(stream, [*data, cls.
|
|
107
|
+
return cls._write_array(stream, [*data, cls.__type__.__default__()], endian=endian)
|
|
108
108
|
|
|
109
109
|
def cdef(cls) -> str:
|
|
110
110
|
"""Render this enum (or flag) back to its C-style definition.
|
|
@@ -113,7 +113,7 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
113
113
|
The C-style definition as a string.
|
|
114
114
|
"""
|
|
115
115
|
keyword = "flag" if issubclass(cls, IntFlag) else "enum"
|
|
116
|
-
underlying = cls.
|
|
116
|
+
underlying = cls.__type__.__name__
|
|
117
117
|
title = f"{keyword} {cls.__name__} : {underlying}" if cls.__name__ else f"{keyword} : {underlying}"
|
|
118
118
|
|
|
119
119
|
lines = [f"{title} {{"]
|
dissect/cstruct/types/int.py
CHANGED
|
@@ -18,10 +18,10 @@ class Int(int, BaseType):
|
|
|
18
18
|
|
|
19
19
|
@classmethod
|
|
20
20
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
21
|
-
data = stream.read(cls.
|
|
21
|
+
data = stream.read(cls.__size__)
|
|
22
22
|
|
|
23
|
-
if len(data) != cls.
|
|
24
|
-
raise EOFError(f"Read {len(data)} bytes, but expected {cls.
|
|
23
|
+
if len(data) != cls.__size__:
|
|
24
|
+
raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
|
|
25
25
|
|
|
26
26
|
return cls.from_bytes(data, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed)
|
|
27
27
|
|
|
@@ -39,4 +39,4 @@ class Int(int, BaseType):
|
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
41
|
def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
|
|
42
|
-
return stream.write(data.to_bytes(cls.
|
|
42
|
+
return stream.write(data.to_bytes(cls.__size__, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed))
|
dissect/cstruct/types/packed.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
|
|
4
5
|
|
|
5
6
|
from dissect.cstruct.types.base import EOF, BaseType
|
|
6
|
-
from dissect.cstruct.util import _struct
|
|
7
|
+
from dissect.cstruct.util import _struct, classproperty
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from typing_extensions import Self
|
|
@@ -17,16 +18,16 @@ T = TypeVar("T", int, float)
|
|
|
17
18
|
class Packed(BaseType, Generic[T]):
|
|
18
19
|
"""Packed type for Python struct (un)packing."""
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
__fmt__: str
|
|
21
22
|
|
|
22
23
|
@classmethod
|
|
23
24
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
24
|
-
data = stream.read(cls.
|
|
25
|
+
data = stream.read(cls.__size__)
|
|
25
26
|
|
|
26
|
-
if len(data) != cls.
|
|
27
|
-
raise EOFError(f"Read {len(data)} bytes, but expected {cls.
|
|
27
|
+
if len(data) != cls.__size__:
|
|
28
|
+
raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
|
|
28
29
|
|
|
29
|
-
return cls.__new__(cls, _struct(endian, cls.
|
|
30
|
+
return cls.__new__(cls, _struct(endian, cls.__fmt__).unpack(data)[0])
|
|
30
31
|
|
|
31
32
|
@classmethod
|
|
32
33
|
def _read_array(
|
|
@@ -35,12 +36,12 @@ class Packed(BaseType, Generic[T]):
|
|
|
35
36
|
if count == EOF:
|
|
36
37
|
data = stream.read()
|
|
37
38
|
length = len(data)
|
|
38
|
-
count = length // cls.
|
|
39
|
+
count = length // cls.__size__
|
|
39
40
|
else:
|
|
40
|
-
length = cls.
|
|
41
|
+
length = cls.__size__ * count
|
|
41
42
|
data = stream.read(length)
|
|
42
43
|
|
|
43
|
-
fmt = _struct(endian, f"{count}{cls.
|
|
44
|
+
fmt = _struct(endian, f"{count}{cls.__fmt__}")
|
|
44
45
|
|
|
45
46
|
if len(data) != length:
|
|
46
47
|
raise EOFError(f"Read {len(data)} bytes, but expected {length}")
|
|
@@ -51,12 +52,12 @@ class Packed(BaseType, Generic[T]):
|
|
|
51
52
|
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None, *, endian: Endianness) -> Self:
|
|
52
53
|
result = []
|
|
53
54
|
|
|
54
|
-
fmt = _struct(endian, cls.
|
|
55
|
+
fmt = _struct(endian, cls.__fmt__)
|
|
55
56
|
while True:
|
|
56
|
-
data = stream.read(cls.
|
|
57
|
+
data = stream.read(cls.__size__)
|
|
57
58
|
|
|
58
|
-
if len(data) != cls.
|
|
59
|
-
raise EOFError(f"Read {len(data)} bytes, but expected {cls.
|
|
59
|
+
if len(data) != cls.__size__:
|
|
60
|
+
raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}")
|
|
60
61
|
|
|
61
62
|
if (value := fmt.unpack(data)[0]) == 0:
|
|
62
63
|
break
|
|
@@ -67,8 +68,18 @@ class Packed(BaseType, Generic[T]):
|
|
|
67
68
|
|
|
68
69
|
@classmethod
|
|
69
70
|
def _write(cls, stream: BinaryIO, data: Packed[T], *, endian: Endianness) -> int:
|
|
70
|
-
return stream.write(_struct(endian, cls.
|
|
71
|
+
return stream.write(_struct(endian, cls.__fmt__).pack(data))
|
|
71
72
|
|
|
72
73
|
@classmethod
|
|
73
74
|
def _write_array(cls, stream: BinaryIO, data: list[Packed[T]], *, endian: Endianness) -> int:
|
|
74
|
-
return stream.write(_struct(endian, f"{len(data)}{cls.
|
|
75
|
+
return stream.write(_struct(endian, f"{len(data)}{cls.__fmt__}").pack(*data))
|
|
76
|
+
|
|
77
|
+
@classproperty
|
|
78
|
+
def packchar(cls) -> str:
|
|
79
|
+
"""Access the struct packing character of packed integer/float types."""
|
|
80
|
+
warnings.warn(
|
|
81
|
+
"The 'packchar' property is deprecated, use '__fmt__' instead.",
|
|
82
|
+
DeprecationWarning,
|
|
83
|
+
stacklevel=2,
|
|
84
|
+
)
|
|
85
|
+
return cls.__fmt__
|
dissect/cstruct/types/pointer.py
CHANGED
|
@@ -21,23 +21,23 @@ T = TypeVar("T", bound=BaseType)
|
|
|
21
21
|
class Pointer(int, BaseType, Generic[T]):
|
|
22
22
|
"""Pointer to some other type."""
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
__type__: type[T]
|
|
25
|
+
__stream: BinaryIO | None
|
|
26
|
+
__context: dict[str, Any] | None
|
|
27
|
+
__endian: Endianness
|
|
28
28
|
|
|
29
29
|
def __new__(
|
|
30
30
|
cls, value: int, stream: BinaryIO | None, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
31
31
|
) -> Self:
|
|
32
32
|
obj = super().__new__(cls, value)
|
|
33
|
-
obj.
|
|
34
|
-
obj.
|
|
35
|
-
obj.
|
|
33
|
+
obj.__stream = stream
|
|
34
|
+
obj.__context = context
|
|
35
|
+
obj.__endian = endian
|
|
36
36
|
obj.dereference = cache(obj.dereference)
|
|
37
37
|
return obj
|
|
38
38
|
|
|
39
39
|
def __repr__(self) -> str:
|
|
40
|
-
return f"<{self.
|
|
40
|
+
return f"<{self.__type__.__name__}* @ {self:#x}>"
|
|
41
41
|
|
|
42
42
|
def __str__(self) -> str:
|
|
43
43
|
return str(self.dereference())
|
|
@@ -51,9 +51,9 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
51
51
|
return type.__call__(
|
|
52
52
|
self.__class__,
|
|
53
53
|
op(self, other),
|
|
54
|
-
self.
|
|
55
|
-
context=self.
|
|
56
|
-
endian=self.
|
|
54
|
+
self.__stream,
|
|
55
|
+
context=self.__context,
|
|
56
|
+
endian=self.__endian,
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
return method
|
|
@@ -74,17 +74,17 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
74
74
|
def __default__(cls) -> Self:
|
|
75
75
|
return cls.__new__(
|
|
76
76
|
cls,
|
|
77
|
-
cls.
|
|
77
|
+
cls.__cs__.pointer.__default__(),
|
|
78
78
|
None,
|
|
79
79
|
context=None,
|
|
80
|
-
endian=cls.
|
|
80
|
+
endian=cls.__cs__.endian,
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
@classmethod
|
|
84
84
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
85
85
|
return cls.__new__(
|
|
86
86
|
cls,
|
|
87
|
-
cls.
|
|
87
|
+
cls.__cs__.pointer._read(stream, context=context, endian=endian),
|
|
88
88
|
stream,
|
|
89
89
|
context=context,
|
|
90
90
|
endian=endian,
|
|
@@ -92,7 +92,7 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
92
92
|
|
|
93
93
|
@classmethod
|
|
94
94
|
def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
|
|
95
|
-
return cls.
|
|
95
|
+
return cls.__cs__.pointer._write(stream, data, endian=endian)
|
|
96
96
|
|
|
97
97
|
def dereference(self, *, endian: AllowedEndianness | None = None) -> T | None:
|
|
98
98
|
"""Dereference the pointer and read the value it points to.
|
|
@@ -101,22 +101,22 @@ class Pointer(int, BaseType, Generic[T]):
|
|
|
101
101
|
endian: Optional endianness to use when reading the value.
|
|
102
102
|
If not provided, the endianness used when reading the pointer itself will be used.
|
|
103
103
|
"""
|
|
104
|
-
if self == 0 or self.
|
|
104
|
+
if self == 0 or self.__stream is None:
|
|
105
105
|
raise NullPointerDereference
|
|
106
106
|
|
|
107
|
-
endian = normalize_endianness(endian) if endian is not None else self.
|
|
108
|
-
if issubclass(self.
|
|
107
|
+
endian = normalize_endianness(endian) if endian is not None else self.__endian
|
|
108
|
+
if issubclass(self.__type__, Void):
|
|
109
109
|
return None
|
|
110
110
|
|
|
111
|
-
position = self.
|
|
112
|
-
self.
|
|
111
|
+
position = self.__stream.tell()
|
|
112
|
+
self.__stream.seek(self)
|
|
113
113
|
|
|
114
|
-
if issubclass(self.
|
|
114
|
+
if issubclass(self.__type__, Char):
|
|
115
115
|
# This makes the assumption that a char pointer is a null-terminated string
|
|
116
|
-
value = self.
|
|
116
|
+
value = self.__type__._read_0(self.__stream, context=self.__context, endian=endian)
|
|
117
117
|
else:
|
|
118
|
-
value = self.
|
|
118
|
+
value = self.__type__._read(self.__stream, context=self.__context, endian=endian)
|
|
119
119
|
|
|
120
120
|
# Restore the stream position after reading the value
|
|
121
|
-
self.
|
|
121
|
+
self.__stream.seek(position)
|
|
122
122
|
return value
|