dissect.cstruct 4.4.dev3__tar.gz → 4.5.dev1__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 (76) hide show
  1. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/PKG-INFO +5 -2
  2. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/bitbuffer.py +15 -4
  3. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/compiler.py +16 -15
  4. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/cstruct.py +202 -40
  5. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/parser.py +25 -15
  6. dissect_cstruct-4.5.dev1/dissect/cstruct/tools/stubgen.py +220 -0
  7. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/__init__.py +2 -2
  8. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/base.py +50 -38
  9. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/char.py +19 -16
  10. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/enum.py +33 -14
  11. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/int.py +6 -3
  12. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/leb128.py +6 -3
  13. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/packed.py +13 -7
  14. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/pointer.py +25 -20
  15. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/structure.py +46 -39
  16. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/wchar.py +11 -11
  17. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/utils.py +10 -9
  18. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/PKG-INFO +5 -2
  19. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/SOURCES.txt +6 -0
  20. dissect_cstruct-4.5.dev1/dissect.cstruct.egg-info/entry_points.txt +2 -0
  21. dissect_cstruct-4.5.dev1/dissect.cstruct.egg-info/requires.txt +3 -0
  22. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/pyproject.toml +8 -0
  23. dissect_cstruct-4.5.dev1/tests/_docs/__init__.py +0 -0
  24. dissect_cstruct-4.5.dev1/tests/test_annotations.py +29 -0
  25. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_basic.py +11 -4
  26. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_compiler.py +2 -2
  27. dissect_cstruct-4.5.dev1/tests/test_ctypes.py +49 -0
  28. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_parser.py +40 -5
  29. dissect_cstruct-4.5.dev1/tests/test_tools_stubgen.py +332 -0
  30. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_base.py +2 -2
  31. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_custom.py +2 -2
  32. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_structure.py +1 -1
  33. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_union.py +1 -1
  34. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/utils.py +5 -0
  35. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tox.ini +1 -0
  36. dissect_cstruct-4.4.dev3/tests/test_ctypes.py +0 -32
  37. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/.git-blame-ignore-revs +0 -0
  38. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/.gitattributes +0 -0
  39. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/COPYRIGHT +0 -0
  40. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/LICENSE +0 -0
  41. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/MANIFEST.in +0 -0
  42. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/README.md +0 -0
  43. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/__init__.py +0 -0
  44. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/exceptions.py +0 -0
  45. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/expression.py +0 -0
  46. {dissect_cstruct-4.4.dev3/tests → dissect_cstruct-4.5.dev1/dissect/cstruct/tools}/__init__.py +0 -0
  47. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/flag.py +0 -0
  48. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/void.py +0 -0
  49. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  50. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/top_level.txt +0 -0
  51. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/disk.py +0 -0
  52. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/mirai.py +0 -0
  53. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/pe.py +0 -0
  54. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/protobuf.py +0 -0
  55. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/secdesc.py +0 -0
  56. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/setup.cfg +0 -0
  57. {dissect_cstruct-4.4.dev3/tests/_docs → dissect_cstruct-4.5.dev1/tests}/__init__.py +0 -0
  58. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_data/testdef.txt +0 -0
  59. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/Makefile +0 -0
  60. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/conf.py +0 -0
  61. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/index.rst +0 -0
  62. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/conftest.py +0 -0
  63. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_align.py +0 -0
  64. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_bitbuffer.py +0 -0
  65. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_bitfield.py +0 -0
  66. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_expression.py +0 -0
  67. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_char.py +0 -0
  68. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_enum.py +0 -0
  69. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_flag.py +0 -0
  70. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_int.py +0 -0
  71. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_leb128.py +0 -0
  72. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_packed.py +0 -0
  73. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_pointer.py +0 -0
  74. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_void.py +0 -0
  75. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_wchar.py +0 -0
  76. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.4.dev3
