PyREUser3 0.1.0__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.
- pyreuser3/__init__.py +65 -0
- pyreuser3/__main__.py +7 -0
- pyreuser3/api.py +410 -0
- pyreuser3/cli.py +207 -0
- pyreuser3/core.py +358 -0
- pyreuser3/export/__init__.py +11 -0
- pyreuser3/export/base.py +245 -0
- pyreuser3/export/enums.py +171 -0
- pyreuser3/export/fields.py +231 -0
- pyreuser3/export/metadata.py +304 -0
- pyreuser3/export/postprocess.py +346 -0
- pyreuser3/export/tree.py +289 -0
- pyreuser3/export/user3.py +415 -0
- pyreuser3/pack/__init__.py +10 -0
- pyreuser3/pack/base.py +281 -0
- pyreuser3/pack/models.py +140 -0
- pyreuser3/pack/plan.py +566 -0
- pyreuser3/pack/writer.py +313 -0
- pyreuser3/rich_ui.py +126 -0
- pyreuser3/schema.py +193 -0
- pyreuser3/web/__init__.py +6 -0
- pyreuser3/web/__main__.py +6 -0
- pyreuser3/web/handler.py +178 -0
- pyreuser3/web/jobs.py +243 -0
- pyreuser3/web/page.py +221 -0
- pyreuser3/web/picker.py +92 -0
- pyreuser3/web/runners.py +238 -0
- pyreuser3/web/server.py +104 -0
- pyreuser3/web/settings.py +42 -0
- pyreuser3-0.1.0.dist-info/METADATA +165 -0
- pyreuser3-0.1.0.dist-info/RECORD +35 -0
- pyreuser3-0.1.0.dist-info/WHEEL +5 -0
- pyreuser3-0.1.0.dist-info/entry_points.txt +3 -0
- pyreuser3-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyreuser3-0.1.0.dist-info/top_level.txt +1 -0
pyreuser3/pack/base.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""JSON 到 `.user.3` 的封包器入口。
|
|
2
|
+
|
|
3
|
+
`User3Packer` 通过组合规划(plan)与写入(writer)两个 Mixin,把项目导出的
|
|
4
|
+
JSON 重新编码成 ``.user.3`` 二进制。本文件负责装配能力、加载枚举反查表,并
|
|
5
|
+
提供单文件、目录批量以及内存对象三种封包入口。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .models import InstanceSpec
|
|
16
|
+
from .plan import PackerPlanMixin
|
|
17
|
+
from .writer import PackerWriterMixin
|
|
18
|
+
from ..core import RSZ_MAGIC, USR_MAGIC, resolve_schema_path
|
|
19
|
+
from ..export import User3Exporter
|
|
20
|
+
from ..rich_ui import BatchProgress
|
|
21
|
+
from ..schema import TypeDB
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class User3Packer(PackerPlanMixin, PackerWriterMixin):
|
|
25
|
+
"""根据导出的 JSON 树重新构造 `.user.3` 二进制文件。
|
|
26
|
+
|
|
27
|
+
组合实例规划与二进制写入能力,支持 readable JSON、完整实例表 JSON 以及
|
|
28
|
+
手写 JSON 三种输入,并在配置 il2cpp dump 时支持枚举名/标签反查。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
schema_dir: str | Path,
|
|
34
|
+
il2cpp_dump_path: str | Path | None = None,
|
|
35
|
+
output_root: str | Path | None = None,
|
|
36
|
+
user_magic: int = USR_MAGIC,
|
|
37
|
+
rsz_magic: int = RSZ_MAGIC,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""初始化封包器。
|
|
40
|
+
|
|
41
|
+
参数:
|
|
42
|
+
schema_dir (str | Path): 历史参数名,实际必须是 RE_RSZ 模板 JSON 文件路径。
|
|
43
|
+
il2cpp_dump_path (str | Path | None): 可选的 ``il2cpp_dump.json``,用于枚举名反查。
|
|
44
|
+
output_root (str | Path | None): 默认输出根目录;为 ``None`` 时使用当前工作目录。
|
|
45
|
+
user_magic (int): 写入 USR 头的 magic。
|
|
46
|
+
rsz_magic (int): 写入 RSZ 块的 magic。
|
|
47
|
+
|
|
48
|
+
返回:
|
|
49
|
+
None: 构造函数,仅初始化实例属性。
|
|
50
|
+
|
|
51
|
+
异常:
|
|
52
|
+
FileNotFoundError: 当传入的 ``il2cpp_dump.json`` 路径不是文件时抛出。
|
|
53
|
+
"""
|
|
54
|
+
self.schema_path = self._resolve_schema_path(Path(schema_dir))
|
|
55
|
+
self.typedb = TypeDB.load(self.schema_path)
|
|
56
|
+
self.il2cpp_dump_path = Path(il2cpp_dump_path) if il2cpp_dump_path else None
|
|
57
|
+
if self.il2cpp_dump_path is not None and not self.il2cpp_dump_path.is_file():
|
|
58
|
+
raise FileNotFoundError(
|
|
59
|
+
f"il2cpp_dump.json not found: {self.il2cpp_dump_path}"
|
|
60
|
+
)
|
|
61
|
+
self.output_root = Path(output_root) if output_root else Path.cwd()
|
|
62
|
+
self.user_magic = int(user_magic)
|
|
63
|
+
self.rsz_magic = int(rsz_magic)
|
|
64
|
+
# 枚举查找表用于把 `[值] 名称` 或成员名还原为整数;没有 dump 时
|
|
65
|
+
# 封包器仍可处理已经是数字的枚举字段。
|
|
66
|
+
self.enum_lookup = self._load_enum_lookup()
|
|
67
|
+
self.member_lookup = self._build_member_lookup()
|
|
68
|
+
self.instances: list[InstanceSpec | None] = []
|
|
69
|
+
|
|
70
|
+
def pack_json_file(self, json_path: str | Path, output_path: str | Path) -> Path:
|
|
71
|
+
"""读取一个 JSON 文件并写出 `.user.3`。
|
|
72
|
+
|
|
73
|
+
参数:
|
|
74
|
+
json_path (str | Path): 源 JSON 文件。
|
|
75
|
+
output_path (str | Path): 目标 ``.user.3`` 路径。
|
|
76
|
+
|
|
77
|
+
返回:
|
|
78
|
+
Path: 实际写入的 ``.user.3`` 文件路径。
|
|
79
|
+
"""
|
|
80
|
+
source = Path(json_path)
|
|
81
|
+
target = Path(output_path)
|
|
82
|
+
with source.open("r", encoding="utf-8") as f:
|
|
83
|
+
data = json.load(f)
|
|
84
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
target.write_bytes(self.pack(data))
|
|
86
|
+
return target
|
|
87
|
+
|
|
88
|
+
def pack_directory(
|
|
89
|
+
self,
|
|
90
|
+
json_root: str | Path,
|
|
91
|
+
output_root: str | Path,
|
|
92
|
+
exclude_regexes: list[str] | None = None,
|
|
93
|
+
) -> dict[str, int]:
|
|
94
|
+
"""批量封包目录或单个 JSON 文件。
|
|
95
|
+
|
|
96
|
+
参数:
|
|
97
|
+
json_root (str | Path): JSON 文件或根目录。
|
|
98
|
+
output_root (str | Path): ``.user.3`` 输出根目录。
|
|
99
|
+
exclude_regexes (list[str] | None): 排除相对路径的正则表达式列表。
|
|
100
|
+
|
|
101
|
+
返回:
|
|
102
|
+
dict[str, int]: 统计字典,含 ``total``、``success``、``failed`` 三个计数。
|
|
103
|
+
|
|
104
|
+
异常:
|
|
105
|
+
FileNotFoundError: 当 ``json_root`` 既不是文件也不是存在的目录时抛出。
|
|
106
|
+
"""
|
|
107
|
+
source_root = Path(json_root)
|
|
108
|
+
target_root = Path(output_root)
|
|
109
|
+
patterns = [re.compile(p) for p in (exclude_regexes or [])]
|
|
110
|
+
if source_root.is_file():
|
|
111
|
+
files = [source_root]
|
|
112
|
+
else:
|
|
113
|
+
if not source_root.is_dir():
|
|
114
|
+
raise FileNotFoundError(f"json root not found: {source_root}")
|
|
115
|
+
# 优先处理稳定封包格式;没有时再处理 readable JSON,
|
|
116
|
+
# 最后退回普通 JSON,方便用户手写输入。
|
|
117
|
+
files = sorted(source_root.rglob("*.user.3.pack.json"))
|
|
118
|
+
if not files:
|
|
119
|
+
files = sorted(source_root.rglob("*.user.3.json"))
|
|
120
|
+
if not files:
|
|
121
|
+
files = sorted(source_root.rglob("*.json"))
|
|
122
|
+
candidates: list[tuple[Path, str]] = []
|
|
123
|
+
for json_file in files:
|
|
124
|
+
rel = (
|
|
125
|
+
json_file.name
|
|
126
|
+
if source_root.is_file()
|
|
127
|
+
else json_file.relative_to(source_root).as_posix()
|
|
128
|
+
)
|
|
129
|
+
if any(pattern.search(rel) for pattern in patterns):
|
|
130
|
+
continue
|
|
131
|
+
candidates.append((json_file, rel))
|
|
132
|
+
|
|
133
|
+
total = success = failed = 0
|
|
134
|
+
with BatchProgress(
|
|
135
|
+
"Packing user3", total=len(candidates), unit="file"
|
|
136
|
+
) as progress:
|
|
137
|
+
progress.log(f"发现 {len(candidates)} 个 JSON 文件。")
|
|
138
|
+
progress.log(f"使用模板: {self.schema_path}")
|
|
139
|
+
progress.log(f"输出目录: {target_root}")
|
|
140
|
+
for json_file, rel in candidates:
|
|
141
|
+
total += 1
|
|
142
|
+
progress.update(advance=0, description=json_file.stem)
|
|
143
|
+
progress.log(f"开始封包 JSON: {rel}")
|
|
144
|
+
try:
|
|
145
|
+
# 单个文件失败不会终止整批任务,便于批量模组输出时逐个排查。
|
|
146
|
+
out_path = self.output_path_for(json_file, source_root, target_root)
|
|
147
|
+
self.pack_json_file(json_file, out_path)
|
|
148
|
+
success += 1
|
|
149
|
+
progress.log(f"user3 封包完成: {out_path}", style="green")
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
failed += 1
|
|
152
|
+
error = f"{exc.__class__.__name__}: {exc}"
|
|
153
|
+
progress.log(f"user3 封包失败: {json_file} ({error})", style="red")
|
|
154
|
+
progress.update(1)
|
|
155
|
+
return {"total": total, "success": success, "failed": failed}
|
|
156
|
+
|
|
157
|
+
def pack(self, data: Any) -> bytes:
|
|
158
|
+
"""把内存中的 JSON 对象编码为 `.user.3` 字节。
|
|
159
|
+
|
|
160
|
+
参数:
|
|
161
|
+
data (Any): 类名包裹对象、这类对象组成的列表,或完整实例表封包文档。
|
|
162
|
+
|
|
163
|
+
返回:
|
|
164
|
+
bytes: 可直接写入文件的 ``.user.3`` 二进制字节。
|
|
165
|
+
"""
|
|
166
|
+
# 实例 0 固定保留为空引用槽,所有真实对象从 1 开始。
|
|
167
|
+
if self._is_pack_document(data):
|
|
168
|
+
roots = self._plan_pack_document(data)
|
|
169
|
+
else:
|
|
170
|
+
self.instances = [None]
|
|
171
|
+
roots: list[int] = []
|
|
172
|
+
for node in self._normalize_roots(data):
|
|
173
|
+
roots.append(self._plan_node(node))
|
|
174
|
+
return self._build_binary(roots)
|
|
175
|
+
|
|
176
|
+
def output_path_for(
|
|
177
|
+
self, json_file: Path, json_root: Path, output_root: Path
|
|
178
|
+
) -> Path:
|
|
179
|
+
"""根据输入 JSON 路径计算输出 `.user.3` 路径。
|
|
180
|
+
|
|
181
|
+
参数:
|
|
182
|
+
json_file (Path): 单个源 JSON 文件路径。
|
|
183
|
+
json_root (Path): 输入根(文件或目录),用于还原相对子目录。
|
|
184
|
+
output_root (Path): 输出根目录。
|
|
185
|
+
|
|
186
|
+
返回:
|
|
187
|
+
Path: 去掉 JSON 后缀、还原 ``.user.3`` 扩展名后的输出文件路径。
|
|
188
|
+
"""
|
|
189
|
+
relative_parent = (
|
|
190
|
+
Path() if json_root.is_file() else json_file.relative_to(json_root).parent
|
|
191
|
+
)
|
|
192
|
+
name = json_file.name
|
|
193
|
+
# 按输入命名约定剥离不同层级的 JSON 后缀,得到目标 .user.3 文件名。
|
|
194
|
+
if name.endswith(".user.3.pack.json"):
|
|
195
|
+
output_name = name[: -len(".pack.json")]
|
|
196
|
+
elif name.endswith(".user.3.json"):
|
|
197
|
+
output_name = name[: -len(".json")]
|
|
198
|
+
elif name.endswith(".json"):
|
|
199
|
+
output_name = f"{name[: -len('.json')]}.user.3"
|
|
200
|
+
else:
|
|
201
|
+
output_name = f"{name}.user.3"
|
|
202
|
+
return output_root / relative_parent / output_name
|
|
203
|
+
|
|
204
|
+
def _resolve_schema_path(self, schema_dir: Path) -> Path:
|
|
205
|
+
"""校验模板路径并拒绝目录输入。
|
|
206
|
+
|
|
207
|
+
参数:
|
|
208
|
+
schema_dir (Path): 历史参数名,实际必须是具体模板 JSON 文件。
|
|
209
|
+
|
|
210
|
+
返回:
|
|
211
|
+
Path: 校验后的模板文件路径。
|
|
212
|
+
"""
|
|
213
|
+
return resolve_schema_path(schema_dir)
|
|
214
|
+
|
|
215
|
+
def _load_enum_lookup(self) -> dict[str, dict[int, tuple[str, int]]]:
|
|
216
|
+
"""从显式 il2cpp dump 构建枚举数值查找表。
|
|
217
|
+
|
|
218
|
+
返回:
|
|
219
|
+
dict[str, dict[int, tuple[str, int]]]: ``枚举类型 -> {数值 -> (成员名, 原始值)}``
|
|
220
|
+
的查找表;未配置 dump 时返回空映射。
|
|
221
|
+
"""
|
|
222
|
+
raw: dict[str, Any] | None = None
|
|
223
|
+
if self.il2cpp_dump_path is not None:
|
|
224
|
+
with self.il2cpp_dump_path.open("r", encoding="utf-8") as f:
|
|
225
|
+
raw = User3Exporter.export_enums_internal(json.load(f))
|
|
226
|
+
if not isinstance(raw, dict):
|
|
227
|
+
return {}
|
|
228
|
+
|
|
229
|
+
lookup: dict[str, dict[int, tuple[str, int]]] = {}
|
|
230
|
+
for enum_type, members in raw.items():
|
|
231
|
+
if not isinstance(enum_type, str) or not isinstance(members, dict):
|
|
232
|
+
continue
|
|
233
|
+
value_map: dict[int, tuple[str, int]] = {}
|
|
234
|
+
for member_name, raw_value in members.items():
|
|
235
|
+
if not isinstance(member_name, str) or not isinstance(raw_value, int):
|
|
236
|
+
continue
|
|
237
|
+
entry = (member_name, raw_value)
|
|
238
|
+
# 同时登记有符号和无符号 32 位形式,兼容 JSON 中的不同写法。
|
|
239
|
+
value_map[self._to_s32(raw_value)] = entry
|
|
240
|
+
value_map[self._to_u32(raw_value)] = entry
|
|
241
|
+
if value_map:
|
|
242
|
+
lookup[enum_type] = value_map
|
|
243
|
+
return lookup
|
|
244
|
+
|
|
245
|
+
def _build_member_lookup(self) -> dict[str, dict[str, int]]:
|
|
246
|
+
"""建立 `枚举类型 -> 成员名 -> 数值` 的反查表。
|
|
247
|
+
|
|
248
|
+
返回:
|
|
249
|
+
dict[str, dict[str, int]]: 用于把枚举成员名还原成整数值的反查表。
|
|
250
|
+
"""
|
|
251
|
+
out: dict[str, dict[str, int]] = {}
|
|
252
|
+
for enum_type, value_map in self.enum_lookup.items():
|
|
253
|
+
members = out.setdefault(enum_type, {})
|
|
254
|
+
for member_name, fixed_value in value_map.values():
|
|
255
|
+
members.setdefault(member_name, fixed_value)
|
|
256
|
+
return out
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def _to_u32(value: int) -> int:
|
|
260
|
+
"""转换为无符号 32 位整数。
|
|
261
|
+
|
|
262
|
+
参数:
|
|
263
|
+
value (int): 输入整数。
|
|
264
|
+
|
|
265
|
+
返回:
|
|
266
|
+
int: 无符号 32 位值(0 ~ 0xFFFFFFFF)。
|
|
267
|
+
"""
|
|
268
|
+
return value & 0xFFFFFFFF
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _to_s32(value: int) -> int:
|
|
272
|
+
"""转换为有符号 32 位整数。
|
|
273
|
+
|
|
274
|
+
参数:
|
|
275
|
+
value (int): 输入整数。
|
|
276
|
+
|
|
277
|
+
返回:
|
|
278
|
+
int: 有符号 32 位值(-0x80000000 ~ 0x7FFFFFFF)。
|
|
279
|
+
"""
|
|
280
|
+
u32 = value & 0xFFFFFFFF
|
|
281
|
+
return u32 if u32 < 0x80000000 else u32 - 0x100000000
|
pyreuser3/pack/models.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""封包阶段共享的数据结构与二进制写入器。
|
|
2
|
+
|
|
3
|
+
这里定义规划(plan)阶段产出、写入(writer)阶段消费的中间数据结构:
|
|
4
|
+
实例引用、结构体值、实例规格,以及一个支持对齐填充的小端二进制写入器。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import struct
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ..core import PACK_JSON_FORMAT, ParseError, align
|
|
14
|
+
from ..schema import ClassDef
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PackError(ParseError):
|
|
18
|
+
"""JSON 数据无法编码为 `.user.3` 时抛出的异常。
|
|
19
|
+
|
|
20
|
+
继承自 :class:`ParseError`,用于把封包阶段可预期的输入错误(缺类、悬空
|
|
21
|
+
引用、不支持的数据段等)与程序内部错误区分开。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class InstanceRef:
|
|
27
|
+
"""RSZ 实例表中的对象引用。
|
|
28
|
+
|
|
29
|
+
属性:
|
|
30
|
+
index (int): 被引用实例在实例表中的编号;0 表示空引用。
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
index: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class StructValue:
|
|
38
|
+
"""待写入的结构体值和声明尺寸。
|
|
39
|
+
|
|
40
|
+
属性:
|
|
41
|
+
class_def (ClassDef): 结构体对应的类型定义。
|
|
42
|
+
fields (dict[str, Any]): 结构体内部字段名到字段值的映射。
|
|
43
|
+
declared_size (int): 模板声明的结构体字节尺寸,用于尾部补齐。
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
class_def: ClassDef
|
|
47
|
+
fields: dict[str, Any]
|
|
48
|
+
declared_size: int
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class InstanceSpec:
|
|
53
|
+
"""封包前规划出的一个 RSZ 实例。
|
|
54
|
+
|
|
55
|
+
属性:
|
|
56
|
+
class_hash (int): 实例类型哈希。
|
|
57
|
+
class_def (ClassDef): 实例对应的类型定义。
|
|
58
|
+
fields (dict[str, Any]): 准备好的字段名到字段值映射,默认空字典。
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
class_hash: int
|
|
62
|
+
class_def: ClassDef
|
|
63
|
+
fields: dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class BinaryWriter:
|
|
67
|
+
"""带对齐辅助的小端二进制写入器。
|
|
68
|
+
|
|
69
|
+
内部维护一个可增长的字节缓冲区,提供原始写入、按 ``struct`` 格式写入,
|
|
70
|
+
以及对齐/填充到指定偏移的能力。
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self) -> None:
|
|
74
|
+
"""初始化空的字节缓冲区。
|
|
75
|
+
|
|
76
|
+
返回:
|
|
77
|
+
None: 构造函数,仅初始化内部缓冲区。
|
|
78
|
+
"""
|
|
79
|
+
self.data = bytearray()
|
|
80
|
+
|
|
81
|
+
def tell(self) -> int:
|
|
82
|
+
"""返回当前写入偏移。
|
|
83
|
+
|
|
84
|
+
返回:
|
|
85
|
+
int: 已写入的字节数(即下一个写入位置)。
|
|
86
|
+
"""
|
|
87
|
+
return len(self.data)
|
|
88
|
+
|
|
89
|
+
def write(self, raw: bytes) -> None:
|
|
90
|
+
"""追加原始字节。
|
|
91
|
+
|
|
92
|
+
参数:
|
|
93
|
+
raw (bytes): 要追加到缓冲区末尾的字节。
|
|
94
|
+
|
|
95
|
+
返回:
|
|
96
|
+
None: 原地修改内部缓冲区。
|
|
97
|
+
"""
|
|
98
|
+
self.data.extend(raw)
|
|
99
|
+
|
|
100
|
+
def write_struct(self, fmt: str, *values: Any) -> None:
|
|
101
|
+
"""按 `struct` 格式打包并写入数值。
|
|
102
|
+
|
|
103
|
+
参数:
|
|
104
|
+
fmt (str): :func:`struct.pack` 使用的格式字符串。
|
|
105
|
+
*values (Any): 与 ``fmt`` 对应的待打包数值。
|
|
106
|
+
|
|
107
|
+
返回:
|
|
108
|
+
None: 把打包后的字节追加到缓冲区。
|
|
109
|
+
"""
|
|
110
|
+
self.write(struct.pack(fmt, *values))
|
|
111
|
+
|
|
112
|
+
def align(self, alignment: int) -> None:
|
|
113
|
+
"""用零字节填充到指定对齐边界。
|
|
114
|
+
|
|
115
|
+
参数:
|
|
116
|
+
alignment (int): 对齐粒度(字节)。
|
|
117
|
+
|
|
118
|
+
返回:
|
|
119
|
+
None: 必要时向缓冲区追加零字节。
|
|
120
|
+
"""
|
|
121
|
+
target = align(self.tell(), alignment)
|
|
122
|
+
if target > self.tell():
|
|
123
|
+
self.write(b"\x00" * (target - self.tell()))
|
|
124
|
+
|
|
125
|
+
def pad_to(self, target: int) -> None:
|
|
126
|
+
"""填充到绝对偏移,禁止回退写入。
|
|
127
|
+
|
|
128
|
+
参数:
|
|
129
|
+
target (int): 目标绝对偏移,必须大于等于当前偏移。
|
|
130
|
+
|
|
131
|
+
返回:
|
|
132
|
+
None: 必要时向缓冲区追加零字节。
|
|
133
|
+
|
|
134
|
+
异常:
|
|
135
|
+
PackError: 当目标偏移小于当前偏移(需要回退)时抛出。
|
|
136
|
+
"""
|
|
137
|
+
if target < self.tell():
|
|
138
|
+
raise PackError(f"cannot pad backwards: {self.tell()} -> {target}")
|
|
139
|
+
if target > self.tell():
|
|
140
|
+
self.write(b"\x00" * (target - self.tell()))
|