dissect.cstruct 5.0.dev7__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/__init__.py +12 -0
- dissect/cstruct/bitbuffer.py +6 -6
- dissect/cstruct/compiler.py +31 -33
- dissect/cstruct/cstruct.py +60 -28
- dissect/cstruct/tools/stubgen.py +5 -5
- dissect/cstruct/types/base.py +121 -59
- 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 -21
- 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 +100 -3
- {dissect_cstruct-5.0.dev7.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.dev7.dist-info/RECORD +0 -32
- dissect_cstruct-5.0.dev7.dist-info/scm_file_list.json +0 -78
- dissect_cstruct-5.0.dev7.dist-info/scm_version.json +0 -8
- {dissect_cstruct-5.0.dev7.dist-info → dissect_cstruct-5.0.dev9.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev7.dist-info → dissect_cstruct-5.0.dev9.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev7.dist-info → dissect_cstruct-5.0.dev9.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev7.dist-info → dissect_cstruct-5.0.dev9.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev7.dist-info → dissect_cstruct-5.0.dev9.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
+
import warnings
|
|
4
5
|
from collections import ChainMap
|
|
5
6
|
from collections.abc import MutableMapping
|
|
6
7
|
from contextlib import contextmanager
|
|
@@ -43,7 +44,7 @@ class Field:
|
|
|
43
44
|
self.type = type_
|
|
44
45
|
self.bits = bits
|
|
45
46
|
self.offset = offset
|
|
46
|
-
self.alignment = type_.
|
|
47
|
+
self.alignment = type_.__alignment__ or 1
|
|
47
48
|
|
|
48
49
|
def __repr__(self) -> str:
|
|
49
50
|
bits_str = f" : {self.bits}" if self.bits else ""
|
|
@@ -53,11 +54,9 @@ class Field:
|
|
|
53
54
|
class StructureMetaType(MetaType):
|
|
54
55
|
"""Base metaclass for cstruct structure type classes."""
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fields: dict[str, Field]
|
|
57
|
+
__members__: dict[str, Field]
|
|
59
58
|
"""Mapping of field names to :class:`Field` objects, including "folded" fields from anonymous structures."""
|
|
60
|
-
|
|
59
|
+
__lookup__: dict[str, Field]
|
|
61
60
|
"""Mapping of "raw" field names to :class:`Field` objects. E.g. holds the anonymous struct and not its fields."""
|
|
62
61
|
__fields__: list[Field]
|
|
63
62
|
"""List of :class:`Field` objects for this structure. This is the structures' Single Source Of Truth."""
|
|
@@ -70,7 +69,7 @@ class StructureMetaType(MetaType):
|
|
|
70
69
|
__static_sizes__: dict[str, int] # Cache of static sizes by field name
|
|
71
70
|
|
|
72
71
|
def __new__(metacls, name: str, bases: tuple[type, ...], classdict: dict[str, Any]) -> Self: # type: ignore
|
|
73
|
-
if (fields := classdict.pop("
|
|
72
|
+
if (fields := classdict.pop("__members__", None)) is not None:
|
|
74
73
|
metacls._update_fields(metacls, fields, align=classdict.get("__align__", False), classdict=classdict)
|
|
75
74
|
|
|
76
75
|
return super().__new__(metacls, name, bases, classdict)
|
|
@@ -96,23 +95,23 @@ class StructureMetaType(MetaType):
|
|
|
96
95
|
if field._name in lookup and field._name != "_":
|
|
97
96
|
raise ValueError(f"Duplicate field name: {field._name}")
|
|
98
97
|
|
|
99
|
-
if not field.type.
|
|
100
|
-
static_sizes[field._name] = field.type.
|
|
98
|
+
if not field.type.__dynamic__:
|
|
99
|
+
static_sizes[field._name] = field.type.__size__
|
|
101
100
|
|
|
102
101
|
if isinstance(field.type, StructureMetaType) and field.name is None:
|
|
103
|
-
for anon_field in field.type.
|
|
102
|
+
for anon_field in field.type.__members__.values():
|
|
104
103
|
attr = f"{field._name}.{anon_field.name}"
|
|
105
104
|
classdict[anon_field.name] = property(attrgetter(attr), attrsetter(attr))
|
|
106
105
|
|
|
107
|
-
lookup.update(field.type.
|
|
106
|
+
lookup.update(field.type.__members__)
|
|
108
107
|
else:
|
|
109
108
|
lookup[field._name] = field
|
|
110
109
|
|
|
111
110
|
raw_lookup[field._name] = field
|
|
112
111
|
|
|
113
112
|
field_names = lookup.keys()
|
|
114
|
-
classdict["
|
|
115
|
-
classdict["
|
|
113
|
+
classdict["__members__"] = lookup
|
|
114
|
+
classdict["__lookup__"] = raw_lookup
|
|
116
115
|
classdict["__fields__"] = fields
|
|
117
116
|
classdict["__static_sizes__"] = static_sizes
|
|
118
117
|
classdict["__bool__"] = _generate__bool__(field_names)
|
|
@@ -138,7 +137,9 @@ class StructureMetaType(MetaType):
|
|
|
138
137
|
from dissect.cstruct import compiler
|
|
139
138
|
|
|
140
139
|
try:
|
|
141
|
-
classdict["_read"] = compiler.Compiler(cls.
|
|
140
|
+
classdict["_read"] = compiler.Compiler(cls.__cs__).compile_read(
|
|
141
|
+
fields, cls.__name__, align=cls.__align__
|
|
142
|
+
)
|
|
142
143
|
classdict["__compiled__"] = True
|
|
143
144
|
except Exception:
|
|
144
145
|
# Revert _read to the slower loop based method
|
|
@@ -148,17 +149,17 @@ class StructureMetaType(MetaType):
|
|
|
148
149
|
# TODO: compile _write
|
|
149
150
|
# TODO: generate cached_property for lazy reading
|
|
150
151
|
|
|
151
|
-
classdict["
|
|
152
|
-
classdict["
|
|
153
|
-
classdict["
|
|
152
|
+
classdict["__size__"] = size
|
|
153
|
+
classdict["__alignment__"] = alignment
|
|
154
|
+
classdict["__dynamic__"] = size is None
|
|
154
155
|
|
|
155
156
|
return classdict
|
|
156
157
|
|
|
157
158
|
def _calculate_size_and_offsets(cls, fields: list[Field], align: bool = False) -> tuple[int | None, int]:
|
|
158
159
|
"""Iterate all fields in this structure to calculate the field offsets and total structure size.
|
|
159
160
|
|
|
160
|
-
If a structure has a dynamic field, further field offsets will be set to None and self.
|
|
161
|
-
will be set to True
|
|
161
|
+
If a structure has a dynamic field, further field offsets will be set to None and ``self.__dynamic__``
|
|
162
|
+
will be set to ``True``.
|
|
162
163
|
"""
|
|
163
164
|
# The current offset, set to None if we become dynamic
|
|
164
165
|
offset = 0
|
|
@@ -188,7 +189,7 @@ class StructureMetaType(MetaType):
|
|
|
188
189
|
field_type = field.type
|
|
189
190
|
|
|
190
191
|
if isinstance(field_type, EnumMetaType):
|
|
191
|
-
field_type = field_type.
|
|
192
|
+
field_type = field_type.__type__
|
|
192
193
|
|
|
193
194
|
# Bit fields have special logic
|
|
194
195
|
if (
|
|
@@ -197,17 +198,17 @@ class StructureMetaType(MetaType):
|
|
|
197
198
|
# Moved to a bit field of another type, e.g. uint16 f1 : 8, uint32 f2 : 8;
|
|
198
199
|
or field_type != bits_type
|
|
199
200
|
# Still processing a bit field, but it's at a different offset due to alignment or a manual offset
|
|
200
|
-
or (bits_type is not None and offset > bits_field_offset + bits_type.
|
|
201
|
+
or (bits_type is not None and offset > bits_field_offset + bits_type.__size__)
|
|
201
202
|
):
|
|
202
203
|
# ... if any of this is true, we have to move to the next field
|
|
203
204
|
bits_type = field_type
|
|
204
|
-
bits_count = bits_type.
|
|
205
|
+
bits_count = bits_type.__size__ * 8
|
|
205
206
|
bits_remaining = bits_count
|
|
206
207
|
bits_field_offset = offset
|
|
207
208
|
|
|
208
209
|
if offset is not None:
|
|
209
210
|
# We're not dynamic, update the structure size and current offset
|
|
210
|
-
offset += bits_type.
|
|
211
|
+
offset += bits_type.__size__
|
|
211
212
|
|
|
212
213
|
field.offset = bits_field_offset
|
|
213
214
|
|
|
@@ -263,7 +264,7 @@ class StructureMetaType(MetaType):
|
|
|
263
264
|
|
|
264
265
|
if field.bits:
|
|
265
266
|
if isinstance(field.type, EnumMetaType):
|
|
266
|
-
value = field.type(bit_buffer.read(field.type.
|
|
267
|
+
value = field.type(bit_buffer.read(field.type.__type__, field.bits))
|
|
267
268
|
else:
|
|
268
269
|
value = bit_buffer.read(field.type, field.bits)
|
|
269
270
|
|
|
@@ -275,12 +276,12 @@ class StructureMetaType(MetaType):
|
|
|
275
276
|
value = field.type._read(stream, context=result, endian=endian)
|
|
276
277
|
|
|
277
278
|
result[field._name] = value
|
|
278
|
-
if field.type.
|
|
279
|
+
if field.type.__dynamic__:
|
|
279
280
|
sizes[field._name] = stream.tell() - offset
|
|
280
281
|
|
|
281
282
|
if cls.__align__:
|
|
282
283
|
# Align the stream
|
|
283
|
-
stream.seek(-stream.tell() & (cls.
|
|
284
|
+
stream.seek(-stream.tell() & (cls.__alignment__ - 1), io.SEEK_CUR)
|
|
284
285
|
|
|
285
286
|
# Using type.__call__ directly calls the __init__ method of the class
|
|
286
287
|
# This is faster than calling cls() and bypasses the metaclass __call__ method
|
|
@@ -305,7 +306,7 @@ class StructureMetaType(MetaType):
|
|
|
305
306
|
field_type = field.type
|
|
306
307
|
|
|
307
308
|
bit_field_type = (
|
|
308
|
-
(field_type.
|
|
309
|
+
(field_type.__type__ if isinstance(field_type, EnumMetaType) else field_type) if field.bits else None
|
|
309
310
|
)
|
|
310
311
|
# Current field is not a bit field, but previous was
|
|
311
312
|
# Or, moved to a bit field of another type, e.g. uint16 f1 : 8, uint32 f2 : 8;
|
|
@@ -338,7 +339,7 @@ class StructureMetaType(MetaType):
|
|
|
338
339
|
|
|
339
340
|
if field.bits:
|
|
340
341
|
if isinstance(field_type, EnumMetaType):
|
|
341
|
-
bit_buffer.write(field_type.
|
|
342
|
+
bit_buffer.write(field_type.__type__, value.value, field.bits)
|
|
342
343
|
else:
|
|
343
344
|
bit_buffer.write(field_type, value, field.bits)
|
|
344
345
|
else:
|
|
@@ -350,7 +351,7 @@ class StructureMetaType(MetaType):
|
|
|
350
351
|
|
|
351
352
|
if cls.__align__:
|
|
352
353
|
# Align the stream
|
|
353
|
-
stream.write(b"\x00" * (-stream.tell() & (cls.
|
|
354
|
+
stream.write(b"\x00" * (-stream.tell() & (cls.__alignment__ - 1)))
|
|
354
355
|
|
|
355
356
|
return num
|
|
356
357
|
|
|
@@ -393,13 +394,13 @@ class StructureMetaType(MetaType):
|
|
|
393
394
|
def _decompose(type_: MetaType) -> tuple[MetaType, int, list[int | Expression | None]]:
|
|
394
395
|
dimensions = []
|
|
395
396
|
while issubclass(type_, BaseArray):
|
|
396
|
-
dimensions.append(type_.
|
|
397
|
-
type_ = type_.
|
|
397
|
+
dimensions.append(type_.__num_entries__)
|
|
398
|
+
type_ = type_.__type__
|
|
398
399
|
|
|
399
400
|
stars = 0
|
|
400
401
|
while issubclass(type_, Pointer):
|
|
401
402
|
stars += 1
|
|
402
|
-
type_ = type_.
|
|
403
|
+
type_ = type_.__type__
|
|
403
404
|
|
|
404
405
|
return type_, stars, dimensions
|
|
405
406
|
|
|
@@ -413,7 +414,7 @@ class StructureMetaType(MetaType):
|
|
|
413
414
|
for token in dim._tokens:
|
|
414
415
|
if (
|
|
415
416
|
token.type in IDENTIFIER_TYPES
|
|
416
|
-
and token.value in cls.
|
|
417
|
+
and token.value in cls.__cs__.consts
|
|
417
418
|
and token.value not in seen
|
|
418
419
|
):
|
|
419
420
|
deps.append(token.value)
|
|
@@ -464,7 +465,7 @@ class StructureMetaType(MetaType):
|
|
|
464
465
|
blocks = []
|
|
465
466
|
for type_ in deps:
|
|
466
467
|
if isinstance(type_, str):
|
|
467
|
-
consts.append(f"#define {type_} {cls.
|
|
468
|
+
consts.append(f"#define {type_} {cls.__cs__.consts[type_]!r}")
|
|
468
469
|
elif isinstance(type_, EnumMetaType):
|
|
469
470
|
blocks.append(type_.cdef())
|
|
470
471
|
else:
|
|
@@ -472,6 +473,26 @@ class StructureMetaType(MetaType):
|
|
|
472
473
|
|
|
473
474
|
return ("\n".join(consts) + "\n\n" if consts else "") + ("\n\n".join(blocks) if blocks else "")
|
|
474
475
|
|
|
476
|
+
@property
|
|
477
|
+
def fields(cls) -> dict[str, Field]:
|
|
478
|
+
"""List of :class:`Field` objects for this structure."""
|
|
479
|
+
warnings.warn(
|
|
480
|
+
"The 'fields' property is deprecated, use '__members__' instead.",
|
|
481
|
+
DeprecationWarning,
|
|
482
|
+
stacklevel=2,
|
|
483
|
+
)
|
|
484
|
+
return cls.__members__
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
def lookup(cls) -> dict[str, Field]:
|
|
488
|
+
"""Mapping of "raw" field names to :class:`Field` objects."""
|
|
489
|
+
warnings.warn(
|
|
490
|
+
"The 'lookup' property is deprecated, use '__lookup__' instead.",
|
|
491
|
+
DeprecationWarning,
|
|
492
|
+
stacklevel=2,
|
|
493
|
+
)
|
|
494
|
+
return cls.__lookup__
|
|
495
|
+
|
|
475
496
|
|
|
476
497
|
class Structure(BaseType, metaclass=StructureMetaType):
|
|
477
498
|
"""Base class for cstruct structure type classes.
|
|
@@ -493,7 +514,7 @@ class Structure(BaseType, metaclass=StructureMetaType):
|
|
|
493
514
|
|
|
494
515
|
def __repr__(self) -> str:
|
|
495
516
|
values = []
|
|
496
|
-
for name, field in self.__class__.
|
|
517
|
+
for name, field in self.__class__.__members__.items():
|
|
497
518
|
value = self[name]
|
|
498
519
|
if issubclass(field.type, int) and not issubclass(field.type, (Pointer, Enum)):
|
|
499
520
|
value = hex(value)
|
|
@@ -529,13 +550,13 @@ class StructureValuesProxy(MutableMapping):
|
|
|
529
550
|
raise KeyError(key)
|
|
530
551
|
|
|
531
552
|
def __contains__(self, key: str) -> bool:
|
|
532
|
-
return key in self._struct.__class__.
|
|
553
|
+
return key in self._struct.__class__.__members__
|
|
533
554
|
|
|
534
555
|
def __iter__(self) -> Iterator[str]:
|
|
535
|
-
return iter(self._struct.__class__.
|
|
556
|
+
return iter(self._struct.__class__.__members__)
|
|
536
557
|
|
|
537
558
|
def __len__(self) -> int:
|
|
538
|
-
return len(self._struct.__class__.
|
|
559
|
+
return len(self._struct.__class__.__members__)
|
|
539
560
|
|
|
540
561
|
def __repr__(self) -> str:
|
|
541
562
|
return repr(dict(self))
|
|
@@ -555,18 +576,18 @@ class UnionMetaType(StructureMetaType):
|
|
|
555
576
|
# If we don't have a _buf attribute, we haven't read from a stream and are initializing with values
|
|
556
577
|
# Set default internal attributes
|
|
557
578
|
object.__setattr__(obj, "_buf", None)
|
|
558
|
-
object.__setattr__(obj, "_endian", cls.
|
|
579
|
+
object.__setattr__(obj, "_endian", cls.__cs__.endian)
|
|
559
580
|
|
|
560
581
|
# Calling with non-stream args or kwargs means we are initializing with values
|
|
561
582
|
if (args and not (len(args) == 1 and (_is_readable_type(args[0]) or _is_buffer_type(args[0])))) or kwargs:
|
|
562
583
|
# We don't support user initialization of dynamic unions yet
|
|
563
|
-
if cls.
|
|
584
|
+
if cls.__dynamic__:
|
|
564
585
|
raise NotImplementedError("Initializing a dynamic union is not yet supported")
|
|
565
586
|
|
|
566
587
|
# User (partial) initialization, rebuild the union
|
|
567
588
|
# First user-provided field is the one used to rebuild the union
|
|
568
589
|
arg_fields = (field._name for _, field in zip(args, cls.__fields__, strict=False))
|
|
569
|
-
kwarg_fields = (name for name in kwargs if name in cls.
|
|
590
|
+
kwarg_fields = (name for name in kwargs if name in cls.__lookup__)
|
|
570
591
|
if (first_field := next(chain(arg_fields, kwarg_fields), None)) is not None:
|
|
571
592
|
obj._rebuild(first_field)
|
|
572
593
|
elif not args and not kwargs:
|
|
@@ -605,12 +626,12 @@ class UnionMetaType(StructureMetaType):
|
|
|
605
626
|
result = {}
|
|
606
627
|
sizes = {}
|
|
607
628
|
|
|
608
|
-
if cls.
|
|
629
|
+
if cls.__size__ is None:
|
|
609
630
|
offset = stream.tell()
|
|
610
631
|
buf = stream
|
|
611
632
|
else:
|
|
612
633
|
offset = 0
|
|
613
|
-
buf = io.BytesIO(stream.read(cls.
|
|
634
|
+
buf = io.BytesIO(stream.read(cls.__size__))
|
|
614
635
|
|
|
615
636
|
for field in cls.__fields__:
|
|
616
637
|
field_type = field.type
|
|
@@ -623,13 +644,13 @@ class UnionMetaType(StructureMetaType):
|
|
|
623
644
|
value = field_type._read(buf, context=result, endian=endian)
|
|
624
645
|
|
|
625
646
|
result[field._name] = value
|
|
626
|
-
if field.type.
|
|
647
|
+
if field.type.__dynamic__:
|
|
627
648
|
sizes[field._name] = buf.tell() - start
|
|
628
649
|
|
|
629
650
|
return result, sizes
|
|
630
651
|
|
|
631
652
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: # type: ignore
|
|
632
|
-
if cls.
|
|
653
|
+
if cls.__size__ is None:
|
|
633
654
|
start = stream.tell()
|
|
634
655
|
result, sizes = cls._read_fields(stream, context=context, endian=endian)
|
|
635
656
|
size = stream.tell() - start
|
|
@@ -638,7 +659,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
638
659
|
else:
|
|
639
660
|
result = {}
|
|
640
661
|
sizes = {}
|
|
641
|
-
buf = stream.read(cls.
|
|
662
|
+
buf = stream.read(cls.__size__)
|
|
642
663
|
|
|
643
664
|
# Create the object and set the values
|
|
644
665
|
# Using type.__call__ directly calls the __init__ method of the class
|
|
@@ -650,7 +671,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
650
671
|
object.__setattr__(obj, "_buf", buf)
|
|
651
672
|
object.__setattr__(obj, "_endian", endian)
|
|
652
673
|
|
|
653
|
-
if cls.
|
|
674
|
+
if cls.__size__ is not None:
|
|
654
675
|
obj._update()
|
|
655
676
|
|
|
656
677
|
# Proxify any nested structures
|
|
@@ -659,14 +680,14 @@ class UnionMetaType(StructureMetaType):
|
|
|
659
680
|
return obj
|
|
660
681
|
|
|
661
682
|
def _write(cls, stream: BinaryIO, data: Union, *, endian: Endianness) -> int:
|
|
662
|
-
if cls.
|
|
683
|
+
if cls.__dynamic__:
|
|
663
684
|
raise NotImplementedError("Writing dynamic unions is not yet supported")
|
|
664
685
|
|
|
665
686
|
offset = stream.tell()
|
|
666
687
|
expected_offset = offset + len(cls)
|
|
667
688
|
|
|
668
689
|
# Sort by largest field
|
|
669
|
-
fields = sorted(cls.__fields__, key=lambda e: e.type.
|
|
690
|
+
fields = sorted(cls.__fields__, key=lambda e: e.type.__size__ or 0, reverse=True)
|
|
670
691
|
anonymous_struct = False
|
|
671
692
|
|
|
672
693
|
# Try to write by largest field
|
|
@@ -701,7 +722,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
701
722
|
return self.__class__ is other.__class__ and bytes(self) == bytes(other)
|
|
702
723
|
|
|
703
724
|
def __setattr__(self, attr: str, value: Any) -> None:
|
|
704
|
-
if self.__class__.
|
|
725
|
+
if self.__class__.__dynamic__:
|
|
705
726
|
raise NotImplementedError("Modifying a dynamic union is not yet supported")
|
|
706
727
|
|
|
707
728
|
super().__setattr__(attr, value)
|
|
@@ -709,10 +730,10 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
709
730
|
|
|
710
731
|
def _rebuild(self, attr: str) -> None:
|
|
711
732
|
if (cur_buf := getattr(self, "_buf", None)) is None:
|
|
712
|
-
cur_buf = b"\x00" * self.__class__.
|
|
733
|
+
cur_buf = b"\x00" * self.__class__.__size__
|
|
713
734
|
|
|
714
735
|
buf = io.BytesIO(cur_buf)
|
|
715
|
-
field = self.__class__.
|
|
736
|
+
field = self.__class__.__lookup__[attr]
|
|
716
737
|
if field.offset:
|
|
717
738
|
buf.seek(field.offset)
|
|
718
739
|
|
dissect/cstruct/types/void.py
CHANGED
dissect/cstruct/types/wchar.py
CHANGED
|
@@ -17,7 +17,7 @@ class WcharArray(str, BaseArray):
|
|
|
17
17
|
|
|
18
18
|
@classmethod
|
|
19
19
|
def __default__(cls) -> WcharArray:
|
|
20
|
-
return type.__call__(cls, "\x00" * (0 if cls.
|
|
20
|
+
return type.__call__(cls, "\x00" * (0 if cls.__dynamic__ or cls.__null_terminated__ else cls.__num_entries__))
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
23
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> WcharArray:
|
|
@@ -25,12 +25,12 @@ class WcharArray(str, BaseArray):
|
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def _write(cls, stream: BinaryIO, data: str, *, endian: Endianness) -> int:
|
|
28
|
-
if cls.
|
|
28
|
+
if cls.__null_terminated__:
|
|
29
29
|
data += "\x00"
|
|
30
30
|
|
|
31
|
-
if not cls.
|
|
31
|
+
if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(data))):
|
|
32
32
|
if remaining < 0:
|
|
33
|
-
raise ArraySizeError(f"Expected static array size {cls.
|
|
33
|
+
raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
|
|
34
34
|
data += "\x00" * remaining
|
|
35
35
|
|
|
36
36
|
return stream.write(data.encode(Wchar.__encoding_map__[endian]))
|
|
@@ -39,7 +39,7 @@ class WcharArray(str, BaseArray):
|
|
|
39
39
|
class Wchar(str, BaseType):
|
|
40
40
|
"""Wide-character type for reading and writing UTF-16 characters."""
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
__ArrayType__ = WcharArray
|
|
43
43
|
|
|
44
44
|
__slots__ = ()
|
|
45
45
|
__encoding_map__: ClassVar[dict[Endianness, str]] = {
|
dissect/cstruct/util.py
CHANGED
|
@@ -3,15 +3,17 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import pprint
|
|
5
5
|
import string
|
|
6
|
+
import struct
|
|
6
7
|
import sys
|
|
7
8
|
from enum import Enum
|
|
8
|
-
from
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from collections.abc import Iterator
|
|
12
14
|
from typing import Literal
|
|
13
15
|
|
|
14
|
-
from dissect.cstruct.cstruct import AllowedEndianness
|
|
16
|
+
from dissect.cstruct.cstruct import AllowedEndianness, Endianness
|
|
15
17
|
from dissect.cstruct.types.base import BaseType
|
|
16
18
|
from dissect.cstruct.types.structure import Structure
|
|
17
19
|
|
|
@@ -64,6 +66,36 @@ ENDIANNESS_TO_BYTEORDER_MAP: dict[AllowedEndianness, Literal["big", "little"]] =
|
|
|
64
66
|
"big": "big",
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
|
|
70
|
+
"<": "<",
|
|
71
|
+
">": ">",
|
|
72
|
+
"!": "!",
|
|
73
|
+
"@": "@",
|
|
74
|
+
"=": "=",
|
|
75
|
+
"network": "!",
|
|
76
|
+
"little": "<",
|
|
77
|
+
"big": ">",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def normalize_endianness(endian: AllowedEndianness) -> Endianness:
|
|
82
|
+
"""Normalize an endianness string to one of the standard format characters."""
|
|
83
|
+
try:
|
|
84
|
+
return ENDIANNESS_MAP[endian]
|
|
85
|
+
except KeyError:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
|
|
88
|
+
) from None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class classproperty:
|
|
92
|
+
def __init__(self, fget: classmethod) -> None:
|
|
93
|
+
self.fget = fget
|
|
94
|
+
|
|
95
|
+
def __get__(self, obj: object | None, owner: type) -> Any:
|
|
96
|
+
return self.fget(owner)
|
|
97
|
+
|
|
98
|
+
|
|
67
99
|
Palette = list[tuple[int, str]]
|
|
68
100
|
|
|
69
101
|
|
|
@@ -327,6 +359,11 @@ def dumpstruct(
|
|
|
327
359
|
return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
|
|
328
360
|
|
|
329
361
|
|
|
362
|
+
@lru_cache(1024)
|
|
363
|
+
def _struct(endian: str, fmt: str) -> struct.Struct:
|
|
364
|
+
return struct.Struct(f"{endian}{fmt}")
|
|
365
|
+
|
|
366
|
+
|
|
330
367
|
def pack(value: int, size: int | None = None, endian: AllowedEndianness = "little") -> bytes:
|
|
331
368
|
"""Pack an integer value to a given bit size, endianness.
|
|
332
369
|
|
|
@@ -400,6 +437,36 @@ def p64(value: int, endian: AllowedEndianness = "little") -> bytes:
|
|
|
400
437
|
return pack(value, 64, endian)
|
|
401
438
|
|
|
402
439
|
|
|
440
|
+
def pf16(value: float, endian: AllowedEndianness = "little") -> bytes:
|
|
441
|
+
"""Pack a 16 bit float.
|
|
442
|
+
|
|
443
|
+
Arguments:
|
|
444
|
+
value: Value to pack.
|
|
445
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
446
|
+
"""
|
|
447
|
+
return _struct(normalize_endianness(endian), "e").pack(value)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def pf32(value: float, endian: AllowedEndianness = "little") -> bytes:
|
|
451
|
+
"""Pack a 32 bit float.
|
|
452
|
+
|
|
453
|
+
Arguments:
|
|
454
|
+
value: Value to pack.
|
|
455
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
456
|
+
"""
|
|
457
|
+
return _struct(normalize_endianness(endian), "f").pack(value)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def pf64(value: float, endian: AllowedEndianness = "little") -> bytes:
|
|
461
|
+
"""Pack a 64 bit float.
|
|
462
|
+
|
|
463
|
+
Arguments:
|
|
464
|
+
value: Value to pack.
|
|
465
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
466
|
+
"""
|
|
467
|
+
return _struct(normalize_endianness(endian), "d").pack(value)
|
|
468
|
+
|
|
469
|
+
|
|
403
470
|
def u8(value: bytes, endian: AllowedEndianness = "little", sign: bool = False) -> int:
|
|
404
471
|
"""Unpack an 8 bit integer.
|
|
405
472
|
|
|
@@ -444,6 +511,36 @@ def u64(value: bytes, endian: AllowedEndianness = "little", sign: bool = False)
|
|
|
444
511
|
return unpack(value, 64, endian, sign)
|
|
445
512
|
|
|
446
513
|
|
|
514
|
+
def f16(value: bytes, endian: AllowedEndianness = "little") -> float:
|
|
515
|
+
"""Unpack a 16 bit float.
|
|
516
|
+
|
|
517
|
+
Arguments:
|
|
518
|
+
value: Value to unpack.
|
|
519
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
520
|
+
"""
|
|
521
|
+
return _struct(normalize_endianness(endian), "e").unpack(value)[0]
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def f32(value: bytes, endian: AllowedEndianness = "little") -> float:
|
|
525
|
+
"""Unpack a 32 bit float.
|
|
526
|
+
|
|
527
|
+
Arguments:
|
|
528
|
+
value: Value to unpack.
|
|
529
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
530
|
+
"""
|
|
531
|
+
return _struct(normalize_endianness(endian), "f").unpack(value)[0]
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def f64(value: bytes, endian: AllowedEndianness = "little") -> float:
|
|
535
|
+
"""Unpack a 64 bit float.
|
|
536
|
+
|
|
537
|
+
Arguments:
|
|
538
|
+
value: Value to unpack.
|
|
539
|
+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
|
|
540
|
+
"""
|
|
541
|
+
return _struct(normalize_endianness(endian), "d").unpack(value)[0]
|
|
542
|
+
|
|
543
|
+
|
|
447
544
|
def swap(value: int, size: int) -> int:
|
|
448
545
|
"""Swap the endianness of an integer with a given bit size.
|
|
449
546
|
|
|
@@ -488,7 +585,7 @@ def sizeof(type_: type[BaseType] | BaseType) -> int:
|
|
|
488
585
|
|
|
489
586
|
def offsetof(type_: type[Structure], name: str) -> int:
|
|
490
587
|
"""Get the offset of a field in a structure."""
|
|
491
|
-
if (field := type_.
|
|
588
|
+
if (field := type_.__members__.get(name)) is None:
|
|
492
589
|
raise ValueError(f"Structure '{type_.__name__}' does not have a field named '{name}'")
|
|
493
590
|
if (offset := field.offset) is None:
|
|
494
591
|
raise ValueError(f"Field '{field._name}' of structure '{type_.__name__}' does not have a known offset")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.dev9
|
|
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-Expression: Apache-2.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
dissect/cstruct/__init__.py,sha256=KOyNeUvu4c0JaAiyOfj7iftGwUIjTmD8MwHtFsLPLSU,1504
|
|
2
|
+
dissect/cstruct/bitbuffer.py,sha256=aNzY4lkWe8ffAc8j7FODXzwZc8gZwbey77UosyoYCsw,2711
|
|
3
|
+
dissect/cstruct/compiler.py,sha256=zcrO5CWCF2vWo2es-pFGn-Mvt-RTMh8W9bntK-hn85k,14343
|
|
4
|
+
dissect/cstruct/cstruct.py,sha256=wC4Xd8MBxTCyl-IC1I2c3etI8M6C6D5yXVfELbfQk6c,25656
|
|
5
|
+
dissect/cstruct/exception.py,sha256=RqD08LuBkbMqa3iQ1A05DNN8XgEmQR_SUnguwe9LGo8,316
|
|
6
|
+
dissect/cstruct/expression.py,sha256=MEwqHGgqA2fUk4kYk4lRLDWNTAMhB4-saJ5BgTxl7OA,7956
|
|
7
|
+
dissect/cstruct/lexer.py,sha256=NDUfAWgyxgRNRuVO8ExXQ51LqQXS2mv9bywVa_M7tFQ,19665
|
|
8
|
+
dissect/cstruct/parser.py,sha256=xCK-1O8c86sVFvjaXXSRgTsJWWd0ncKM6KJf2wbyGG8,21348
|
|
9
|
+
dissect/cstruct/util.py,sha256=vAf-kdOkk3zprW_gD6QmQeN3VIRgQqhPS4B_ha1jEpQ,18040
|
|
10
|
+
dissect/cstruct/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
dissect/cstruct/tools/stubgen.py,sha256=MeZkLmSeNSLNcAEwlAdIGv1lKvlvK3UQ4WN85ovsxTM,7672
|
|
12
|
+
dissect/cstruct/types/__init__.py,sha256=6P71Pu94Dv7RNjZpDZ19pqzx-G0QzmQw4q0-3zWALso,911
|
|
13
|
+
dissect/cstruct/types/base.py,sha256=vJUgrnsPg2ZA9Mel2P9JBBstI9z9oMxqi5Pnx9FlBIA,15023
|
|
14
|
+
dissect/cstruct/types/char.py,sha256=zsRMlcZGW1cHYH_xQBNORDnM_a5CXcuZLGly1N1i_Ms,3029
|
|
15
|
+
dissect/cstruct/types/enum.py,sha256=j2U6oE_80c4H2505YMddHPnF99az2K5IuDhX40Q7OEI,8590
|
|
16
|
+
dissect/cstruct/types/flag.py,sha256=7xxQjFEDPHSQOxBHgcc75WzPiAxflOquwr7Z7c3erLE,2347
|
|
17
|
+
dissect/cstruct/types/int.py,sha256=RCT3YjrajqRqh8cyeM-zaqzCmBfeJrXkD5W1FcRzXag,1340
|
|
18
|
+
dissect/cstruct/types/leb128.py,sha256=V97XTJT_Xbw1LpTwTN1OkjPHuQQAQUxd8OGuA6yEDmE,2387
|
|
19
|
+
dissect/cstruct/types/packed.py,sha256=QF1mAqgxLbgiibRZLhn6cp6SnuH2IxjGr9DsQdXCCSE,2713
|
|
20
|
+
dissect/cstruct/types/pointer.py,sha256=FNuB9bfENWRebbKX-R6Vs6xWPMHepjvOhhomeUhr7I8,3985
|
|
21
|
+
dissect/cstruct/types/structure.py,sha256=QZBVCCERu9eoXLqrumF7Kqy0gudywl12-cSZx6n3tPI,38402
|
|
22
|
+
dissect/cstruct/types/void.py,sha256=fV4hKk50uop_EKbR1nfy1cdtAzvUYrSymXsdakCyFk8,1222
|
|
23
|
+
dissect/cstruct/types/wchar.py,sha256=X-dRu6ujjQIIsBfzKbujwMO5skKdARtptF6P7R5v8Jo,3232
|
|
24
|
+
dissect_cstruct-5.0.dev9.dist-info/licenses/COPYRIGHT,sha256=H-18RXfshdH9AdHwW2eO1Xa-5s6tY5eipHh5c0whDu4,316
|
|
25
|
+
dissect_cstruct-5.0.dev9.dist-info/licenses/LICENSE,sha256=PhUqiw6jAh2KbBdVRPBq_hfAvfcTBin7nZ3CK7NQbTM,11341
|
|
26
|
+
dissect_cstruct-5.0.dev9.dist-info/METADATA,sha256=1TPbiY0D5dnQscZ0QDgEJvsSjlguJxBNXgqM3_Xf0V4,9255
|
|
27
|
+
dissect_cstruct-5.0.dev9.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
28
|
+
dissect_cstruct-5.0.dev9.dist-info/entry_points.txt,sha256=z53zqZqwD2OLrAkRwrP4wTeiU9CQe7xrMly0T2c0_wQ,71
|
|
29
|
+
dissect_cstruct-5.0.dev9.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
30
|
+
dissect_cstruct-5.0.dev9.dist-info/RECORD,,
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
dissect/cstruct/__init__.py,sha256=8VNVQfPH4aH5V_kD-jvtLPwQ-ag9ppKHTy3dbxIR6ik,1378
|
|
2
|
-
dissect/cstruct/bitbuffer.py,sha256=lZuLUOluHx4mzFGRp62_FO45_iJwXayyzv9-v8b0Q08,2687
|
|
3
|
-
dissect/cstruct/compiler.py,sha256=vVg2H-pi9ugQ8Xvhs1JQ--4BjA5gtO2tW-1W8jr36V4,14283
|
|
4
|
-
dissect/cstruct/cstruct.py,sha256=bsCesSpbE0H4MUsHwb2iEbeadOBlOSAVENwyoP-UkP8,24929
|
|
5
|
-
dissect/cstruct/exception.py,sha256=RqD08LuBkbMqa3iQ1A05DNN8XgEmQR_SUnguwe9LGo8,316
|
|
6
|
-
dissect/cstruct/expression.py,sha256=MEwqHGgqA2fUk4kYk4lRLDWNTAMhB4-saJ5BgTxl7OA,7956
|
|
7
|
-
dissect/cstruct/lexer.py,sha256=NDUfAWgyxgRNRuVO8ExXQ51LqQXS2mv9bywVa_M7tFQ,19665
|
|
8
|
-
dissect/cstruct/parser.py,sha256=xCK-1O8c86sVFvjaXXSRgTsJWWd0ncKM6KJf2wbyGG8,21348
|
|
9
|
-
dissect/cstruct/util.py,sha256=S__kLEb9HKZg_WZR4CGtXDxkIkERLKbzFTHoU8FT8tU,15313
|
|
10
|
-
dissect/cstruct/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
dissect/cstruct/tools/stubgen.py,sha256=FLQieYE-MeKK31DcrDzwNUropUYHPjwdfcDb1X2L6j4,7651
|
|
12
|
-
dissect/cstruct/types/__init__.py,sha256=6P71Pu94Dv7RNjZpDZ19pqzx-G0QzmQw4q0-3zWALso,911
|
|
13
|
-
dissect/cstruct/types/base.py,sha256=rU1S2FQllx7X7BQG7qMOwNa7waxAJyuTHTNwtkiAz0g,12763
|
|
14
|
-
dissect/cstruct/types/char.py,sha256=s4zDmnbuOjwHgcsnEihjSqgC6H3t1jsfBTYH2ykwvHU,2997
|
|
15
|
-
dissect/cstruct/types/enum.py,sha256=YIWyP3H0tdxmuSI82enoULiN0YevpnU0UaY5ioBCYfI,8518
|
|
16
|
-
dissect/cstruct/types/flag.py,sha256=7xxQjFEDPHSQOxBHgcc75WzPiAxflOquwr7Z7c3erLE,2347
|
|
17
|
-
dissect/cstruct/types/int.py,sha256=Ewejng5pBYnQHPtzL5W0wQMJVUPYyrY8ALES8mFaabI,1324
|
|
18
|
-
dissect/cstruct/types/leb128.py,sha256=V97XTJT_Xbw1LpTwTN1OkjPHuQQAQUxd8OGuA6yEDmE,2387
|
|
19
|
-
dissect/cstruct/types/packed.py,sha256=78nQWm-yylgfK3858nN-XZhO5jiqyO-piGNEyPD2ps4,2458
|
|
20
|
-
dissect/cstruct/types/pointer.py,sha256=eGMrB_tTnKXKmYrcqyA_12IdukXcxRG3WOeQ1dgt7lk,3927
|
|
21
|
-
dissect/cstruct/types/structure.py,sha256=7pBg0moZbszeRomFEp5CbAHZcd1J8IVTmEG9Mo2eKS0,37571
|
|
22
|
-
dissect/cstruct/types/void.py,sha256=LCYnTcxsFPaxln36QEUxWuCZVioHRRckMuqtlm6tH54,1218
|
|
23
|
-
dissect/cstruct/types/wchar.py,sha256=YCYlu91D9AMVpWOLbVMdpvDDURH-EqyppM5UMDU5UQw,3200
|
|
24
|
-
dissect_cstruct-5.0.dev7.dist-info/licenses/COPYRIGHT,sha256=H-18RXfshdH9AdHwW2eO1Xa-5s6tY5eipHh5c0whDu4,316
|
|
25
|
-
dissect_cstruct-5.0.dev7.dist-info/licenses/LICENSE,sha256=PhUqiw6jAh2KbBdVRPBq_hfAvfcTBin7nZ3CK7NQbTM,11341
|
|
26
|
-
dissect_cstruct-5.0.dev7.dist-info/METADATA,sha256=IUF-_jFf7_GABYqAww23JWUTr-f73OKwmaICtDcOGc8,9255
|
|
27
|
-
dissect_cstruct-5.0.dev7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
28
|
-
dissect_cstruct-5.0.dev7.dist-info/entry_points.txt,sha256=z53zqZqwD2OLrAkRwrP4wTeiU9CQe7xrMly0T2c0_wQ,71
|
|
29
|
-
dissect_cstruct-5.0.dev7.dist-info/scm_file_list.json,sha256=nuFWhDGL6VmjcwOB94CSfjNBblnsa9imUy8X4BLyvhw,2306
|
|
30
|
-
dissect_cstruct-5.0.dev7.dist-info/scm_version.json,sha256=wMGNV4ui6dgAUOW0hK23797wTuOlVvhVvvLFz_dXXFk,162
|
|
31
|
-
dissect_cstruct-5.0.dev7.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
32
|
-
dissect_cstruct-5.0.dev7.dist-info/RECORD,,
|