obstruct 0.2.1__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.
- obstruct/__init__.py +4 -0
- obstruct/__version__.py +1 -0
- obstruct/aot/__init__.py +2 -0
- obstruct/aot/aot.py +2 -0
- obstruct/aot/base.py +145 -0
- obstruct/aot/pack.py +58 -0
- obstruct/aot/unpack.py +141 -0
- obstruct/decorator.py +41 -0
- obstruct/methods.py +49 -0
- obstruct/struct_type.py +52 -0
- obstruct/types/__init__.py +1 -0
- obstruct/types/base.py +6 -0
- obstruct/types/types.py +101 -0
- obstruct/types/types.pyi +67 -0
- obstruct-0.2.1.dist-info/METADATA +12 -0
- obstruct-0.2.1.dist-info/RECORD +17 -0
- obstruct-0.2.1.dist-info/WHEEL +4 -0
obstruct/__init__.py
ADDED
obstruct/__version__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.2.1"
|
obstruct/aot/__init__.py
ADDED
obstruct/aot/aot.py
ADDED
obstruct/aot/base.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import itertools
|
|
5
|
+
import typing
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import typing_inspection.typing_objects
|
|
9
|
+
|
|
10
|
+
from _hydrogenlib_core.typefunc import enhanced_generator
|
|
11
|
+
from obstruct.methods import get_ctype
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _objective_node_operator:
|
|
15
|
+
_node: ast.stmt
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def node(self):
|
|
19
|
+
return self._node
|
|
20
|
+
|
|
21
|
+
def add_node(self, node: ast.AST):
|
|
22
|
+
self._node.body.append(node)
|
|
23
|
+
return node
|
|
24
|
+
|
|
25
|
+
def add_code(self, code: str):
|
|
26
|
+
tree = ast.parse(code)
|
|
27
|
+
if isinstance(tree, ast.Module):
|
|
28
|
+
self._node.body.extend(tree.body)
|
|
29
|
+
return tree
|
|
30
|
+
elif isinstance(tree, ast.Expr):
|
|
31
|
+
self._node.body.append(tree.value)
|
|
32
|
+
return tree.value
|
|
33
|
+
else:
|
|
34
|
+
self._node.body.append(tree)
|
|
35
|
+
return tree
|
|
36
|
+
|
|
37
|
+
# def insert_code(self, index, code: str):
|
|
38
|
+
# tree = ast.parse(code)
|
|
39
|
+
# if isinstance(tree, ast.Module):
|
|
40
|
+
# for node in reversed(tree.body):
|
|
41
|
+
# self._node.body.insert(index, node)
|
|
42
|
+
# return tree
|
|
43
|
+
# elif isinstance(tree, ast.Expr):
|
|
44
|
+
# self._node.body.insert(index, tree.value)
|
|
45
|
+
# return tree.value
|
|
46
|
+
# else:
|
|
47
|
+
# self._node.body.insert(index, tree)
|
|
48
|
+
# return tree
|
|
49
|
+
|
|
50
|
+
def compile(self):
|
|
51
|
+
module = ast.Module([self._node])
|
|
52
|
+
ast.fix_missing_locations(module)
|
|
53
|
+
return compile(
|
|
54
|
+
module, filename="<string>", mode="exec"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def exec(self, globals=None, locals=None, closure=None):
|
|
58
|
+
globals = globals or {}
|
|
59
|
+
exec(self.compile(), globals, locals, closure=closure)
|
|
60
|
+
return globals[self._node.name]
|
|
61
|
+
|
|
62
|
+
def generate_code(self):
|
|
63
|
+
module = ast.Module([self._node])
|
|
64
|
+
ast.fix_missing_locations(module)
|
|
65
|
+
return ast.unparse(module)
|
|
66
|
+
|
|
67
|
+
def __enter__(self):
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class function_def(_objective_node_operator):
|
|
75
|
+
def __init__(self, signature: str):
|
|
76
|
+
self._signature = signature
|
|
77
|
+
self._node = ast.parse(f'def {signature}: pass').body[0] # type: ast.FunctionDef
|
|
78
|
+
self._node.body.clear()
|
|
79
|
+
|
|
80
|
+
def add_decorator(self, expr: str | ast.expr):
|
|
81
|
+
if isinstance(expr, str):
|
|
82
|
+
node = ast.parse(expr).body[0]
|
|
83
|
+
|
|
84
|
+
if not isinstance(node, ast.Expr):
|
|
85
|
+
raise ValueError(expr)
|
|
86
|
+
|
|
87
|
+
expr = node.value
|
|
88
|
+
|
|
89
|
+
self._node.decorator_list.append(expr)
|
|
90
|
+
return expr
|
|
91
|
+
|
|
92
|
+
def add_return(self, expr: str | ast.expr):
|
|
93
|
+
if isinstance(expr, str):
|
|
94
|
+
node = ast.parse(expr).body[0]
|
|
95
|
+
if not isinstance(node, ast.Expr):
|
|
96
|
+
raise ValueError(expr)
|
|
97
|
+
self._node.body.append(ast.Return(node.value))
|
|
98
|
+
else:
|
|
99
|
+
self._node.body.append(ast.Return(expr))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class for_stmt(_objective_node_operator):
|
|
103
|
+
def __init__(self, expr):
|
|
104
|
+
self._node = ast.parse(f"for {expr}: ...").body[0] # type: ast.For
|
|
105
|
+
self._node.body.clear()
|
|
106
|
+
|
|
107
|
+
def exec(self, globals=None, locals=None, closure=None):
|
|
108
|
+
return exec(self.compile(), globals, locals, closure=closure)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
available_characters = tuple(
|
|
112
|
+
chr(i) for i in range(65, 91)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@enhanced_generator(history=True)
|
|
117
|
+
def unique_name_generator():
|
|
118
|
+
cnt = 0
|
|
119
|
+
while True:
|
|
120
|
+
cnt += 1
|
|
121
|
+
yield from map(lambda x: ''.join(x),
|
|
122
|
+
itertools.combinations(available_characters, cnt))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def is_struct(tp):
|
|
126
|
+
return hasattr(tp, '__cstruct__')
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_array(tp):
|
|
130
|
+
if typing_inspection.typing_objects.is_annotated(typing.get_origin(tp)):
|
|
131
|
+
return True
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_array_spec(tp):
|
|
136
|
+
ls_type, length = typing.get_args(tp)
|
|
137
|
+
return typing.get_args(ls_type)[0], length
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_array_ctype(tp) -> str | None | Any:
|
|
141
|
+
return get_ctype(get_array_spec(tp)[0])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def to_tuple_expression(names, prefix='self.'):
|
|
145
|
+
return ', '.join(f'{prefix}{name}' for name in names)
|
obstruct/aot/pack.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import array
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from .base import unique_name_generator, is_struct, is_array, get_array_ctype, to_tuple_expression, function_def
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from .. import struct_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_pack_methods(cls: struct_type.Struct):
|
|
13
|
+
"""
|
|
14
|
+
Generate `pack` and `pack_into` method
|
|
15
|
+
"""
|
|
16
|
+
with (
|
|
17
|
+
function_def('pack(self)') as pack,
|
|
18
|
+
function_def('pack_into(self, buffer, offset=0)') as pack_into,
|
|
19
|
+
):
|
|
20
|
+
ung = unique_name_generator()
|
|
21
|
+
flat_fields = []
|
|
22
|
+
|
|
23
|
+
def add_code(code):
|
|
24
|
+
pack.add_code(code)
|
|
25
|
+
pack_into.add_code(code)
|
|
26
|
+
|
|
27
|
+
def walk(fields, path, parent_name=None):
|
|
28
|
+
# parent name 标识了这个字段的父字段被赋值给了哪个临时变量,这样可以复用已有的临时变量,减少结构体属性访问次数
|
|
29
|
+
name = 'self' # 这里的 name 是默认的 self,如果当前处理的是内嵌的结构体,那么 name 会被替换成临时变量
|
|
30
|
+
|
|
31
|
+
if len(path) > 1: # 当前处理的是内嵌结构体
|
|
32
|
+
name = ung.next() # 生成一个临时变量缓存其值
|
|
33
|
+
add_code(f'{name} = {parent_name}.{path[-1]}')
|
|
34
|
+
|
|
35
|
+
for field in fields: # 遍历结构体的所有字段
|
|
36
|
+
if is_struct(field.type): # 又是一个内嵌的结构体字段
|
|
37
|
+
walk(field.type.__cfields__, path + (field.name,), name) # 递归
|
|
38
|
+
elif is_array(field.type): # 一个数组类型
|
|
39
|
+
d_ctype = get_array_ctype(field.type)
|
|
40
|
+
|
|
41
|
+
_ = ung.next()
|
|
42
|
+
add_code(f'{_} = array({d_ctype!r}, {name}.{field.name})')
|
|
43
|
+
flat_fields.append(f'{_}.tobytes()')
|
|
44
|
+
|
|
45
|
+
else:
|
|
46
|
+
flat_fields.append(f'{name}.{field.name}') # 普通的字段,添加到传递目标列表中
|
|
47
|
+
|
|
48
|
+
walk(cls.__cfields__, ('self',)) # 启动递归函数
|
|
49
|
+
# names = [field.name for field in fields]
|
|
50
|
+
|
|
51
|
+
expression = to_tuple_expression(flat_fields, prefix='')
|
|
52
|
+
pack.add_return(f"xpack({expression})") # 返回打包的结果,将所有字段参数展开传递
|
|
53
|
+
pack_into.add_code(f"xpack_into(buffer, offset, {expression})")
|
|
54
|
+
# print(pack.generate_code()) # Debug
|
|
55
|
+
return (
|
|
56
|
+
pack.exec({'xpack': cls.__cstruct__.pack, 'array': array.array}),
|
|
57
|
+
pack_into.exec({'xpack_into': cls.__cstruct__.pack_into, 'array': array.array})
|
|
58
|
+
)
|
obstruct/aot/unpack.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import array
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from .base import unique_name_generator, is_struct, is_array, get_array_ctype, function_def, for_stmt
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from .. import struct_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_unpack_methods(cls: type[struct_type.Struct]):
|
|
13
|
+
with (
|
|
14
|
+
function_def('unpack(cls, buffer)') as unpack,
|
|
15
|
+
function_def('update(self, buffer)') as update,
|
|
16
|
+
function_def('unpack_from(cls, buffer, offset=0)') as unpack_from,
|
|
17
|
+
function_def('update_from(self, buffer, offset=0)') as update_from,
|
|
18
|
+
function_def('iter_unpack(cls, buffer)') as iter_unpack,
|
|
19
|
+
function_def('iter_update(cls, buffer, obj=None)') as iter_update,
|
|
20
|
+
):
|
|
21
|
+
unpack.add_decorator('classmethod') # unpack 是一个类方法
|
|
22
|
+
unpack_from.add_decorator('classmethod') # unpack_from 也是
|
|
23
|
+
iter_unpack.add_decorator('classmethod')
|
|
24
|
+
iter_update.add_decorator('classmethod')
|
|
25
|
+
|
|
26
|
+
ung = unique_name_generator()
|
|
27
|
+
|
|
28
|
+
def update_add_code(code):
|
|
29
|
+
update.add_code(code)
|
|
30
|
+
update_from.add_code(code)
|
|
31
|
+
iter_update_inner.add_code(code)
|
|
32
|
+
|
|
33
|
+
def unpack_add_code(code):
|
|
34
|
+
unpack.add_code(code)
|
|
35
|
+
unpack_from.add_code(code)
|
|
36
|
+
iter_unpack_inner.add_code(code)
|
|
37
|
+
|
|
38
|
+
post_processors = [] # 后处理代码列表
|
|
39
|
+
assigns = [] # 赋值目标列表
|
|
40
|
+
inner_classes = {} # 内嵌的结构体的类型映射(所有内嵌结构体的类型在生成代码时都会被代替为无意义的名称)
|
|
41
|
+
|
|
42
|
+
iter_update.add_code("self = cls() if obj is None else obj")
|
|
43
|
+
|
|
44
|
+
iter_unpack_inner = for_stmt("values in x_iter_unpack(buffer)")
|
|
45
|
+
iter_update_inner = for_stmt("values in x_iter_unpack(buffer)")
|
|
46
|
+
|
|
47
|
+
iter_unpack.add_node(iter_unpack_inner.node)
|
|
48
|
+
iter_update.add_node(iter_update_inner.node)
|
|
49
|
+
|
|
50
|
+
def walk(fields, path, parent_name=None, current_class_name='cls'):
|
|
51
|
+
name = 'self'
|
|
52
|
+
|
|
53
|
+
if len(path) > 1: # 在处理内嵌的结构体
|
|
54
|
+
name = ung.next() # 临时变量的名称
|
|
55
|
+
update_add_code(f"{name} = {parent_name}.{path[-1]}")
|
|
56
|
+
|
|
57
|
+
unpack_add_code(f"{parent_name}.{path[-1]} = {name} = __new__({current_class_name})") # 组合赋值
|
|
58
|
+
else:
|
|
59
|
+
unpack_add_code(f"{name} = __new__({current_class_name})") # 不需要临时变量存储根对象
|
|
60
|
+
# update 在这里不需要创建新对象
|
|
61
|
+
|
|
62
|
+
for field in fields: # 遍历字段
|
|
63
|
+
field_ref = f'{name}.{field.name}'
|
|
64
|
+
|
|
65
|
+
if is_struct(field.type): # 是个内嵌的结构体
|
|
66
|
+
if field.type not in inner_classes: # 如果这个内嵌的结构体类型不在已知的结构体中(没有分配类名称)
|
|
67
|
+
type_name = ung.next() # 生成一个唯一的类名
|
|
68
|
+
inner_classes[field.type] = type_name # 添加
|
|
69
|
+
else:
|
|
70
|
+
type_name = inner_classes[field.type] # 如果在,那么提取
|
|
71
|
+
|
|
72
|
+
walk(field.type.__cfields__, path + (field.name,), name, type_name)
|
|
73
|
+
elif is_array(field.type):
|
|
74
|
+
d_ctype = get_array_ctype(field.type)
|
|
75
|
+
post_processors.append(
|
|
76
|
+
f'_ = array({d_ctype!r});'
|
|
77
|
+
f'_.frombytes({field_ref});'
|
|
78
|
+
f'{field_ref} = _'
|
|
79
|
+
)
|
|
80
|
+
assigns.append(field_ref)
|
|
81
|
+
else:
|
|
82
|
+
assigns.append(field_ref)
|
|
83
|
+
|
|
84
|
+
walk(cls.__cfields__, ('A',))
|
|
85
|
+
|
|
86
|
+
assign_code = f"{', '.join(assigns)} = x_unpack(buffer)" # 生成普通的 unpack, update 用的赋值语句
|
|
87
|
+
unpack.add_code(assign_code)
|
|
88
|
+
update.add_code(assign_code)
|
|
89
|
+
|
|
90
|
+
assign_code = assign_code.replace("x_unpack(buffer)", "values") # 生成 iter_xxx 的赋值语句
|
|
91
|
+
iter_unpack_inner.add_code(assign_code)
|
|
92
|
+
iter_update_inner.add_code(assign_code)
|
|
93
|
+
|
|
94
|
+
assign_code = assign_code.replace("values", "x_unpack_from(buffer, offset)") # 生成 xxx_from 的赋值语句
|
|
95
|
+
unpack_from.add_code(assign_code)
|
|
96
|
+
update_from.add_code(assign_code)
|
|
97
|
+
|
|
98
|
+
for post_processor in post_processors:
|
|
99
|
+
unpack_add_code(post_processor)
|
|
100
|
+
update_add_code(post_processor)
|
|
101
|
+
|
|
102
|
+
unpack.add_return('self')
|
|
103
|
+
update.add_return('self')
|
|
104
|
+
unpack_from.add_return('self')
|
|
105
|
+
update_from.add_return('self')
|
|
106
|
+
|
|
107
|
+
iter_unpack_inner.add_code('yield self') # iter 方法是 yield,而非 return
|
|
108
|
+
iter_update_inner.add_code('yield self')
|
|
109
|
+
|
|
110
|
+
# print(unpack.generate_code())
|
|
111
|
+
# print(update.generate_code())
|
|
112
|
+
# print(unpack_from.generate_code())
|
|
113
|
+
# print(update_from.generate_code())
|
|
114
|
+
# print(iter_unpack.generate_code())
|
|
115
|
+
print(iter_update.generate_code())
|
|
116
|
+
|
|
117
|
+
globals = {
|
|
118
|
+
v: k for k, v in inner_classes.items() # 翻转字典
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
globals.update(dict(
|
|
122
|
+
__new__=object.__new__,
|
|
123
|
+
x_unpack=cls.__cstruct__.unpack,
|
|
124
|
+
x_unpack_from=cls.__cstruct__.unpack_from,
|
|
125
|
+
x_iter_unpack=cls.__cstruct__.iter_unpack,
|
|
126
|
+
array=array.array
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
return unpack.exec(
|
|
130
|
+
globals
|
|
131
|
+
), update.exec(
|
|
132
|
+
globals
|
|
133
|
+
), unpack_from.exec(
|
|
134
|
+
globals
|
|
135
|
+
), update_from.exec(
|
|
136
|
+
globals
|
|
137
|
+
), iter_unpack.exec(
|
|
138
|
+
globals
|
|
139
|
+
), iter_update.exec(
|
|
140
|
+
globals
|
|
141
|
+
)
|
obstruct/decorator.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from . import struct_type
|
|
5
|
+
from .aot import pack, unpack
|
|
6
|
+
from .methods import to_format_string
|
|
7
|
+
|
|
8
|
+
endian_control_characters = tuple('<@=!>')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def struct(maybe_cls=None, *, endian='@'):
|
|
12
|
+
def decorator[T](cls: T) -> type[struct_type.Struct]:
|
|
13
|
+
import struct
|
|
14
|
+
|
|
15
|
+
# module = sys.modules[cls.__module__]
|
|
16
|
+
|
|
17
|
+
fields = dataclasses.fields(cls)
|
|
18
|
+
# namesp = module.__dict__
|
|
19
|
+
# print(namesp)
|
|
20
|
+
real_annotations = typing.get_type_hints(cls)
|
|
21
|
+
# print(cls, real_annotations)
|
|
22
|
+
|
|
23
|
+
for field in fields:
|
|
24
|
+
print(cls, field.name, repr(field.type))
|
|
25
|
+
if isinstance(field.type, str):
|
|
26
|
+
field.type = real_annotations[field.name]
|
|
27
|
+
|
|
28
|
+
cls.__ctype__ = to_format_string([field.type for field in fields])
|
|
29
|
+
# print(cls.__ctype__)
|
|
30
|
+
cls.__cfields__ = tuple(fields) # type: tuple[dataclasses.Field, ...]
|
|
31
|
+
cls.__cstruct__ = struct.Struct(endian + cls.__ctype__) # 构造 Struct 类型
|
|
32
|
+
cls.__cendian__ = endian
|
|
33
|
+
|
|
34
|
+
cls.pack, cls.pack_into = pack.create_pack_methods(cls)
|
|
35
|
+
cls.unpack, cls.update, cls.unpack_from, cls.update_from, cls.iter_unpack, cls.iter_update = (
|
|
36
|
+
unpack.create_unpack_methods(cls)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return cls
|
|
40
|
+
|
|
41
|
+
return decorator(maybe_cls) if maybe_cls is not None else decorator
|
obstruct/methods.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from collections.abc import Buffer
|
|
3
|
+
from typing import Any, Sequence, Iterator, get_args, get_origin
|
|
4
|
+
|
|
5
|
+
from typing_inspection.typing_objects import is_annotated
|
|
6
|
+
|
|
7
|
+
from .struct_type import CType
|
|
8
|
+
|
|
9
|
+
mapping = {
|
|
10
|
+
int: 'i', float: 'f', bool: '?'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
CTypesFields = Sequence[str | CType | type] | Iterator[str | CType | type]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_ctype(tp):
|
|
17
|
+
if isinstance(tp, str):
|
|
18
|
+
return tp
|
|
19
|
+
elif _ := getattr(tp, '__ctype__', None):
|
|
20
|
+
return _
|
|
21
|
+
elif _ := mapping.get(tp):
|
|
22
|
+
return _
|
|
23
|
+
elif is_annotated(get_origin(tp)):
|
|
24
|
+
# 目前需要 Annotated 的类型只有 array
|
|
25
|
+
tp_args = get_args(tp)
|
|
26
|
+
dtype = get_args(tp_args[0])[0]
|
|
27
|
+
length = tp_args[1]
|
|
28
|
+
c_size = struct.calcsize(get_ctype(dtype))
|
|
29
|
+
return f'{length * c_size}s'
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError(f'Unrecognized type: {tp}')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_format_string(fields: CTypesFields):
|
|
35
|
+
format = ''.join(
|
|
36
|
+
map(
|
|
37
|
+
get_ctype,
|
|
38
|
+
fields
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
return format
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def pack(format: CTypesFields, *data: Any) -> bytes:
|
|
45
|
+
return struct.pack(to_format_string(format), *data)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def unpack(format: CTypesFields, data: Buffer):
|
|
49
|
+
return struct.unpack(to_format_string(format), data)
|
obstruct/struct_type.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import struct
|
|
3
|
+
import typing
|
|
4
|
+
from collections.abc import Buffer
|
|
5
|
+
from typing import Self, Generator, Any, ClassVar
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CType(typing.Protocol):
|
|
9
|
+
__ctype__: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Struct(CType):
|
|
13
|
+
"""
|
|
14
|
+
代表C语言结构体的Python类。
|
|
15
|
+
提供了将结构体对象与字节序列之间互相转换的功能,
|
|
16
|
+
包括打包、解包、更新等操作。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
__cfields__: ClassVar[tuple[dataclasses.Field]]
|
|
20
|
+
__cstruct__: ClassVar[struct.Struct]
|
|
21
|
+
__cendian__: ClassVar[str]
|
|
22
|
+
|
|
23
|
+
def pack(self) -> bytes:
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def pack_into(self, buffer: Buffer, offset=0) -> None:
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def unpack(cls, buffer) -> Self:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def unpack_from(cls, buffer, offset=0) -> Self:
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def iter_unpack(cls, buffer) -> Generator[Self, None, None]:
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def iter_update(cls, buffer) -> Generator[Self, None, None]:
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def update(self, buffer: Buffer) -> None:
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def update_from(self, buffer: Buffer, offset=0) -> None:
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict[str, Any]:
|
|
52
|
+
return dataclasses.asdict(self)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .types import *
|
obstruct/types/base.py
ADDED
obstruct/types/types.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from .base import make_struct_type
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@lru_cache(maxsize=32)
|
|
9
|
+
def pad(size: int):
|
|
10
|
+
return make_struct_type(
|
|
11
|
+
f'pad_{size}', builtins.bytes, f'{size}x'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
char = make_struct_type(
|
|
16
|
+
'char', builtins.bytes, 'c'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
signed_char = make_struct_type(
|
|
20
|
+
'signed_char', builtins.int, 'b'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
unsigned_char = make_struct_type(
|
|
24
|
+
'unsigned_char', builtins.int, 'B'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# bool = make_struct_type(
|
|
28
|
+
# 'bool', builtins.int, '?'
|
|
29
|
+
# ) # 布尔类型不能添加子类,也不允许添加新的类属性,只能特殊处理
|
|
30
|
+
|
|
31
|
+
short = make_struct_type(
|
|
32
|
+
'short', builtins.int, 'h'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
unsigned_short = make_struct_type(
|
|
36
|
+
'unsigned_short', builtins.int, 'H'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
unsigned_int = make_struct_type(
|
|
40
|
+
'unsigned_int', builtins.int, 'I'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
long = make_struct_type(
|
|
44
|
+
'long', builtins.int, 'l'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
unsigned_long = make_struct_type(
|
|
48
|
+
'unsigned_long', builtins.int, 'L'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
long_long = make_struct_type(
|
|
52
|
+
'long_long', builtins.int, 'q'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
unsigned_long_long = make_struct_type(
|
|
56
|
+
'unsigned_long_long', builtins.int, 'Q'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
double = make_struct_type(
|
|
60
|
+
'double', builtins.float, 'd'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
short_float = make_struct_type(
|
|
64
|
+
'short_float', builtins.float, 'e'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
size_t = make_struct_type(
|
|
68
|
+
'size_t', builtins.int, 'n'
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
ssize_t = make_struct_type(
|
|
72
|
+
'ssize_t', builtins.int, 'N'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# string (char[]), length must be specified when used
|
|
77
|
+
@lru_cache(maxsize=32)
|
|
78
|
+
def string(length):
|
|
79
|
+
return make_struct_type(f'string_{length}', builtins.bytes, f'{length}s')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# pascal string (length-prefixed)
|
|
83
|
+
@lru_cache(maxsize=32)
|
|
84
|
+
def pascal_string(length):
|
|
85
|
+
return make_struct_type(f'pascal_string_{length}', builtins.bytes, f'{length}p')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# pointer-sized integer (platform dependent)
|
|
89
|
+
pointer = make_struct_type(
|
|
90
|
+
'pointer', builtins.int, 'P'
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# >= 3.14
|
|
94
|
+
if sys.version_info.major >= 3 and sys.version_info.minor >= 14:
|
|
95
|
+
float_complex = make_struct_type(
|
|
96
|
+
'complex', builtins.complex, 'F'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
double_complex = make_struct_type(
|
|
100
|
+
'double_complex', builtins.complex, 'D'
|
|
101
|
+
)
|
obstruct/types/types.pyi
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Literal, Sequence
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _ctype:
|
|
6
|
+
__ctype__: str
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pad(size: int) -> type: ...
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class char(bytes, _ctype): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class signed_char(int, _ctype): ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class unsigned_char(int, _ctype): ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class long(int, _ctype): ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class unsigned_long(int, _ctype): ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class short(int, _ctype): ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class unsigned_short(int, _ctype): ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class unsigned_int(int, _ctype): ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class long_long(int, _ctype): ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class unsigned_long_long(int, _ctype): ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class double(float, _ctype): ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class short_float(float, _ctype): ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class size_t(int, _ctype): ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ssize_t(int, _ctype): ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def string(length: int) -> type[bytes]: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pascal_string(length: int) -> type[bytes]: ...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class pointer(int, _ctype): ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if sys.version_info >= (3, 14, 0):
|
|
64
|
+
class float_complex(complex, _ctype): ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class double_complex(complex, _ctype): ...
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: obstruct
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Author-email: LittleNightSong <LittleNightSongYO@outlook.com>
|
|
5
|
+
Classifier: Development Status :: 3 - Alpha
|
|
6
|
+
Classifier: Programming Language :: Python
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
11
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
12
|
+
Requires-Python: >=3.12
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
obstruct/__init__.py,sha256=zM70fUvOP_Ne_9iAQQBRyST5GGag3yYNis4VAhQLWE4,121
|
|
2
|
+
obstruct/__version__.py,sha256=S7TnOI7A9GMGYPhpcWZGb5luwiex3yTP5q-e9kmMZtw,19
|
|
3
|
+
obstruct/decorator.py,sha256=D7KFh6tfLm-yAE1zhr5aqnyEVNPSOILM9hDguoUCYpI,1389
|
|
4
|
+
obstruct/methods.py,sha256=UIUuvypBo7_SYPqQyknKootLqF-ggYGG39v5rZqvOzU,1278
|
|
5
|
+
obstruct/struct_type.py,sha256=zxByrMqiKkH-i8VCpGM7jQdOyFzKo_edwrDq2RkNj2U,1233
|
|
6
|
+
obstruct/aot/__init__.py,sha256=xj1UpN3cEyramzthzMILYwVWwkFmaAC2OiYxfDsKjk8,52
|
|
7
|
+
obstruct/aot/aot.py,sha256=b1eOn14uy4OTMsjW7T8f-0Lk1vLsVd2dBPGNbWc253s,82
|
|
8
|
+
obstruct/aot/base.py,sha256=L1UpXowWVLaIE3iChvyUdCP6lQx7XC2Qp62QeqziVL0,4115
|
|
9
|
+
obstruct/aot/pack.py,sha256=IfSKYYA9n3n-ZTsnaxgcJFepDR_5rgKmKZO60k3-jVg,2550
|
|
10
|
+
obstruct/aot/unpack.py,sha256=FHHVgJp4wkcnicEg7ZJ2KZ0GVxcSq94_xXgRN2wCRgU,5698
|
|
11
|
+
obstruct/types/__init__.py,sha256=DGan05iQmXP3zYxpjkpv0DAClg41oBCF6WFZD1wSGDE,22
|
|
12
|
+
obstruct/types/base.py,sha256=IZr2JIEtm5I3zxI5JcGM8X5_bZfTcxHyX8YfqPmNRMI,163
|
|
13
|
+
obstruct/types/types.py,sha256=M9M7BCqQZswyHdxEXkOZtt2_lBv96HuicawrOH-o254,2167
|
|
14
|
+
obstruct/types/types.pyi,sha256=p51EY4JjOVQ3HYUQvI0bZCmY9S5BFZO8JXQXCXnUh5A,966
|
|
15
|
+
obstruct-0.2.1.dist-info/METADATA,sha256=U74R9PFbQ_sLLlRHPH_h6EgTt-zpLFgNMx7P80pHszI,519
|
|
16
|
+
obstruct-0.2.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
17
|
+
obstruct-0.2.1.dist-info/RECORD,,
|