dissect.cstruct 4.8.dev9__py3-none-any.whl → 5.0.dev6__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 +196 -75
- dissect/cstruct/{exceptions.py → exception.py} +4 -4
- dissect/cstruct/expression.py +188 -271
- dissect/cstruct/lexer.py +584 -0
- dissect/cstruct/parser.py +444 -593
- dissect/cstruct/tools/stubgen.py +18 -22
- dissect/cstruct/types/base.py +74 -31
- dissect/cstruct/types/char.py +12 -8
- 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 +128 -28
- dissect/cstruct/types/void.py +6 -4
- dissect/cstruct/types/wchar.py +19 -14
- dissect/cstruct/{utils.py → util.py} +60 -35
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev6.dist-info/RECORD +32 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/scm_file_list.json +4 -2
- dissect_cstruct-5.0.dev6.dist-info/scm_version.json +8 -0
- dissect_cstruct-4.8.dev9.dist-info/RECORD +0 -31
- dissect_cstruct-4.8.dev9.dist-info/scm_version.json +0 -8
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/WHEEL +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/top_level.txt +0 -0
dissect/cstruct/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
4
4
|
from dissect.cstruct.cstruct import cstruct, ctypes, ctypes_type
|
|
5
|
-
from dissect.cstruct.
|
|
5
|
+
from dissect.cstruct.exception import (
|
|
6
6
|
Error,
|
|
7
7
|
NullPointerDereference,
|
|
8
8
|
ParserError,
|
|
@@ -28,7 +28,7 @@ from dissect.cstruct.types import (
|
|
|
28
28
|
Wchar,
|
|
29
29
|
WcharArray,
|
|
30
30
|
)
|
|
31
|
-
from dissect.cstruct.
|
|
31
|
+
from dissect.cstruct.util import (
|
|
32
32
|
dumpstruct,
|
|
33
33
|
hexdump,
|
|
34
34
|
p8,
|
dissect/cstruct/bitbuffer.py
CHANGED
|
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|
|
9
9
|
class BitBuffer:
|
|
10
10
|
"""Implements a bit buffer that can read and write bit fields."""
|
|
11
11
|
|
|
12
|
-
def __init__(self, stream: BinaryIO, endian: str):
|
|
12
|
+
def __init__(self, stream: BinaryIO, *, endian: str):
|
|
13
13
|
self.stream = stream
|
|
14
14
|
self.endian = endian
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ class BitBuffer:
|
|
|
24
24
|
|
|
25
25
|
self._type = field_type
|
|
26
26
|
self._remaining = field_type.size * 8
|
|
27
|
-
self._buffer = field_type._read(self.stream)
|
|
27
|
+
self._buffer = field_type._read(self.stream, endian=self.endian)
|
|
28
28
|
|
|
29
29
|
if isinstance(self._buffer, bytes):
|
|
30
30
|
if self.endian == "<":
|
|
@@ -71,7 +71,7 @@ class BitBuffer:
|
|
|
71
71
|
|
|
72
72
|
def flush(self) -> None:
|
|
73
73
|
if self._type is not None:
|
|
74
|
-
self._type._write(self.stream, self._buffer)
|
|
74
|
+
self._type._write(self.stream, self._buffer, endian=self.endian)
|
|
75
75
|
self._type = None
|
|
76
76
|
self._remaining = 0
|
|
77
77
|
self._buffer = 0
|
dissect/cstruct/compiler.py
CHANGED
|
@@ -119,7 +119,7 @@ class _ReadSourceGenerator:
|
|
|
119
119
|
"""
|
|
120
120
|
|
|
121
121
|
if any(field.bits for field in self.fields):
|
|
122
|
-
preamble += "bit_reader = BitBuffer(stream,
|
|
122
|
+
preamble += "bit_reader = BitBuffer(stream, endian=endian)\n"
|
|
123
123
|
|
|
124
124
|
read_code = "\n".join(self._generate_fields())
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ class _ReadSourceGenerator:
|
|
|
132
132
|
|
|
133
133
|
code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
|
|
134
134
|
|
|
135
|
-
return f"def _read(cls, stream, context=None):\n{code}"
|
|
135
|
+
return f"def _read(cls, stream, *, context=None, endian):\n{code}"
|
|
136
136
|
|
|
137
137
|
def _generate_fields(self) -> Iterator[str]:
|
|
138
138
|
current_offset = 0
|
|
@@ -229,7 +229,7 @@ class _ReadSourceGenerator:
|
|
|
229
229
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
230
230
|
template = f"""
|
|
231
231
|
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
232
|
-
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
232
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
|
|
233
233
|
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
234
234
|
"""
|
|
235
235
|
|
|
@@ -238,7 +238,7 @@ class _ReadSourceGenerator:
|
|
|
238
238
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
239
239
|
template = f"""
|
|
240
240
|
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
241
|
-
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
241
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
|
|
242
242
|
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
243
243
|
"""
|
|
244
244
|
|
|
@@ -311,7 +311,7 @@ class _ReadSourceGenerator:
|
|
|
311
311
|
item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.size}]")
|
|
312
312
|
list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.size})]"
|
|
313
313
|
elif issubclass(field_type.type, Pointer):
|
|
314
|
-
item_parser = "_et.__new__(_et, e, stream, r)"
|
|
314
|
+
item_parser = "_et.__new__(_et, e, stream, context=r, endian=endian)"
|
|
315
315
|
list_comp = f"[{item_parser} for e in {getter}]"
|
|
316
316
|
else:
|
|
317
317
|
item_parser = parser_template.format(type="_et", getter="e")
|
|
@@ -322,7 +322,7 @@ class _ReadSourceGenerator:
|
|
|
322
322
|
parser = f"type.__call__({self._map_field(field)}, {getter})"
|
|
323
323
|
elif issubclass(field_type, Pointer):
|
|
324
324
|
reads.append(f"_pt = {self._map_field(field)}")
|
|
325
|
-
parser = f"_pt.__new__(_pt, {getter}, stream, r)"
|
|
325
|
+
parser = f"_pt.__new__(_pt, {getter}, stream, context=r, endian=endian)"
|
|
326
326
|
else:
|
|
327
327
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
328
328
|
|
|
@@ -335,7 +335,7 @@ class _ReadSourceGenerator:
|
|
|
335
335
|
if fmt == "x" or (len(fmt) == 2 and fmt[1] == "x"):
|
|
336
336
|
unpack = ""
|
|
337
337
|
else:
|
|
338
|
-
unpack = f'data = _struct(
|
|
338
|
+
unpack = f'data = _struct(endian, "{fmt}").unpack(buf)\n'
|
|
339
339
|
|
|
340
340
|
template = f"""
|
|
341
341
|
buf = stream.read({size})
|
dissect/cstruct/cstruct.py
CHANGED
|
@@ -2,15 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import ctypes as _ctypes
|
|
5
|
+
import inspect
|
|
5
6
|
import struct
|
|
6
7
|
import sys
|
|
7
8
|
import types
|
|
9
|
+
import warnings
|
|
10
|
+
from contextlib import contextmanager
|
|
8
11
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
|
|
12
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Literal, TypeVar, cast
|
|
10
13
|
|
|
11
|
-
from dissect.cstruct.
|
|
14
|
+
from dissect.cstruct.exception import ResolveError
|
|
12
15
|
from dissect.cstruct.expression import Expression
|
|
13
|
-
from dissect.cstruct.parser import CStyleParser
|
|
16
|
+
from dissect.cstruct.parser import CStyleParser
|
|
14
17
|
from dissect.cstruct.types import (
|
|
15
18
|
LEB128,
|
|
16
19
|
BaseArray,
|
|
@@ -26,10 +29,10 @@ from dissect.cstruct.types import (
|
|
|
26
29
|
Void,
|
|
27
30
|
Wchar,
|
|
28
31
|
)
|
|
29
|
-
from dissect.cstruct.types.base import
|
|
32
|
+
from dissect.cstruct.types.base import normalize_endianness
|
|
30
33
|
|
|
31
34
|
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Iterable
|
|
35
|
+
from collections.abc import Iterable, Iterator
|
|
33
36
|
from typing import TypeAlias
|
|
34
37
|
|
|
35
38
|
from dissect.cstruct.types import (
|
|
@@ -40,26 +43,30 @@ if TYPE_CHECKING:
|
|
|
40
43
|
|
|
41
44
|
T = TypeVar("T", bound=BaseType)
|
|
42
45
|
|
|
46
|
+
AllowedEndianness: TypeAlias = Literal["little", "big", "network", "<", ">", "!", "@", "="]
|
|
47
|
+
Endianness: TypeAlias = Literal["<", ">", "!", "@", "="]
|
|
48
|
+
|
|
43
49
|
|
|
44
50
|
class cstruct:
|
|
45
51
|
"""Main class of cstruct. All types are registered in here.
|
|
46
52
|
|
|
47
53
|
Args:
|
|
48
|
-
endian: The endianness to use when parsing.
|
|
54
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
49
55
|
pointer: The pointer type to use for pointers.
|
|
50
56
|
"""
|
|
51
57
|
|
|
52
58
|
DEF_CSTYLE = 1
|
|
53
59
|
DEF_LEGACY = 2
|
|
54
60
|
|
|
55
|
-
def __init__(self, load: str = "", *, endian:
|
|
56
|
-
self.endian = endian
|
|
61
|
+
def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: str | None = None):
|
|
62
|
+
self.endian = normalize_endianness(endian)
|
|
63
|
+
|
|
64
|
+
self.consts: dict[str, int | str | bytes] = {}
|
|
65
|
+
self.types: dict[str, type[BaseType]] = {}
|
|
66
|
+
self.includes: list[str] = []
|
|
57
67
|
|
|
58
|
-
self.consts = {}
|
|
59
|
-
self.lookups = {}
|
|
60
|
-
self.includes = []
|
|
61
68
|
# fmt: off
|
|
62
|
-
|
|
69
|
+
initial_types = {
|
|
63
70
|
# Internal types
|
|
64
71
|
"int8": self._make_packed_type("int8", "b", int),
|
|
65
72
|
"uint8": self._make_packed_type("uint8", "B", int),
|
|
@@ -103,6 +110,22 @@ class cstruct:
|
|
|
103
110
|
"signed long long": "int64",
|
|
104
111
|
"unsigned long long": "uint64",
|
|
105
112
|
|
|
113
|
+
# Other convenience types
|
|
114
|
+
"u8": "uint8",
|
|
115
|
+
"u16": "uint16",
|
|
116
|
+
"u32": "uint32",
|
|
117
|
+
"u64": "uint64",
|
|
118
|
+
"u128": "uint128",
|
|
119
|
+
"__u8": "uint8",
|
|
120
|
+
"__u16": "uint16",
|
|
121
|
+
"__u32": "uint32",
|
|
122
|
+
"__u64": "uint64",
|
|
123
|
+
"__u128": "uint128",
|
|
124
|
+
"uchar": "uint8",
|
|
125
|
+
"ushort": "uint16",
|
|
126
|
+
"uint": "uint32",
|
|
127
|
+
"ulong": "uint32",
|
|
128
|
+
|
|
106
129
|
# Windows types
|
|
107
130
|
"BYTE": "uint8",
|
|
108
131
|
"CHAR": "char",
|
|
@@ -170,24 +193,12 @@ class cstruct:
|
|
|
170
193
|
"_DWORD": "uint32",
|
|
171
194
|
"_QWORD": "uint64",
|
|
172
195
|
"_OWORD": "uint128",
|
|
173
|
-
|
|
174
|
-
# Other convenience types
|
|
175
|
-
"u1": "uint8",
|
|
176
|
-
"u2": "uint16",
|
|
177
|
-
"u4": "uint32",
|
|
178
|
-
"u8": "uint64",
|
|
179
|
-
"u16": "uint128",
|
|
180
|
-
"__u8": "uint8",
|
|
181
|
-
"__u16": "uint16",
|
|
182
|
-
"__u32": "uint32",
|
|
183
|
-
"__u64": "uint64",
|
|
184
|
-
"uchar": "uint8",
|
|
185
|
-
"ushort": "uint16",
|
|
186
|
-
"uint": "uint32",
|
|
187
|
-
"ulong": "uint32",
|
|
188
196
|
}
|
|
189
197
|
# fmt: on
|
|
190
198
|
|
|
199
|
+
for name, type_ in initial_types.items():
|
|
200
|
+
self.add_type(name, type_)
|
|
201
|
+
|
|
191
202
|
pointer = pointer or ("uint64" if sys.maxsize > 2**32 else "uint32")
|
|
192
203
|
self.pointer: type[BaseType] = self.resolve(pointer)
|
|
193
204
|
self._anonymous_count = 0
|
|
@@ -202,28 +213,25 @@ class cstruct:
|
|
|
202
213
|
pass
|
|
203
214
|
|
|
204
215
|
try:
|
|
205
|
-
return self.
|
|
216
|
+
return self.types[attr]
|
|
206
217
|
except KeyError:
|
|
207
218
|
pass
|
|
208
219
|
|
|
209
|
-
raise AttributeError(f"
|
|
220
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute {attr!r}")
|
|
210
221
|
|
|
211
222
|
def __copy__(self) -> cstruct:
|
|
212
223
|
cs = cstruct(endian=self.endian, pointer=self.pointer.__name__)
|
|
213
224
|
cs._anonymous_count = self._anonymous_count
|
|
214
|
-
cs.consts = self.consts.copy()
|
|
215
|
-
cs.lookups = self.lookups.copy()
|
|
216
225
|
cs.includes = self.includes.copy()
|
|
217
226
|
|
|
218
|
-
# Update
|
|
219
|
-
for name,
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
# Update types to point to the new cstruct instance
|
|
228
|
+
for name, type_ in self.types.items():
|
|
229
|
+
new_type = copy.copy(type_)
|
|
230
|
+
new_type.cs = cs
|
|
231
|
+
cs.add_type(name, new_type, replace=True)
|
|
222
232
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
new_type.cs = cs
|
|
226
|
-
cs.typedefs[name] = new_type
|
|
233
|
+
for name, value in self.consts.items():
|
|
234
|
+
cs.add_const(name, value)
|
|
227
235
|
|
|
228
236
|
return cs
|
|
229
237
|
|
|
@@ -232,22 +240,34 @@ class cstruct:
|
|
|
232
240
|
self._anonymous_count += 1
|
|
233
241
|
return name
|
|
234
242
|
|
|
243
|
+
def _add_attr(self, name: str, value: Any) -> None:
|
|
244
|
+
# Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
|
|
245
|
+
# to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
|
|
246
|
+
if name in _RESERVED_NAMES:
|
|
247
|
+
return
|
|
248
|
+
setattr(self, name, value)
|
|
249
|
+
|
|
235
250
|
def add_type(self, name: str, type_: type[BaseType] | str, replace: bool = False) -> None:
|
|
236
|
-
"""Add a type or type
|
|
251
|
+
"""Add a type or type alias.
|
|
237
252
|
|
|
238
253
|
Only use this method when creating type aliases or adding already bound types.
|
|
254
|
+
All types will be resolved to their actual type objects prior to being added.
|
|
239
255
|
|
|
240
256
|
Args:
|
|
241
257
|
name: Name of the type to be added.
|
|
242
258
|
type_: The type to be added. Can be a str reference to another type or a compatible type class.
|
|
259
|
+
If a str is given, it will be resolved to the actual type object.
|
|
260
|
+
replace: Whether to replace the type if it already exists.
|
|
243
261
|
|
|
244
262
|
Raises:
|
|
245
263
|
ValueError: If the type already exists.
|
|
246
264
|
"""
|
|
247
|
-
|
|
265
|
+
typeobj = self.resolve(type_)
|
|
266
|
+
if not replace and (existing := self.types.get(name)) is not None and existing is not typeobj:
|
|
248
267
|
raise ValueError(f"Duplicate type: {name}")
|
|
249
268
|
|
|
250
|
-
self.
|
|
269
|
+
self.types[name] = typeobj
|
|
270
|
+
self._add_attr(name, typeobj)
|
|
251
271
|
|
|
252
272
|
addtype = add_type
|
|
253
273
|
|
|
@@ -266,19 +286,56 @@ class cstruct:
|
|
|
266
286
|
alignment: The alignment of the type.
|
|
267
287
|
**kwargs: Additional attributes to add to the type.
|
|
268
288
|
"""
|
|
289
|
+
# In cstruct 5.0 we changed the function signature of _read and _write
|
|
290
|
+
# Check if the function signature is compatible, and warn if not
|
|
291
|
+
for type_to_check in (type_, type_.ArrayType):
|
|
292
|
+
type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "")
|
|
293
|
+
|
|
294
|
+
for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"):
|
|
295
|
+
if not hasattr(type_to_check, method):
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
signature = inspect.signature(getattr(type_to_check, method))
|
|
299
|
+
|
|
300
|
+
# Only warn if the method doesn't accept an endian parameter
|
|
301
|
+
if "endian" not in signature.parameters:
|
|
302
|
+
warnings.warn(
|
|
303
|
+
f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501
|
|
304
|
+
"Please refer to the changelog of dissect.cstruct 5.0 for more information.",
|
|
305
|
+
stacklevel=2,
|
|
306
|
+
)
|
|
307
|
+
|
|
269
308
|
self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
|
|
270
309
|
|
|
310
|
+
def add_const(self, name: str, value: Any) -> None:
|
|
311
|
+
"""Add a constant value.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
name: Name of the constant to be added.
|
|
315
|
+
value: The value of the constant.
|
|
316
|
+
"""
|
|
317
|
+
self.consts[name] = value
|
|
318
|
+
self._add_attr(name, value)
|
|
319
|
+
|
|
320
|
+
def del_const(self, name: str) -> None:
|
|
321
|
+
"""Delete a constant value.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
name: Name of the constant to be deleted.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
KeyError: If the constant does not exist.
|
|
328
|
+
"""
|
|
329
|
+
del self.consts[name]
|
|
330
|
+
self.__dict__.pop(name, None)
|
|
331
|
+
|
|
271
332
|
def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
|
|
272
333
|
"""Parse structures from the given definitions using the given definition type.
|
|
273
334
|
|
|
274
335
|
Definitions can be parsed using different parsers. Currently, there's
|
|
275
336
|
only one supported parser - DEF_CSTYLE. Parsers can add types and
|
|
276
337
|
modify this cstruct instance. Arguments can be passed to parsers
|
|
277
|
-
using kwargs
|
|
278
|
-
|
|
279
|
-
The CSTYLE parser was recently replaced with token based parser,
|
|
280
|
-
instead of a strictly regex based one. The old parser is still available
|
|
281
|
-
by using DEF_LEGACY.
|
|
338
|
+
using ``kwargs``.
|
|
282
339
|
|
|
283
340
|
Args:
|
|
284
341
|
definition: The definition to parse.
|
|
@@ -288,9 +345,9 @@ class cstruct:
|
|
|
288
345
|
deftype = deftype or cstruct.DEF_CSTYLE
|
|
289
346
|
|
|
290
347
|
if deftype == cstruct.DEF_CSTYLE:
|
|
291
|
-
TokenParser(self, **kwargs).parse(definition)
|
|
292
|
-
elif deftype == cstruct.DEF_LEGACY:
|
|
293
348
|
CStyleParser(self, **kwargs).parse(definition)
|
|
349
|
+
else:
|
|
350
|
+
raise ValueError(f"Unknown definition type: {deftype}")
|
|
294
351
|
|
|
295
352
|
return self
|
|
296
353
|
|
|
@@ -307,6 +364,42 @@ class cstruct:
|
|
|
307
364
|
with Path(path).open() as fh:
|
|
308
365
|
self.load(fh.read(), deftype, **kwargs)
|
|
309
366
|
|
|
367
|
+
def cdef(self) -> str:
|
|
368
|
+
"""Render all constants, structure, union, enum and flag definitions back to their C-style definitions.
|
|
369
|
+
|
|
370
|
+
Note that the result is semantically equivalent to the original definitions, but not necessarily identical.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
The C-style definitions as a string.
|
|
374
|
+
"""
|
|
375
|
+
empty = cstruct()
|
|
376
|
+
|
|
377
|
+
blocks = []
|
|
378
|
+
|
|
379
|
+
defines = []
|
|
380
|
+
for name, value in self.consts.items():
|
|
381
|
+
if name in empty.consts:
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
defines.append(f"#define {name} {value!r}")
|
|
385
|
+
|
|
386
|
+
if defines:
|
|
387
|
+
blocks.append("\n".join(defines))
|
|
388
|
+
|
|
389
|
+
for name, typedef in self.types.items():
|
|
390
|
+
if name in empty.types or not isinstance(typedef, type):
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
if not issubclass(typedef, (Structure, Enum, Flag)):
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
if typedef.__name__ == name:
|
|
397
|
+
blocks.append(typedef.cdef())
|
|
398
|
+
else:
|
|
399
|
+
blocks.append(f"typedef {typedef.__name__} {name};")
|
|
400
|
+
|
|
401
|
+
return "\n\n".join(blocks)
|
|
402
|
+
|
|
310
403
|
def read(self, name: str, stream: BinaryIO) -> Any:
|
|
311
404
|
"""Parse data using a given type.
|
|
312
405
|
|
|
@@ -334,20 +427,13 @@ class cstruct:
|
|
|
334
427
|
Raises:
|
|
335
428
|
ResolveError: If the type can't be resolved.
|
|
336
429
|
"""
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return type_name
|
|
430
|
+
if not isinstance(name, str):
|
|
431
|
+
return name
|
|
340
432
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
type_name = self.typedefs[type_name]
|
|
346
|
-
|
|
347
|
-
if not isinstance(type_name, str):
|
|
348
|
-
return type_name
|
|
349
|
-
|
|
350
|
-
raise ResolveError(f"Recursion limit exceeded while resolving type {name}")
|
|
433
|
+
try:
|
|
434
|
+
return self.types[name]
|
|
435
|
+
except KeyError:
|
|
436
|
+
raise ResolveError(f"Unknown type {name}") from None
|
|
351
437
|
|
|
352
438
|
def copy(self) -> cstruct:
|
|
353
439
|
"""Create a copy of this cstruct instance.
|
|
@@ -357,6 +443,28 @@ class cstruct:
|
|
|
357
443
|
"""
|
|
358
444
|
return copy.copy(self)
|
|
359
445
|
|
|
446
|
+
@contextmanager
|
|
447
|
+
def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]:
|
|
448
|
+
"""Context manager for temporarily changing the endianness of this cstruct instance."""
|
|
449
|
+
original = self.endian
|
|
450
|
+
self.endian = normalize_endianness(endian)
|
|
451
|
+
try:
|
|
452
|
+
yield self
|
|
453
|
+
finally:
|
|
454
|
+
self.endian = original
|
|
455
|
+
|
|
456
|
+
@contextmanager
|
|
457
|
+
def big_endian(self) -> Iterator[cstruct]:
|
|
458
|
+
"""Context manager for temporarily changing the endianness to big endian."""
|
|
459
|
+
with self.endianness(">") as cs:
|
|
460
|
+
yield cs
|
|
461
|
+
|
|
462
|
+
@contextmanager
|
|
463
|
+
def little_endian(self) -> Iterator[cstruct]:
|
|
464
|
+
"""Context manager for temporarily changing the endianness to little endian."""
|
|
465
|
+
with self.endianness("<") as cs:
|
|
466
|
+
yield cs
|
|
467
|
+
|
|
360
468
|
def _make_type(
|
|
361
469
|
self,
|
|
362
470
|
name: str,
|
|
@@ -524,6 +632,21 @@ class cstruct:
|
|
|
524
632
|
# signed long long: TypeAlias = int64
|
|
525
633
|
# unsigned long long: TypeAlias = uint64
|
|
526
634
|
|
|
635
|
+
u8: TypeAlias = uint8
|
|
636
|
+
u16: TypeAlias = uint16
|
|
637
|
+
u32: TypeAlias = uint32
|
|
638
|
+
u64: TypeAlias = uint64
|
|
639
|
+
u128: TypeAlias = uint128
|
|
640
|
+
__u8: TypeAlias = uint8
|
|
641
|
+
__u16: TypeAlias = uint16
|
|
642
|
+
__u32: TypeAlias = uint32
|
|
643
|
+
__u64: TypeAlias = uint64
|
|
644
|
+
__u128: TypeAlias = uint128
|
|
645
|
+
uchar: TypeAlias = uint8
|
|
646
|
+
ushort: TypeAlias = uint16
|
|
647
|
+
uint: TypeAlias = uint32
|
|
648
|
+
ulong: TypeAlias = uint32
|
|
649
|
+
|
|
527
650
|
BYTE: TypeAlias = uint8
|
|
528
651
|
CHAR: TypeAlias = char
|
|
529
652
|
SHORT: TypeAlias = int16
|
|
@@ -589,19 +712,17 @@ class cstruct:
|
|
|
589
712
|
_QWORD: TypeAlias = uint64
|
|
590
713
|
_OWORD: TypeAlias = uint128
|
|
591
714
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
uint: TypeAlias = uint32
|
|
604
|
-
ulong: TypeAlias = uint32
|
|
715
|
+
|
|
716
|
+
# Attribute names that types and constants may never shadow: the public API and methods of the cstruct
|
|
717
|
+
# class itself, plus the instance attributes created in __init__.
|
|
718
|
+
_RESERVED_NAMES = frozenset(dir(cstruct)) | {
|
|
719
|
+
"endian",
|
|
720
|
+
"consts",
|
|
721
|
+
"types",
|
|
722
|
+
"includes",
|
|
723
|
+
"pointer",
|
|
724
|
+
"_anonymous_count",
|
|
725
|
+
}
|
|
605
726
|
|
|
606
727
|
|
|
607
728
|
def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
|
|
@@ -5,6 +5,10 @@ class Error(Exception):
|
|
|
5
5
|
pass
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class LexerError(Error):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
class ParserError(Error):
|
|
9
13
|
pass
|
|
10
14
|
|
|
@@ -23,7 +27,3 @@ class ArraySizeError(Error):
|
|
|
23
27
|
|
|
24
28
|
class ExpressionParserError(Error):
|
|
25
29
|
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ExpressionTokenizerError(Error):
|
|
29
|
-
pass
|