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.
- {dissect_cstruct-3.14.dev8/dissect.cstruct.egg-info → dissect_cstruct-4.0.dev0}/PKG-INFO +2 -2
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/README.md +1 -1
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/__init__.py +36 -33
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/bitbuffer.py +4 -4
- dissect_cstruct-4.0.dev0/dissect/cstruct/compiler.py +418 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/cstruct.py +191 -52
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/expression.py +13 -12
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/parser.py +80 -71
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/__init__.py +32 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/base.py +279 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/char.py +79 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/enum.py +183 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/flag.py +72 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/int.py +37 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/types/leb128.py +15 -28
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/packed.py +65 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/pointer.py +95 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/structure.py +678 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/void.py +20 -0
- dissect_cstruct-4.0.dev0/dissect/cstruct/types/wchar.py +79 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/utils.py +31 -19
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0/dissect.cstruct.egg-info}/PKG-INFO +2 -2
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/SOURCES.txt +20 -15
- dissect_cstruct-4.0.dev0/examples/protobuf.py +83 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/secdesc.py +5 -8
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/pyproject.toml +0 -1
- dissect_cstruct-4.0.dev0/tests/conftest.py +13 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_align.py +78 -72
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_basic.py +120 -138
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_bitbuffer.py +2 -4
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_bitfield.py +14 -20
- dissect_cstruct-4.0.dev0/tests/test_compiler.py +326 -0
- dissect_cstruct-4.0.dev0/tests/test_ctypes.py +30 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_expression.py +5 -6
- dissect_cstruct-4.0.dev0/tests/test_parser.py +75 -0
- dissect_cstruct-4.0.dev0/tests/test_types_base.py +133 -0
- dissect_cstruct-4.0.dev0/tests/test_types_char.py +45 -0
- dissect_cstruct-4.0.dev0/tests/test_types_custom.py +74 -0
- dissect_cstruct-3.14.dev8/tests/test_enum.py → dissect_cstruct-4.0.dev0/tests/test_types_enum.py +106 -27
- dissect_cstruct-4.0.dev0/tests/test_types_flag.py +235 -0
- dissect_cstruct-4.0.dev0/tests/test_types_int.py +402 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_types_leb128.py +37 -56
- dissect_cstruct-4.0.dev0/tests/test_types_packed.py +171 -0
- dissect_cstruct-4.0.dev0/tests/test_types_pointer.py +237 -0
- dissect_cstruct-4.0.dev0/tests/test_types_structure.py +531 -0
- dissect_cstruct-4.0.dev0/tests/test_types_union.py +338 -0
- dissect_cstruct-4.0.dev0/tests/test_types_void.py +13 -0
- dissect_cstruct-4.0.dev0/tests/test_types_wchar.py +77 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/test_utils.py +87 -2
- dissect_cstruct-4.0.dev0/tests/utils.py +7 -0
- dissect_cstruct-3.14.dev8/dissect/cstruct/compiler.py +0 -413
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/__init__.py +0 -34
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/base.py +0 -209
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/bytesinteger.py +0 -115
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/chartype.py +0 -59
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/enum.py +0 -124
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/flag.py +0 -106
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/instance.py +0 -68
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/packedtype.py +0 -59
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/pointer.py +0 -127
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/structure.py +0 -376
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/voidtype.py +0 -13
- dissect_cstruct-3.14.dev8/dissect/cstruct/types/wchartype.py +0 -57
- dissect_cstruct-3.14.dev8/tests/conftest.py +0 -6
- dissect_cstruct-3.14.dev8/tests/test_bytesinteger.py +0 -260
- dissect_cstruct-3.14.dev8/tests/test_ctypes_type.py +0 -25
- dissect_cstruct-3.14.dev8/tests/test_flag.py +0 -149
- dissect_cstruct-3.14.dev8/tests/test_packedtype.py +0 -66
- dissect_cstruct-3.14.dev8/tests/test_parser.py +0 -25
- dissect_cstruct-3.14.dev8/tests/test_pointer.py +0 -162
- dissect_cstruct-3.14.dev8/tests/test_struct.py +0 -320
- dissect_cstruct-3.14.dev8/tests/test_union.py +0 -89
- dissect_cstruct-3.14.dev8/tests/test_util.py +0 -90
- dissect_cstruct-3.14.dev8/tests/utils.py +0 -2
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/COPYRIGHT +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/LICENSE +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/MANIFEST.in +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/disk.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/mirai.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/examples/pe.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/setup.cfg +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/__init__.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/data/testdef.txt +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/Makefile +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/conf.py +0 -0
- {dissect_cstruct-3.14.dev8 → dissect_cstruct-4.0.dev0}/tests/docs/index.rst +0 -0
- {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
|
+
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 `
|
|
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 `
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"Field",
|
|
47
|
-
"Instance",
|
|
49
|
+
"cstruct",
|
|
50
|
+
"ctypes",
|
|
51
|
+
"ctypes_type",
|
|
48
52
|
"LEB128",
|
|
49
|
-
"
|
|
50
|
-
"Expression",
|
|
51
|
-
"PackedType",
|
|
52
|
-
"Pointer",
|
|
53
|
-
"PointerInstance",
|
|
54
|
-
"VoidType",
|
|
55
|
-
"WcharType",
|
|
56
|
-
"RawType",
|
|
53
|
+
"Array",
|
|
57
54
|
"BaseType",
|
|
58
|
-
"
|
|
55
|
+
"Char",
|
|
56
|
+
"CharArray",
|
|
59
57
|
"Enum",
|
|
60
|
-
"
|
|
58
|
+
"Expression",
|
|
59
|
+
"Field",
|
|
61
60
|
"Flag",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
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
|
|
3
|
+
from typing import TYPE_CHECKING, BinaryIO
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from dissect.cstruct.types import
|
|
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:
|
|
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:
|
|
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_)
|