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.
@@ -0,0 +1,171 @@
1
+ """从 il2cpp dump 提取枚举源数据的逻辑。
2
+
3
+ REFramework 导出的 ``il2cpp_dump.json`` 里包含大量类型/字段/方法信息。本模块
4
+ 负责从中抽取两类资料:枚举成员表(值 -> 名称),以及把字段、可序列化包装
5
+ 类型和泛型容器关联到“固定枚举”(``*_Fixed``)的上下文,供导出后处理使用。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Any
12
+
13
+ from ..core import ENUM_UNUSED_KEY
14
+
15
+
16
+ class ExporterEnumSourceMixin:
17
+ """负责把 REFramework dump 转换成导出器可用的枚举原始资料。"""
18
+
19
+ @staticmethod
20
+ def export_enums_internal(dump_json: dict) -> dict:
21
+ """从 `il2cpp_dump.json` 中提取枚举成员表。
22
+
23
+ 参数:
24
+ dump_json (dict): 已解析的 il2cpp dump 对象。
25
+
26
+ 返回:
27
+ dict: ``枚举类型 -> {成员名 -> 数值}`` 的映射。
28
+ """
29
+ enums_internal = {}
30
+ for key, value in dump_json.items():
31
+ if isinstance(value, dict):
32
+ obj = dump_json[key]
33
+ # REFramework dump 中枚举类型的 parent 通常为 System.Enum。
34
+ if "parent" in obj and obj["parent"] == "System.Enum":
35
+ val = {}
36
+ for _k, _v in obj["fields"].items():
37
+ # 跳过枚举的占位字段(value__),只保留真实成员。
38
+ if _k != ENUM_UNUSED_KEY:
39
+ val[_k] = _v["default"]
40
+ enums_internal[key] = val
41
+ return enums_internal
42
+
43
+ @staticmethod
44
+ def export_enum_context_internal(dump_json: dict) -> dict:
45
+ """从 il2cpp dump 中提取枚举字段上下文。
46
+
47
+ 参数:
48
+ dump_json (dict): 已解析的 il2cpp dump 对象。
49
+
50
+ 返回:
51
+ dict: 含三个键的上下文字典:``class_field_fixed_types``(类 -> 字段 -> 枚举类型)、
52
+ ``serializable_to_fixed``(可序列化包装类型 -> 固定枚举)、
53
+ ``generic_container_rules``(泛型容器 -> {param_type, enum_type})。
54
+ """
55
+
56
+ def extract_fixed_enum_type(type_name: Any) -> str | None:
57
+ """从类型表达式中提取唯一的 `*_Fixed` 枚举类型。
58
+
59
+ 参数:
60
+ type_name (Any): 字段、方法参数或返回值上的类型表达式(通常是 str)。
61
+
62
+ 返回:
63
+ str | None: 找到且唯一时返回枚举类型名,否则返回 ``None``。
64
+ """
65
+ if not isinstance(type_name, str):
66
+ return None
67
+ matches = re.findall(r"[A-Za-z0-9_.]+_Fixed", type_name)
68
+ if not matches:
69
+ return None
70
+ # 去重后若只剩一个候选,才能确定字段唯一对应的固定枚举类型。
71
+ unique = list(dict.fromkeys(matches))
72
+ if len(unique) == 1:
73
+ return unique[0]
74
+ return None
75
+
76
+ class_field_fixed_types: dict[str, dict[str, str]] = {}
77
+ serializable_to_fixed: dict[str, str] = {}
78
+ generic_container_rules: dict[str, dict[str, str]] = {}
79
+
80
+ for class_name, obj in dump_json.items():
81
+ if not isinstance(class_name, str) or not isinstance(obj, dict):
82
+ continue
83
+
84
+ field_map: dict[str, str] = {}
85
+ fields_obj = obj.get("fields")
86
+ if isinstance(fields_obj, dict):
87
+ for field_name, field_info in fields_obj.items():
88
+ if not isinstance(field_name, str) or not isinstance(
89
+ field_info, dict
90
+ ):
91
+ continue
92
+ fixed_type = extract_fixed_enum_type(field_info.get("type"))
93
+ if fixed_type is not None:
94
+ field_map[field_name] = fixed_type
95
+
96
+ # RSZ 元数据也是字段名到枚举类型关系的权威来源。
97
+ rsz_fields = obj.get("RSZ")
98
+ if isinstance(rsz_fields, list):
99
+ for rsz_field in rsz_fields:
100
+ if not isinstance(rsz_field, dict):
101
+ continue
102
+ potential_name = rsz_field.get("potential_name")
103
+ fixed_type = extract_fixed_enum_type(rsz_field.get("type"))
104
+ if isinstance(potential_name, str) and fixed_type is not None:
105
+ field_map.setdefault(potential_name, fixed_type)
106
+
107
+ # 反射属性里可能带有数组元素类型信息,也可以补充字段上下文。
108
+ reflection_props = obj.get("reflection_properties")
109
+ if isinstance(reflection_props, dict):
110
+ for prop_name, prop_info in reflection_props.items():
111
+ if not isinstance(prop_name, str) or not isinstance(
112
+ prop_info, dict
113
+ ):
114
+ continue
115
+ fixed_type = extract_fixed_enum_type(prop_info.get("type"))
116
+ if fixed_type is not None:
117
+ field_map.setdefault(prop_name, fixed_type)
118
+
119
+ if field_map:
120
+ class_field_fixed_types[class_name] = field_map
121
+
122
+ if class_name.endswith("_Serializable"):
123
+ # `xxx_Serializable` 往往是固定枚举的可序列化包装类型。
124
+ # 如果方法签名里只出现一个 `*_Fixed`,即可建立一对一映射。
125
+ fixed_types: set[str] = set()
126
+ methods_obj = obj.get("methods")
127
+ if isinstance(methods_obj, dict):
128
+ for method in methods_obj.values():
129
+ if not isinstance(method, dict):
130
+ continue
131
+ params = method.get("params")
132
+ if isinstance(params, list):
133
+ for param in params:
134
+ if not isinstance(param, dict):
135
+ continue
136
+ fixed_type = extract_fixed_enum_type(param.get("type"))
137
+ if fixed_type is not None:
138
+ fixed_types.add(fixed_type)
139
+ returns = method.get("returns")
140
+ if isinstance(returns, dict):
141
+ fixed_type = extract_fixed_enum_type(returns.get("type"))
142
+ if fixed_type is not None:
143
+ fixed_types.add(fixed_type)
144
+ if len(fixed_types) == 1:
145
+ serializable_to_fixed[class_name] = next(iter(fixed_types))
146
+
147
+ generic_args = obj.get("generic_arg_types")
148
+ if isinstance(generic_args, list) and len(generic_args) >= 2:
149
+ # 泛型容器常见形态是 <枚举类型, 参数类型>,后处理时可用它
150
+ # 推断参数对象内部 `_FixedID` 等字段对应哪个枚举。
151
+ enum_arg = generic_args[0]
152
+ param_arg = generic_args[1]
153
+ enum_type = (
154
+ extract_fixed_enum_type(enum_arg.get("type"))
155
+ if isinstance(enum_arg, dict)
156
+ else None
157
+ )
158
+ param_type = (
159
+ param_arg.get("type") if isinstance(param_arg, dict) else None
160
+ )
161
+ if isinstance(enum_type, str) and isinstance(param_type, str):
162
+ generic_container_rules[class_name] = {
163
+ "param_type": param_type,
164
+ "enum_type": enum_type,
165
+ }
166
+
167
+ return {
168
+ "class_field_fixed_types": class_field_fixed_types,
169
+ "serializable_to_fixed": serializable_to_fixed,
170
+ "generic_container_rules": generic_container_rules,
171
+ }
@@ -0,0 +1,231 @@
1
+ """`.user.3` 字段级二进制读取逻辑。
2
+
3
+ 本模块提供按 RE_RSZ 字段类型从二进制流中读取标量、数组和嵌套结构体的能力,
4
+ 并在模板缺失或递归过深时保留原始字节,保证导出信息可逆、不丢失。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from ..core import (
12
+ BinaryReader,
13
+ ParseError,
14
+ align,
15
+ read_guid_like,
16
+ read_len_c8,
17
+ read_len_utf16,
18
+ )
19
+ from ..schema import ClassDef, FieldDef
20
+
21
+
22
+ class ExporterFieldParserMixin:
23
+ """负责按 RE_RSZ 字段类型读取标量、数组和结构体。"""
24
+
25
+ def _parse_scalar(
26
+ self, reader: BinaryReader, field: FieldDef, depth: int = 0
27
+ ) -> Any:
28
+ """按字段类型从二进制流中读取一个标量值。
29
+
30
+ 参数:
31
+ reader (BinaryReader): 二进制读取器,会从其当前游标处读取。
32
+ field (FieldDef): RE_RSZ 字段定义,决定读取方式与尺寸。
33
+ depth (int): 当前结构体递归深度,用于限制嵌套结构体的展开层数。
34
+
35
+ 返回:
36
+ Any: 解析出的 Python 值;标量为基础类型,对象/资源/结构体为 dict,
37
+ 未知类型则返回保留原始字节的 dict。
38
+ """
39
+ t = field.field_type
40
+ # 基础数值类型直接按小端格式读取。
41
+ if t == "Bool":
42
+ return bool(reader.read_u8())
43
+ if t == "S8":
44
+ return reader.read_s8()
45
+ if t == "U8":
46
+ return reader.read_u8()
47
+ if t == "S16":
48
+ return reader.read_s16()
49
+ if t == "U16":
50
+ return reader.read_u16()
51
+ if t in ("S32", "Sfix"):
52
+ return reader.read_s32()
53
+ if t == "Enum":
54
+ return reader.read_s32()
55
+ if t == "U32":
56
+ return reader.read_u32()
57
+ if t == "S64":
58
+ return reader.read_s64()
59
+ if t == "U64":
60
+ return reader.read_u64()
61
+ if t == "F32":
62
+ return reader.read_f32()
63
+ if t == "F64":
64
+ return reader.read_f64()
65
+ if t in ("Object", "UserData"):
66
+ # 对象和用户数据字段在数据段中保存的是实例编号引用。
67
+ return {"ref_instance_id": reader.read_s32()}
68
+ if t in ("String", "Resource"):
69
+ return read_len_utf16(reader)
70
+ if t == "C8":
71
+ return read_len_c8(reader)
72
+ if t in ("Guid", "GameObjectRef", "Uri"):
73
+ return read_guid_like(reader)
74
+ if t == "Struct":
75
+ if depth >= 4:
76
+ # 结构体递归过深时保留原始字节,避免错误模板造成无限嵌套。
77
+ to_read = max(0, min(field.size, reader.size - reader.tell()))
78
+ return {"raw": reader.read(to_read).hex(), "truncated": True}
79
+ struct_hash = self.typedb.resolve_struct_hash(field.original_type)
80
+ if struct_hash is None:
81
+ # 模板中找不到结构体定义时也不丢数据,保留原始字节便于人工排查。
82
+ to_read = max(0, min(field.size, reader.size - reader.tell()))
83
+ return {
84
+ "raw": reader.read(to_read).hex(),
85
+ "unknown_struct": field.original_type,
86
+ }
87
+ struct_cls = self.typedb.get_class(struct_hash)
88
+ if struct_cls is None:
89
+ to_read = max(0, min(field.size, reader.size - reader.tell()))
90
+ return {
91
+ "raw": reader.read(to_read).hex(),
92
+ "unknown_struct": field.original_type,
93
+ }
94
+ start = reader.tell()
95
+ out: dict[str, Any] = {}
96
+ for sf in struct_cls.fields:
97
+ # 结构体内部字段仍然遵守各自的对齐规则。
98
+ reader.seek(
99
+ align(reader.tell(), 4 if sf.is_array else max(sf.align, 1))
100
+ )
101
+ out[sf.name or "unnamed"] = self._parse_field_value(
102
+ reader, sf, depth=depth + 1
103
+ )
104
+ consumed = reader.tell() - start
105
+ if field.size > consumed:
106
+ # 结构体声明尺寸可能大于字段实际消费,跳过尾部填充。
107
+ reader.seek(reader.tell() + (field.size - consumed))
108
+ return out
109
+ if t in {
110
+ "Float2",
111
+ "Float3",
112
+ "Float4",
113
+ "Vec2",
114
+ "Vec3",
115
+ "Vec4",
116
+ "Quaternion",
117
+ "Color",
118
+ "AABB",
119
+ "Capsule",
120
+ "OBB",
121
+ "Mat3",
122
+ "Mat4",
123
+ "Position",
124
+ }:
125
+ # 这些向量/矩阵类型按 4 字节浮点元素连续读取,数量由声明尺寸推断。
126
+ count = max(field.size // 4, 1)
127
+ return [reader.read_f32() for _ in range(count)]
128
+
129
+ if field.size <= 0:
130
+ return None
131
+ to_read = max(0, min(field.size, reader.size - reader.tell()))
132
+ # 未识别类型保留原始十六进制内容,避免封包信息不可逆丢失。
133
+ return {"raw": reader.read(to_read).hex(), "type": t}
134
+
135
+ def _parse_field_value(
136
+ self, reader: BinaryReader, field: FieldDef, depth: int = 0
137
+ ) -> Any:
138
+ """读取字段值,自动处理数组与标量分支。
139
+
140
+ 参数:
141
+ reader (BinaryReader): 二进制读取器。
142
+ field (FieldDef): 字段定义。
143
+ depth (int): 当前结构体递归深度。
144
+
145
+ 返回:
146
+ Any: 数组字段返回元素列表,非数组字段返回单个标量值。
147
+ """
148
+ if field.is_array:
149
+ count = reader.read_u32()
150
+ if count > 1_000_000:
151
+ # 明显异常的数组长度通常意味着模板或游标已错位,直接返回空数组。
152
+ return []
153
+ items = []
154
+ for _ in range(count):
155
+ if reader.tell() >= reader.size:
156
+ break
157
+ reader.seek(align(reader.tell(), max(field.align, 1)))
158
+ # 数组头保存元素数量,元素本身按非数组字段解析。
159
+ non_array = FieldDef(
160
+ name=field.name,
161
+ field_type=field.field_type,
162
+ original_type=field.original_type,
163
+ size=field.size,
164
+ align=field.align,
165
+ is_array=False,
166
+ )
167
+ items.append(self._parse_scalar(reader, non_array, depth=depth))
168
+ return items
169
+ return self._parse_scalar(reader, field, depth=depth)
170
+
171
+ def _estimate_min_instance_size(self, cls: ClassDef) -> int:
172
+ """估算一个实例至少会占用多少字节。
173
+
174
+ 参数:
175
+ cls (ClassDef): 类型定义。
176
+
177
+ 返回:
178
+ int: 估算的最小实例字节数(至少为 1)。
179
+ """
180
+ pos = 0
181
+ for field in cls.fields:
182
+ # 估算只用于解析失败后的游标跳过,因此宁可保守也不做复杂展开。
183
+ align_to = 4 if field.is_array else max(field.align, 1)
184
+ pos = align(pos, align_to)
185
+ t = field.field_type
186
+ if field.is_array:
187
+ pos += 4
188
+ elif t in ("String", "Resource", "C8"):
189
+ pos += 4
190
+ elif t in ("Object", "UserData"):
191
+ pos += 4
192
+ elif t in ("Guid", "GameObjectRef", "Uri"):
193
+ pos += 16
194
+ elif t in ("S8", "U8", "Bool"):
195
+ pos += 1
196
+ elif t in ("S16", "U16"):
197
+ pos += 2
198
+ elif t in ("S32", "U32", "Enum", "Sfix", "F32"):
199
+ pos += 4
200
+ elif t in ("S64", "U64", "F64"):
201
+ pos += 8
202
+ else:
203
+ pos += max(field.size, 0)
204
+ return max(pos, 1)
205
+
206
+ def _parse_instance(self, reader: BinaryReader, class_hash: int) -> dict[str, Any]:
207
+ """解析一个类型实例的数据段。
208
+
209
+ 参数:
210
+ reader (BinaryReader): 二进制读取器。
211
+ class_hash (int): 实例表中的类型哈希。
212
+
213
+ 返回:
214
+ dict[str, Any]: 含 ``_class``(类名)和 ``fields``(字段名 -> 值)的实例字典。
215
+
216
+ 异常:
217
+ ParseError: 当类型哈希在模板中找不到时抛出。
218
+ """
219
+ cls = self.typedb.get_class(class_hash)
220
+ if cls is None:
221
+ raise ParseError(f"class hash 0x{class_hash:08x} not found in schema")
222
+ out: dict[str, Any] = {"_class": cls.name, "fields": {}}
223
+ for field in cls.fields:
224
+ # 每个字段读取前都按模板声明的对齐要求推进游标。
225
+ reader.seek(
226
+ align(reader.tell(), 4 if field.is_array else max(field.align, 1))
227
+ )
228
+ out["fields"][field.name or "unnamed"] = self._parse_field_value(
229
+ reader, field, depth=0
230
+ )
231
+ return out