3
+ Version: 4.5.dev1
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -22,6 +22,9 @@ Requires-Python: ~=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  License-File: COPYRIGHT
25
+ Provides-Extra: dev
26
+ Requires-Dist: typing_extensions; extra == "dev"
27
+ Dynamic: license-file
25
28
 
26
29
  # dissect.cstruct
27
30
 
@@ -13,12 +13,15 @@ class BitBuffer:
13
13
  self.stream = stream
14
14
  self.endian = endian
15
15
 
16
- self._type = None
16
+ self._type: type[BaseType] | None = None
17
17
  self._buffer = 0
18
18
  self._remaining = 0
19
19
 
20
- def read(self, field_type: BaseType, bits: int) -> int:
20
+ def read(self, field_type: type[BaseType], bits: int) -> int:
21
21
  if self._remaining == 0 or self._type != field_type:
22
+ if field_type.size is None:
23
+ raise ValueError("Reading variable-length fields is unsupported")
24
+
22
25
  self._type = field_type
23
26
  self._remaining = field_type.size * 8
24
27
  self._buffer = field_type._read(self.stream)
@@ -43,13 +46,20 @@ class BitBuffer:
43
46
 
44
47
  return v
45
48
 
46
- def write(self, field_type: BaseType, data: int, bits: int) -> None:
49
+ def write(self, field_type: type[BaseType], data: int, bits: int) -> None:
47
50
  if self._remaining == 0 or self._type != field_type:
48
51
  if self._type:
49
52
  self.flush()
53
+
54
+ if field_type.size is None:
55
+ raise ValueError("Writing variable-length fields is unsupported")
56
+
50
57
  self._remaining = field_type.size * 8
51
58
  self._type = field_type
52
59
 
60
+ if self._type is None or self._type.size is None:
61
+ raise ValueError("Invalid state")
62
+
53
63
  if self.endian == "<":
54
64
  self._buffer |= data << (self._type.size * 8 - self._remaining)
55
65
  else:
@@ -60,7 +70,8 @@ class BitBuffer:
60
70
  self.flush()
61
71
 
62
72
  def flush(self) -> None:
63
- self._type._write(self.stream, self._buffer)
73
+ if self._type is not None:
74
+ self._type._write(self.stream, self._buffer)
64
75
  self._type = None
65
76
  self._remaining = 0
66
77
  self._buffer = 0
@@ -11,12 +11,11 @@ from typing import TYPE_CHECKING
11
11
  from dissect.cstruct.bitbuffer import BitBuffer
