dissect.cstruct 5.0.dev12__py3-none-any.whl → 5.0.dev13__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/compiler.py +119 -81
- dissect/cstruct/cstruct.py +1 -1
- dissect/cstruct/types/base.py +23 -9
- dissect/cstruct/types/char.py +7 -3
- dissect/cstruct/types/structure.py +31 -32
- dissect/cstruct/types/wchar.py +18 -7
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/METADATA +1 -1
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/RECORD +13 -13
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/top_level.txt +0 -0
dissect/cstruct/compiler.py
CHANGED
|
@@ -182,8 +182,10 @@ _GLOBALS = {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
class
|
|
186
|
-
"""
|
|
185
|
+
class _SourceGenerator:
|
|
186
|
+
"""Base class for generating the source code of an optimized method of a structure or union."""
|
|
187
|
+
|
|
188
|
+
name: str
|
|
187
189
|
|
|
188
190
|
def __init__(self, cls: type[Structure], fields: list[Field], inline: bool = True):
|
|
189
191
|
self.cls = cls
|
|
@@ -191,24 +193,17 @@ class _ReadSourceGenerator:
|
|
|
191
193
|
self.align = cls.__align__
|
|
192
194
|
self.inline = inline and not self.align
|
|
193
195
|
|
|
196
|
+
# The values available to the generated code
|
|
197
|
+
self.symbols: dict[str, Any] = {"cs": cls.__cs__}
|
|
198
|
+
self._symbol_names: dict[int, str] = {}
|
|
199
|
+
|
|
194
200
|
# Witnesses of inlined structure types (symbol of the type -> symbol of its __init__ at compile time)
|
|
195
201
|
self._witnesses: dict[str, str] = {}
|
|
196
|
-
self._fallback_symbol: str | None = None
|
|
197
202
|
|
|
198
203
|
# The state of the bit buffer: the type of its current value and the remaining bits
|
|
199
204
|
self._bits: tuple[type[BaseType], int] | None = None
|
|
200
205
|
self._var_ids: dict[str, int] = {}
|
|
201
206
|
|
|
202
|
-
# The values available to the generated code
|
|
203
|
-
self.symbols: dict[str, Any] = {"cs": cls.__cs__}
|
|
204
|
-
self._symbol_names: dict[int, str] = {}
|
|
205
|
-
|
|
206
|
-
# (field name, local variable name) of all fields assigned so far
|
|
207
|
-
self._assigned: list[tuple[str, str]] = []
|
|
208
|
-
self._has_context = False
|
|
209
|
-
self._uses_start = False
|
|
210
|
-
self._uses_sizes = False
|
|
211
|
-
|
|
212
207
|
def _type_symbol(self, type_: type[BaseType]) -> str:
|
|
213
208
|
"""Add a type as symbol, returns its name."""
|
|
214
209
|
return self._add_symbol(type_, "type")
|
|
@@ -220,39 +215,12 @@ class _ReadSourceGenerator:
|
|
|
220
215
|
self.symbols[symbol] = value
|
|
221
216
|
return symbol
|
|
222
217
|
|
|
223
|
-
def _context(self, lines: list[str]) -> str:
|
|
224
|
-
"""Return the name of the context dictionary, creating it from the fields read so far if needed."""
|
|
225
|
-
if not self._has_context:
|
|
226
|
-
self._has_context = True
|
|
227
|
-
items = ", ".join(f"{name!r}: {var}" for name, var in self._assigned)
|
|
228
|
-
lines.append(f"values = {{{items}}}")
|
|
229
|
-
|
|
230
|
-
return "values"
|
|
231
|
-
|
|
232
218
|
def _var(self, name: str) -> str:
|
|
233
219
|
"""Return a unique variable name for the generated code."""
|
|
234
220
|
index = self._var_ids.get(name, 0)
|
|
235
221
|
self._var_ids[name] = index + 1
|
|
236
222
|
return f"_{name}{index}"
|
|
237
223
|
|
|
238
|
-
def _assign(self, field: Field, value: str) -> str:
|
|
239
|
-
"""Assign a value to a new local variable for the given field, and the context dictionary if it exists."""
|
|
240
|
-
var = f"_value{len(self._assigned)}"
|
|
241
|
-
self._assigned.append((field._name, var))
|
|
242
|
-
if self._has_context:
|
|
243
|
-
return f"{var} = values[{field._name!r}] = {value}"
|
|
244
|
-
return f"{var} = {value}"
|
|
245
|
-
|
|
246
|
-
def _assign_local(self, field: Field, value: str) -> list[str]:
|
|
247
|
-
return [self._assign(field, value)]
|
|
248
|
-
|
|
249
|
-
def _fallback(self) -> str:
|
|
250
|
-
"""Return the symbol of the non-inlined fallback read function."""
|
|
251
|
-
if self._fallback_symbol is None:
|
|
252
|
-
fallback = _Fallback(self.cls, self.fields)
|
|
253
|
-
self._fallback_symbol = self._add_symbol(fallback, "fallback")
|
|
254
|
-
return self._fallback_symbol
|
|
255
|
-
|
|
256
224
|
def _witness(self, type_: type[Structure]) -> str:
|
|
257
225
|
"""Add an inlined type as symbol, with a witness to detect modifications to it after compilation."""
|
|
258
226
|
symbol = self._type_symbol(type_)
|
|
@@ -263,10 +231,22 @@ class _ReadSourceGenerator:
|
|
|
263
231
|
"""Return the conditions that are true if any of the inlined types were modified after compilation."""
|
|
264
232
|
return [f"{type_}.__init__ is not {init}" for type_, init in self._witnesses.items()]
|
|
265
233
|
|
|
234
|
+
def _starts_bit_value(self, field: Field) -> bool:
|
|
235
|
+
"""Return whether a bit field is the first of a new value of the bit buffer."""
|
|
236
|
+
return self._bits is None or self._bits[1] == 0 or self._bits[0] is not _get_bit_buffer_type(field)
|
|
237
|
+
|
|
238
|
+
def _advance_bits(self, field: Field) -> int:
|
|
239
|
+
"""Update the state of the bit buffer for a bit field, returns the bits that were remaining before it."""
|
|
240
|
+
if self._starts_bit_value(field):
|
|
241
|
+
self._bits = (_get_bit_buffer_type(field), _get_bits_type(field).__size__ * 8)
|
|
242
|
+
remaining = self._bits[1]
|
|
243
|
+
self._bits = (self._bits[0], remaining - field.bits)
|
|
244
|
+
return remaining
|
|
245
|
+
|
|
266
246
|
def generate(self) -> MethodType:
|
|
267
247
|
source = self.generate_source()
|
|
268
248
|
|
|
269
|
-
code = python_compile(source, f"<compiled {self.cls.__name__}.
|
|
249
|
+
code = python_compile(source, f"<compiled {self.cls.__name__}.{self.name}>", "exec")
|
|
270
250
|
exec(code, {**_GLOBALS, **self.symbols}, d := {})
|
|
271
251
|
obj = d.popitem()[1]
|
|
272
252
|
obj.__source__ = source
|
|
@@ -278,6 +258,56 @@ class _ReadSourceGenerator:
|
|
|
278
258
|
return self._generate_union_source()
|
|
279
259
|
return self._generate_struct_source()
|
|
280
260
|
|
|
261
|
+
def _generate_struct_source(self) -> str:
|
|
262
|
+
raise NotImplementedError
|
|
263
|
+
|
|
264
|
+
def _generate_union_source(self) -> str:
|
|
265
|
+
raise NotImplementedError
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class _ReadSourceGenerator(_SourceGenerator):
|
|
269
|
+
"""Generates the source code of an optimized ``_read`` method for a structure or union."""
|
|
270
|
+
|
|
271
|
+
name = "_read"
|
|
272
|
+
|
|
273
|
+
def __init__(self, cls: type[Structure], fields: list[Field], inline: bool = True):
|
|
274
|
+
super().__init__(cls, fields, inline)
|
|
275
|
+
|
|
276
|
+
self._fallback_symbol: str | None = None
|
|
277
|
+
|
|
278
|
+
# (field name, local variable name) of all fields assigned so far
|
|
279
|
+
self._assigned: list[tuple[str, str]] = []
|
|
280
|
+
self._has_context = False
|
|
281
|
+
self._uses_start = False
|
|
282
|
+
self._uses_sizes = False
|
|
283
|
+
|
|
284
|
+
def _context(self, lines: list[str]) -> str:
|
|
285
|
+
"""Return the name of the context dictionary, creating it from the fields read so far if needed."""
|
|
286
|
+
if not self._has_context:
|
|
287
|
+
self._has_context = True
|
|
288
|
+
items = ", ".join(f"{name!r}: {var}" for name, var in self._assigned)
|
|
289
|
+
lines.append(f"values = {{{items}}}")
|
|
290
|
+
|
|
291
|
+
return "values"
|
|
292
|
+
|
|
293
|
+
def _assign(self, field: Field, value: str) -> str:
|
|
294
|
+
"""Assign a value to a new local variable for the given field, and the context dictionary if it exists."""
|
|
295
|
+
var = f"_value{len(self._assigned)}"
|
|
296
|
+
self._assigned.append((field._name, var))
|
|
297
|
+
if self._has_context:
|
|
298
|
+
return f"{var} = values[{field._name!r}] = {value}"
|
|
299
|
+
return f"{var} = {value}"
|
|
300
|
+
|
|
301
|
+
def _assign_local(self, field: Field, value: str) -> list[str]:
|
|
302
|
+
return [self._assign(field, value)]
|
|
303
|
+
|
|
304
|
+
def _fallback(self) -> str:
|
|
305
|
+
"""Return the symbol of the non-inlined fallback read function."""
|
|
306
|
+
if self._fallback_symbol is None:
|
|
307
|
+
fallback = _Fallback(self.cls, self.fields)
|
|
308
|
+
self._fallback_symbol = self._add_symbol(fallback, "fallback")
|
|
309
|
+
return self._fallback_symbol
|
|
310
|
+
|
|
281
311
|
def _generate_struct_source(self) -> str:
|
|
282
312
|
"""Generate the code for reading a structure."""
|
|
283
313
|
read_code = "\n".join(self._generate_fields_source())
|
|
@@ -405,6 +435,16 @@ class _ReadSourceGenerator:
|
|
|
405
435
|
|
|
406
436
|
# Everything else - basic and composite types (and arrays of them), and inlined structures
|
|
407
437
|
else:
|
|
438
|
+
if field.offset is None:
|
|
439
|
+
# The alignment depends on the position in the stream, so this needs a new block
|
|
440
|
+
if self.align:
|
|
441
|
+
yield from flush()
|
|
442
|
+
# A block is read in order, so a field before the current position needs a new block
|
|
443
|
+
elif current_offset is None or field.offset < current_offset:
|
|
444
|
+
yield from flush()
|
|
445
|
+
elif current_block:
|
|
446
|
+
current_offset = field.offset
|
|
447
|
+
|
|
408
448
|
if not current_block:
|
|
409
449
|
yield from align_to_field(field)
|
|
410
450
|
current_block.append(field)
|
|
@@ -456,18 +496,11 @@ class _ReadSourceGenerator:
|
|
|
456
496
|
element = self._type_symbol(field.type.__type__)
|
|
457
497
|
return f"_type_call({type_}, {element}._read_array(stream, _count, context={context}, endian=endian))"
|
|
458
498
|
|
|
459
|
-
def _starts_bit_value(self, field: Field) -> bool:
|
|
460
|
-
"""Return whether a bit field is read from a new value of the bit buffer."""
|
|
461
|
-
return self._bits is None or self._bits[1] == 0 or self._bits[0] is not _get_bits_type(field)
|
|
462
|
-
|
|
463
499
|
def _generate_bits_source(self, field: Field) -> Iterator[str]:
|
|
464
500
|
"""Generate the code for reading a bit field."""
|
|
465
501
|
read_type = _get_bits_type(field)
|
|
466
502
|
new_value = self._starts_bit_value(field)
|
|
467
|
-
|
|
468
|
-
self._bits = (read_type, read_type.__size__ * 8)
|
|
469
|
-
remaining = self._bits[1]
|
|
470
|
-
self._bits = (read_type, remaining - field.bits)
|
|
503
|
+
remaining = self._advance_bits(field)
|
|
471
504
|
|
|
472
505
|
lines = []
|
|
473
506
|
if new_value:
|
|
@@ -498,7 +531,7 @@ class _ReadSourceGenerator:
|
|
|
498
531
|
"""Generate the code for reading consecutive packed fields."""
|
|
499
532
|
unpack = _Unpack("data")
|
|
500
533
|
reads = []
|
|
501
|
-
size = self._pack_fields(fields, 0, unpack, reads, self._assign_local
|
|
534
|
+
size = self._pack_fields(fields, 0, unpack, reads, self._assign_local)
|
|
502
535
|
|
|
503
536
|
lines = [f"buf = stream.read({size})", _check_eof(size)]
|
|
504
537
|
if unpack.has_values():
|
|
@@ -513,7 +546,6 @@ class _ReadSourceGenerator:
|
|
|
513
546
|
unpack: _Unpack,
|
|
514
547
|
lines: list[str],
|
|
515
548
|
target: Callable[[Field, str], list[str]],
|
|
516
|
-
align: bool = False,
|
|
517
549
|
) -> int:
|
|
518
550
|
"""Generate the code for reading the given fields from ``buf``, starting at the given offset.
|
|
519
551
|
|
|
@@ -523,7 +555,7 @@ class _ReadSourceGenerator:
|
|
|
523
555
|
Returns the offset after the last field.
|
|
524
556
|
"""
|
|
525
557
|
inline = _inline_kind if self.inline else None
|
|
526
|
-
for field, count, char in _generate_struct_info(fields,
|
|
558
|
+
for field, count, char in _generate_struct_info(fields, inline=inline):
|
|
527
559
|
if field is None:
|
|
528
560
|
# Padding
|
|
529
561
|
unpack.add(count, "x")
|
|
@@ -595,16 +627,15 @@ class _ReadSourceGenerator:
|
|
|
595
627
|
lines.append(f"_object_setattr({var}, '_endian', endian)")
|
|
596
628
|
self._proxify(var, var, fields, lines)
|
|
597
629
|
|
|
598
|
-
def _proxify(self, union: str, var: str, fields: list[Field], lines: list[str]) -> None:
|
|
630
|
+
def _proxify(self, union: str, var: str, fields: list[Field], lines: list[str], attr: str | None = None) -> None:
|
|
599
631
|
"""Generate the code equivalent to ``Union._proxify``."""
|
|
600
632
|
for field in fields:
|
|
601
633
|
if issubclass(field.type, Structure):
|
|
602
634
|
nested = self._var("nested")
|
|
635
|
+
member = attr or field._name
|
|
603
636
|
lines.append(f"{nested} = _getattr({var}, {field._name!r})")
|
|
604
|
-
lines.append(
|
|
605
|
-
|
|
606
|
-
)
|
|
607
|
-
self._proxify(union, nested, field.type.__fields__, lines)
|
|
637
|
+
lines.append(f"_object_setattr({var}, {field._name!r}, _UnionProxy({union}, {member!r}, {nested}))")
|
|
638
|
+
self._proxify(union, nested, field.type.__fields__, lines, member)
|
|
608
639
|
|
|
609
640
|
def _generate_packed_field_source(
|
|
610
641
|
self,
|
|
@@ -622,19 +653,18 @@ class _ReadSourceGenerator:
|
|
|
622
653
|
array = self._type_symbol(field.type)
|
|
623
654
|
element = self._type_symbol(field_type.__type__)
|
|
624
655
|
|
|
625
|
-
if issubclass(
|
|
626
|
-
|
|
627
|
-
|
|
656
|
+
if issubclass(read_type, Int):
|
|
657
|
+
# Everything that's read as an integer type is sliced directly out of the buffer
|
|
658
|
+
elem_size = read_type.__size__
|
|
659
|
+
signed = bool(read_type.signed)
|
|
628
660
|
lines.append(f"_array_buf = {getter}")
|
|
629
661
|
raw = f"_int_from_bytes(_array_buf[i:i + {elem_size}], _byteorder[endian], signed={signed})"
|
|
630
|
-
|
|
631
|
-
|
|
662
|
+
getter = f"[{raw} for i in range(0, {count}, {elem_size})]"
|
|
663
|
+
|
|
664
|
+
if issubclass(field_type.__type__, Pointer):
|
|
632
665
|
context = self._context(lines)
|
|
633
666
|
item = f"{element}.__new__({element}, value, stream, context={context}, endian=endian)"
|
|
634
667
|
value = f"[{item} for value in {getter}]"
|
|
635
|
-
elif issubclass(read_type, (Wchar, Int)):
|
|
636
|
-
# Preserve the behaviour of the generic parsing path for these types
|
|
637
|
-
value = f"[{element}(value) for value in {getter}]"
|
|
638
668
|
else:
|
|
639
669
|
value = f"list(map(_type_call, _repeat({element}), {getter}))"
|
|
640
670
|
|
|
@@ -647,7 +677,7 @@ class _ReadSourceGenerator:
|
|
|
647
677
|
encoding = self._add_symbol(read_type.__encoding_map__, "encoding")
|
|
648
678
|
# Using str() instead of .decode() also supports other bytes-like objects returned by the stream
|
|
649
679
|
type_ = self._type_symbol(field.type)
|
|
650
|
-
lines.extend(target(field, f"_type_call({type_}, str({getter}, {encoding}[endian]))"))
|
|
680
|
+
lines.extend(target(field, f"_type_call({type_}, str({getter}, {encoding}[endian], 'surrogatepass'))"))
|
|
651
681
|
|
|
652
682
|
elif issubclass(field_type, Pointer):
|
|
653
683
|
context = self._context(lines)
|
|
@@ -694,8 +724,7 @@ class _Unpack:
|
|
|
694
724
|
|
|
695
725
|
def functions(self, name: str) -> dict[str, Callable]:
|
|
696
726
|
"""Return the given function of the struct for every endianness."""
|
|
697
|
-
|
|
698
|
-
return {endian: getattr(_struct(endian, fmt), name) for endian in _ENDIANNESS}
|
|
727
|
+
return _struct_functions(_optimize_struct_fmt(self.items), name)
|
|
699
728
|
|
|
700
729
|
|
|
701
730
|
def _check_union(cls: type[Union], fields: list[Field], inline: bool = True) -> int:
|
|
@@ -809,25 +838,18 @@ def _block(lines: list[str]) -> str:
|
|
|
809
838
|
|
|
810
839
|
def _generate_struct_info(
|
|
811
840
|
fields: list[Field],
|
|
812
|
-
align: bool = False,
|
|
813
841
|
inline: Callable[[type[BaseType]], str | None] | None = None,
|
|
814
842
|
) -> Iterator[tuple[Field, int, str]]:
|
|
815
843
|
if not fields:
|
|
816
844
|
return
|
|
817
845
|
|
|
818
846
|
current_offset = fields[0].offset
|
|
819
|
-
imaginary_offset = 0
|
|
820
847
|
for field in fields:
|
|
821
848
|
# We moved -- probably due to alignment
|
|
822
849
|
if field.offset is not None and (drift := field.offset - current_offset) > 0:
|
|
823
850
|
yield None, drift, "x"
|
|
824
851
|
current_offset += drift
|
|
825
852
|
|
|
826
|
-
if align and field.offset is None and (drift := -imaginary_offset & (field.alignment - 1)) > 0:
|
|
827
|
-
# Assume we started at a correctly aligned boundary
|
|
828
|
-
yield None, drift, "x"
|
|
829
|
-
imaginary_offset += drift
|
|
830
|
-
|
|
831
853
|
count = 1
|
|
832
854
|
read_type = _get_read_type(field.type)
|
|
833
855
|
|
|
@@ -853,25 +875,41 @@ def _generate_struct_info(
|
|
|
853
875
|
elif issubclass(read_type, (Char, Wchar, Int)):
|
|
854
876
|
yield field, count * read_type.__size__, "x"
|
|
855
877
|
|
|
856
|
-
size = count * read_type.__size__
|
|
857
|
-
imaginary_offset += size
|
|
858
878
|
if current_offset is not None:
|
|
859
|
-
current_offset +=
|
|
879
|
+
current_offset += count * read_type.__size__
|
|
860
880
|
|
|
861
881
|
|
|
862
882
|
def _optimize_struct_fmt(items: Iterable[tuple[int, str]]) -> str:
|
|
863
|
-
"""Create a struct format string from ``(count, char)`` items, merging consecutive items of the same char.
|
|
883
|
+
"""Create a struct format string from ``(count, char)`` items, merging consecutive items of the same char.
|
|
884
|
+
|
|
885
|
+
Byte strings aren't merged, since those are a single value regardless of their count.
|
|
886
|
+
"""
|
|
864
887
|
chars = []
|
|
865
888
|
for char, group in groupby(items, key=itemgetter(1)):
|
|
866
|
-
if
|
|
889
|
+
if char == "s":
|
|
890
|
+
chars.extend(f"{count}{char}" for count, _ in group)
|
|
891
|
+
elif count := sum(count for count, _ in group):
|
|
867
892
|
chars.append(f"{count if count > 1 else ''}{char}")
|
|
868
893
|
|
|
869
894
|
return "".join(chars)
|
|
870
895
|
|
|
871
896
|
|
|
897
|
+
def _struct_functions(fmt: str, name: str) -> dict[str, Callable]:
|
|
898
|
+
"""Return the given function of a struct with the given format for every endianness.
|
|
899
|
+
|
|
900
|
+
The ``@`` endianness uses the ``=`` format, since ``@`` would add padding between items to align them.
|
|
901
|
+
"""
|
|
902
|
+
return {endian: getattr(_struct("=" if endian == "@" else endian, fmt), name) for endian in _ENDIANNESS}
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def _get_bit_buffer_type(field: Field) -> type[BaseType]:
|
|
906
|
+
"""Return the type that ``BitBuffer`` uses for a bit field, only bit fields of the same type share a value."""
|
|
907
|
+
return field.type.__type__ if issubclass(field.type, (Enum, Flag)) else field.type
|
|
908
|
+
|
|
909
|
+
|
|
872
910
|
def _get_bits_type(field: Field) -> type[BaseType]:
|
|
873
911
|
"""Return the type that ``BitBuffer`` reads a bit field as."""
|
|
874
|
-
type_ =
|
|
912
|
+
type_ = _get_bit_buffer_type(field)
|
|
875
913
|
return type_.__cs__.uint8 if issubclass(type_, Char) else type_
|
|
876
914
|
|
|
877
915
|
|
dissect/cstruct/cstruct.py
CHANGED
|
@@ -86,7 +86,7 @@ class cstruct:
|
|
|
86
86
|
"int24": self._make_int_type("int24", 3, True, alignment=4),
|
|
87
87
|
"uint24": self._make_int_type("uint24", 3, False, alignment=4),
|
|
88
88
|
"int48": self._make_int_type("int48", 6, True, alignment=8),
|
|
89
|
-
"uint48": self._make_int_type("
|
|
89
|
+
"uint48": self._make_int_type("uint48", 6, False, alignment=8),
|
|
90
90
|
"int128": self._make_int_type("int128", 16, True, alignment=16),
|
|
91
91
|
"uint128": self._make_int_type("uint128", 16, False, alignment=16),
|
|
92
92
|
|
dissect/cstruct/types/base.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import functools
|
|
4
4
|
import warnings
|
|
5
5
|
from io import SEEK_CUR, BytesIO
|
|
6
|
+
from types import MethodType
|
|
6
7
|
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar
|
|
7
8
|
|
|
8
9
|
from dissect.cstruct.exception import ArraySizeError
|
|
@@ -19,6 +20,8 @@ if TYPE_CHECKING:
|
|
|
19
20
|
|
|
20
21
|
EOF = -0xE0F # Negative counts are illegal anyway, so abuse that for our EOF sentinel
|
|
21
22
|
|
|
23
|
+
_IMMUTABLE_TYPES = (int, float, bytes, str)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class MetaType(type):
|
|
24
27
|
"""Base metaclass for cstruct type classes."""
|
|
@@ -218,7 +221,8 @@ class MetaType(type):
|
|
|
218
221
|
array: The array to write.
|
|
219
222
|
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
220
223
|
"""
|
|
221
|
-
|
|
224
|
+
write = cls._write
|
|
225
|
+
return sum(write(stream, entry, endian=endian) for entry in array)
|
|
222
226
|
|
|
223
227
|
def _write_0(cls, stream: BinaryIO, array: list[Self], *, endian: Endianness) -> int: # type: ignore
|
|
224
228
|
"""Internal function for writing null-terminated arrays.
|
|
@@ -295,20 +299,29 @@ class _overload:
|
|
|
295
299
|
b'\\x7b\\x00\\x00\\x00'
|
|
296
300
|
"""
|
|
297
301
|
|
|
298
|
-
def __init__(self, func: Callable[..., Any]) -> None:
|
|
302
|
+
def __init__(self, func: Callable[..., Any], method: Callable[..., Any]) -> None:
|
|
299
303
|
self.func = func
|
|
304
|
+
self.method = method
|
|
300
305
|
|
|
301
|
-
def __get__(self, instance: BaseType | None, owner: type[BaseType]) -> Callable[
|
|
306
|
+
def __get__(self, instance: BaseType | None, owner: type[BaseType]) -> Callable[..., Any]:
|
|
302
307
|
if instance is None:
|
|
303
308
|
return functools.partial(self.func, owner)
|
|
304
|
-
return
|
|
309
|
+
return MethodType(self.method, instance)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _instance_dumps(self: BaseType, *, endian: AllowedEndianness | None = None) -> bytes:
|
|
313
|
+
return MetaType.dumps(self.__class__, self, endian=endian)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _instance_write(self: BaseType, stream: BinaryIO, *, endian: AllowedEndianness | None = None) -> int:
|
|
317
|
+
return MetaType.write(self.__class__, stream, self, endian=endian)
|
|
305
318
|
|
|
306
319
|
|
|
307
320
|
class BaseType(metaclass=MetaType):
|
|
308
321
|
"""Base class for cstruct type classes."""
|
|
309
322
|
|
|
310
|
-
dumps = _overload(MetaType.dumps)
|
|
311
|
-
write = _overload(MetaType.write)
|
|
323
|
+
dumps = _overload(MetaType.dumps, _instance_dumps)
|
|
324
|
+
write = _overload(MetaType.write, _instance_write)
|
|
312
325
|
|
|
313
326
|
def __len__(self) -> int:
|
|
314
327
|
"""Return the byte size of the type."""
|
|
@@ -338,9 +351,10 @@ class BaseArray(BaseType):
|
|
|
338
351
|
|
|
339
352
|
@classmethod
|
|
340
353
|
def __default__(cls) -> BaseType:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
354
|
+
count = cls.__num_entries__ if isinstance(cls.__num_entries__, int) else 0
|
|
355
|
+
if isinstance(value := cls.__type__.__default__(), _IMMUTABLE_TYPES):
|
|
356
|
+
return type.__call__(cls, [value] * count)
|
|
357
|
+
return type.__call__(cls, [cls.__type__.__default__() for _ in range(count)])
|
|
344
358
|
|
|
345
359
|
@classmethod
|
|
346
360
|
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[BaseType]:
|
dissect/cstruct/types/char.py
CHANGED
|
@@ -23,8 +23,9 @@ class CharArray(bytes, BaseArray):
|
|
|
23
23
|
return type.__call__(cls, super()._read(stream, context=context, endian=endian))
|
|
24
24
|
|
|
25
25
|
@classmethod
|
|
26
|
-
def _write(cls, stream: BinaryIO, data: bytes | str | list[int], *, endian: Endianness) -> int:
|
|
27
|
-
|
|
26
|
+
def _write(cls, stream: BinaryIO, data: bytes | bytearray | str | list[int], *, endian: Endianness) -> int:
|
|
27
|
+
# Copy mutable data types to bytes so we don't modify them in place
|
|
28
|
+
if isinstance(data, (list, bytearray)):
|
|
28
29
|
data = bytes(data)
|
|
29
30
|
elif isinstance(data, str):
|
|
30
31
|
data = data.encode("latin-1")
|
|
@@ -92,4 +93,7 @@ class Char(bytes, BaseType):
|
|
|
92
93
|
if isinstance(data, str):
|
|
93
94
|
data = data.encode("latin-1")
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
if (actual_size := len(data)) > 1:
|
|
97
|
+
raise ValueError(f"Expected a single character, got {actual_size} instead.")
|
|
98
|
+
|
|
99
|
+
return stream.write(data or b"\x00")
|
|
@@ -7,7 +7,7 @@ from collections.abc import MutableMapping
|
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from functools import lru_cache
|
|
10
|
-
from itertools import chain
|
|
10
|
+
from itertools import chain, repeat
|
|
11
11
|
from operator import attrgetter
|
|
12
12
|
from textwrap import dedent
|
|
13
13
|
from types import FunctionType
|
|
@@ -18,6 +18,7 @@ from dissect.cstruct.expression import Expression
|
|
|
18
18
|
from dissect.cstruct.lexer import IDENTIFIER_TYPES
|
|
19
19
|
from dissect.cstruct.types.base import (
|
|
20
20
|
_BUFFER_TYPES,
|
|
21
|
+
_IMMUTABLE_TYPES,
|
|
21
22
|
BaseArray,
|
|
22
23
|
BaseType,
|
|
23
24
|
MetaType,
|
|
@@ -141,7 +142,6 @@ class StructureMetaType(MetaType):
|
|
|
141
142
|
else:
|
|
142
143
|
size, alignment = cls._calculate_size_and_offsets(fields, align)
|
|
143
144
|
|
|
144
|
-
# TODO: compile _write
|
|
145
145
|
# TODO: generate cached_property for lazy reading
|
|
146
146
|
|
|
147
147
|
classdict["__size__"] = size
|
|
@@ -193,7 +193,7 @@ class StructureMetaType(MetaType):
|
|
|
193
193
|
# Moved to a bit field of another type, e.g. uint16 f1 : 8, uint32 f2 : 8;
|
|
194
194
|
or field_type != bits_type
|
|
195
195
|
# Still processing a bit field, but it's at a different offset due to alignment or a manual offset
|
|
196
|
-
or (
|
|
196
|
+
or (None not in (offset, bits_field_offset) and offset > bits_field_offset + bits_type.__size__)
|
|
197
197
|
):
|
|
198
198
|
# ... if any of this is true, we have to move to the next field
|
|
199
199
|
bits_type = field_type
|
|
@@ -299,7 +299,6 @@ class StructureMetaType(MetaType):
|
|
|
299
299
|
def _write(cls, stream: BinaryIO, data: Structure, *, endian: Endianness) -> int:
|
|
300
300
|
bit_buffer = None
|
|
301
301
|
struct_start = stream.tell()
|
|
302
|
-
num = 0
|
|
303
302
|
|
|
304
303
|
for field in cls.__fields__:
|
|
305
304
|
field_type = field.type
|
|
@@ -331,7 +330,7 @@ class StructureMetaType(MetaType):
|
|
|
331
330
|
stream.write(b"\x00" * align_pad)
|
|
332
331
|
offset += align_pad
|
|
333
332
|
|
|
334
|
-
value = getattr(data, field._name
|
|
333
|
+
value = getattr(data, field._name)
|
|
335
334
|
if value is None:
|
|
336
335
|
value = field_type.__default__()
|
|
337
336
|
|
|
@@ -345,7 +344,6 @@ class StructureMetaType(MetaType):
|
|
|
345
344
|
bit_buffer.write(field_type, value, field.bits)
|
|
346
345
|
else:
|
|
347
346
|
field_type._write(stream, value, endian=endian)
|
|
348
|
-
num += stream.tell() - offset
|
|
349
347
|
|
|
350
348
|
if bit_buffer is not None and bit_buffer._type is not None:
|
|
351
349
|
bit_buffer.flush()
|
|
@@ -354,7 +352,7 @@ class StructureMetaType(MetaType):
|
|
|
354
352
|
# Align the stream
|
|
355
353
|
stream.write(b"\x00" * (-stream.tell() & (cls.__alignment__ - 1)))
|
|
356
354
|
|
|
357
|
-
return
|
|
355
|
+
return stream.tell() - struct_start
|
|
358
356
|
|
|
359
357
|
def add_field(cls, name: str, type_: type[BaseType], bits: int | None = None, offset: int | None = None) -> None:
|
|
360
358
|
field = Field(name, type_, bits=bits, offset=offset)
|
|
@@ -698,6 +696,10 @@ class UnionMetaType(StructureMetaType):
|
|
|
698
696
|
|
|
699
697
|
return obj
|
|
700
698
|
|
|
699
|
+
def _write_field(cls) -> Field | None:
|
|
700
|
+
"""Return the field to write a union by, its largest field and preferably not an anonymous structure."""
|
|
701
|
+
return max(cls.__fields__, key=lambda e: (e.type.__size__ or 0, e.name is not None), default=None)
|
|
702
|
+
|
|
701
703
|
def _write(cls, stream: BinaryIO, data: Union, *, endian: Endianness) -> int:
|
|
702
704
|
if cls.__dynamic__:
|
|
703
705
|
raise NotImplementedError("Writing dynamic unions is not yet supported")
|
|
@@ -705,24 +707,9 @@ class UnionMetaType(StructureMetaType):
|
|
|
705
707
|
offset = stream.tell()
|
|
706
708
|
expected_offset = offset + len(cls)
|
|
707
709
|
|
|
708
|
-
|
|
709
|
-
fields = sorted(cls.__fields__, key=lambda e: e.type.__size__ or 0, reverse=True)
|
|
710
|
-
anonymous_struct = False
|
|
711
|
-
|
|
712
|
-
# Try to write by largest field
|
|
713
|
-
for field in fields:
|
|
714
|
-
if isinstance(field.type, StructureMetaType) and field.name is None:
|
|
715
|
-
# Prefer to write regular fields initially
|
|
716
|
-
anonymous_struct = field.type
|
|
717
|
-
continue
|
|
718
|
-
|
|
710
|
+
if (field := cls._write_field()) is not None:
|
|
719
711
|
# Write the value
|
|
720
712
|
field.type._write(stream, getattr(data, field._name), endian=endian)
|
|
721
|
-
break
|
|
722
|
-
|
|
723
|
-
# If we haven't written anything yet and we initially skipped an anonymous struct, write it now
|
|
724
|
-
if stream.tell() == offset and anonymous_struct:
|
|
725
|
-
anonymous_struct._write(stream, data, endian=endian)
|
|
726
713
|
|
|
727
714
|
# If we haven't filled the union size yet, pad it
|
|
728
715
|
if remaining := expected_offset - stream.tell():
|
|
@@ -745,7 +732,9 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
745
732
|
raise NotImplementedError("Modifying a dynamic union is not yet supported")
|
|
746
733
|
|
|
747
734
|
super().__setattr__(attr, value)
|
|
748
|
-
|
|
735
|
+
# Fields of an anonymous structure are rebuilt through the proxy of that structure
|
|
736
|
+
if attr in self.__class__.__lookup__:
|
|
737
|
+
self._rebuild(attr)
|
|
749
738
|
|
|
750
739
|
def _rebuild(self, attr: str) -> None:
|
|
751
740
|
if (cur_buf := getattr(self, "_buf", None)) is None:
|
|
@@ -773,13 +762,17 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
773
762
|
object.__setattr__(self, "__dynamic_sizes__", sizes)
|
|
774
763
|
|
|
775
764
|
def _proxify(self) -> None:
|
|
776
|
-
def _proxy_structure(value: Structure) -> None:
|
|
765
|
+
def _proxy_structure(value: Structure, attr: str | None = None) -> None:
|
|
777
766
|
for field in value.__class__.__fields__:
|
|
778
767
|
if issubclass(field.type, Structure):
|
|
779
768
|
nested_value = getattr(value, field._name)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
769
|
+
# Changing a nested field rebuilds the union from the top level field it's in
|
|
770
|
+
member = attr or field._name
|
|
771
|
+
object.__setattr__(value, field._name, UnionProxy(self, member, nested_value))
|
|
772
|
+
# The value of a nested union is already a proxy, so continue with the value behind it
|
|
773
|
+
if isinstance(nested_value, UnionProxy):
|
|
774
|
+
nested_value = nested_value.__target__
|
|
775
|
+
_proxy_structure(nested_value, member)
|
|
783
776
|
|
|
784
777
|
_proxy_structure(self)
|
|
785
778
|
|
|
@@ -864,7 +857,7 @@ def _make_structure__init__(fields: list[str]) -> str:
|
|
|
864
857
|
"""
|
|
865
858
|
field_args = ", ".join(f"{field} = None" for field in fields)
|
|
866
859
|
field_init = "\n".join(
|
|
867
|
-
f" self.{name} = {name} if {name} is not None else _{i}_default" for i, name in enumerate(fields)
|
|
860
|
+
f" self.{name} = {name} if {name} is not None else _{i}_default()" for i, name in enumerate(fields)
|
|
868
861
|
)
|
|
869
862
|
|
|
870
863
|
code = f"def __init__(self{', ' + field_args or ''}):\n"
|
|
@@ -880,7 +873,7 @@ def _make_union__init__(fields: list[str]) -> str:
|
|
|
880
873
|
"""
|
|
881
874
|
field_args = ", ".join(f"{field} = None" for field in fields)
|
|
882
875
|
field_init = "\n".join(
|
|
883
|
-
f" object.__setattr__(self, '{name}', {name} if {name} is not None else _{i}_default)"
|
|
876
|
+
f" object.__setattr__(self, '{name}', {name} if {name} is not None else _{i}_default())"
|
|
884
877
|
for i, name in enumerate(fields)
|
|
885
878
|
)
|
|
886
879
|
|
|
@@ -976,7 +969,7 @@ def _generate_structure__init__(fields: list[Field]) -> FunctionType:
|
|
|
976
969
|
co_names=_remap_co_values(template.__code__.co_names, mapping),
|
|
977
970
|
co_varnames=_remap_co_values(template.__code__.co_varnames, mapping),
|
|
978
971
|
),
|
|
979
|
-
template.__globals__ | {f"__{field._name}_default__": field.type
|
|
972
|
+
template.__globals__ | {f"__{field._name}_default__": _default_factory(field.type) for field in fields},
|
|
980
973
|
argdefs=template.__defaults__,
|
|
981
974
|
)
|
|
982
975
|
|
|
@@ -996,11 +989,17 @@ def _generate_union__init__(fields: list[Field]) -> FunctionType:
|
|
|
996
989
|
co_names=_remap_co_values(template.__code__.co_names, mapping),
|
|
997
990
|
co_varnames=_remap_co_values(template.__code__.co_varnames, mapping),
|
|
998
991
|
),
|
|
999
|
-
template.__globals__ | {f"__{field._name}_default__": field.type
|
|
992
|
+
template.__globals__ | {f"__{field._name}_default__": _default_factory(field.type) for field in fields},
|
|
1000
993
|
argdefs=template.__defaults__,
|
|
1001
994
|
)
|
|
1002
995
|
|
|
1003
996
|
|
|
997
|
+
def _default_factory(type_: type[BaseType]) -> Callable[[], Any]:
|
|
998
|
+
"""Return a function that returns default values of a type, which are only shared if they're immutable."""
|
|
999
|
+
value = type_.__default__()
|
|
1000
|
+
return repeat(value).__next__ if isinstance(value, _IMMUTABLE_TYPES) else type_.__default__
|
|
1001
|
+
|
|
1002
|
+
|
|
1004
1003
|
def _generate_co_mapping(fields: list[Field]) -> dict[str, str]:
|
|
1005
1004
|
"""Generates a mapping of generated code object names to field names.
|
|
1006
1005
|
|
dissect/cstruct/types/wchar.py
CHANGED
|
@@ -28,12 +28,15 @@ class WcharArray(str, BaseArray):
|
|
|
28
28
|
if cls.__null_terminated__:
|
|
29
29
|
data += "\x00"
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
buf = data.encode(Wchar.__encoding_map__[endian], "surrogatepass")
|
|
32
|
+
|
|
33
|
+
# A surrogate pair is a single character that takes up two entries of the array
|
|
34
|
+
if not cls.__dynamic__ and (remaining := cls.__num_entries__ - (actual_size := len(buf) // 2)):
|
|
32
35
|
if remaining < 0:
|
|
33
36
|
raise ArraySizeError(f"Expected static array size {cls.__num_entries__}, got {actual_size} instead.")
|
|
34
|
-
|
|
37
|
+
buf += b"\x00\x00" * remaining
|
|
35
38
|
|
|
36
|
-
return stream.write(
|
|
39
|
+
return stream.write(buf)
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
class Wchar(str, BaseType):
|
|
@@ -42,6 +45,7 @@ class Wchar(str, BaseType):
|
|
|
42
45
|
__ArrayType__ = WcharArray
|
|
43
46
|
|
|
44
47
|
__slots__ = ()
|
|
48
|
+
# Decode with "surrogatepass" so lone surrogates are handled correctly
|
|
45
49
|
__encoding_map__: ClassVar[dict[Endianness, str]] = {
|
|
46
50
|
"@": f"utf-16-{sys.byteorder[0]}e",
|
|
47
51
|
"=": f"utf-16-{sys.byteorder[0]}e",
|
|
@@ -72,12 +76,13 @@ class Wchar(str, BaseType):
|
|
|
72
76
|
if count != EOF and len(data) != count:
|
|
73
77
|
raise EOFError(f"Read {len(data)} bytes, but expected {count}")
|
|
74
78
|
|
|
75
|
-
return type.__call__(cls, str(data, cls.__encoding_map__[endian]))
|
|
79
|
+
return type.__call__(cls, str(data, cls.__encoding_map__[endian], "surrogatepass"))
|
|
76
80
|
|
|
77
81
|
@classmethod
|
|
78
82
|
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Wchar:
|
|
79
83
|
if _is_seekable(stream):
|
|
80
|
-
|
|
84
|
+
data = _read_null_terminated(stream, 2)
|
|
85
|
+
return type.__call__(cls, data.decode(cls.__encoding_map__[endian], "surrogatepass"))
|
|
81
86
|
|
|
82
87
|
buf = []
|
|
83
88
|
while True:
|
|
@@ -90,8 +95,14 @@ class Wchar(str, BaseType):
|
|
|
90
95
|
|
|
91
96
|
buf.append(point)
|
|
92
97
|
|
|
93
|
-
return type.__call__(cls, b"".join(buf).decode(cls.__encoding_map__[endian]))
|
|
98
|
+
return type.__call__(cls, b"".join(buf).decode(cls.__encoding_map__[endian], "surrogatepass"))
|
|
94
99
|
|
|
95
100
|
@classmethod
|
|
96
101
|
def _write(cls, stream: BinaryIO, data: str, *, endian: Endianness) -> int:
|
|
97
|
-
|
|
102
|
+
buf = data.encode(cls.__encoding_map__[endian], "surrogatepass")
|
|
103
|
+
|
|
104
|
+
# A surrogate pair is a single character that doesn't fit in a single wchar
|
|
105
|
+
if (actual_size := len(buf) // 2) > 1:
|
|
106
|
+
raise ValueError(f"Expected a single character, got {actual_size} instead.")
|
|
107
|
+
|
|
108
|
+
return stream.write(buf or b"\x00\x00")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.dev13
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
dissect/cstruct/__init__.py,sha256=KOyNeUvu4c0JaAiyOfj7iftGwUIjTmD8MwHtFsLPLSU,1504
|
|
2
2
|
dissect/cstruct/bitbuffer.py,sha256=aNzY4lkWe8ffAc8j7FODXzwZc8gZwbey77UosyoYCsw,2711
|
|
3
|
-
dissect/cstruct/compiler.py,sha256=
|
|
4
|
-
dissect/cstruct/cstruct.py,sha256=
|
|
3
|
+
dissect/cstruct/compiler.py,sha256=4oiJto_RqUvSOArScWo3XfgmiTkswxBgpu-qfQ9nYPA,36673
|
|
4
|
+
dissect/cstruct/cstruct.py,sha256=mJerzPouWV29RdK5LqjGitDDMvNKcadoT2hj1zpExnM,26551
|
|
5
5
|
dissect/cstruct/exception.py,sha256=RqD08LuBkbMqa3iQ1A05DNN8XgEmQR_SUnguwe9LGo8,316
|
|
6
6
|
dissect/cstruct/expression.py,sha256=Y27-N48BomBoRFozbWhGDF_DS2lfklRwWVUbCzGnxHE,10873
|
|
7
7
|
dissect/cstruct/lexer.py,sha256=NDUfAWgyxgRNRuVO8ExXQ51LqQXS2mv9bywVa_M7tFQ,19665
|
|
@@ -10,21 +10,21 @@ dissect/cstruct/util.py,sha256=vAf-kdOkk3zprW_gD6QmQeN3VIRgQqhPS4B_ha1jEpQ,18040
|
|
|
10
10
|
dissect/cstruct/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
dissect/cstruct/tools/stubgen.py,sha256=MeZkLmSeNSLNcAEwlAdIGv1lKvlvK3UQ4WN85ovsxTM,7672
|
|
12
12
|
dissect/cstruct/types/__init__.py,sha256=6P71Pu94Dv7RNjZpDZ19pqzx-G0QzmQw4q0-3zWALso,911
|
|
13
|
-
dissect/cstruct/types/base.py,sha256=
|
|
14
|
-
dissect/cstruct/types/char.py,sha256=
|
|
13
|
+
dissect/cstruct/types/base.py,sha256=KOVgzQBsotj7xZoQcWnMzj5q56S3kUSnnNfXrtfTkqQ,17850
|
|
14
|
+
dissect/cstruct/types/char.py,sha256=N19R7Zzn2uMuG-ULW5X2UgmgkLvjaBj1Td7AQQI93Fg,3417
|
|
15
15
|
dissect/cstruct/types/enum.py,sha256=nOdI6vGtqTfPMhOBkKuiuOriIB2YHSt8Q9ruri37s3U,9177
|
|
16
16
|
dissect/cstruct/types/flag.py,sha256=YxQHix8ZhK1qQX3J_Og7GJp_4JAxCwzUbUibuJeDaWQ,3479
|
|
17
17
|
dissect/cstruct/types/int.py,sha256=9RbdrkvAI28zATM97VVORQnras0XhLaVMtdPjJwpTH4,2089
|
|
18
18
|
dissect/cstruct/types/leb128.py,sha256=V97XTJT_Xbw1LpTwTN1OkjPHuQQAQUxd8OGuA6yEDmE,2387
|
|
19
19
|
dissect/cstruct/types/packed.py,sha256=NgCX0YWX8SH8z47atq0f39w9MBjU_eBFJtUisUWdO-M,3867
|
|
20
20
|
dissect/cstruct/types/pointer.py,sha256=FNuB9bfENWRebbKX-R6Vs6xWPMHepjvOhhomeUhr7I8,3985
|
|
21
|
-
dissect/cstruct/types/structure.py,sha256=
|
|
21
|
+
dissect/cstruct/types/structure.py,sha256=mxDvurQ0oAjI2flykhAPlT-03280cDpwGTz0-bqbr-0,39558
|
|
22
22
|
dissect/cstruct/types/void.py,sha256=fV4hKk50uop_EKbR1nfy1cdtAzvUYrSymXsdakCyFk8,1222
|
|
23
|
-
dissect/cstruct/types/wchar.py,sha256=
|
|
24
|
-
dissect_cstruct-5.0.
|
|
25
|
-
dissect_cstruct-5.0.
|
|
26
|
-
dissect_cstruct-5.0.
|
|
27
|
-
dissect_cstruct-5.0.
|
|
28
|
-
dissect_cstruct-5.0.
|
|
29
|
-
dissect_cstruct-5.0.
|
|
30
|
-
dissect_cstruct-5.0.
|
|
23
|
+
dissect/cstruct/types/wchar.py,sha256=VrnPXsP_lsyA-1xr4D2aSIVijumMhUuZWNlDHcn12-s,3964
|
|
24
|
+
dissect_cstruct-5.0.dev13.dist-info/licenses/COPYRIGHT,sha256=H-18RXfshdH9AdHwW2eO1Xa-5s6tY5eipHh5c0whDu4,316
|
|
25
|
+
dissect_cstruct-5.0.dev13.dist-info/licenses/LICENSE,sha256=PhUqiw6jAh2KbBdVRPBq_hfAvfcTBin7nZ3CK7NQbTM,11341
|
|
26
|
+
dissect_cstruct-5.0.dev13.dist-info/METADATA,sha256=4sVLwtg9EZ3n5dRjzL-PqDvA1SQCr-JzbfoyTDcpcpM,9256
|
|
27
|
+
dissect_cstruct-5.0.dev13.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
28
|
+
dissect_cstruct-5.0.dev13.dist-info/entry_points.txt,sha256=z53zqZqwD2OLrAkRwrP4wTeiU9CQe7xrMly0T2c0_wQ,71
|
|
29
|
+
dissect_cstruct-5.0.dev13.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
30
|
+
dissect_cstruct-5.0.dev13.dist-info/RECORD,,
|
|
File without changes
|
{dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/licenses/COPYRIGHT
RENAMED
|
File without changes
|
{dissect_cstruct-5.0.dev12.dist-info → dissect_cstruct-5.0.dev13.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|