dissect.cstruct 4.4.dev2__py3-none-any.whl → 4.5.dev1__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 +15 -4
- dissect/cstruct/compiler.py +16 -15
- dissect/cstruct/cstruct.py +202 -40
- dissect/cstruct/parser.py +25 -15
- dissect/cstruct/tools/__init__.py +0 -0
- dissect/cstruct/tools/stubgen.py +220 -0
- dissect/cstruct/types/__init__.py +2 -2
- dissect/cstruct/types/base.py +50 -38
- dissect/cstruct/types/char.py +19 -16
- dissect/cstruct/types/enum.py +35 -16
- dissect/cstruct/types/flag.py +2 -2
- dissect/cstruct/types/int.py +6 -3
- dissect/cstruct/types/leb128.py +6 -3
- dissect/cstruct/types/packed.py +13 -7
- dissect/cstruct/types/pointer.py +25 -20
- dissect/cstruct/types/structure.py +46 -39
- dissect/cstruct/types/wchar.py +11 -11
- dissect/cstruct/utils.py +10 -9
- {dissect.cstruct-4.4.dev2.dist-info → dissect_cstruct-4.5.dev1.dist-info}/METADATA +5 -2
- dissect_cstruct-4.5.dev1.dist-info/RECORD +29 -0
- {dissect.cstruct-4.4.dev2.dist-info → dissect_cstruct-4.5.dev1.dist-info}/WHEEL +1 -1
- dissect_cstruct-4.5.dev1.dist-info/entry_points.txt +2 -0
- dissect.cstruct-4.4.dev2.dist-info/RECORD +0 -26
- {dissect.cstruct-4.4.dev2.dist-info → dissect_cstruct-4.5.dev1.dist-info/licenses}/COPYRIGHT +0 -0
- {dissect.cstruct-4.4.dev2.dist-info → dissect_cstruct-4.5.dev1.dist-info/licenses}/LICENSE +0 -0
- {dissect.cstruct-4.4.dev2.dist-info → dissect_cstruct-4.5.dev1.dist-info}/top_level.txt +0 -0
dissect/cstruct/types/int.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, BinaryIO
|
|
3
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
4
4
|
|
|
5
5
|
from dissect.cstruct.types.base import BaseType
|
|
6
6
|
from dissect.cstruct.utils import ENDIANNESS_MAP
|
|
7
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class Int(int, BaseType):
|
|
10
13
|
"""Integer type that can span an arbitrary amount of bytes."""
|
|
@@ -12,7 +15,7 @@ class Int(int, BaseType):
|
|
|
12
15
|
signed: bool
|
|
13
16
|
|
|
14
17
|
@classmethod
|
|
15
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
18
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
16
19
|
data = stream.read(cls.size)
|
|
17
20
|
|
|
18
21
|
if len(data) != cls.size:
|
|
@@ -21,7 +24,7 @@ class Int(int, BaseType):
|
|
|
21
24
|
return cls.from_bytes(data, ENDIANNESS_MAP[cls.cs.endian], signed=cls.signed)
|
|
22
25
|
|
|
23
26
|
@classmethod
|
|
24
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
27
|
+
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
25
28
|
result = []
|
|
26
29
|
|
|
27
30
|
while True:
|
dissect/cstruct/types/leb128.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, BinaryIO
|
|
3
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
4
4
|
|
|
5
5
|
from dissect.cstruct.types.base import BaseType
|
|
6
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
class LEB128(int, BaseType):
|
|
9
12
|
"""Variable-length code compression to store an arbitrarily large integer in a small number of bytes.
|
|
@@ -14,7 +17,7 @@ class LEB128(int, BaseType):
|
|
|
14
17
|
signed: bool
|
|
15
18
|
|
|
16
19
|
@classmethod
|
|
17
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
20
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
18
21
|
result = 0
|
|
19
22
|
shift = 0
|
|
20
23
|
while True:
|
|
@@ -34,7 +37,7 @@ class LEB128(int, BaseType):
|
|
|
34
37
|
return cls.__new__(cls, result)
|
|
35
38
|
|
|
36
39
|
@classmethod
|
|
37
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
40
|
+
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]:
|
|
38
41
|
result = []
|
|
39
42
|
|
|
40
43
|
while True:
|
dissect/cstruct/types/packed.py
CHANGED
|
@@ -2,27 +2,33 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from functools import lru_cache
|
|
4
4
|
from struct import Struct
|
|
5
|
-
from typing import Any, BinaryIO
|
|
5
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
|
|
6
6
|
|
|
7
7
|
from dissect.cstruct.types.base import EOF, BaseType
|
|
8
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
@lru_cache(1024)
|
|
11
14
|
def _struct(endian: str, packchar: str) -> Struct:
|
|
12
15
|
return Struct(f"{endian}{packchar}")
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
T = TypeVar("T", int, float)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Packed(BaseType, Generic[T]):
|
|
16
22
|
"""Packed type for Python struct (un)packing."""
|
|
17
23
|
|
|
18
24
|
packchar: str
|
|
19
25
|
|
|
20
26
|
@classmethod
|
|
21
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
27
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
22
28
|
return cls._read_array(stream, 1, context)[0]
|
|
23
29
|
|
|
24
30
|
@classmethod
|
|
25
|
-
def _read_array(cls, stream: BinaryIO, count: int, context: dict[str, Any] | None = None) -> list[
|
|
31
|
+
def _read_array(cls, stream: BinaryIO, count: int, context: dict[str, Any] | None = None) -> list[Self]:
|
|
26
32
|
if count == EOF:
|
|
27
33
|
data = stream.read()
|
|
28
34
|
length = len(data)
|
|
@@ -39,7 +45,7 @@ class Packed(BaseType):
|
|
|
39
45
|
return [cls.__new__(cls, value) for value in fmt.unpack(data)]
|
|
40
46
|
|
|
41
47
|
@classmethod
|
|
42
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
48
|
+
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
43
49
|
result = []
|
|
44
50
|
|
|
45
51
|
fmt = _struct(cls.cs.endian, cls.packchar)
|
|
@@ -57,9 +63,9 @@ class Packed(BaseType):
|
|
|
57
63
|
return result
|
|
58
64
|
|
|
59
65
|
@classmethod
|
|
60
|
-
def _write(cls, stream: BinaryIO, data: Packed) -> int:
|
|
66
|
+
def _write(cls, stream: BinaryIO, data: Packed[T]) -> int:
|
|
61
67
|
return stream.write(_struct(cls.cs.endian, cls.packchar).pack(data))
|
|
62
68
|
|
|
63
69
|
@classmethod
|
|
64
|
-
def _write_array(cls, stream: BinaryIO, data: list[Packed]) -> int:
|
|
70
|
+
def _write_array(cls, stream: BinaryIO, data: list[Packed[T]]) -> int:
|
|
65
71
|
return stream.write(_struct(cls.cs.endian, f"{len(data)}{cls.packchar}").pack(*data))
|
dissect/cstruct/types/pointer.py
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, BinaryIO
|
|
3
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
|
|
4
4
|
|
|
5
5
|
from dissect.cstruct.exceptions import NullPointerDereference
|
|
6
|
-
from dissect.cstruct.types.base import BaseType
|
|
6
|
+
from dissect.cstruct.types.base import BaseType
|
|
7
7
|
from dissect.cstruct.types.char import Char
|
|
8
8
|
from dissect.cstruct.types.void import Void
|
|
9
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing_extensions import Self
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
T = TypeVar("T", bound=BaseType)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Pointer(int, BaseType, Generic[T]):
|
|
12
17
|
"""Pointer to some other type."""
|
|
13
18
|
|
|
14
|
-
type:
|
|
19
|
+
type: type[T]
|
|
15
20
|
_stream: BinaryIO | None
|
|
16
21
|
_context: dict[str, Any] | None
|
|
17
|
-
_value:
|
|
22
|
+
_value: T | None
|
|
18
23
|
|
|
19
|
-
def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) ->
|
|
24
|
+
def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Self:
|
|
20
25
|
obj = super().__new__(cls, value)
|
|
21
26
|
obj._stream = stream
|
|
22
27
|
obj._context = context
|
|
@@ -32,52 +37,52 @@ class Pointer(int, BaseType):
|
|
|
32
37
|
def __getattr__(self, attr: str) -> Any:
|
|
33
38
|
return getattr(self.dereference(), attr)
|
|
34
39
|
|
|
35
|
-
def __add__(self, other: int) ->
|
|
40
|
+
def __add__(self, other: int) -> Self:
|
|
36
41
|
return type.__call__(self.__class__, int.__add__(self, other), self._stream, self._context)
|
|
37
42
|
|
|
38
|
-
def __sub__(self, other: int) ->
|
|
43
|
+
def __sub__(self, other: int) -> Self:
|
|
39
44
|
return type.__call__(self.__class__, int.__sub__(self, other), self._stream, self._context)
|
|
40
45
|
|
|
41
|
-
def __mul__(self, other: int) ->
|
|
46
|
+
def __mul__(self, other: int) -> Self:
|
|
42
47
|
return type.__call__(self.__class__, int.__mul__(self, other), self._stream, self._context)
|
|
43
48
|
|
|
44
|
-
def __floordiv__(self, other: int) ->
|
|
49
|
+
def __floordiv__(self, other: int) -> Self:
|
|
45
50
|
return type.__call__(self.__class__, int.__floordiv__(self, other), self._stream, self._context)
|
|
46
51
|
|
|
47
|
-
def __mod__(self, other: int) ->
|
|
52
|
+
def __mod__(self, other: int) -> Self:
|
|
48
53
|
return type.__call__(self.__class__, int.__mod__(self, other), self._stream, self._context)
|
|
49
54
|
|
|
50
|
-
def __pow__(self, other: int) ->
|
|
55
|
+
def __pow__(self, other: int) -> Self:
|
|
51
56
|
return type.__call__(self.__class__, int.__pow__(self, other), self._stream, self._context)
|
|
52
57
|
|
|
53
|
-
def __lshift__(self, other: int) ->
|
|
58
|
+
def __lshift__(self, other: int) -> Self:
|
|
54
59
|
return type.__call__(self.__class__, int.__lshift__(self, other), self._stream, self._context)
|
|
55
60
|
|
|
56
|
-
def __rshift__(self, other: int) ->
|
|
61
|
+
def __rshift__(self, other: int) -> Self:
|
|
57
62
|
return type.__call__(self.__class__, int.__rshift__(self, other), self._stream, self._context)
|
|
58
63
|
|
|
59
|
-
def __and__(self, other: int) ->
|
|
64
|
+
def __and__(self, other: int) -> Self:
|
|
60
65
|
return type.__call__(self.__class__, int.__and__(self, other), self._stream, self._context)
|
|
61
66
|
|
|
62
|
-
def __xor__(self, other: int) ->
|
|
67
|
+
def __xor__(self, other: int) -> Self:
|
|
63
68
|
return type.__call__(self.__class__, int.__xor__(self, other), self._stream, self._context)
|
|
64
69
|
|
|
65
|
-
def __or__(self, other: int) ->
|
|
70
|
+
def __or__(self, other: int) -> Self:
|
|
66
71
|
return type.__call__(self.__class__, int.__or__(self, other), self._stream, self._context)
|
|
67
72
|
|
|
68
73
|
@classmethod
|
|
69
|
-
def __default__(cls) ->
|
|
74
|
+
def __default__(cls) -> Self:
|
|
70
75
|
return cls.__new__(cls, cls.cs.pointer.__default__(), None, None)
|
|
71
76
|
|
|
72
77
|
@classmethod
|
|
73
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
78
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
74
79
|
return cls.__new__(cls, cls.cs.pointer._read(stream, context), stream, context)
|
|
75
80
|
|
|
76
81
|
@classmethod
|
|
77
82
|
def _write(cls, stream: BinaryIO, data: int) -> int:
|
|
78
83
|
return cls.cs.pointer._write(stream, data)
|
|
79
84
|
|
|
80
|
-
def dereference(self) ->
|
|
85
|
+
def dereference(self) -> T:
|
|
81
86
|
if self == 0 or self._stream is None:
|
|
82
87
|
raise NullPointerDereference
|
|
83
88
|
|
|
@@ -7,6 +7,7 @@ from functools import lru_cache
|
|
|
7
7
|
from itertools import chain
|
|
8
8
|
from operator import attrgetter
|
|
9
9
|
from textwrap import dedent
|
|
10
|
+
from types import FunctionType
|
|
10
11
|
from typing import TYPE_CHECKING, Any, BinaryIO, Callable
|
|
11
12
|
|
|
12
13
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
@@ -23,12 +24,15 @@ if TYPE_CHECKING:
|
|
|
23
24
|
from collections.abc import Iterator
|
|
24
25
|
from types import FunctionType
|
|
25
26
|
|
|
27
|
+
from typing_extensions import Self
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
class Field:
|
|
28
31
|
"""Structure field."""
|
|
29
32
|
|
|
30
|
-
def __init__(self, name: str, type_:
|
|
31
|
-
self.name = name
|
|
33
|
+
def __init__(self, name: str | None, type_: type[BaseType], bits: int | None = None, offset: int | None = None):
|
|
34
|
+
self.name = name # The name of the field, or None if anonymous
|
|
35
|
+
self._name = name or type_.__name__ # The name of the field, or the type name if anonymous
|
|
32
36
|
self.type = type_
|
|
33
37
|
self.bits = bits
|
|
34
38
|
self.offset = offset
|
|
@@ -57,13 +61,13 @@ class StructureMetaType(MetaType):
|
|
|
57
61
|
__updating__ = False
|
|
58
62
|
__compiled__ = False
|
|
59
63
|
|
|
60
|
-
def __new__(metacls, name: str, bases: tuple[type, ...], classdict: dict[str, Any]) ->
|
|
64
|
+
def __new__(metacls, name: str, bases: tuple[type, ...], classdict: dict[str, Any]) -> Self: # type: ignore
|
|
61
65
|
if (fields := classdict.pop("fields", None)) is not None:
|
|
62
66
|
metacls._update_fields(metacls, fields, align=classdict.get("__align__", False), classdict=classdict)
|
|
63
67
|
|
|
64
68
|
return super().__new__(metacls, name, bases, classdict)
|
|
65
69
|
|
|
66
|
-
def __call__(cls, *args, **kwargs) ->
|
|
70
|
+
def __call__(cls, *args, **kwargs) -> Self: # type: ignore
|
|
67
71
|
if (
|
|
68
72
|
cls.__fields__
|
|
69
73
|
and len(args) == len(cls.__fields__) == 1
|
|
@@ -81,26 +85,28 @@ class StructureMetaType(MetaType):
|
|
|
81
85
|
|
|
82
86
|
return super().__call__(*args, **kwargs)
|
|
83
87
|
|
|
84
|
-
def _update_fields(
|
|
88
|
+
def _update_fields(
|
|
89
|
+
cls, fields: list[Field], align: bool = False, classdict: dict[str, Any] | None = None
|
|
90
|
+
) -> dict[str, Any]:
|
|
85
91
|
classdict = classdict or {}
|
|
86
92
|
|
|
87
93
|
lookup = {}
|
|
88
94
|
raw_lookup = {}
|
|
89
95
|
field_names = []
|
|
90
96
|
for field in fields:
|
|
91
|
-
if field.
|
|
92
|
-
raise ValueError(f"Duplicate field name: {field.
|
|
97
|
+
if field._name in lookup and field._name != "_":
|
|
98
|
+
raise ValueError(f"Duplicate field name: {field._name}")
|
|
93
99
|
|
|
94
|
-
if isinstance(field.type, StructureMetaType) and field.
|
|
100
|
+
if isinstance(field.type, StructureMetaType) and field.name is None:
|
|
95
101
|
for anon_field in field.type.fields.values():
|
|
96
|
-
attr = f"{field.
|
|
102
|
+
attr = f"{field._name}.{anon_field.name}"
|
|
97
103
|
classdict[anon_field.name] = property(attrgetter(attr), attrsetter(attr))
|
|
98
104
|
|
|
99
105
|
lookup.update(field.type.fields)
|
|
100
106
|
else:
|
|
101
|
-
lookup[field.
|
|
107
|
+
lookup[field._name] = field
|
|
102
108
|
|
|
103
|
-
raw_lookup[field.
|
|
109
|
+
raw_lookup[field._name] = field
|
|
104
110
|
|
|
105
111
|
field_names = lookup.keys()
|
|
106
112
|
classdict["fields"] = lookup
|
|
@@ -133,7 +139,7 @@ class StructureMetaType(MetaType):
|
|
|
133
139
|
classdict["__compiled__"] = True
|
|
134
140
|
except Exception:
|
|
135
141
|
# Revert _read to the slower loop based method
|
|
136
|
-
classdict["_read"] = classmethod(
|
|
142
|
+
classdict["_read"] = classmethod(Structure._read.__func__)
|
|
137
143
|
classdict["__compiled__"] = False
|
|
138
144
|
|
|
139
145
|
# TODO: compile _write
|
|
@@ -233,7 +239,7 @@ class StructureMetaType(MetaType):
|
|
|
233
239
|
# The structure size is whatever the currently calculated offset is
|
|
234
240
|
return offset, alignment
|
|
235
241
|
|
|
236
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
242
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self: # type: ignore
|
|
237
243
|
bit_buffer = BitBuffer(stream, cls.cs.endian)
|
|
238
244
|
struct_start = stream.tell()
|
|
239
245
|
|
|
@@ -241,7 +247,6 @@ class StructureMetaType(MetaType):
|
|
|
241
247
|
sizes = {}
|
|
242
248
|
for field in cls.__fields__:
|
|
243
249
|
offset = stream.tell()
|
|
244
|
-
field_type = cls.cs.resolve(field.type)
|
|
245
250
|
|
|
246
251
|
if field.offset is not None and offset != struct_start + field.offset:
|
|
247
252
|
# Field is at a specific offset, either alligned or added that way
|
|
@@ -254,21 +259,20 @@ class StructureMetaType(MetaType):
|
|
|
254
259
|
stream.seek(offset)
|
|
255
260
|
|
|
256
261
|
if field.bits:
|
|
257
|
-
if isinstance(
|
|
258
|
-
value =
|
|
262
|
+
if isinstance(field.type, EnumMetaType):
|
|
263
|
+
value = field.type(bit_buffer.read(field.type.type, field.bits))
|
|
259
264
|
else:
|
|
260
|
-
value = bit_buffer.read(
|
|
265
|
+
value = bit_buffer.read(field.type, field.bits)
|
|
261
266
|
|
|
262
|
-
|
|
263
|
-
result[field.name] = value
|
|
267
|
+
result[field._name] = value
|
|
264
268
|
continue
|
|
269
|
+
|
|
265
270
|
bit_buffer.reset()
|
|
266
271
|
|
|
267
|
-
value =
|
|
272
|
+
value = field.type._read(stream, result)
|
|
268
273
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
result[field.name] = value
|
|
274
|
+
sizes[field._name] = stream.tell() - offset
|
|
275
|
+
result[field._name] = value
|
|
272
276
|
|
|
273
277
|
if cls.__align__:
|
|
274
278
|
# Align the stream
|
|
@@ -281,7 +285,7 @@ class StructureMetaType(MetaType):
|
|
|
281
285
|
obj._values = result
|
|
282
286
|
return obj
|
|
283
287
|
|
|
284
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[
|
|
288
|
+
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]: # type: ignore
|
|
285
289
|
result = []
|
|
286
290
|
|
|
287
291
|
while obj := cls._read(stream, context):
|
|
@@ -325,7 +329,7 @@ class StructureMetaType(MetaType):
|
|
|
325
329
|
stream.write(b"\x00" * align_pad)
|
|
326
330
|
offset += align_pad
|
|
327
331
|
|
|
328
|
-
value = getattr(data, field.
|
|
332
|
+
value = getattr(data, field._name, None)
|
|
329
333
|
if value is None:
|
|
330
334
|
value = field_type.__default__()
|
|
331
335
|
|
|
@@ -347,7 +351,7 @@ class StructureMetaType(MetaType):
|
|
|
347
351
|
|
|
348
352
|
return num
|
|
349
353
|
|
|
350
|
-
def add_field(cls, name: str, type_: BaseType, bits: int | None = None, offset: int | None = None) -> None:
|
|
354
|
+
def add_field(cls, name: str, type_: type[BaseType], bits: int | None = None, offset: int | None = None) -> None:
|
|
351
355
|
field = Field(name, type_, bits=bits, offset=offset)
|
|
352
356
|
cls.__fields__.append(field)
|
|
353
357
|
|
|
@@ -401,7 +405,7 @@ class Structure(BaseType, metaclass=StructureMetaType):
|
|
|
401
405
|
class UnionMetaType(StructureMetaType):
|
|
402
406
|
"""Base metaclass for cstruct union type classes."""
|
|
403
407
|
|
|
404
|
-
def __call__(cls, *args, **kwargs) ->
|
|
408
|
+
def __call__(cls, *args, **kwargs) -> Self: # type: ignore
|
|
405
409
|
obj: Union = super().__call__(*args, **kwargs)
|
|
406
410
|
|
|
407
411
|
# Calling with non-stream args or kwargs means we are initializing with values
|
|
@@ -412,7 +416,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
412
416
|
|
|
413
417
|
# User (partial) initialization, rebuild the union
|
|
414
418
|
# First user-provided field is the one used to rebuild the union
|
|
415
|
-
arg_fields = (field.
|
|
419
|
+
arg_fields = (field._name for _, field in zip(args, cls.__fields__))
|
|
416
420
|
kwarg_fields = (name for name in kwargs if name in cls.lookup)
|
|
417
421
|
if (first_field := next(chain(arg_fields, kwarg_fields), None)) is not None:
|
|
418
422
|
obj._rebuild(first_field)
|
|
@@ -469,12 +473,12 @@ class UnionMetaType(StructureMetaType):
|
|
|
469
473
|
buf.seek(offset + start)
|
|
470
474
|
value = field_type._read(buf, result)
|
|
471
475
|
|
|
472
|
-
sizes[field.
|
|
473
|
-
result[field.
|
|
476
|
+
sizes[field._name] = buf.tell() - start
|
|
477
|
+
result[field._name] = value
|
|
474
478
|
|
|
475
479
|
return result, sizes
|
|
476
480
|
|
|
477
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) ->
|
|
481
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self: # type: ignore
|
|
478
482
|
if cls.size is None:
|
|
479
483
|
start = stream.tell()
|
|
480
484
|
result, sizes = cls._read_fields(stream, context)
|
|
@@ -517,13 +521,13 @@ class UnionMetaType(StructureMetaType):
|
|
|
517
521
|
|
|
518
522
|
# Try to write by largest field
|
|
519
523
|
for field in fields:
|
|
520
|
-
if isinstance(field.type, StructureMetaType) and field.
|
|
524
|
+
if isinstance(field.type, StructureMetaType) and field.name is None:
|
|
521
525
|
# Prefer to write regular fields initially
|
|
522
526
|
anonymous_struct = field.type
|
|
523
527
|
continue
|
|
524
528
|
|
|
525
529
|
# Write the value
|
|
526
|
-
field.type._write(stream, getattr(data, field.
|
|
530
|
+
field.type._write(stream, getattr(data, field._name))
|
|
527
531
|
break
|
|
528
532
|
|
|
529
533
|
# If we haven't written anything yet and we initially skipped an anonymous struct, write it now
|
|
@@ -582,9 +586,9 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
582
586
|
def _proxy_structure(value: Structure) -> None:
|
|
583
587
|
for field in value.__class__.__fields__:
|
|
584
588
|
if issubclass(field.type, Structure):
|
|
585
|
-
nested_value = getattr(value, field.
|
|
586
|
-
proxy = UnionProxy(self, field.
|
|
587
|
-
object.__setattr__(value, field.
|
|
589
|
+
nested_value = getattr(value, field._name)
|
|
590
|
+
proxy = UnionProxy(self, field._name, nested_value)
|
|
591
|
+
object.__setattr__(value, field._name, proxy)
|
|
588
592
|
_proxy_structure(nested_value)
|
|
589
593
|
|
|
590
594
|
_proxy_structure(self)
|
|
@@ -771,7 +775,7 @@ def _generate_structure__init__(fields: list[Field]) -> FunctionType:
|
|
|
771
775
|
Args:
|
|
772
776
|
fields: List of field names.
|
|
773
777
|
"""
|
|
774
|
-
field_names = [field.
|
|
778
|
+
field_names = [field._name for field in fields]
|
|
775
779
|
|
|
776
780
|
template: FunctionType = _make_structure__init__(len(field_names))
|
|
777
781
|
return type(template)(
|
|
@@ -791,12 +795,15 @@ def _generate_union__init__(fields: list[Field]) -> FunctionType:
|
|
|
791
795
|
Args:
|
|
792
796
|
fields: List of field names.
|
|
793
797
|
"""
|
|
794
|
-
field_names = [field.
|
|
798
|
+
field_names = [field._name for field in fields]
|
|
795
799
|
|
|
796
800
|
template: FunctionType = _make_union__init__(len(field_names))
|
|
797
801
|
return type(template)(
|
|
798
802
|
template.__code__.replace(
|
|
799
|
-
co_consts=(
|
|
803
|
+
co_consts=(
|
|
804
|
+
None,
|
|
805
|
+
*sum([(field._name, field.type.__default__()) for field in fields], ()),
|
|
806
|
+
),
|
|
800
807
|
co_varnames=("self", *field_names),
|
|
801
808
|
),
|
|
802
809
|
template.__globals__,
|
dissect/cstruct/types/wchar.py
CHANGED
|
@@ -3,17 +3,21 @@ from __future__ import annotations
|
|
|
3
3
|
import sys
|
|
4
4
|
from typing import Any, BinaryIO, ClassVar
|
|
5
5
|
|
|
6
|
-
from dissect.cstruct.types.base import EOF,
|
|
6
|
+
from dissect.cstruct.types.base import EOF, BaseArray, BaseType
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class WcharArray(str,
|
|
9
|
+
class WcharArray(str, BaseArray):
|
|
10
10
|
"""Wide-character array type for reading and writing UTF-16 strings."""
|
|
11
11
|
|
|
12
12
|
__slots__ = ()
|
|
13
13
|
|
|
14
|
+
@classmethod
|
|
15
|
+
def __default__(cls) -> WcharArray:
|
|
16
|
+
return type.__call__(cls, "\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
|
|
17
|
+
|
|
14
18
|
@classmethod
|
|
15
19
|
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> WcharArray:
|
|
16
|
-
return type.__call__(cls,
|
|
20
|
+
return type.__call__(cls, super()._read(stream, context))
|
|
17
21
|
|
|
18
22
|
@classmethod
|
|
19
23
|
def _write(cls, stream: BinaryIO, data: str) -> int:
|
|
@@ -21,10 +25,6 @@ class WcharArray(str, BaseType, metaclass=ArrayMetaType):
|
|
|
21
25
|
data += "\x00"
|
|
22
26
|
return stream.write(data.encode(Wchar.__encoding_map__[cls.cs.endian]))
|
|
23
27
|
|
|
24
|
-
@classmethod
|
|
25
|
-
def __default__(cls) -> WcharArray:
|
|
26
|
-
return type.__call__(cls, "\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
|
|
27
|
-
|
|
28
28
|
|
|
29
29
|
class Wchar(str, BaseType):
|
|
30
30
|
"""Wide-character type for reading and writing UTF-16 characters."""
|
|
@@ -40,6 +40,10 @@ class Wchar(str, BaseType):
|
|
|
40
40
|
"!": "utf-16-be",
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
@classmethod
|
|
44
|
+
def __default__(cls) -> Wchar:
|
|
45
|
+
return type.__call__(cls, "\x00")
|
|
46
|
+
|
|
43
47
|
@classmethod
|
|
44
48
|
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Wchar:
|
|
45
49
|
return cls._read_array(stream, 1, context)
|
|
@@ -76,7 +80,3 @@ class Wchar(str, BaseType):
|
|
|
76
80
|
@classmethod
|
|
77
81
|
def _write(cls, stream: BinaryIO, data: str) -> int:
|
|
78
82
|
return stream.write(data.encode(cls.__encoding_map__[cls.cs.endian]))
|
|
79
|
-
|
|
80
|
-
@classmethod
|
|
81
|
-
def __default__(cls) -> Wchar:
|
|
82
|
-
return type.__call__(cls, "\x00")
|
dissect/cstruct/utils.py
CHANGED
|
@@ -11,6 +11,7 @@ from dissect.cstruct.types.structure import Structure
|
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from collections.abc import Iterator
|
|
14
|
+
from typing import Literal
|
|
14
15
|
|
|
15
16
|
COLOR_RED = "\033[1;31m"
|
|
16
17
|
COLOR_GREEN = "\033[1;32m"
|
|
@@ -31,7 +32,7 @@ COLOR_BG_WHITE = "\033[1;47m\033[1;30m"
|
|
|
31
32
|
|
|
32
33
|
PRINTABLE = string.digits + string.ascii_letters + string.punctuation + " "
|
|
33
34
|
|
|
34
|
-
ENDIANNESS_MAP = {
|
|
35
|
+
ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
|
|
35
36
|
"@": sys.byteorder,
|
|
36
37
|
"=": sys.byteorder,
|
|
37
38
|
"<": "little",
|
|
@@ -40,7 +41,7 @@ ENDIANNESS_MAP = {
|
|
|
40
41
|
"network": "big",
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
Palette = list[tuple[
|
|
44
|
+
Palette = list[tuple[int, str]]
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefix: str = "") -> Iterator[str]:
|
|
@@ -158,10 +159,10 @@ def _dumpstruct(
|
|
|
158
159
|
|
|
159
160
|
if color:
|
|
160
161
|
foreground, background = colors[ci % len(colors)]
|
|
161
|
-
palette.append((structure._sizes[field.
|
|
162
|
+
palette.append((structure._sizes[field._name], background))
|
|
162
163
|
ci += 1
|
|
163
164
|
|
|
164
|
-
value = getattr(structure, field.
|
|
165
|
+
value = getattr(structure, field._name)
|
|
165
166
|
if isinstance(value, (str, Pointer, Enum)):
|
|
166
167
|
value = repr(value)
|
|
167
168
|
elif isinstance(value, int):
|
|
@@ -169,12 +170,12 @@ def _dumpstruct(
|
|
|
169
170
|
elif isinstance(value, list):
|
|
170
171
|
value = pprint.pformat(value)
|
|
171
172
|
if "\n" in value:
|
|
172
|
-
value = value.replace("\n", f"\n{' ' * (len(field.
|
|
173
|
+
value = value.replace("\n", f"\n{' ' * (len(field._name) + 4)}")
|
|
173
174
|
|
|
174
175
|
if color:
|
|
175
|
-
out.append(f"- {foreground}{field.
|
|
176
|
+
out.append(f"- {foreground}{field._name}{COLOR_NORMAL}: {value}")
|
|
176
177
|
else:
|
|
177
|
-
out.append(f"- {field.
|
|
178
|
+
out.append(f"- {field._name}: {value}")
|
|
178
179
|
|
|
179
180
|
out = "\n".join(out)
|
|
180
181
|
|
|
@@ -184,7 +185,7 @@ def _dumpstruct(
|
|
|
184
185
|
print()
|
|
185
186
|
print(out)
|
|
186
187
|
elif output == "string":
|
|
187
|
-
return "\n
|
|
188
|
+
return f"\n{hexdump(data, palette, offset=offset, output='string')}\n\n{out}"
|
|
188
189
|
return None
|
|
189
190
|
|
|
190
191
|
|
|
@@ -210,7 +211,7 @@ def dumpstruct(
|
|
|
210
211
|
|
|
211
212
|
if isinstance(obj, Structure):
|
|
212
213
|
return _dumpstruct(obj, obj.dumps(), offset, color, output)
|
|
213
|
-
if issubclass(obj, Structure) and data:
|
|
214
|
+
if issubclass(obj, Structure) and data is not None:
|
|
214
215
|
return _dumpstruct(obj(data), data, offset, color, output)
|
|
215
216
|
raise ValueError("Invalid arguments")
|
|
216
217
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.dev1
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -22,6 +22,9 @@ Requires-Python: ~=3.9
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
License-File: COPYRIGHT
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: typing_extensions; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
25
28
|
|
|
26
29
|
# dissect.cstruct
|
|
27
30
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
dissect/cstruct/__init__.py,sha256=mILXFvtajLG5EqAPSeCJ2g5klZSnRbu7uqyGcH_DF2k,1344
|
|
2
|
+
dissect/cstruct/bitbuffer.py,sha256=bid_N4ZsaTeC3x5Fzs8viUrldT863dMPtEDih20Nt6k,2644
|
|
3
|
+
dissect/cstruct/compiler.py,sha256=luyUXwPlOXFKRoqztNFY7YeENk_WEjsponvNfnjMKT4,14053
|
|
4
|
+
dissect/cstruct/cstruct.py,sha256=IVvue9unr0T-ZZXSVM9mjIovwG4YdopKOqktmB8vIDk,19379
|
|
5
|
+
dissect/cstruct/exceptions.py,sha256=WqsUW4OiIpRLQLvixfLrfKl0rtvU1qx7pvfBrz9Sz-I,293
|
|
6
|
+
dissect/cstruct/expression.py,sha256=BJeQJYKwTvoDOIuv48rtfDp1kvyAjIkxpy2-VZMZ7Gc,10870
|
|
7
|
+
dissect/cstruct/parser.py,sha256=v_D6x46J1EB3EJF5OssLf8nedYcShpQke4FJK4G7AyU,20910
|
|
8
|
+
dissect/cstruct/utils.py,sha256=yItxcZ-IG9EZGABXap0456Y3LjLjh8zNEd5KOBD7YmU,10525
|
|
9
|
+
dissect/cstruct/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
dissect/cstruct/tools/stubgen.py,sha256=Cg_nHTp-KMm7eI0kCCeWflaYXeCGDFn728IdX60o508,7332
|
|
11
|
+
dissect/cstruct/types/__init__.py,sha256=e9Z6BhhAPquT4MJoK3i_SjC9AgIGWFH_-tAVFCFBiqE,847
|
|
12
|
+
dissect/cstruct/types/base.py,sha256=UFDolAxpVW1W9JoVlykEf96wBzt_suXvuYy08iB54Sw,9677
|
|
13
|
+
dissect/cstruct/types/char.py,sha256=9XYCjCF0D_5o3EYnmIcjev-5GI2NzxOhd1ct1xF6SAM,2457
|
|
14
|
+
dissect/cstruct/types/enum.py,sha256=9mzlIeHYLHOdNtTmiPgGNvGBYe71HeDX5qQDspX5O80,7409
|
|
15
|
+
dissect/cstruct/types/flag.py,sha256=7xxQjFEDPHSQOxBHgcc75WzPiAxflOquwr7Z7c3erLE,2347
|
|
16
|
+
dissect/cstruct/types/int.py,sha256=MGsdUxJt-lj3nl9wzAgGX8cb_vJOtGLiCHwYZs1j_IA,1156
|
|
17
|
+
dissect/cstruct/types/leb128.py,sha256=kDmsWGXX6vr1bm4uJcilsrQ7JABdQRio16rBZV21pno,2243
|
|
18
|
+
dissect/cstruct/types/packed.py,sha256=tpZpb8tiSMXr1np6p0-nZqT4sY7zieO-4E16lmMmqJA,2128
|
|
19
|
+
dissect/cstruct/types/pointer.py,sha256=PqeJOoNBUfMd5uO2kpF6OHeW7Q6R1N1W1V4q1MakN4I,3912
|
|
20
|
+
dissect/cstruct/types/structure.py,sha256=6OBxXr7hMYhufe4GvHkBHpME1fvJQYlfbFxchouHIIc,29814
|
|
21
|
+
dissect/cstruct/types/void.py,sha256=VL2qJ86rq-oBUnkBprNsPPgPGgHV6UENRJTQ2jw0rxc,669
|
|
22
|
+
dissect/cstruct/types/wchar.py,sha256=N9Y_XX2_hZEe2DwepJjxsB6xuRJ6zINRalcUofR59kY,2608
|
|
23
|
+
dissect_cstruct-4.5.dev1.dist-info/licenses/COPYRIGHT,sha256=H-18RXfshdH9AdHwW2eO1Xa-5s6tY5eipHh5c0whDu4,316
|
|
24
|
+
dissect_cstruct-4.5.dev1.dist-info/licenses/LICENSE,sha256=PhUqiw6jAh2KbBdVRPBq_hfAvfcTBin7nZ3CK7NQbTM,11341
|
|
25
|
+
dissect_cstruct-4.5.dev1.dist-info/METADATA,sha256=eeA43wdx2j8YrTB3tP3fda3_Iv1NsMkIbbhW1ThT53g,8649
|
|
26
|
+
dissect_cstruct-4.5.dev1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
27
|
+
dissect_cstruct-4.5.dev1.dist-info/entry_points.txt,sha256=z53zqZqwD2OLrAkRwrP4wTeiU9CQe7xrMly0T2c0_wQ,71
|
|
28
|
+
dissect_cstruct-4.5.dev1.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
29
|
+
dissect_cstruct-4.5.dev1.dist-info/RECORD,,
|