12
12
  from dissect.cstruct.types import (
13
13
  Array,
14
- ArrayMetaType,
14
+ BaseType,
15
15
  Char,
16
16
  CharArray,
17
17
  Flag,
18
18
  Int,
19
- MetaType,
20
19
  Packed,
21
20
  Pointer,
22
21
  Structure,
@@ -25,6 +24,7 @@ from dissect.cstruct.types import (
25
24
  Wchar,
26
25
  WcharArray,
27
26
  )
27
+ from dissect.cstruct.types.base import BaseArray
28
28
  from dissect.cstruct.types.enum import EnumMetaType
29
29
  from dissect.cstruct.types.packed import _struct
30
30
 
@@ -159,7 +159,7 @@ class _ReadSourceGenerator:
159
159
  yield f"stream.seek(-stream.tell() & ({field.alignment} - 1), {io.SEEK_CUR})"
160
160
 
161
161
  for field in self.fields:
162
- field_type = self.cs.resolve(field.type)
162
+ field_type = field.type
163
163
 
164
164
  if isinstance(field_type, EnumMetaType):
165
165
  field_type = field_type.type
@@ -187,7 +187,7 @@ class _ReadSourceGenerator:
187
187
 
188
188
  # Array of structures and multi-dimensional arrays
189
189
  elif issubclass(field_type, (Array, CharArray, WcharArray)) and (
190
- issubclass(field_type.type, Structure) or isinstance(field_type.type, ArrayMetaType) or is_dynamic
190
+ issubclass(field_type.type, Structure) or issubclass(field_type.type, BaseArray) or is_dynamic
191
191
  ):
192
192
  yield from flush()
193
193
  yield from align_to_field(field)
@@ -195,6 +195,9 @@ class _ReadSourceGenerator:
195
195
 
196
196
  # Bit fields
197
197
  elif field.bits:
198
+ if size is None:
199
+ raise TypeError(f"Unsupported type for bit field: {field_type}")
200
+
198
201
  if not prev_was_bits:
199
202
  prev_bits_type = field_type
200
203
  prev_was_bits = True
@@ -223,8 +226,8 @@ class _ReadSourceGenerator:
223
226
  def _generate_structure(self, field: Field) -> Iterator[str]:
224
227
  template = f"""
225
228
  _s = stream.tell()
226
- r["{field.name}"] = {self._map_field(field)}._read(stream, context=r)
227
- s["{field.name}"] = stream.tell() - _s
229
+ r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
230
+ s["{field._name}"] = stream.tell() - _s
228
231
  """
229
232
 
230
233
  yield dedent(template)
@@ -232,8 +235,8 @@ class _ReadSourceGenerator:
232
235
  def _generate_array(self, field: Field) -> Iterator[str]:
233
236
  template = f"""
234
237
  _s = stream.tell()
235
- r["{field.name}"] = {self._map_field(field)}._read(stream, context=r)
236
- s["{field.name}"] = stream.tell() - _s
238
+ r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
239
+ s["{field._name}"] = stream.tell() - _s
237
240
  """
238
241
 
239
242
  yield dedent(template)
@@ -252,7 +255,7 @@ class _ReadSourceGenerator:
252
255
 
253
256
  template = f"""
254
257
  _t = {lookup}
255
- r["{field.name}"] = type.__call__(_t, bit_reader.read({read_type}, {field.bits}))
258
+ r["{field._name}"] = type.__call__(_t, bit_reader.read({read_type}, {field.bits}))
256
259
  """
257
260
 
258
261
  yield dedent(template)
@@ -269,7 +272,7 @@ class _ReadSourceGenerator:
269
272
  size += count
270
273
  continue
271
274
 
272
- field_type = self.cs.resolve(field.type)
275
+ field_type = field.type
273
276
  read_type = _get_read_type(self.cs, field_type)
274
277
 
275
278
  if issubclass(field_type, (Array, CharArray, WcharArray)):
@@ -320,8 +323,8 @@ class _ReadSourceGenerator:
320
323
  else:
321
324
  parser = parser_template.format(type=self._map_field(field), getter=getter)
322
325
 
323
- reads.append(f'r["{field.name}"] = {parser}')
324
- reads.append(f's["{field.name}"] = {field_type.size}')
326
+ reads.append(f'r["{field._name}"] = {parser}')
327
+ reads.append(f's["{field._name}"] = {field_type.size}')
325
328
  reads.append("") # Generates a newline in the resulting code
326
329
 
327
330
  size += field_type.size
@@ -411,9 +414,7 @@ def _optimize_struct_fmt(info: Iterator[tuple[Field, int, str]]) -> str:
411
414
  return "".join(f"{count if count > 1 else ''}{char}" for count, char in chars)
412
415
 
413
416
 
414
- def _get_read_type(cs: cstruct, type_: MetaType | str) -> MetaType:
415
- type_ = cs.resolve(type_)
416
-
417
+ def _get_read_type(cs: cstruct, type_: type[BaseType]) -> type[BaseType]:
417
418
  if issubclass(type_, (Enum, Flag)):
418
419
  type_ = type_.type
419
420
 
@@ -5,21 +5,21 @@ import struct
5
5
  import sys
6
6
  import types
7
7
  from pathlib import Path
8
- from typing import TYPE_CHECKING, Any, BinaryIO
8
+ from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
9
9
 
10
10
  from dissect.cstruct.exceptions import ResolveError
11
11
  from dissect.cstruct.expression import Expression
12
12
  from dissect.cstruct.parser import CStyleParser, TokenParser
13
13
  from dissect.cstruct.types import (
14
14
  LEB128,
15
- ArrayMetaType,
15
+ Array,
16
+ BaseArray,
16
17
  BaseType,
17
18
  Char,
18
19
  Enum,
19
20
  Field,
20
21
  Flag,
21
22
  Int,
22
- MetaType,
23
23
  Packed,
24
24
  Pointer,
25
25
  Structure,
@@ -29,7 +29,12 @@ from dissect.cstruct.types import (
29
29
  )
30
30
 
31
31
  if TYPE_CHECKING:
32
- from collections.abc import Iterator
32
+ from collections.abc import Iterable
33
+
34
+ from typing_extensions import TypeAlias
35
+
36
+
37
+ T = TypeVar("T", bound=BaseType)
33
38
 
34
39
 
35
40
  class cstruct:
@@ -37,7 +42,7 @@ class cstruct:
37
42
 
38
43
  Args:
39
44
  endian: The endianness to use when parsing.
40
- pointer: The pointer type to use for Pointers.
45
+ pointer: The pointer type to use for pointers.
41
46
  """
42
47
 
43
48
  DEF_CSTYLE = 1
@@ -81,16 +86,16 @@ class cstruct:
81
86
  "signed char": "int8",
82
87
  "unsigned char": "char",
83
88
  "short": "int16",
84
- "signed short": "short",
89
+ "signed short": "int16",
85
90
  "unsigned short": "uint16",
86
91
  "int": "int32",
87
- "signed int": "int",
92
+ "signed int": "int32",
88
93
  "unsigned int": "uint32",
89
94
  "long": "int32",
90
- "signed long": "long",
95
+ "signed long": "int32",
91
96
  "unsigned long": "uint32",
92
97
  "long long": "int64",
93
- "signed long long": "long long",
98
+ "signed long long": "int64",
94
99
  "unsigned long long": "uint64",
95
100
 
96
101
  # Windows types
@@ -113,14 +118,14 @@ class cstruct:
113
118
  "ULONG64": "uint64",
114
119
  "ULONGLONG": "uint64",
115
120
 
116
- "INT": "int",
121
+ "INT": "int32",
117
122
  "INT8": "int8",
118
123
  "INT16": "int16",
119
124
  "INT32": "int32",
120
125
  "INT64": "int64",
121
126
  "INT128": "int128",
122
127
 
123
- "UINT": "uint",
128
+ "UINT": "uint32",
124
129
  "UINT8": "uint8",
125
130
  "UINT16": "uint16",
126
131
  "UINT32": "uint32",
@@ -172,14 +177,14 @@ class cstruct:
172
177
  "__u32": "uint32",
173
178
  "__u64": "uint64",
174
179
  "uchar": "uint8",
175
- "ushort": "unsigned short",
176
- "uint": "unsigned int",
177
- "ulong": "unsigned long",
180
+ "ushort": "uint16",
181
+ "uint": "uint32",
182
+ "ulong": "uint32",
178
183
  }
179
184
  # fmt: on
180
185
 
181
186
  pointer = pointer or ("uint64" if sys.maxsize > 2**32 else "uint32")
182
- self.pointer = self.resolve(pointer)
187
+ self.pointer: type[BaseType] = self.resolve(pointer)
183
188
  self._anonymous_count = 0
184
189
 
185
190
  def __getattr__(self, attr: str) -> Any:
@@ -200,7 +205,7 @@ class cstruct:
200
205
  self._anonymous_count += 1
201
206
  return name
202
207
 
203
- def add_type(self, name: str, type_: MetaType | str, replace: bool = False) -> None:
208
+ def add_type(self, name: str, type_: type[BaseType] | str, replace: bool = False) -> None:
204
209
  """Add a type or type reference.
205
210
 
206
211
  Only use this method when creating type aliases or adding already bound types.
@@ -220,7 +225,7 @@ class cstruct:
220
225
  addtype = add_type
221
226
 
222
227
  def add_custom_type(
223
- self, name: str, type_: MetaType, size: int | None = None, alignment: int | None = None, **kwargs
228
+ self, name: str, type_: type[BaseType], size: int | None = None, alignment: int | None = None, **kwargs
224
229
  ) -> None:
225
230
  """Add a custom type.
226
231
 
@@ -265,7 +270,7 @@ class cstruct:
265
270
  def loadfile(self, path: str, deftype: int | None = None, **kwargs) -> None:
266
271
  """Load structure definitions from a file.
267
272
 
268
- The given path will be read and parsed using the .load() function.
273
+ The given path will be read and parsed using the :meth:`~cstruct.load` function.
269
274
 
270
275
  Args:
271
276
  path: The path to load definitions from.
@@ -287,7 +292,7 @@ class cstruct:
287
292
  """
288
293
  return self.resolve(name).read(stream)
289
294
 
290
- def resolve(self, name: str) -> MetaType:
295
+ def resolve(self, name: type[BaseType] | str) -> type[BaseType]:
291
296
  """Resolve a type name to get the actual type object.
292
297
 
293
298
  Types can be referenced using different names. When we want
@@ -320,7 +325,7 @@ class cstruct:
320
325
  def _make_type(
321
326
  self,
322
327
  name: str,
323
- bases: Iterator[object],
328
+ bases: Iterable[object],
324
329
  size: int | None,
325
330
  *,
326
331
  alignment: int | None = None,
@@ -341,10 +346,18 @@ class cstruct:
341
346
  )
342
347
  return types.new_class(name, bases, {}, lambda ns: ns.update(attrs))
343
348
 
344
- def _make_array(self, type_: MetaType, num_entries: int | Expression | None) -> ArrayMetaType:
345
- null_terminated = num_entries is None
346
- dynamic = isinstance(num_entries, Expression) or type_.dynamic
347
- size = None if (null_terminated or dynamic) else (num_entries * type_.size)
349
+ def _make_array(self, type_: T, num_entries: int | Expression | None) -> type[Array[T]]:
350
+ null_terminated = False
351
+ if num_entries is None:
352
+ null_terminated = True
353
+ size = None
354
+ elif isinstance(num_entries, Expression) or type_.dynamic:
355
+ size = None
356
+ else:
357
+ if type_.size is None:
358
+ raise ValueError(f"Cannot create array of dynamic type: {type_.__name__}")
359
+ size = num_entries * type_.size
360
+
348
361
  name = f"{type_.__name__}[]" if null_terminated else f"{type_.__name__}[{num_entries}]"
349
362
 
350
363
  bases = (type_.ArrayType,)
@@ -355,27 +368,30 @@ class cstruct:
355
368
  "null_terminated": null_terminated,
356
369
  }
357
370
 
358
- return self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)
371
+ return cast(type[Array], self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
359
372
 
360
373
  def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
361
- return self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})
374
+ return cast(type[Int], self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
362
375
 
363
376
  def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
364
- return self._make_type(
365
- name,
366
- (base, Packed),
367
- struct.calcsize(packchar),
368
- alignment=alignment,
369
- attrs={"packchar": packchar},
377
+ return cast(
378
+ type[Packed],
379
+ self._make_type(
380
+ name,
381
+ (base, Packed),
382
+ struct.calcsize(packchar),
383
+ alignment=alignment,
384
+ attrs={"packchar": packchar},
385
+ ),
370
386
  )
371
387
 
372
- def _make_enum(self, name: str, type_: MetaType, values: dict[str, int]) -> type[Enum]:
388
+ def _make_enum(self, name: str, type_: type[BaseType], values: dict[str, int]) -> type[Enum]:
373
389
  return Enum(self, name, type_, values)
374
390
 
375
- def _make_flag(self, name: str, type_: MetaType, values: dict[str, int]) -> type[Flag]:
391
+ def _make_flag(self, name: str, type_: type[BaseType], values: dict[str, int]) -> type[Flag]:
376
392
  return Flag(self, name, type_, values)
377
393
 
378
- def _make_pointer(self, target: MetaType) -> type[Pointer]:
394
+ def _make_pointer(self, target: type[BaseType]) -> type[Pointer]:
379
395
  return self._make_type(
380
396
  f"{target.__name__}*",
381
397
  (Pointer,),
@@ -409,18 +425,164 @@ class cstruct:
409
425
  ) -> type[Structure]:
410
426
  return self._make_struct(name, fields, align=align, anonymous=anonymous, base=Union)
411
427
 
428
+ Z = TYPE_CHECKING
429
+
430
+ if TYPE_CHECKING:
431
+ A = 1
432
+ # ruff: noqa: PYI042
433
+ _int = int
434
+ _float = float
435
+
436
+ class int8(_int, Packed[_int]): ...
437
+
438
+ class uint8(_int, Packed[_int]): ...
439
+
440
+ class int16(_int, Packed[_int]): ...
441
+
442
+ class uint16(_int, Packed[_int]): ...
443
+
444
+ class int32(_int, Packed[_int]): ...
445
+
446
+ class uint32(_int, Packed[_int]): ...
447
+
448
+ class int64(_int, Packed[_int]): ...
449
+
450
+ class uint64(_int, Packed[_int]): ...
451
+
452
+ class float16(_float, Packed[_float]): ...
453
+
454
+ class float(_float, Packed[_float]): ...
455
+
456
+ class double(_float, Packed[_float]): ...
457
+
458
+ class char(Char): ...
459
+
460
+ class wchar(Wchar): ...
461
+
462
+ class int24(Int): ...
463
+
464
+ class uint24(Int): ...
465
+
466
+ class int48(Int): ...
467
+
468
+ class uint48(Int): ...
469
+
470
+ class int128(Int): ...
471
+
472
+ class uint128(Int): ...
473
+
474
+ class uleb128(LEB128): ...
475
+
476
+ class ileb128(LEB128): ...
477
+
478
+ class void(Void): ...
479
+
480
+ # signed char: TypeAlias = int8
481
+ # signed char: TypeAlias = char
482
+ short: TypeAlias = int16
483
+ # signed short: TypeAlias = int16
484
+ # unsigned short: TypeAlias = uint16
485
+ int: TypeAlias = int32
486
+ # signed int: TypeAlias = int32
487
+ # unsigned int: TypeAlias = uint32
488
+ long: TypeAlias = int32
489
+ # signed long: TypeAlias = int32
490
+ # unsigned long: TypeAlias = uint32
491
+ # long long: TypeAlias = int64
492
+ # signed long long: TypeAlias = int64
493
+ # unsigned long long: TypeAlias = uint64
494
+
495
+ BYTE: TypeAlias = uint8
496
+ CHAR: TypeAlias = char
497
+ SHORT: TypeAlias = int16
498
+ WORD: TypeAlias = uint16
499
+ DWORD: TypeAlias = uint32
500
+ LONG: TypeAlias = int32
501
+ LONG32: TypeAlias = int32
502
+ LONG64: TypeAlias = int64
503
+ LONGLONG: TypeAlias = int64
504
+ QWORD: TypeAlias = uint64
505
+ OWORD: TypeAlias = uint128
506
+ WCHAR: TypeAlias = wchar
507
+
508
+ UCHAR: TypeAlias = uint8
509
+ USHORT: TypeAlias = uint16
510
+ ULONG: TypeAlias = uint32
511
+ ULONG64: TypeAlias = uint64
512
+ ULONGLONG: TypeAlias = uint64
513
+
514
+ INT: TypeAlias = int32
515
+ INT8: TypeAlias = int8
516
+ INT16: TypeAlias = int16
517
+ INT32: TypeAlias = int32
518
+ INT64: TypeAlias = int64
519
+ INT128: TypeAlias = int128
520
+
521
+ UINT: TypeAlias = uint32
522
+ UINT8: TypeAlias = uint8
523
+ UINT16: TypeAlias = uint16
524
+ UINT32: TypeAlias = uint32
525
+ UINT64: TypeAlias = uint64
526
+ UINT128: TypeAlias = uint128
527
+
528
+ __int8: TypeAlias = int8
529
+ __int16: TypeAlias = int16
530
+ __int32: TypeAlias = int32
531
+ __int64: TypeAlias = int64
532
+ __int128: TypeAlias = int128
533
+
534
+ # unsigned __int8: TypeAlias = uint8
535
+ # unsigned __int16: TypeAlias = uint16
536
+ # unsigned __int32: TypeAlias = uint32
537
+ # unsigned __int64: TypeAlias = uint64
538
+ # unsigned __int128: TypeAlias = uint128
539
+
540
+ wchar_t: TypeAlias = wchar
412
541
 
413
- def ctypes(structure: Structure) -> _ctypes.Structure:
542
+ int8_t: TypeAlias = int8
543
+ int16_t: TypeAlias = int16
544
+ int32_t: TypeAlias = int32
545
+ int64_t: TypeAlias = int64
546
+ int128_t: TypeAlias = int128
547
+
548
+ uint8_t: TypeAlias = uint8
549
+ uint16_t: TypeAlias = uint16
550
+ uint32_t: TypeAlias = uint32
551
+ uint64_t: TypeAlias = uint64
552
+ uint128_t: TypeAlias = uint128
553
+
554
+ _BYTE: TypeAlias = uint8
555
+ _WORD: TypeAlias = uint16
556
+ _DWORD: TypeAlias = uint32
557
+ _QWORD: TypeAlias = uint64
558
+ _OWORD: TypeAlias = uint128
559
+
560
+ u1: TypeAlias = uint8
561
+ u2: TypeAlias = uint16
562
+ u4: TypeAlias = uint32
563
+ u8: TypeAlias = uint64
564
+ u16: TypeAlias = uint128
565
+ __u8: TypeAlias = uint8
566
+ __u16: TypeAlias = uint16
567
+ __u32: TypeAlias = uint32
568
+ __u64: TypeAlias = uint64
569
+ uchar: TypeAlias = uint8
570
+ ushort: TypeAlias = uint16
571
+ uint: TypeAlias = uint32
572
+ ulong: TypeAlias = uint32
573
+
574
+
575
+ def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
414
576
  """Create ctypes structures from cstruct structures."""
415
577
  fields = []
416
578
  for field in structure.__fields__:
417
579
  t = ctypes_type(field.type)
418
- fields.append((field.name, t))
580
+ fields.append((field._name, t))
419
581
 
420
- return type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
582
+ return type(structure.__name__, (_ctypes.Structure,), {"_fields_": fields})
421
583
 
422
584
 
423
- def ctypes_type(type_: MetaType) -> Any:
585
+ def ctypes_type(type_: type[BaseType]) -> Any:
424
586
  mapping = {
425
587
  "b": _ctypes.c_int8,
426
588
  "B": _ctypes.c_uint8,
@@ -443,7 +605,7 @@ def ctypes_type(type_: MetaType) -> Any:
443
605
  if issubclass(type_, Wchar):
444
606
  return _ctypes.c_wchar
445
607
 
446
- if isinstance(type_, ArrayMetaType):
608
+ if issubclass(type_, BaseArray):
447
609
  subtype = ctypes_type(type_.type)
448
610
  return subtype * type_.num_entries
449
611
 
@@ -11,7 +11,7 @@ from dissect.cstruct.exceptions import (
11
11
  ParserError,
12
12
  )
13
13
  from dissect.cstruct.expression import Expression
14
- from dissect.cstruct.types import ArrayMetaType, Field, MetaType
14
+ from dissect.cstruct.types import BaseArray, BaseType, Field, Structure
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from dissect.cstruct import cstruct
@@ -147,22 +147,28 @@ class TokenParser(Parser):
147
147
  tokens.consume()
148
148
  type_ = None
149
149
 
150
+ names = []
151
+
150
152
  if tokens.next == self.TOK.IDENTIFIER:
151
153
  type_ = self.cstruct.resolve(self._identifier(tokens))
152
154
  elif tokens.next == self.TOK.STRUCT:
153
- # The register thing is a bit dirty
154
- # Basically consumes all NAME tokens and
155
- # registers the struct
156
- type_ = self._struct(tokens, register=True)
155
+ type_ = self._struct(tokens)
156
+ if not type_.__anonymous__:
157
+ names.append(type_.__name__)
157
158
 
158
- names = self._names(tokens)
159
+ names.extend(self._names(tokens))
159
160
  for name in names:
161
+ if issubclass(type_, Structure) and type_.__anonymous__:
162
+ type_.__anonymous__ = False
163
+ type_.__name__ = name
164
+ type_.__qualname__ = name
165
+
160
166
  type_, name, bits = self._parse_field_type(type_, name)
161
167
  if bits is not None:
162
168
  raise ParserError(f"line {self._lineno(tokens.previous)}: typedefs cannot have bitfields")
163
169
  self.cstruct.add_type(name, type_)
164
170
 
165
- def _struct(self, tokens: TokenConsumer, register: bool = False) -> None:
171
+ def _struct(self, tokens: TokenConsumer, register: bool = False) -> type[Structure]:
166
172
  stype = tokens.consume()
167
173
 
168
174
  factory = self.cstruct._make_union if stype.value.startswith("union") else self.cstruct._make_struct
@@ -204,9 +210,14 @@ class TokenParser(Parser):
204
210
  field = self._parse_field(tokens)
205
211
  fields.append(field)
206
212
 
207
- # All names from here on are from typedef's
208
- # Parsing names consumes the EOL token
209
- names.extend(self._names(tokens))
213
+ if register:
214
+ names.extend(self._names(tokens))
215
+
216
+ # If the next token is EOL, consume it
217
+ # Otherwise we're part of a typedef or field definition
218
+ if tokens.next == self.TOK.EOL:
219
+ tokens.eol()
220
+
210
221
  name = names[0] if names else None
211
222
 
212
223
  if st is None:
@@ -251,8 +262,7 @@ class TokenParser(Parser):
251
262
  type_ = self._struct(tokens)
252
263
 
253
264
  if tokens.next != self.TOK.NAME:
254
- type_, name, bits = self._parse_field_type(type_, type_.__name__)
255
- return Field(name.strip(), type_, bits)
265
+ return Field(None, type_, None)
256
266
 
257
267
  if tokens.next != self.TOK.NAME:
258
268
  raise ParserError(f"line {self._lineno(tokens.next)}: expected name")
@@ -263,7 +273,7 @@ class TokenParser(Parser):
263
273
  tokens.eol()
264
274
  return Field(name.strip(), type_, bits)
265
275
 
266
- def _parse_field_type(self, type_: MetaType, name: str) -> tuple[MetaType, str, int | None]:
276
+ def _parse_field_type(self, type_: type[BaseType], name: str) -> tuple[type[BaseType], str, int | None]:
267
277
  pattern = self.TOK.patterns[self.TOK.NAME]
268
278
  # Dirty trick because the regex expects a ; but we don't want it to be part of the value
269
279
  d = pattern.match(name + ";").groupdict()
@@ -289,7 +299,7 @@ class TokenParser(Parser):
289
299
  except Exception:
290
300
  pass
291
301
 
292
- if isinstance(type_, ArrayMetaType) and count is None:
302
+ if issubclass(type_, BaseArray) and count is None:
293
303
  raise ParserError("Depth required for multi-dimensional array")
294
304
 
295
305
  type_ = self.cstruct._make_array(type_, count)
@@ -571,7 +581,7 @@ class TokenCollection:
571
581
 
572
582
  return object.__getattribute__(self, attr)
573
583
 
574
- def add(self, regex: str, name: str) -> None:
584
+ def add(self, regex: str, name: str | None) -> None:
575
585
  if name is None:
576
586
  self.tokens.append((regex, None))
577
587
  else: