dissect.cstruct 4.8.dev6__py3-none-any.whl → 4.8.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect/cstruct/utils.py +138 -30
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/METADATA +1 -1
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/RECORD +8 -8
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/WHEEL +0 -0
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/top_level.txt +0 -0
dissect/cstruct/utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import pprint
|
|
4
5
|
import string
|
|
5
6
|
import sys
|
|
@@ -13,15 +14,29 @@ if TYPE_CHECKING:
|
|
|
13
14
|
from collections.abc import Iterator
|
|
14
15
|
from typing import Literal
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
# Regular ANSI colors
|
|
18
|
+
COLOR_RED = "\033[0;31m"
|
|
19
|
+
COLOR_GREEN = "\033[0;32m"
|
|
20
|
+
COLOR_YELLOW = "\033[0;93m"
|
|
21
|
+
COLOR_BLUE = "\033[0;34m"
|
|
22
|
+
COLOR_PURPLE = "\033[0;35m"
|
|
23
|
+
COLOR_CYAN = "\033[0;36m"
|
|
24
|
+
COLOR_WHITE = "\033[0;37m"
|
|
25
|
+
COLOR_BLACK = "\033[0;30m"
|
|
26
|
+
COLOR_GREY = "\033[0;90m"
|
|
27
|
+
|
|
28
|
+
# Bold ANSI colors
|
|
29
|
+
COLOR_RED_BOLD = "\033[1;31m"
|
|
30
|
+
COLOR_GREEN_BOLD = "\033[1;32m"
|
|
31
|
+
COLOR_YELLOW_BOLD = "\033[1;33m"
|
|
32
|
+
COLOR_BLUE_BOLD = "\033[1;34m"
|
|
33
|
+
COLOR_PURPLE_BOLD = "\033[1;35m"
|
|
34
|
+
COLOR_CYAN_BOLD = "\033[1;36m"
|
|
35
|
+
COLOR_WHITE_BOLD = "\033[1;37m"
|
|
36
|
+
COLOR_BLACK_BOLD = "\033[1;30m"
|
|
37
|
+
COLOR_GREY_BOLD = "\033[1;90m"
|
|
38
|
+
|
|
39
|
+
# Background ANSI colors
|
|
25
40
|
COLOR_BG_RED = "\033[1;41m\033[1;37m"
|
|
26
41
|
COLOR_BG_GREEN = "\033[1;42m\033[1;37m"
|
|
27
42
|
COLOR_BG_YELLOW = "\033[1;43m\033[1;37m"
|
|
@@ -30,6 +45,10 @@ COLOR_BG_PURPLE = "\033[1;45m\033[1;37m"
|
|
|
30
45
|
COLOR_BG_CYAN = "\033[1;46m\033[1;37m"
|
|
31
46
|
COLOR_BG_WHITE = "\033[1;47m\033[1;30m"
|
|
32
47
|
|
|
48
|
+
# Reset ANSI codes
|
|
49
|
+
COLOR_CLEAR = "\033[0m"
|
|
50
|
+
COLOR_CLEAR_BOLD = "\033[1;0m"
|
|
51
|
+
|
|
33
52
|
PRINTABLE = string.digits + string.ascii_letters + string.punctuation + " "
|
|
34
53
|
|
|
35
54
|
ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
|
|
@@ -44,20 +63,67 @@ ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
|
|
|
44
63
|
Palette = list[tuple[int, str]]
|
|
45
64
|
|
|
46
65
|
|
|
47
|
-
def
|
|
66
|
+
def _human_colors() -> dict[str, str]:
|
|
67
|
+
"""Generates a dictionary of characters with a human-readable ANSI color they should be in a hexdump.
|
|
68
|
+
|
|
69
|
+
Coloring logic implementation derived from HexFriend and ImHex.
|
|
70
|
+
"""
|
|
71
|
+
# Make all characters not in any rules below light green
|
|
72
|
+
colors = {chr(char): COLOR_GREEN for char in range(256)}
|
|
73
|
+
|
|
74
|
+
# Make all ASCII extended characters yellow
|
|
75
|
+
for char in colors:
|
|
76
|
+
if ord(char) & 0x80 == 0:
|
|
77
|
+
colors[char] = COLOR_YELLOW
|
|
78
|
+
|
|
79
|
+
# Make null bytes grey
|
|
80
|
+
colors["\00"] = COLOR_GREY
|
|
81
|
+
|
|
82
|
+
# Make printable ASCII characters bold white (0x32-0x7E)
|
|
83
|
+
for char in PRINTABLE:
|
|
84
|
+
colors[char] = COLOR_WHITE_BOLD
|
|
85
|
+
|
|
86
|
+
# Make ASCII whitespace characters green bold (0x9, 0xA, 0xB, 0xC, 0xD, 0x20)
|
|
87
|
+
for char in ("\t", "\n", "\11", "\12", "\r", "\20"):
|
|
88
|
+
colors[char] = COLOR_GREEN_BOLD
|
|
89
|
+
|
|
90
|
+
return colors
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
HUMAN_COLORS = _human_colors()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _hexdump(
|
|
97
|
+
data: bytes,
|
|
98
|
+
*,
|
|
99
|
+
palette: Palette | None = None,
|
|
100
|
+
offset: int = 0,
|
|
101
|
+
prefix: str = "",
|
|
102
|
+
pretty: bool | None = False,
|
|
103
|
+
autoskip: bool = False,
|
|
104
|
+
) -> Iterator[str]:
|
|
48
105
|
"""Hexdump some data.
|
|
49
106
|
|
|
50
107
|
Args:
|
|
51
108
|
data: Bytes to hexdump.
|
|
109
|
+
palette: Colorize the hexdump using this color pattern.
|
|
52
110
|
offset: Byte offset of the hexdump.
|
|
53
111
|
prefix: Optional prefix.
|
|
54
|
-
|
|
112
|
+
pretty: Use pretty colors, mutual exclusive with palette.
|
|
113
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
55
114
|
"""
|
|
56
115
|
if palette:
|
|
57
116
|
palette = palette[::-1]
|
|
58
117
|
|
|
118
|
+
# only happy little accidents
|
|
119
|
+
if pretty and palette:
|
|
120
|
+
raise ValueError("Cannot use argument 'pretty' in combination with 'palette', please pick one")
|
|
121
|
+
|
|
59
122
|
remaining = 0
|
|
60
123
|
active = None
|
|
124
|
+
in_null_run = False
|
|
125
|
+
in_collapsed_null_run = False
|
|
126
|
+
last_offset = len(data) - 16
|
|
61
127
|
|
|
62
128
|
for i in range(0, len(data), 16):
|
|
63
129
|
values = ""
|
|
@@ -87,42 +153,80 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
|
|
|
87
153
|
|
|
88
154
|
if active:
|
|
89
155
|
values += f"{ord(char):02x}"
|
|
90
|
-
chars.append(active + print_char +
|
|
156
|
+
chars.append(active + print_char + COLOR_CLEAR_BOLD)
|
|
91
157
|
else:
|
|
92
|
-
|
|
93
|
-
|
|
158
|
+
if pretty and (color := HUMAN_COLORS.get(char, "")):
|
|
159
|
+
values += f"{color}{ord(char):02x}{COLOR_CLEAR}"
|
|
160
|
+
chars.append(color + print_char + COLOR_CLEAR)
|
|
161
|
+
else:
|
|
162
|
+
values += f"{ord(char):02x}"
|
|
163
|
+
chars.append(print_char)
|
|
94
164
|
|
|
95
165
|
remaining -= 1
|
|
96
166
|
if remaining == 0:
|
|
97
167
|
active = None
|
|
98
168
|
|
|
99
169
|
if palette is not None:
|
|
100
|
-
values +=
|
|
170
|
+
values += COLOR_CLEAR_BOLD
|
|
101
171
|
|
|
102
172
|
if j == 15 and palette is not None:
|
|
103
|
-
values +=
|
|
173
|
+
values += COLOR_CLEAR_BOLD
|
|
104
174
|
|
|
105
175
|
values += " "
|
|
106
176
|
if j == 7:
|
|
107
177
|
values += " "
|
|
108
178
|
|
|
179
|
+
if autoskip and 0 < i < last_offset and data[i : i + 16] == b"\x00" * 16:
|
|
180
|
+
if in_null_run:
|
|
181
|
+
if not in_collapsed_null_run:
|
|
182
|
+
yield "*"
|
|
183
|
+
in_collapsed_null_run = True
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# Keep the first interior NUL line visible, collapse from the second onwards.
|
|
187
|
+
in_null_run = True
|
|
188
|
+
else:
|
|
189
|
+
in_null_run = False
|
|
190
|
+
in_collapsed_null_run = False
|
|
191
|
+
|
|
109
192
|
chars = "".join(chars)
|
|
110
193
|
yield f"{prefix}{offset + i:08x} {values:48s} {chars}"
|
|
111
194
|
|
|
112
195
|
|
|
113
196
|
def hexdump(
|
|
114
|
-
data: bytes,
|
|
197
|
+
data: bytes,
|
|
198
|
+
*,
|
|
199
|
+
palette: Palette | None = None,
|
|
200
|
+
offset: int = 0,
|
|
201
|
+
prefix: str = "",
|
|
202
|
+
output: str = "print",
|
|
203
|
+
pretty: bool | None = None,
|
|
204
|
+
autoskip: bool = False,
|
|
115
205
|
) -> Iterator[str] | str | None:
|
|
116
206
|
"""Hexdump some data.
|
|
117
207
|
|
|
208
|
+
Uses colored ANSI output with output type "print" by default. Disable with ``pretty=False``
|
|
209
|
+
or set the environment variable ``NO_COLOR``.
|
|
210
|
+
|
|
118
211
|
Args:
|
|
119
212
|
data: Bytes to hexdump.
|
|
120
213
|
palette: Colorize the hexdump using this color pattern.
|
|
121
214
|
offset: Byte offset of the hexdump.
|
|
122
215
|
prefix: Optional prefix.
|
|
123
216
|
output: Output format, can be 'print', 'generator' or 'string'.
|
|
217
|
+
pretty: Use pretty colors for improved human readability.
|
|
218
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
124
219
|
"""
|
|
125
|
-
|
|
220
|
+
# Enable pretty colors by default if ...
|
|
221
|
+
if (
|
|
222
|
+
output == "print" # the output type is set to 'print'
|
|
223
|
+
and not palette # no palette is given (structdump only)
|
|
224
|
+
and pretty is not False # pretty was not explicitly set to False
|
|
225
|
+
and not os.environ.get("NO_COLOR") # and the environment allows colors
|
|
226
|
+
):
|
|
227
|
+
pretty = True
|
|
228
|
+
|
|
229
|
+
generator = _hexdump(data, palette=palette, offset=offset, prefix=prefix, pretty=pretty, autoskip=autoskip)
|
|
126
230
|
if output == "print":
|
|
127
231
|
print("\n".join(generator))
|
|
128
232
|
return None
|
|
@@ -139,16 +243,17 @@ def _dumpstruct(
|
|
|
139
243
|
offset: int,
|
|
140
244
|
color: bool,
|
|
141
245
|
output: str,
|
|
246
|
+
autoskip: bool,
|
|
142
247
|
) -> str | None:
|
|
143
248
|
palette = []
|
|
144
249
|
colors = [
|
|
145
|
-
(
|
|
146
|
-
(
|
|
147
|
-
(
|
|
148
|
-
(
|
|
149
|
-
(
|
|
150
|
-
(
|
|
151
|
-
(
|
|
250
|
+
(COLOR_RED_BOLD, COLOR_BG_RED),
|
|
251
|
+
(COLOR_GREEN_BOLD, COLOR_BG_GREEN),
|
|
252
|
+
(COLOR_YELLOW_BOLD, COLOR_BG_YELLOW),
|
|
253
|
+
(COLOR_BLUE_BOLD, COLOR_BG_BLUE),
|
|
254
|
+
(COLOR_PURPLE_BOLD, COLOR_BG_PURPLE),
|
|
255
|
+
(COLOR_CYAN_BOLD, COLOR_BG_CYAN),
|
|
256
|
+
(COLOR_WHITE_BOLD, COLOR_BG_WHITE),
|
|
152
257
|
]
|
|
153
258
|
ci = 0
|
|
154
259
|
out = [f"struct {structure.__class__.__name__}:"]
|
|
@@ -172,7 +277,7 @@ def _dumpstruct(
|
|
|
172
277
|
size = structure.__sizes__[field._name]
|
|
173
278
|
palette.append((size, background))
|
|
174
279
|
ci += 1
|
|
175
|
-
out.append(f"- {foreground}{field._name}{
|
|
280
|
+
out.append(f"- {foreground}{field._name}{COLOR_CLEAR_BOLD}: {value}")
|
|
176
281
|
else:
|
|
177
282
|
out.append(f"- {field._name}: {value}")
|
|
178
283
|
|
|
@@ -180,11 +285,11 @@ def _dumpstruct(
|
|
|
180
285
|
|
|
181
286
|
if output == "print":
|
|
182
287
|
print()
|
|
183
|
-
hexdump(data, palette, offset=offset)
|
|
288
|
+
hexdump(data, palette=palette, offset=offset, autoskip=autoskip)
|
|
184
289
|
print()
|
|
185
290
|
print(out)
|
|
186
291
|
elif output == "string":
|
|
187
|
-
return f"\n{hexdump(data, palette, offset=offset, output='string')}\n\n{out}"
|
|
292
|
+
return f"\n{hexdump(data, palette=palette, offset=offset, output='string', autoskip=autoskip)}\n\n{out}"
|
|
188
293
|
return None
|
|
189
294
|
|
|
190
295
|
|
|
@@ -193,6 +298,7 @@ def dumpstruct(
|
|
|
193
298
|
data: bytes | None = None,
|
|
194
299
|
offset: int = 0,
|
|
195
300
|
color: bool = True,
|
|
301
|
+
autoskip: bool = False,
|
|
196
302
|
output: str = "print",
|
|
197
303
|
) -> str | None:
|
|
198
304
|
"""Dump a structure or parsed structure instance.
|
|
@@ -203,15 +309,17 @@ def dumpstruct(
|
|
|
203
309
|
obj: Structure to dump.
|
|
204
310
|
data: Bytes to parse the Structure on, if obj is not a parsed Structure already.
|
|
205
311
|
offset: Byte offset of the hexdump.
|
|
312
|
+
color: Colorize the hexdump and structure output.
|
|
313
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
206
314
|
output: Output format, can be 'print' or 'string'.
|
|
207
315
|
"""
|
|
208
316
|
if output not in ("print", "string"):
|
|
209
317
|
raise ValueError(f"Invalid output argument: {output!r} (should be 'print' or 'string').")
|
|
210
318
|
|
|
211
319
|
if isinstance(obj, Structure):
|
|
212
|
-
return _dumpstruct(obj, obj.dumps(), offset, color, output)
|
|
320
|
+
return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
|
|
213
321
|
if issubclass(obj, Structure) and data is not None:
|
|
214
|
-
return _dumpstruct(obj(data), data, offset, color, output)
|
|
322
|
+
return _dumpstruct(obj(data), data, offset, color, output, autoskip)
|
|
215
323
|
raise ValueError("Invalid arguments")
|
|
216
324
|
|
|
217
325
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.8.
|
|
3
|
+
Version: 4.8.dev8
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -5,7 +5,7 @@ dissect/cstruct/cstruct.py,sha256=1lHbxZwj5x2EmmXLR0sJlp-wQqaTtd_ht1lvwit0KtQ,19
|
|
|
5
5
|
dissect/cstruct/exceptions.py,sha256=SX05-_HqQn8lYC7SBHv5OrYEepYRMNmRiIzVPW8bvMQ,330
|
|
6
6
|
dissect/cstruct/expression.py,sha256=mf5GFGLAn_2Tz3QK86Yqoi317l8cjMcffBKzP2STE6M,10841
|
|
7
7
|
dissect/cstruct/parser.py,sha256=Mk8QbkDov_xfauZnp2MqUmFGOPZlp7O2rSLYbB9S3g4,24550
|
|
8
|
-
dissect/cstruct/utils.py,sha256=
|
|
8
|
+
dissect/cstruct/utils.py,sha256=jSWZY8AWoLHF1W9ix-7EH2eI7pTHzLfnSfQHWH1qgKM,14190
|
|
9
9
|
dissect/cstruct/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
dissect/cstruct/tools/stubgen.py,sha256=wvl07h3VnJZWe9ygX4QMxjEH1ZcWaI_6_pSdAJoOEXk,7820
|
|
11
11
|
dissect/cstruct/types/__init__.py,sha256=6P71Pu94Dv7RNjZpDZ19pqzx-G0QzmQw4q0-3zWALso,911
|
|
@@ -20,10 +20,10 @@ dissect/cstruct/types/pointer.py,sha256=PqeJOoNBUfMd5uO2kpF6OHeW7Q6R1N1W1V4q1Mak
|
|
|
20
20
|
dissect/cstruct/types/structure.py,sha256=I9LVeZ81rNE5GDT6wxpUL-PqMO31Ae6TVLLzTGyd6mM,33012
|
|
21
21
|
dissect/cstruct/types/void.py,sha256=Gvh0HqI6jQSj2uesdo3J8-G41brWCz8pmP16l8xTBxE,1074
|
|
22
22
|
dissect/cstruct/types/wchar.py,sha256=N9Y_XX2_hZEe2DwepJjxsB6xuRJ6zINRalcUofR59kY,2608
|
|
23
|
-
dissect_cstruct-4.8.
|
|
24
|
-
dissect_cstruct-4.8.
|
|
25
|
-
dissect_cstruct-4.8.
|
|
26
|
-
dissect_cstruct-4.8.
|
|
27
|
-
dissect_cstruct-4.8.
|
|
28
|
-
dissect_cstruct-4.8.
|
|
29
|
-
dissect_cstruct-4.8.
|
|
23
|
+
dissect_cstruct-4.8.dev8.dist-info/licenses/COPYRIGHT,sha256=H-18RXfshdH9AdHwW2eO1Xa-5s6tY5eipHh5c0whDu4,316
|
|
24
|
+
dissect_cstruct-4.8.dev8.dist-info/licenses/LICENSE,sha256=PhUqiw6jAh2KbBdVRPBq_hfAvfcTBin7nZ3CK7NQbTM,11341
|
|
25
|
+
dissect_cstruct-4.8.dev8.dist-info/METADATA,sha256=LpS2_xD11bTw0F2YOz0X9ZWNDgptmNd_FcgzHnrR8nI,9255
|
|
26
|
+
dissect_cstruct-4.8.dev8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
27
|
+
dissect_cstruct-4.8.dev8.dist-info/entry_points.txt,sha256=z53zqZqwD2OLrAkRwrP4wTeiU9CQe7xrMly0T2c0_wQ,71
|
|
28
|
+
dissect_cstruct-4.8.dev8.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
29
|
+
dissect_cstruct-4.8.dev8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{dissect_cstruct-4.8.dev6.dist-info → dissect_cstruct-4.8.dev8.dist-info}/licenses/COPYRIGHT
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|