dissect.cstruct 3.14.dev8__tar.gz → 4.0.dev0__tar.gz

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.
Files changed (90) hide show
  1. {dissect_cstruct-3.14.dev8/dissect.cstruct.egg-info → dissect_cstruct-4.0.dev0}/PKG-INFO +2 -2
  2. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/README.md +1 -1
  3. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/__init__.py +36 -33
  4. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/bitbuffer.py +4 -4
  5. dissect_cstruct-4.0.dev0/dissect/cstruct/compiler.py +418 -0
  6. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/cstruct.py +191 -52
  7. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/expression.py +13 -12
  8. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/parser.py +80 -71
  9. dissect_cstruct-4.0.dev0/dissect/cstruct/types/__init__.py +32 -0
  10. dissect_cstruct-4.0.dev0/dissect/cstruct/types/base.py +279 -0
  11. dissect_cstruct-4.0.dev0/dissect/cstruct/types/char.py +79 -0
  12. dissect_cstruct-4.0.dev0/dissect/cstruct/types/enum.py +183 -0
  13. dissect_cstruct-4.0.dev0/dissect/cstruct/types/flag.py +72 -0
  14. dissect_cstruct-4.0.dev0/dissect/cstruct/types/int.py +37 -0
  15. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/types/leb128.py +15 -28
  16. dissect_cstruct-4.0.dev0/dissect/cstruct/types/packed.py +65 -0
  17. dissect_cstruct-4.0.dev0/dissect/cstruct/types/pointer.py +95 -0
  18. dissect_cstruct-4.0.dev0/dissect/cstruct/types/structure.py +678 -0
  19. dissect_cstruct-4.0.dev0/dissect/cstruct/types/void.py +20 -0
  20. dissect_cstruct-4.0.dev0/dissect/cstruct/types/wchar.py +79 -0
  21. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/utils.py +31 -19
  22. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0/dissect.cstruct.egg-info}/PKG-INFO +2 -2
  23. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/SOURCES.txt +20 -15
  24. dissect_cstruct-4.0.dev0/examples/protobuf.py +83 -0
  25. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/secdesc.py +5 -8
  26. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/pyproject.toml +0 -1
  27. dissect_cstruct-4.0.dev0/tests/conftest.py +13 -0
  28. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_align.py +78 -72
  29. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_basic.py +120 -138
  30. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_bitbuffer.py +2 -4
  31. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_bitfield.py +14 -20
  32. dissect_cstruct-4.0.dev0/tests/test_compiler.py +326 -0
  33. dissect_cstruct-4.0.dev0/tests/test_ctypes.py +30 -0
  34. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_expression.py +5 -6
  35. dissect_cstruct-4.0.dev0/tests/test_parser.py +75 -0
  36. dissect_cstruct-4.0.dev0/tests/test_types_base.py +133 -0
  37. dissect_cstruct-4.0.dev0/tests/test_types_char.py +45 -0
  38. dissect_cstruct-4.0.dev0/tests/test_types_custom.py +74 -0
  39. dissect_cstruct-3.14.dev8/tests/test_enum.py → dissect_cstruct-4.0.dev0/tests/test_types_enum.py +106 -27
  40. dissect_cstruct-4.0.dev0/tests/test_types_flag.py +235 -0
  41. dissect_cstruct-4.0.dev0/tests/test_types_int.py +402 -0
  42. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_types_leb128.py +37 -56
  43. dissect_cstruct-4.0.dev0/tests/test_types_packed.py +171 -0
  44. dissect_cstruct-4.0.dev0/tests/test_types_pointer.py +237 -0
  45. dissect_cstruct-4.0.dev0/tests/test_types_structure.py +531 -0
  46. dissect_cstruct-4.0.dev0/tests/test_types_union.py +338 -0
  47. dissect_cstruct-4.0.dev0/tests/test_types_void.py +13 -0
  48. dissect_cstruct-4.0.dev0/tests/test_types_wchar.py +77 -0
  49. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_utils.py +87 -2
  50. dissect_cstruct-4.0.dev0/tests/utils.py +7 -0
  51. dissect_cstruct-3.14.dev8/dissect/cstruct/compiler.py +0 -413
  52. dissect_cstruct-3.14.dev8/dissect/cstruct/types/__init__.py +0 -34
  53. dissect_cstruct-3.14.dev8/dissect/cstruct/types/base.py +0 -209
  54. dissect_cstruct-3.14.dev8/dissect/cstruct/types/bytesinteger.py +0 -115
  55. dissect_cstruct-3.14.dev8/dissect/cstruct/types/chartype.py +0 -59
  56. dissect_cstruct-3.14.dev8/dissect/cstruct/types/enum.py +0 -124
  57. dissect_cstruct-3.14.dev8/dissect/cstruct/types/flag.py +0 -106
  58. dissect_cstruct-3.14.dev8/dissect/cstruct/types/instance.py +0 -68
  59. dissect_cstruct-3.14.dev8/dissect/cstruct/types/packedtype.py +0 -59
  60. dissect_cstruct-3.14.dev8/dissect/cstruct/types/pointer.py +0 -127
  61. dissect_cstruct-3.14.dev8/dissect/cstruct/types/structure.py +0 -376
  62. dissect_cstruct-3.14.dev8/dissect/cstruct/types/voidtype.py +0 -13
  63. dissect_cstruct-3.14.dev8/dissect/cstruct/types/wchartype.py +0 -57
  64. dissect_cstruct-3.14.dev8/tests/conftest.py +0 -6
  65. dissect_cstruct-3.14.dev8/tests/test_bytesinteger.py +0 -260
  66. dissect_cstruct-3.14.dev8/tests/test_ctypes_type.py +0 -25
  67. dissect_cstruct-3.14.dev8/tests/test_flag.py +0 -149
  68. dissect_cstruct-3.14.dev8/tests/test_packedtype.py +0 -66
  69. dissect_cstruct-3.14.dev8/tests/test_parser.py +0 -25
  70. dissect_cstruct-3.14.dev8/tests/test_pointer.py +0 -162
  71. dissect_cstruct-3.14.dev8/tests/test_struct.py +0 -320
  72. dissect_cstruct-3.14.dev8/tests/test_union.py +0 -89
  73. dissect_cstruct-3.14.dev8/tests/test_util.py +0 -90
  74. dissect_cstruct-3.14.dev8/tests/utils.py +0 -2
  75. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/COPYRIGHT +0 -0
  76. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/LICENSE +0 -0
  77. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/MANIFEST.in +0 -0
  78. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/exceptions.py +0 -0
  79. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  80. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/top_level.txt +0 -0
  81. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/disk.py +0 -0
  82. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/mirai.py +0 -0
  83. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/pe.py +0 -0
  84. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/setup.cfg +0 -0
  85. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/__init__.py +0 -0
  86. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/data/testdef.txt +0 -0
  87. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/Makefile +0 -0
  88. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/conf.py +0 -0
  89. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/index.rst +0 -0
  90. {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.cstruct
3
- Version: 3.14.dev8
3
+ Version: 4.0.dev0
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -224,7 +224,7 @@ assert a.dumps() == d
224
224
  The API to access enum members and their values is similar to that of the native Enum type in Python 3. Functionally, it's best comparable to the IntEnum type.
225
225
 
226
226
  ### Custom types
227
- You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `addtype(name, type)`
227
+ You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `add_type(name, type)`
228
228
 
229
229
  ### Custom definition parsers
230
230
  Don't like the C-like definition syntax? Write your own syntax parser!
@@ -199,7 +199,7 @@ assert a.dumps() == d
199
199
  The API to access enum members and their values is similar to that of the native Enum type in Python 3. Functionally, it's best comparable to the IntEnum type.
200
200
 
201
201
  ### Custom types
202
- You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `addtype(name, type)`
202
+ You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `add_type(name, type)`
203
203
 
204
204
  ### Custom definition parsers
205
205
  Don't like the C-like definition syntax? Write your own syntax parser!
@@ -1,5 +1,4 @@
1
1
  from dissect.cstruct.bitbuffer import BitBuffer
2
- from dissect.cstruct.compiler import Compiler
3
2
  from dissect.cstruct.cstruct import cstruct, ctypes, ctypes_type
4
3
  from dissect.cstruct.exceptions import (
5
4
  Error,
@@ -8,18 +7,25 @@ from dissect.cstruct.exceptions import (
8
7
  ResolveError,
9
8
  )
10
9
  from dissect.cstruct.expression import Expression
11
- from dissect.cstruct.types.base import Array, BaseType, RawType
12
- from dissect.cstruct.types.bytesinteger import BytesInteger
13
- from dissect.cstruct.types.chartype import CharType
14
- from dissect.cstruct.types.enum import Enum, EnumInstance
15
- from dissect.cstruct.types.flag import Flag, FlagInstance
16
- from dissect.cstruct.types.instance import Instance
17
- from dissect.cstruct.types.leb128 import LEB128
18
- from dissect.cstruct.types.packedtype import PackedType
19
- from dissect.cstruct.types.pointer import Pointer, PointerInstance
20
- from dissect.cstruct.types.structure import Field, Structure, Union
21
- from dissect.cstruct.types.voidtype import VoidType
22
- from dissect.cstruct.types.wchartype import WcharType
10
+ from dissect.cstruct.types import (
11
+ LEB128,
12
+ Array,
13
+ BaseType,
14
+ Char,
15
+ CharArray,
16
+ Enum,
17
+ Field,
18
+ Flag,
19
+ Int,
20
+ MetaType,
21
+ Packed,
22
+ Pointer,
23
+ Structure,
24
+ Union,
25
+ Void,
26
+ Wchar,
27
+ WcharArray,
28
+ )
23
29
  from dissect.cstruct.utils import (
24
30
  dumpstruct,
25
31
  hexdump,
@@ -40,31 +46,28 @@ from dissect.cstruct.utils import (
40
46
  )
41
47
 
42
48
  __all__ = [
43
- "Compiler",
44
- "Array",
45
- "Union",
46
- "Field",
47
- "Instance",
49
+ "cstruct",
50
+ "ctypes",
51
+ "ctypes_type",
48
52
  "LEB128",
49
- "Structure",
50
- "Expression",
51
- "PackedType",
52
- "Pointer",
53
- "PointerInstance",
54
- "VoidType",
55
- "WcharType",
56
- "RawType",
53
+ "Array",
57
54
  "BaseType",
58
- "CharType",
55
+ "Char",
56
+ "CharArray",
59
57
  "Enum",
60
- "EnumInstance",
58
+ "Expression",
59
+ "Field",
61
60
  "Flag",
62
- "FlagInstance",
63
- "BytesInteger",
61
+ "Int",
62
+ "MetaType",
63
+ "Packed",
64
+ "Pointer",
65
+ "Structure",
66
+ "Union",
67
+ "Void",
68
+ "Wchar",
69
+ "WcharArray",
64
70
  "BitBuffer",
65
- "cstruct",
66
- "ctypes",
67
- "ctypes_type",
68
71
  "dumpstruct",
69
72
  "hexdump",
70
73
  "pack",
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, BinaryIO, Union
3
+ from typing import TYPE_CHECKING, BinaryIO
4
4
 
5
5
  if TYPE_CHECKING:
6
- from dissect.cstruct.types import RawType
6
+ from dissect.cstruct.types import BaseType
7
7
 
8
8
 
9
9
  class BitBuffer:
@@ -17,7 +17,7 @@ class BitBuffer:
17
17
  self._buffer = 0
18
18
  self._remaining = 0
19
19
 
20
- def read(self, field_type: RawType, bits: Union[int, bytes]) -> int:
20
+ def read(self, field_type: BaseType, bits: int) -> int:
21
21
  if self._remaining == 0 or self._type != field_type:
22
22
  self._type = field_type
23
23
  self._remaining = field_type.size * 8
@@ -43,7 +43,7 @@ class BitBuffer:
43
43
 
44
44
  return v
45
45
 
46
- def write(self, field_type: RawType, data: int, bits: int) -> None:
46
+ def write(self, field_type: BaseType, data: int, bits: int) -> None:
47
47
  if self._remaining == 0 or self._type != field_type:
48
48
  if self._type:
49
49
  self.flush()
@@ -0,0 +1,418 @@
1
+ # Made in Japan
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import logging
7
+ from enum import Enum
8
+ from textwrap import dedent, indent
9
+ from types import MethodType
10
+ from typing import TYPE_CHECKING, Iterator
11
+
12
+ from dissect.cstruct.bitbuffer import BitBuffer
13
+ from dissect.cstruct.types import (
14
+ Array,
15
+ ArrayMetaType,
16
+ Char,
17
+ CharArray,
18
+ Flag,
19
+ Int,
20
+ MetaType,
21
+ Packed,
22
+ Pointer,
23
+ Structure,
24
+ Union,
25
+ Void,
26
+ Wchar,
27
+ WcharArray,
28
+ )
29
+ from dissect.cstruct.types.packed import _struct
30
+
31
+ if TYPE_CHECKING:
32
+ from dissect.cstruct.cstruct import cstruct
33
+ from dissect.cstruct.types.structure import Field
34
+
35
+ SUPPORTED_TYPES = (
36
+ Array,
37
+ Char,
38
+ CharArray,
39
+ Enum,
40
+ Flag,
41
+ Int,
42
+ Packed,
43
+ Pointer,
44
+ Structure,
45
+ Void,
46
+ Wchar,
47
+ WcharArray,
48
+ )
49
+
50
+ log = logging.getLogger(__name__)
51
+
52
+ python_compile = compile
53
+
54
+
55
+ def compile(structure: type[Structure]) -> type[Structure]:
56
+ return Compiler(structure.cs).compile(structure)
57
+
58
+
59
+ class Compiler:
60
+ def __init__(self, cs: cstruct):
61
+ self.cs = cs
62
+
63
+ def compile(self, structure: type[Structure]) -> type[Structure]:
64
+ if issubclass(structure, Union):
65
+ return structure
66
+
67
+ try:
68
+ structure._read = self.compile_read(structure.__fields__, structure.__name__, structure.__align__)
69
+ structure.__compiled__ = True
70
+ except Exception as e:
71
+ # Silently ignore, we didn't compile unfortunately
72
+ log.debug("Failed to compile %s", structure, exc_info=e)
73
+
74
+ return structure
75
+
76
+ def compile_read(self, fields: list[Field], name: str | None = None, align: bool = False) -> MethodType:
77
+ return _ReadSourceGenerator(self.cs, fields, name, align).generate()
78
+
79
+
80
+ class _ReadSourceGenerator:
81
+ def __init__(self, cs: cstruct, fields: list[Field], name: str | None = None, align: bool = False):
82
+ self.cs = cs
83
+ self.fields = fields
84
+ self.name = name
85
+ self.align = align
86
+
87
+ self.field_map: dict[str, Field] = {}
88
+ self._token_id = 0
89
+
90
+ def _map_field(self, field: Field) -> str:
91
+ token = f"_{self._token_id}"
92
+ self.field_map[token] = field
93
+ self._token_id += 1
94
+ return token
95
+
96
+ def generate(self) -> MethodType:
97
+ source = self.generate_source()
98
+ symbols = {token: field.type for token, field in self.field_map.items()}
99
+
100
+ code = python_compile(source, f"<compiled {self.name or 'anonymous'}._read>", "exec")
101
+ exec(code, {"BitBuffer": BitBuffer, "_struct": _struct, **symbols}, d := {})
102
+ obj = d.popitem()[1]
103
+ obj.__source__ = source
104
+
105
+ return classmethod(obj)
106
+
107
+ def generate_source(self) -> str:
108
+ preamble = """
109
+ r = {}
110
+ s = {}
111
+ """
112
+
113
+ if any(field.bits for field in self.fields):
114
+ preamble += "bit_reader = BitBuffer(stream, cls.cs.endian)\n"
115
+
116
+ read_code = "\n".join(self._generate_fields())
117
+
118
+ outro = """
119
+ obj = type.__call__(cls, **r)
120
+ obj._sizes = s
121
+ obj._values = r
122
+
123
+ return obj
124
+ """
125
+
126
+ code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
127
+
128
+ template = f"def _read(cls, stream, context=None):\n{code}"
129
+ return template
130
+
131
+ def _generate_fields(self) -> Iterator[str]:
132
+ current_offset = 0
133
+ current_block: list[Field] = []
134
+ prev_was_bits = False
135
+ prev_bits_type = None
136
+ bits_remaining = 0
137
+ bits_rollover = False
138
+
139
+ def flush() -> Iterator[str]:
140
+ if current_block:
141
+ if self.align and current_block[0].offset is None:
142
+ yield f"stream.seek(-stream.tell() & ({current_block[0].alignment} - 1), {io.SEEK_CUR})"
143
+
144
+ yield from self._generate_packed(current_block)
145
+ current_block[:] = []
146
+
147
+ def align_to_field(field: Field) -> Iterator[str]:
148
+ nonlocal current_offset
149
+
150
+ if field.offset is not None and field.offset != current_offset:
151
+ # If a field has a set offset and it's not the same as the current tracked offset, seek to it
152
+ yield f"stream.seek({field.offset})"
153
+ current_offset = field.offset
154
+
155
+ if self.align and field.offset is None:
156
+ yield f"stream.seek(-stream.tell() & ({field.alignment} - 1), {io.SEEK_CUR})"
157
+
158
+ for field in self.fields:
159
+ field_type = self.cs.resolve(field.type)
160
+
161
+ if not issubclass(field_type, SUPPORTED_TYPES):
162
+ raise TypeError(f"Unsupported type for compiler: {field_type}")
163
+
164
+ if prev_was_bits and not field.bits:
165
+ yield "bit_reader.reset()"
166
+ prev_was_bits = False
167
+ bits_remaining = 0
168
+
169
+ try:
170
+ size = len(field_type)
171
+ is_dynamic = False
172
+ except TypeError:
173
+ size = None
174
+ is_dynamic = True
175
+
176
+ # Sub structure
177
+ if issubclass(field_type, Structure):
178
+ yield from flush()
179
+ yield from align_to_field(field)
180
+ yield from self._generate_structure(field)
181
+
182
+ # Array of structures and multi-dimensional arrays
183
+ elif issubclass(field_type, (Array, CharArray, WcharArray)) and (
184
+ issubclass(field_type.type, Structure) or isinstance(field_type.type, ArrayMetaType) or is_dynamic
185
+ ):
186
+ yield from flush()
187
+ yield from align_to_field(field)
188
+ yield from self._generate_array(field)
189
+
190
+ # Bit fields
191
+ elif field.bits:
192
+ if not prev_was_bits:
193
+ prev_bits_type = field.type
194
+ prev_was_bits = True
195
+
196
+ if bits_remaining == 0 or prev_bits_type != field.type:
197
+ bits_remaining = (size * 8) - field.bits
198
+ bits_rollover = True
199
+
200
+ yield from flush()
201
+ yield from align_to_field(field)
202
+ yield from self._generate_bits(field)
203
+
204
+ # Everything else - basic and composite types (and arrays of them)
205
+ else:
206
+ current_block.append(field)
207
+
208
+ if current_offset is not None and size is not None:
209
+ if not field.bits or (field.bits and bits_rollover):
210
+ current_offset += size
211
+ bits_rollover = False
212
+
213
+ yield from flush()
214
+
215
+ if self.align:
216
+ yield f"stream.seek(-stream.tell() & (cls.alignment - 1), {io.SEEK_CUR})"
217
+
218
+ def _generate_structure(self, field: Field) -> Iterator[str]:
219
+ template = f"""
220
+ _s = stream.tell()
221
+ r["{field.name}"] = {self._map_field(field)}._read(stream, context=r)
222
+ s["{field.name}"] = stream.tell() - _s
223
+ """
224
+
225
+ yield dedent(template)
226
+
227
+ def _generate_array(self, field: Field) -> Iterator[str]:
228
+ template = f"""
229
+ _s = stream.tell()
230
+ r["{field.name}"] = {self._map_field(field)}._read(stream, context=r)
231
+ s["{field.name}"] = stream.tell() - _s
232
+ """
233
+
234
+ yield dedent(template)
235
+
236
+ def _generate_bits(self, field: Field) -> Iterator[str]:
237
+ lookup = self._map_field(field)
238
+ read_type = "_t"
239
+ field_type = field.type
240
+ if issubclass(field_type, (Enum, Flag)):
241
+ read_type += ".type"
242
+ field_type = field_type.type
243
+
244
+ if issubclass(field_type, Char):
245
+ field_type = field_type.cs.uint8
246
+ lookup = "cls.cs.uint8"
247
+
248
+ template = f"""
249
+ _t = {lookup}
250
+ r["{field.name}"] = type.__call__(_t, bit_reader.read({read_type}, {field.bits}))
251
+ """
252
+
253
+ yield dedent(template)
254
+
255
+ def _generate_packed(self, fields: list[Field]) -> Iterator[str]:
256
+ info = list(_generate_struct_info(self.cs, fields, self.align))
257
+ reads = []
258
+
259
+ size = 0
260
+ slice_index = 0
261
+ for field, count, _ in info:
262
+ if field is None:
263
+ # Padding
264
+ size += count
265
+ continue
266
+
267
+ field_type = self.cs.resolve(field.type)
268
+ read_type = _get_read_type(self.cs, field_type)
269
+
270
+ if issubclass(field_type, (Array, CharArray, WcharArray)):
271
+ count = field_type.num_entries
272
+ read_type = _get_read_type(self.cs, field_type.type)
273
+
274
+ if issubclass(read_type, (Char, Wchar, Int)):
275
+ count *= read_type.size
276
+ getter = f"buf[{size}:{size + count}]"
277
+ else:
278
+ getter = f"data[{slice_index}:{slice_index + count}]"
279
+ slice_index += count
280
+ elif issubclass(read_type, (Char, Wchar, Int)):
281
+ getter = f"buf[{size}:{size + read_type.size}]"
282
+ else:
283
+ getter = f"data[{slice_index}]"
284
+ slice_index += 1
285
+
286
+ if issubclass(read_type, (Wchar, Int)):
287
+ # Types that parse bytes further down to their own type
288
+ parser_template = "{type}({getter})"
289
+ else:
290
+ # All other types can be simply intialized
291
+ parser_template = "type.__call__({type}, {getter})"
292
+
293
+ # Create the final reading code
294
+ if issubclass(field_type, Array):
295
+ reads.append(f"_t = {self._map_field(field)}")
296
+ reads.append("_et = _t.type")
297
+
298
+ if issubclass(field_type.type, Int):
299
+ reads.append(f"_b = {getter}")
300
+ item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.size}]")
301
+ list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.size})]"
302
+ elif issubclass(field_type.type, Pointer):
303
+ item_parser = "_et.__new__(_et, e, stream, r)"
304
+ list_comp = f"[{item_parser} for e in {getter}]"
305
+ else:
306
+ item_parser = parser_template.format(type="_et", getter="e")
307
+ list_comp = f"[{item_parser} for e in {getter}]"
308
+
309
+ parser = f"type.__call__(_t, {list_comp})"
310
+ elif issubclass(field_type, CharArray):
311
+ parser = f"type.__call__({self._map_field(field)}, {getter})"
312
+ elif issubclass(field_type, Pointer):
313
+ reads.append(f"_pt = {self._map_field(field)}")
314
+ parser = f"_pt.__new__(_pt, {getter}, stream, r)"
315
+ else:
316
+ parser = parser_template.format(type=self._map_field(field), getter=getter)
317
+
318
+ reads.append(f'r["{field.name}"] = {parser}')
319
+ reads.append(f's["{field.name}"] = {field_type.size}')
320
+ reads.append("") # Generates a newline in the resulting code
321
+
322
+ size += field_type.size
323
+
324
+ fmt = _optimize_struct_fmt(info)
325
+ if fmt == "x" or (len(fmt) == 2 and fmt[1] == "x"):
326
+ unpack = ""
327
+ else:
328
+ unpack = f'data = _struct(cls.cs.endian, "{fmt}").unpack(buf)\n'
329
+
330
+ template = f"""
331
+ buf = stream.read({size})
332
+ if len(buf) != {size}: raise EOFError()
333
+ {unpack}
334
+ """
335
+
336
+ yield dedent(template) + "\n".join(reads)
337
+
338
+
339
+ def _generate_struct_info(cs: cstruct, fields: list[Field], align: bool = False) -> Iterator[tuple[Field, int, str]]:
340
+ if not fields:
341
+ return
342
+
343
+ current_offset = fields[0].offset
344
+ imaginary_offset = 0
345
+ for field in fields:
346
+ # We moved -- probably due to alignment
347
+ if field.offset is not None and (drift := field.offset - current_offset) > 0:
348
+ yield None, drift, "x"
349
+ current_offset += drift
350
+
351
+ if align and field.offset is None and (drift := -imaginary_offset & (field.alignment - 1)) > 0:
352
+ # Assume we started at a correctly aligned boundary
353
+ yield None, drift, "x"
354
+ imaginary_offset += drift
355
+
356
+ count = 1
357
+ read_type = _get_read_type(cs, field.type)
358
+
359
+ # Drop voids
360
+ if issubclass(read_type, Void):
361
+ continue
362
+
363
+ # Array of more complex types are handled elsewhere
364
+ if issubclass(read_type, (Array, CharArray, WcharArray)):
365
+ count = read_type.num_entries
366
+ read_type = _get_read_type(cs, read_type.type)
367
+
368
+ # Take the pack char for Packed
369
+ if issubclass(read_type, Packed):
370
+ yield field, count, read_type.packchar
371
+
372
+ # Other types are byte based
373
+ # We don't actually unpack anything here but slice directly out of the buffer
374
+ elif issubclass(read_type, (Char, Wchar, Int)):
375
+ yield field, count * read_type.size, "x"
376
+
377
+ size = count * read_type.size
378
+ imaginary_offset += size
379
+ if current_offset is not None:
380
+ current_offset += size
381
+
382
+
383
+ def _optimize_struct_fmt(info: Iterator[tuple[Field, int, str]]) -> str:
384
+ chars = []
385
+
386
+ current_count = 0
387
+ current_char = None
388
+
389
+ for _, count, char in info:
390
+ if current_char is None:
391
+ current_count = count
392
+ current_char = char
393
+ continue
394
+
395
+ if char != current_char:
396
+ if current_count:
397
+ chars.append((current_count, current_char))
398
+ current_count = count
399
+ current_char = char
400
+ else:
401
+ current_count += count
402
+
403
+ if current_char is not None and current_count:
404
+ chars.append((current_count, current_char))
405
+
406
+ return "".join(f"{count if count > 1 else ''}{char}" for count, char in chars)
407
+
408
+
409
+ def _get_read_type(cs: cstruct, type_: MetaType | str) -> MetaType:
410
+ type_ = cs.resolve(type_)
411
+
412
+ if issubclass(type_, (Enum, Flag)):
413
+ type_ = type_.type
414
+
415
+ if issubclass(type_, Pointer):
416
+ type_ = cs.pointer
417
+
418
+ return cs.resolve(type_)