dissect.cstruct 4.4.dev3__tar.gz → 4.5.dev2__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.dev2}/PKG-INFO +5 -2
  2. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/bitbuffer.py +15 -4
  3. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/compiler.py +16 -15
  4. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/cstruct.py +199 -40
  5. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/parser.py +25 -15
  6. dissect_cstruct-4.5.dev2/dissect/cstruct/tools/stubgen.py +226 -0
  7. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/__init__.py +2 -2
  8. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/base.py +50 -38
  9. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/char.py +19 -16
  10. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/enum.py +33 -14
  11. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/int.py +6 -3
  12. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/leb128.py +6 -3
  13. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/packed.py +13 -7
  14. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/pointer.py +25 -20
  15. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/structure.py +46 -39
  16. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/wchar.py +11 -11
  17. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/utils.py +10 -9
  18. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect.cstruct.egg-info/PKG-INFO +5 -2
  19. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect.cstruct.egg-info/SOURCES.txt +6 -0
  20. dissect_cstruct-4.5.dev2/dissect.cstruct.egg-info/entry_points.txt +2 -0
  21. dissect_cstruct-4.5.dev2/dissect.cstruct.egg-info/requires.txt +3 -0
  22. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/pyproject.toml +8 -0
  23. dissect_cstruct-4.5.dev2/tests/_docs/__init__.py +0 -0
  24. dissect_cstruct-4.5.dev2/tests/test_annotations.py +29 -0
  25. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_basic.py +11 -4
  26. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_compiler.py +2 -2
  27. dissect_cstruct-4.5.dev2/tests/test_ctypes.py +49 -0
  28. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_parser.py +40 -5
  29. dissect_cstruct-4.5.dev2/tests/test_tools_stubgen.py +399 -0
  30. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_base.py +2 -2
  31. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_custom.py +2 -2
  32. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_structure.py +1 -1
  33. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_union.py +1 -1
  34. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/utils.py +5 -0
  35. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/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.dev2}/.git-blame-ignore-revs +0 -0
  38. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/.gitattributes +0 -0
  39. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/COPYRIGHT +0 -0
  40. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/LICENSE +0 -0
  41. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/MANIFEST.in +0 -0
  42. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/README.md +0 -0
  43. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/__init__.py +0 -0
  44. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/exceptions.py +0 -0
  45. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/expression.py +0 -0
  46. {dissect_cstruct-4.4.dev3/tests → dissect_cstruct-4.5.dev2/dissect/cstruct/tools}/__init__.py +0 -0
  47. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/flag.py +0 -0
  48. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect/cstruct/types/void.py +0 -0
  49. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  50. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/dissect.cstruct.egg-info/top_level.txt +0 -0
  51. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/examples/disk.py +0 -0
  52. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/examples/mirai.py +0 -0
  53. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/examples/pe.py +0 -0
  54. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/examples/protobuf.py +0 -0
  55. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/examples/secdesc.py +0 -0
  56. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/setup.cfg +0 -0
  57. {dissect_cstruct-4.4.dev3/tests/_docs → dissect_cstruct-4.5.dev2/tests}/__init__.py +0 -0
  58. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/_data/testdef.txt +0 -0
  59. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/_docs/Makefile +0 -0
  60. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/_docs/conf.py +0 -0
  61. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/_docs/index.rst +0 -0
  62. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/conftest.py +0 -0
  63. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_align.py +0 -0
  64. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_bitbuffer.py +0 -0
  65. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_bitfield.py +0 -0
  66. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_expression.py +0 -0
  67. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_char.py +0 -0
  68. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_enum.py +0 -0
  69. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_flag.py +0 -0
  70. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_int.py +0 -0
  71. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_leb128.py +0 -0
  72. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_packed.py +0 -0
  73. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_pointer.py +0 -0
  74. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_void.py +0 -0
  75. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/tests/test_types_wchar.py +0 -0
  76. {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev2}/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.dev2
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,161 @@ class cstruct:
409
425
  ) -> type[Structure]:
410
426
  return self._make_struct(name, fields, align=align, anonymous=anonymous, base=Union)
411
427
 
428
+ if TYPE_CHECKING:
429
+ # ruff: noqa: PYI042
430
+ _int = int
431
+ _float = float
432
+
433
+ class int8(_int, Packed[_int]): ...
434
+
435
+ class uint8(_int, Packed[_int]): ...
436
+
437
+ class int16(_int, Packed[_int]): ...
438
+
439
+ class uint16(_int, Packed[_int]): ...
440
+
441
+ class int32(_int, Packed[_int]): ...
442
+
443
+ class uint32(_int, Packed[_int]): ...
444
+
445
+ class int64(_int, Packed[_int]): ...
446
+
447
+ class uint64(_int, Packed[_int]): ...
448
+
449
+ class float16(_float, Packed[_float]): ...
450
+
451
+ class float(_float, Packed[_float]): ...
452
+
453
+ class double(_float, Packed[_float]): ...
454
+
455
+ class char(Char): ...
456
+
457
+ class wchar(Wchar): ...
458
+
459
+ class int24(Int): ...
460
+
461
+ class uint24(Int): ...
462
+
463
+ class int48(Int): ...
464
+
465
+ class uint48(Int): ...
466
+
467
+ class int128(Int): ...
468
+
469
+ class uint128(Int): ...
470
+
471
+ class uleb128(LEB128): ...
472
+
473
+ class ileb128(LEB128): ...
474
+
475
+ class void(Void): ...
476
+
477
+ # signed char: TypeAlias = int8
478
+ # signed char: TypeAlias = char
479
+ short: TypeAlias = int16
480
+ # signed short: TypeAlias = int16
481
+ # unsigned short: TypeAlias = uint16
482
+ int: TypeAlias = int32
483
+ # signed int: TypeAlias = int32
484
+ # unsigned int: TypeAlias = uint32
485
+ long: TypeAlias = int32
486
+ # signed long: TypeAlias = int32
487
+ # unsigned long: TypeAlias = uint32
488
+ # long long: TypeAlias = int64
489
+ # signed long long: TypeAlias = int64
490
+ # unsigned long long: TypeAlias = uint64
491
+
492
+ BYTE: TypeAlias = uint8
493
+ CHAR: TypeAlias = char
494
+ SHORT: TypeAlias = int16
495
+ WORD: TypeAlias = uint16
496
+ DWORD: TypeAlias = uint32
497
+ LONG: TypeAlias = int32
498
+ LONG32: TypeAlias = int32
499
+ LONG64: TypeAlias = int64
500
+ LONGLONG: TypeAlias = int64
501
+ QWORD: TypeAlias = uint64
502
+ OWORD: TypeAlias = uint128
503
+ WCHAR: TypeAlias = wchar
504
+
505
+ UCHAR: TypeAlias = uint8
506
+ USHORT: TypeAlias = uint16
507
+ ULONG: TypeAlias = uint32
508
+ ULONG64: TypeAlias = uint64
509
+ ULONGLONG: TypeAlias = uint64
510
+
511
+ INT: TypeAlias = int32
512
+ INT8: TypeAlias = int8
513
+ INT16: TypeAlias = int16
514
+ INT32: TypeAlias = int32
515
+ INT64: TypeAlias = int64
516
+ INT128: TypeAlias = int128
517
+
518
+ UINT: TypeAlias = uint32
519
+ UINT8: TypeAlias = uint8
520
+ UINT16: TypeAlias = uint16
521
+ UINT32: TypeAlias = uint32
522
+ UINT64: TypeAlias = uint64
523
+ UINT128: TypeAlias = uint128
524
+
525
+ __int8: TypeAlias = int8
526
+ __int16: TypeAlias = int16
527
+ __int32: TypeAlias = int32
528
+ __int64: TypeAlias = int64
529
+ __int128: TypeAlias = int128
530
+
531
+ # unsigned __int8: TypeAlias = uint8
532
+ # unsigned __int16: TypeAlias = uint16
533
+ # unsigned __int32: TypeAlias = uint32
534
+ # unsigned __int64: TypeAlias = uint64
535
+ # unsigned __int128: TypeAlias = uint128
536
+
537
+ wchar_t: TypeAlias = wchar
412
538
 
413
- def ctypes(structure: Structure) -> _ctypes.Structure:
539
+ int8_t: TypeAlias = int8
540
+ int16_t: TypeAlias = int16
541
+ int32_t: TypeAlias = int32
542
+ int64_t: TypeAlias = int64
543
+ int128_t: TypeAlias = int128
544
+
545
+ uint8_t: TypeAlias = uint8
546
+ uint16_t: TypeAlias = uint16
547
+ uint32_t: TypeAlias = uint32
548
+ uint64_t: TypeAlias = uint64
549
+ uint128_t: TypeAlias = uint128
550
+
551
+ _BYTE: TypeAlias = uint8
552
+ _WORD: TypeAlias = uint16
553
+ _DWORD: TypeAlias = uint32
554
+ _QWORD: TypeAlias = uint64
555
+ _OWORD: TypeAlias = uint128
556
+
557
+ u1: TypeAlias = uint8
558
+ u2: TypeAlias = uint16
559
+ u4: TypeAlias = uint32
560
+ u8: TypeAlias = uint64
561
+ u16: TypeAlias = uint128
562
+ __u8: TypeAlias = uint8
563
+ __u16: TypeAlias = uint16
564
+ __u32: TypeAlias = uint32
565
+ __u64: TypeAlias = uint64
566
+ uchar: TypeAlias = uint8
567
+ ushort: TypeAlias = uint16
568
+ uint: TypeAlias = uint32
569
+ ulong: TypeAlias = uint32
570
+
571
+
572
+ def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
414
573
  """Create ctypes structures from cstruct structures."""
415
574
  fields = []
416
575
  for field in structure.__fields__:
417
576
  t = ctypes_type(field.type)
418
- fields.append((field.name, t))
577
+ fields.append((field._name, t))
419
578
 
420
- return type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
579
+ return type(structure.__name__, (_ctypes.Structure,), {"_fields_": fields})
421
580
 
422
581
 
423
- def ctypes_type(type_: MetaType) -> Any:
582
+ def ctypes_type(type_: type[BaseType]) -> Any:
424
583
  mapping = {
425
584
  "b": _ctypes.c_int8,
426
585
  "B": _ctypes.c_uint8,
@@ -443,7 +602,7 @@ def ctypes_type(type_: MetaType) -> Any:
443
602
  if issubclass(type_, Wchar):
444
603
  return _ctypes.c_wchar
445
604
 
446
- if isinstance(type_, ArrayMetaType):
605
+ if issubclass(type_, BaseArray):
447
606
  subtype = ctypes_type(type_.type)
448
607
  return subtype * type_.num_entries
449
608
 
@@ -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: