pyflashkit 1.0.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.
- flashkit/__init__.py +54 -0
- flashkit/abc/__init__.py +79 -0
- flashkit/abc/builder.py +847 -0
- flashkit/abc/constants.py +198 -0
- flashkit/abc/disasm.py +364 -0
- flashkit/abc/parser.py +434 -0
- flashkit/abc/types.py +275 -0
- flashkit/abc/writer.py +230 -0
- flashkit/analysis/__init__.py +28 -0
- flashkit/analysis/call_graph.py +317 -0
- flashkit/analysis/inheritance.py +267 -0
- flashkit/analysis/references.py +371 -0
- flashkit/analysis/strings.py +299 -0
- flashkit/cli/__init__.py +75 -0
- flashkit/cli/_util.py +52 -0
- flashkit/cli/build.py +36 -0
- flashkit/cli/callees.py +30 -0
- flashkit/cli/callers.py +30 -0
- flashkit/cli/class_cmd.py +83 -0
- flashkit/cli/classes.py +71 -0
- flashkit/cli/disasm.py +77 -0
- flashkit/cli/extract.py +36 -0
- flashkit/cli/info.py +41 -0
- flashkit/cli/packages.py +30 -0
- flashkit/cli/refs.py +31 -0
- flashkit/cli/strings.py +58 -0
- flashkit/cli/tags.py +32 -0
- flashkit/cli/tree.py +52 -0
- flashkit/errors.py +33 -0
- flashkit/info/__init__.py +31 -0
- flashkit/info/class_info.py +176 -0
- flashkit/info/member_info.py +275 -0
- flashkit/info/package_info.py +60 -0
- flashkit/search/__init__.py +16 -0
- flashkit/search/search.py +456 -0
- flashkit/swf/__init__.py +66 -0
- flashkit/swf/builder.py +283 -0
- flashkit/swf/parser.py +164 -0
- flashkit/swf/tags.py +120 -0
- flashkit/workspace/__init__.py +20 -0
- flashkit/workspace/resource.py +189 -0
- flashkit/workspace/workspace.py +232 -0
- pyflashkit-1.0.0.dist-info/METADATA +281 -0
- pyflashkit-1.0.0.dist-info/RECORD +48 -0
- pyflashkit-1.0.0.dist-info/WHEEL +5 -0
- pyflashkit-1.0.0.dist-info/entry_points.txt +2 -0
- pyflashkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyflashkit-1.0.0.dist-info/top_level.txt +1 -0
flashkit/abc/builder.py
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Programmatic ABC (ActionScript Byte Code) builder.
|
|
3
|
+
|
|
4
|
+
Construct an ``AbcFile`` step-by-step: add strings, namespaces,
|
|
5
|
+
multinames, methods, classes, and method bodies through a high-level API.
|
|
6
|
+
The resulting ``AbcFile`` can be serialized with ``serialize_abc()``.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from flashkit.abc.builder import AbcBuilder
|
|
11
|
+
from flashkit.abc.writer import serialize_abc
|
|
12
|
+
|
|
13
|
+
b = AbcBuilder()
|
|
14
|
+
|
|
15
|
+
# Build constant pools
|
|
16
|
+
cls_str = b.string("MyClass")
|
|
17
|
+
pkg_str = b.string("com.example")
|
|
18
|
+
ns = b.package_namespace(pkg_str)
|
|
19
|
+
cls_mn = b.qname(ns, cls_str)
|
|
20
|
+
|
|
21
|
+
# Build a method
|
|
22
|
+
ctor = b.method()
|
|
23
|
+
b.method_body(ctor, code=b.asm(
|
|
24
|
+
b.op_getlocal_0(),
|
|
25
|
+
b.op_pushscope(),
|
|
26
|
+
b.op_returnvoid(),
|
|
27
|
+
))
|
|
28
|
+
|
|
29
|
+
# Build a class
|
|
30
|
+
b.define_class(name=cls_mn, super_name=0, constructor=ctor)
|
|
31
|
+
|
|
32
|
+
abc = b.build()
|
|
33
|
+
raw = serialize_abc(abc)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from .types import (
|
|
39
|
+
AbcFile, NamespaceInfo, NsSetInfo, MultinameInfo,
|
|
40
|
+
MethodInfo, MetadataInfo, TraitInfo, InstanceInfo,
|
|
41
|
+
ClassInfo as AbcClassInfo, ScriptInfo, ExceptionInfo, MethodBodyInfo,
|
|
42
|
+
)
|
|
43
|
+
from .parser import write_u30
|
|
44
|
+
from .constants import (
|
|
45
|
+
CONSTANT_QName, CONSTANT_QNameA,
|
|
46
|
+
CONSTANT_RTQName, CONSTANT_RTQNameA,
|
|
47
|
+
CONSTANT_Multiname, CONSTANT_MultinameA,
|
|
48
|
+
CONSTANT_MultinameL, CONSTANT_MultinameLA,
|
|
49
|
+
CONSTANT_TypeName,
|
|
50
|
+
CONSTANT_Namespace, CONSTANT_PackageNamespace, CONSTANT_PackageInternalNs,
|
|
51
|
+
CONSTANT_ProtectedNamespace, CONSTANT_ExplicitNamespace,
|
|
52
|
+
CONSTANT_StaticProtectedNs, CONSTANT_PrivateNs,
|
|
53
|
+
TRAIT_Slot, TRAIT_Method, TRAIT_Getter, TRAIT_Setter,
|
|
54
|
+
TRAIT_Class, TRAIT_Function, TRAIT_Const,
|
|
55
|
+
ATTR_Final, ATTR_Override, ATTR_Metadata,
|
|
56
|
+
METHOD_HasOptional, METHOD_HasParamNames,
|
|
57
|
+
METHOD_NeedArguments, METHOD_NeedActivation, METHOD_NeedRest,
|
|
58
|
+
INSTANCE_Sealed, INSTANCE_Final, INSTANCE_Interface, INSTANCE_ProtectedNs,
|
|
59
|
+
OP_getlocal_0, OP_pushscope, OP_returnvoid, OP_returnvalue,
|
|
60
|
+
OP_constructsuper, OP_pushstring, OP_callpropvoid, OP_callproperty,
|
|
61
|
+
OP_getproperty, OP_setproperty, OP_getlex, OP_findpropstrict,
|
|
62
|
+
OP_constructprop, OP_newarray, OP_newclass, OP_coerce,
|
|
63
|
+
OP_pop, OP_dup, OP_swap, OP_pushtrue, OP_pushfalse, OP_pushnull,
|
|
64
|
+
OP_pushundefined, OP_pushbyte, OP_pushshort, OP_pushint, OP_pushuint,
|
|
65
|
+
OP_pushdouble, OP_convert_i, OP_convert_s, OP_convert_d,
|
|
66
|
+
OP_coerce_a, OP_coerce_s, OP_initproperty, OP_getlocal,
|
|
67
|
+
OP_setlocal, OP_getlocal_1, OP_getlocal_2, OP_getlocal_3,
|
|
68
|
+
OP_setlocal_0, OP_setlocal_1, OP_setlocal_2, OP_setlocal_3,
|
|
69
|
+
OP_newfunction, OP_call, OP_construct,
|
|
70
|
+
OP_jump, OP_iftrue, OP_iffalse,
|
|
71
|
+
OP_add, OP_subtract, OP_multiply, OP_divide,
|
|
72
|
+
OP_equals, OP_strictequals, OP_lessthan, OP_greaterequals,
|
|
73
|
+
OP_not, OP_nop, OP_label, OP_throw, OP_debugfile, OP_debugline,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _encode_s24(value: int) -> bytes:
|
|
78
|
+
"""Encode a signed 24-bit offset (little-endian)."""
|
|
79
|
+
if value < 0:
|
|
80
|
+
value += 1 << 24
|
|
81
|
+
return bytes([value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AbcBuilder:
|
|
85
|
+
"""High-level builder for constructing an AbcFile programmatically.
|
|
86
|
+
|
|
87
|
+
Pools are managed automatically — adding the same string twice
|
|
88
|
+
returns the same index. Index 0 in each pool is reserved for
|
|
89
|
+
the implicit default.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
self._int_pool: list[int] = [0]
|
|
94
|
+
self._uint_pool: list[int] = [0]
|
|
95
|
+
self._double_pool: list[float] = [0.0]
|
|
96
|
+
self._string_pool: list[str] = [""]
|
|
97
|
+
self._namespace_pool: list[NamespaceInfo] = [NamespaceInfo(0, 0)]
|
|
98
|
+
self._ns_set_pool: list[NsSetInfo] = [NsSetInfo([])]
|
|
99
|
+
self._multiname_pool: list[MultinameInfo] = [MultinameInfo(0)]
|
|
100
|
+
|
|
101
|
+
self._methods: list[MethodInfo] = []
|
|
102
|
+
self._metadata: list[MetadataInfo] = []
|
|
103
|
+
self._instances: list[InstanceInfo] = []
|
|
104
|
+
self._classes: list[AbcClassInfo] = []
|
|
105
|
+
self._scripts: list[ScriptInfo] = []
|
|
106
|
+
self._method_bodies: list[MethodBodyInfo] = []
|
|
107
|
+
|
|
108
|
+
# Dedup caches
|
|
109
|
+
self._string_cache: dict[str, int] = {"": 0}
|
|
110
|
+
|
|
111
|
+
# ── Constant pool: strings ─────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
def string(self, value: str) -> int:
|
|
114
|
+
"""Add a string to the pool (or return existing index).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
value: The string value.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Index into the string pool.
|
|
121
|
+
"""
|
|
122
|
+
if value in self._string_cache:
|
|
123
|
+
return self._string_cache[value]
|
|
124
|
+
idx = len(self._string_pool)
|
|
125
|
+
self._string_pool.append(value)
|
|
126
|
+
self._string_cache[value] = idx
|
|
127
|
+
return idx
|
|
128
|
+
|
|
129
|
+
# ── Constant pool: integers / doubles ──────────────────────────────
|
|
130
|
+
|
|
131
|
+
def integer(self, value: int) -> int:
|
|
132
|
+
"""Add a signed integer to the int pool.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Index into the int pool.
|
|
136
|
+
"""
|
|
137
|
+
# Check for existing (skip default at 0)
|
|
138
|
+
for i in range(1, len(self._int_pool)):
|
|
139
|
+
if self._int_pool[i] == value:
|
|
140
|
+
return i
|
|
141
|
+
idx = len(self._int_pool)
|
|
142
|
+
self._int_pool.append(value)
|
|
143
|
+
return idx
|
|
144
|
+
|
|
145
|
+
def uint(self, value: int) -> int:
|
|
146
|
+
"""Add an unsigned integer to the uint pool.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Index into the uint pool.
|
|
150
|
+
"""
|
|
151
|
+
for i in range(1, len(self._uint_pool)):
|
|
152
|
+
if self._uint_pool[i] == value:
|
|
153
|
+
return i
|
|
154
|
+
idx = len(self._uint_pool)
|
|
155
|
+
self._uint_pool.append(value)
|
|
156
|
+
return idx
|
|
157
|
+
|
|
158
|
+
def double(self, value: float) -> int:
|
|
159
|
+
"""Add a double to the double pool.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Index into the double pool.
|
|
163
|
+
"""
|
|
164
|
+
for i in range(1, len(self._double_pool)):
|
|
165
|
+
if self._double_pool[i] == value:
|
|
166
|
+
return i
|
|
167
|
+
idx = len(self._double_pool)
|
|
168
|
+
self._double_pool.append(value)
|
|
169
|
+
return idx
|
|
170
|
+
|
|
171
|
+
# ── Constant pool: namespaces ──────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
def namespace(self, kind: int, name: int) -> int:
|
|
174
|
+
"""Add a namespace to the pool.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
kind: Namespace kind constant (CONSTANT_Namespace, etc.).
|
|
178
|
+
name: String pool index for the namespace name.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Index into the namespace pool.
|
|
182
|
+
"""
|
|
183
|
+
for i in range(1, len(self._namespace_pool)):
|
|
184
|
+
ns = self._namespace_pool[i]
|
|
185
|
+
if ns.kind == kind and ns.name == name:
|
|
186
|
+
return i
|
|
187
|
+
idx = len(self._namespace_pool)
|
|
188
|
+
self._namespace_pool.append(NamespaceInfo(kind, name))
|
|
189
|
+
return idx
|
|
190
|
+
|
|
191
|
+
def package_namespace(self, name: int | str) -> int:
|
|
192
|
+
"""Add a public package namespace.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
name: String pool index, or a string (auto-added to pool).
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Namespace pool index.
|
|
199
|
+
"""
|
|
200
|
+
if isinstance(name, str):
|
|
201
|
+
name = self.string(name)
|
|
202
|
+
return self.namespace(CONSTANT_PackageNamespace, name)
|
|
203
|
+
|
|
204
|
+
def private_namespace(self, name: int | str = 0) -> int:
|
|
205
|
+
"""Add a private namespace.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
name: String pool index, or a string. Default 0 (empty).
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Namespace pool index.
|
|
212
|
+
"""
|
|
213
|
+
if isinstance(name, str):
|
|
214
|
+
name = self.string(name)
|
|
215
|
+
return self.namespace(CONSTANT_PrivateNs, name)
|
|
216
|
+
|
|
217
|
+
def internal_namespace(self, name: int | str) -> int:
|
|
218
|
+
"""Add a package-internal namespace.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
name: String pool index, or a string.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Namespace pool index.
|
|
225
|
+
"""
|
|
226
|
+
if isinstance(name, str):
|
|
227
|
+
name = self.string(name)
|
|
228
|
+
return self.namespace(CONSTANT_PackageInternalNs, name)
|
|
229
|
+
|
|
230
|
+
def protected_namespace(self, name: int | str) -> int:
|
|
231
|
+
"""Add a protected namespace.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
name: String pool index, or a string.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Namespace pool index.
|
|
238
|
+
"""
|
|
239
|
+
if isinstance(name, str):
|
|
240
|
+
name = self.string(name)
|
|
241
|
+
return self.namespace(CONSTANT_ProtectedNamespace, name)
|
|
242
|
+
|
|
243
|
+
# ── Constant pool: namespace sets ──────────────────────────────────
|
|
244
|
+
|
|
245
|
+
def ns_set(self, namespaces: list[int]) -> int:
|
|
246
|
+
"""Add a namespace set to the pool.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
namespaces: List of namespace pool indices.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Index into the namespace set pool.
|
|
253
|
+
"""
|
|
254
|
+
idx = len(self._ns_set_pool)
|
|
255
|
+
self._ns_set_pool.append(NsSetInfo(list(namespaces)))
|
|
256
|
+
return idx
|
|
257
|
+
|
|
258
|
+
# ── Constant pool: multinames ──────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
def qname(self, ns: int, name: int | str) -> int:
|
|
261
|
+
"""Add a QName (qualified name) multiname.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
ns: Namespace pool index.
|
|
265
|
+
name: String pool index, or a string.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Multiname pool index.
|
|
269
|
+
"""
|
|
270
|
+
if isinstance(name, str):
|
|
271
|
+
name = self.string(name)
|
|
272
|
+
for i in range(1, len(self._multiname_pool)):
|
|
273
|
+
mn = self._multiname_pool[i]
|
|
274
|
+
if mn.kind == CONSTANT_QName and mn.ns == ns and mn.name == name:
|
|
275
|
+
return i
|
|
276
|
+
idx = len(self._multiname_pool)
|
|
277
|
+
self._multiname_pool.append(MultinameInfo(
|
|
278
|
+
kind=CONSTANT_QName, ns=ns, name=name))
|
|
279
|
+
return idx
|
|
280
|
+
|
|
281
|
+
def multiname(self, name: int | str, ns_set: int) -> int:
|
|
282
|
+
"""Add a Multiname (name + namespace set).
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
name: String pool index, or a string.
|
|
286
|
+
ns_set: Namespace set pool index.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Multiname pool index.
|
|
290
|
+
"""
|
|
291
|
+
if isinstance(name, str):
|
|
292
|
+
name = self.string(name)
|
|
293
|
+
idx = len(self._multiname_pool)
|
|
294
|
+
self._multiname_pool.append(MultinameInfo(
|
|
295
|
+
kind=CONSTANT_Multiname, name=name, ns_set=ns_set))
|
|
296
|
+
return idx
|
|
297
|
+
|
|
298
|
+
def rtqname(self, name: int | str) -> int:
|
|
299
|
+
"""Add a runtime-qualified name (namespace from stack).
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
name: String pool index, or a string.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Multiname pool index.
|
|
306
|
+
"""
|
|
307
|
+
if isinstance(name, str):
|
|
308
|
+
name = self.string(name)
|
|
309
|
+
idx = len(self._multiname_pool)
|
|
310
|
+
self._multiname_pool.append(MultinameInfo(
|
|
311
|
+
kind=CONSTANT_RTQName, name=name))
|
|
312
|
+
return idx
|
|
313
|
+
|
|
314
|
+
# ── Methods ────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
def method(
|
|
317
|
+
self,
|
|
318
|
+
params: list[int] | None = None,
|
|
319
|
+
return_type: int = 0,
|
|
320
|
+
name: int | str = 0,
|
|
321
|
+
flags: int = 0,
|
|
322
|
+
param_names: list[int | str] | None = None,
|
|
323
|
+
options: list[tuple[int, int]] | None = None,
|
|
324
|
+
) -> int:
|
|
325
|
+
"""Add a method signature.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
params: List of multiname indices for parameter types.
|
|
329
|
+
return_type: Multiname index for return type (0 = any).
|
|
330
|
+
name: String pool index or string for method name.
|
|
331
|
+
flags: Method flags bitmask.
|
|
332
|
+
param_names: String pool indices or strings for debug param names.
|
|
333
|
+
options: Default values as (value_index, value_kind) pairs.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Method index.
|
|
337
|
+
"""
|
|
338
|
+
params = params or []
|
|
339
|
+
if isinstance(name, str):
|
|
340
|
+
name = self.string(name) if name else 0
|
|
341
|
+
|
|
342
|
+
resolved_flags = flags
|
|
343
|
+
resolved_param_names: list[int] = []
|
|
344
|
+
if param_names:
|
|
345
|
+
resolved_flags |= METHOD_HasParamNames
|
|
346
|
+
for pn in param_names:
|
|
347
|
+
if isinstance(pn, str):
|
|
348
|
+
resolved_param_names.append(self.string(pn))
|
|
349
|
+
else:
|
|
350
|
+
resolved_param_names.append(pn)
|
|
351
|
+
|
|
352
|
+
resolved_options: list[tuple[int, int]] = []
|
|
353
|
+
if options:
|
|
354
|
+
resolved_flags |= METHOD_HasOptional
|
|
355
|
+
resolved_options = list(options)
|
|
356
|
+
|
|
357
|
+
mi = MethodInfo(
|
|
358
|
+
param_count=len(params),
|
|
359
|
+
return_type=return_type,
|
|
360
|
+
param_types=list(params),
|
|
361
|
+
name=name,
|
|
362
|
+
flags=resolved_flags,
|
|
363
|
+
options=resolved_options,
|
|
364
|
+
param_names=resolved_param_names,
|
|
365
|
+
)
|
|
366
|
+
idx = len(self._methods)
|
|
367
|
+
self._methods.append(mi)
|
|
368
|
+
return idx
|
|
369
|
+
|
|
370
|
+
# ── Method bodies ──────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
def method_body(
|
|
373
|
+
self,
|
|
374
|
+
method: int,
|
|
375
|
+
code: bytes,
|
|
376
|
+
max_stack: int = 2,
|
|
377
|
+
local_count: int = 1,
|
|
378
|
+
init_scope_depth: int = 0,
|
|
379
|
+
max_scope_depth: int = 1,
|
|
380
|
+
exceptions: list[ExceptionInfo] | None = None,
|
|
381
|
+
) -> int:
|
|
382
|
+
"""Add a method body (bytecode) for a method.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
method: Method index this body belongs to.
|
|
386
|
+
code: Raw AVM2 bytecode bytes (use asm() or op_*() to build).
|
|
387
|
+
max_stack: Maximum operand stack depth.
|
|
388
|
+
local_count: Number of local registers.
|
|
389
|
+
init_scope_depth: Initial scope depth.
|
|
390
|
+
max_scope_depth: Maximum scope depth.
|
|
391
|
+
exceptions: Exception handler table.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Index into the method bodies array.
|
|
395
|
+
"""
|
|
396
|
+
mb = MethodBodyInfo(
|
|
397
|
+
method=method,
|
|
398
|
+
max_stack=max_stack,
|
|
399
|
+
local_count=local_count,
|
|
400
|
+
init_scope_depth=init_scope_depth,
|
|
401
|
+
max_scope_depth=max_scope_depth,
|
|
402
|
+
code=code,
|
|
403
|
+
exceptions=exceptions or [],
|
|
404
|
+
)
|
|
405
|
+
idx = len(self._method_bodies)
|
|
406
|
+
self._method_bodies.append(mb)
|
|
407
|
+
return idx
|
|
408
|
+
|
|
409
|
+
# ── Traits ─────────────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def trait_slot(
|
|
413
|
+
name: int,
|
|
414
|
+
type_mn: int = 0,
|
|
415
|
+
slot_id: int = 0,
|
|
416
|
+
default_value: int = 0,
|
|
417
|
+
default_kind: int = 0,
|
|
418
|
+
is_const: bool = False,
|
|
419
|
+
) -> TraitInfo:
|
|
420
|
+
"""Build a slot/const trait (field).
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
name: Multiname index for the field name.
|
|
424
|
+
type_mn: Multiname index for the field type (0 = any).
|
|
425
|
+
slot_id: Slot index.
|
|
426
|
+
default_value: Default value pool index (0 = none).
|
|
427
|
+
default_kind: Default value kind (only if default_value != 0).
|
|
428
|
+
is_const: If True, TRAIT_Const; else TRAIT_Slot.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
TraitInfo ready to attach to an instance or class.
|
|
432
|
+
"""
|
|
433
|
+
kind = TRAIT_Const if is_const else TRAIT_Slot
|
|
434
|
+
data = bytearray()
|
|
435
|
+
data += write_u30(name)
|
|
436
|
+
data += bytes([kind])
|
|
437
|
+
data += write_u30(slot_id)
|
|
438
|
+
data += write_u30(type_mn)
|
|
439
|
+
data += write_u30(default_value)
|
|
440
|
+
if default_value:
|
|
441
|
+
data += bytes([default_kind])
|
|
442
|
+
return TraitInfo(name=name, kind=kind, data=bytes(data))
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def trait_method(
|
|
446
|
+
name: int,
|
|
447
|
+
method: int,
|
|
448
|
+
disp_id: int = 0,
|
|
449
|
+
kind: int = TRAIT_Method,
|
|
450
|
+
attrs: int = 0,
|
|
451
|
+
) -> TraitInfo:
|
|
452
|
+
"""Build a method/getter/setter trait.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
name: Multiname index for the method name.
|
|
456
|
+
method: Method index.
|
|
457
|
+
disp_id: Dispatch ID (usually 0).
|
|
458
|
+
kind: TRAIT_Method, TRAIT_Getter, or TRAIT_Setter.
|
|
459
|
+
attrs: Attribute flags (ATTR_Final, ATTR_Override).
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
TraitInfo ready to attach.
|
|
463
|
+
"""
|
|
464
|
+
kind_byte = kind | (attrs << 4)
|
|
465
|
+
data = bytearray()
|
|
466
|
+
data += write_u30(name)
|
|
467
|
+
data += bytes([kind_byte])
|
|
468
|
+
data += write_u30(disp_id)
|
|
469
|
+
data += write_u30(method)
|
|
470
|
+
return TraitInfo(name=name, kind=kind, data=bytes(data))
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def trait_class(name: int, class_index: int, slot_id: int = 0) -> TraitInfo:
|
|
474
|
+
"""Build a class trait (for script-level class definitions).
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
name: Multiname index for the class name.
|
|
478
|
+
class_index: Index into the class array.
|
|
479
|
+
slot_id: Slot index.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
TraitInfo ready to attach to a script.
|
|
483
|
+
"""
|
|
484
|
+
data = bytearray()
|
|
485
|
+
data += write_u30(name)
|
|
486
|
+
data += bytes([TRAIT_Class])
|
|
487
|
+
data += write_u30(slot_id)
|
|
488
|
+
data += write_u30(class_index)
|
|
489
|
+
return TraitInfo(name=name, kind=TRAIT_Class, data=bytes(data))
|
|
490
|
+
|
|
491
|
+
# ── Classes ────────────────────────────────────────────────────────
|
|
492
|
+
|
|
493
|
+
def define_class(
|
|
494
|
+
self,
|
|
495
|
+
name: int,
|
|
496
|
+
super_name: int = 0,
|
|
497
|
+
constructor: int | None = None,
|
|
498
|
+
static_init: int | None = None,
|
|
499
|
+
flags: int = INSTANCE_Sealed,
|
|
500
|
+
interfaces: list[int] | None = None,
|
|
501
|
+
protected_ns: int = 0,
|
|
502
|
+
instance_traits: list[TraitInfo] | None = None,
|
|
503
|
+
static_traits: list[TraitInfo] | None = None,
|
|
504
|
+
) -> int:
|
|
505
|
+
"""Define a class (instance + static side).
|
|
506
|
+
|
|
507
|
+
If constructor or static_init are not provided, empty methods
|
|
508
|
+
are created automatically.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
name: Multiname index for the class name.
|
|
512
|
+
super_name: Multiname index for the superclass (0 = Object).
|
|
513
|
+
constructor: Method index for the constructor. Auto-created if None.
|
|
514
|
+
static_init: Method index for the static initializer. Auto-created if None.
|
|
515
|
+
flags: Instance flags bitmask.
|
|
516
|
+
interfaces: List of multiname indices for interfaces.
|
|
517
|
+
protected_ns: Protected namespace index (set flag automatically).
|
|
518
|
+
instance_traits: Instance-side traits (fields, methods).
|
|
519
|
+
static_traits: Static-side traits.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Class index (same index in both instances and classes arrays).
|
|
523
|
+
"""
|
|
524
|
+
# Auto-create constructor if not provided
|
|
525
|
+
if constructor is None:
|
|
526
|
+
constructor = self.method()
|
|
527
|
+
self.method_body(constructor, code=bytes([
|
|
528
|
+
OP_getlocal_0, OP_pushscope,
|
|
529
|
+
OP_getlocal_0, OP_constructsuper, 0x00, # 0 args
|
|
530
|
+
OP_returnvoid,
|
|
531
|
+
]), max_stack=1, local_count=1,
|
|
532
|
+
init_scope_depth=0, max_scope_depth=1)
|
|
533
|
+
|
|
534
|
+
# Auto-create static init if not provided
|
|
535
|
+
if static_init is None:
|
|
536
|
+
static_init = self.method()
|
|
537
|
+
self.method_body(static_init, code=bytes([OP_returnvoid]),
|
|
538
|
+
max_stack=0, local_count=1,
|
|
539
|
+
init_scope_depth=0, max_scope_depth=1)
|
|
540
|
+
|
|
541
|
+
inst_flags = flags
|
|
542
|
+
if protected_ns:
|
|
543
|
+
inst_flags |= INSTANCE_ProtectedNs
|
|
544
|
+
|
|
545
|
+
inst = InstanceInfo(
|
|
546
|
+
name=name,
|
|
547
|
+
super_name=super_name,
|
|
548
|
+
flags=inst_flags,
|
|
549
|
+
protectedNs=protected_ns,
|
|
550
|
+
interfaces=interfaces or [],
|
|
551
|
+
iinit=constructor,
|
|
552
|
+
traits=instance_traits or [],
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
cls = AbcClassInfo(
|
|
556
|
+
cinit=static_init,
|
|
557
|
+
traits=static_traits or [],
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
idx = len(self._instances)
|
|
561
|
+
self._instances.append(inst)
|
|
562
|
+
self._classes.append(cls)
|
|
563
|
+
return idx
|
|
564
|
+
|
|
565
|
+
# ── Scripts ────────────────────────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
def script(
|
|
568
|
+
self,
|
|
569
|
+
init: int | None = None,
|
|
570
|
+
traits: list[TraitInfo] | None = None,
|
|
571
|
+
) -> int:
|
|
572
|
+
"""Add a script entry point.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
init: Method index for the script initializer. Auto-created if None.
|
|
576
|
+
traits: Script-level traits (class definitions, etc.).
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Script index.
|
|
580
|
+
"""
|
|
581
|
+
if init is None:
|
|
582
|
+
init = self.method()
|
|
583
|
+
self.method_body(init, code=bytes([OP_returnvoid]),
|
|
584
|
+
max_stack=0, local_count=1)
|
|
585
|
+
|
|
586
|
+
si = ScriptInfo(init=init, traits=traits or [])
|
|
587
|
+
idx = len(self._scripts)
|
|
588
|
+
self._scripts.append(si)
|
|
589
|
+
return idx
|
|
590
|
+
|
|
591
|
+
# ── Bytecode assembly helpers ──────────────────────────────────────
|
|
592
|
+
|
|
593
|
+
@staticmethod
|
|
594
|
+
def asm(*parts: bytes) -> bytes:
|
|
595
|
+
"""Concatenate bytecode fragments into a single code block.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
*parts: Bytecode fragments from op_*() methods.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
Combined bytecode bytes.
|
|
602
|
+
"""
|
|
603
|
+
return b"".join(parts)
|
|
604
|
+
|
|
605
|
+
# Simple opcodes (no operands)
|
|
606
|
+
@staticmethod
|
|
607
|
+
def op_nop() -> bytes: return bytes([OP_nop])
|
|
608
|
+
@staticmethod
|
|
609
|
+
def op_label() -> bytes: return bytes([OP_label])
|
|
610
|
+
@staticmethod
|
|
611
|
+
def op_throw() -> bytes: return bytes([OP_throw])
|
|
612
|
+
@staticmethod
|
|
613
|
+
def op_getlocal_0() -> bytes: return bytes([OP_getlocal_0])
|
|
614
|
+
@staticmethod
|
|
615
|
+
def op_getlocal_1() -> bytes: return bytes([OP_getlocal_1])
|
|
616
|
+
@staticmethod
|
|
617
|
+
def op_getlocal_2() -> bytes: return bytes([OP_getlocal_2])
|
|
618
|
+
@staticmethod
|
|
619
|
+
def op_getlocal_3() -> bytes: return bytes([OP_getlocal_3])
|
|
620
|
+
@staticmethod
|
|
621
|
+
def op_setlocal_0() -> bytes: return bytes([OP_setlocal_0])
|
|
622
|
+
@staticmethod
|
|
623
|
+
def op_setlocal_1() -> bytes: return bytes([OP_setlocal_1])
|
|
624
|
+
@staticmethod
|
|
625
|
+
def op_setlocal_2() -> bytes: return bytes([OP_setlocal_2])
|
|
626
|
+
@staticmethod
|
|
627
|
+
def op_setlocal_3() -> bytes: return bytes([OP_setlocal_3])
|
|
628
|
+
@staticmethod
|
|
629
|
+
def op_pushscope() -> bytes: return bytes([OP_pushscope])
|
|
630
|
+
@staticmethod
|
|
631
|
+
def op_returnvoid() -> bytes: return bytes([OP_returnvoid])
|
|
632
|
+
@staticmethod
|
|
633
|
+
def op_returnvalue() -> bytes: return bytes([OP_returnvalue])
|
|
634
|
+
@staticmethod
|
|
635
|
+
def op_pop() -> bytes: return bytes([OP_pop])
|
|
636
|
+
@staticmethod
|
|
637
|
+
def op_dup() -> bytes: return bytes([OP_dup])
|
|
638
|
+
@staticmethod
|
|
639
|
+
def op_swap() -> bytes: return bytes([OP_swap])
|
|
640
|
+
@staticmethod
|
|
641
|
+
def op_pushnull() -> bytes: return bytes([OP_pushnull])
|
|
642
|
+
@staticmethod
|
|
643
|
+
def op_pushundefined() -> bytes: return bytes([OP_pushundefined])
|
|
644
|
+
@staticmethod
|
|
645
|
+
def op_pushtrue() -> bytes: return bytes([OP_pushtrue])
|
|
646
|
+
@staticmethod
|
|
647
|
+
def op_pushfalse() -> bytes: return bytes([OP_pushfalse])
|
|
648
|
+
@staticmethod
|
|
649
|
+
def op_convert_i() -> bytes: return bytes([OP_convert_i])
|
|
650
|
+
@staticmethod
|
|
651
|
+
def op_convert_s() -> bytes: return bytes([OP_convert_s])
|
|
652
|
+
@staticmethod
|
|
653
|
+
def op_convert_d() -> bytes: return bytes([OP_convert_d])
|
|
654
|
+
@staticmethod
|
|
655
|
+
def op_coerce_a() -> bytes: return bytes([OP_coerce_a])
|
|
656
|
+
@staticmethod
|
|
657
|
+
def op_coerce_s() -> bytes: return bytes([OP_coerce_s])
|
|
658
|
+
@staticmethod
|
|
659
|
+
def op_add() -> bytes: return bytes([OP_add])
|
|
660
|
+
@staticmethod
|
|
661
|
+
def op_subtract() -> bytes: return bytes([OP_subtract])
|
|
662
|
+
@staticmethod
|
|
663
|
+
def op_multiply() -> bytes: return bytes([OP_multiply])
|
|
664
|
+
@staticmethod
|
|
665
|
+
def op_divide() -> bytes: return bytes([OP_divide])
|
|
666
|
+
@staticmethod
|
|
667
|
+
def op_equals() -> bytes: return bytes([OP_equals])
|
|
668
|
+
@staticmethod
|
|
669
|
+
def op_strictequals() -> bytes: return bytes([OP_strictequals])
|
|
670
|
+
@staticmethod
|
|
671
|
+
def op_lessthan() -> bytes: return bytes([OP_lessthan])
|
|
672
|
+
@staticmethod
|
|
673
|
+
def op_greaterequals() -> bytes: return bytes([OP_greaterequals])
|
|
674
|
+
@staticmethod
|
|
675
|
+
def op_not() -> bytes: return bytes([OP_not])
|
|
676
|
+
|
|
677
|
+
# Opcodes with u30 operand
|
|
678
|
+
@staticmethod
|
|
679
|
+
def op_getlocal(reg: int) -> bytes:
|
|
680
|
+
return bytes([OP_getlocal]) + write_u30(reg)
|
|
681
|
+
@staticmethod
|
|
682
|
+
def op_setlocal(reg: int) -> bytes:
|
|
683
|
+
return bytes([OP_setlocal]) + write_u30(reg)
|
|
684
|
+
@staticmethod
|
|
685
|
+
def op_pushbyte(val: int) -> bytes:
|
|
686
|
+
return bytes([OP_pushbyte, val & 0xFF])
|
|
687
|
+
@staticmethod
|
|
688
|
+
def op_pushshort(val: int) -> bytes:
|
|
689
|
+
return bytes([OP_pushshort]) + write_u30(val)
|
|
690
|
+
@staticmethod
|
|
691
|
+
def op_pushstring(index: int) -> bytes:
|
|
692
|
+
return bytes([OP_pushstring]) + write_u30(index)
|
|
693
|
+
@staticmethod
|
|
694
|
+
def op_pushint(index: int) -> bytes:
|
|
695
|
+
return bytes([OP_pushint]) + write_u30(index)
|
|
696
|
+
@staticmethod
|
|
697
|
+
def op_pushuint(index: int) -> bytes:
|
|
698
|
+
return bytes([OP_pushuint]) + write_u30(index)
|
|
699
|
+
@staticmethod
|
|
700
|
+
def op_pushdouble(index: int) -> bytes:
|
|
701
|
+
return bytes([OP_pushdouble]) + write_u30(index)
|
|
702
|
+
@staticmethod
|
|
703
|
+
def op_getproperty(index: int) -> bytes:
|
|
704
|
+
return bytes([OP_getproperty]) + write_u30(index)
|
|
705
|
+
@staticmethod
|
|
706
|
+
def op_setproperty(index: int) -> bytes:
|
|
707
|
+
return bytes([OP_setproperty]) + write_u30(index)
|
|
708
|
+
@staticmethod
|
|
709
|
+
def op_initproperty(index: int) -> bytes:
|
|
710
|
+
return bytes([OP_initproperty]) + write_u30(index)
|
|
711
|
+
@staticmethod
|
|
712
|
+
def op_getlex(index: int) -> bytes:
|
|
713
|
+
return bytes([OP_getlex]) + write_u30(index)
|
|
714
|
+
@staticmethod
|
|
715
|
+
def op_findpropstrict(index: int) -> bytes:
|
|
716
|
+
return bytes([OP_findpropstrict]) + write_u30(index)
|
|
717
|
+
@staticmethod
|
|
718
|
+
def op_coerce(index: int) -> bytes:
|
|
719
|
+
return bytes([OP_coerce]) + write_u30(index)
|
|
720
|
+
@staticmethod
|
|
721
|
+
def op_constructsuper(arg_count: int) -> bytes:
|
|
722
|
+
return bytes([OP_constructsuper]) + write_u30(arg_count)
|
|
723
|
+
@staticmethod
|
|
724
|
+
def op_newarray(arg_count: int) -> bytes:
|
|
725
|
+
return bytes([OP_newarray]) + write_u30(arg_count)
|
|
726
|
+
@staticmethod
|
|
727
|
+
def op_newclass(class_index: int) -> bytes:
|
|
728
|
+
return bytes([OP_newclass]) + write_u30(class_index)
|
|
729
|
+
@staticmethod
|
|
730
|
+
def op_newfunction(method_index: int) -> bytes:
|
|
731
|
+
return bytes([OP_newfunction]) + write_u30(method_index)
|
|
732
|
+
@staticmethod
|
|
733
|
+
def op_call(arg_count: int) -> bytes:
|
|
734
|
+
return bytes([OP_call]) + write_u30(arg_count)
|
|
735
|
+
@staticmethod
|
|
736
|
+
def op_construct(arg_count: int) -> bytes:
|
|
737
|
+
return bytes([OP_construct]) + write_u30(arg_count)
|
|
738
|
+
@staticmethod
|
|
739
|
+
def op_debugfile(index: int) -> bytes:
|
|
740
|
+
return bytes([OP_debugfile]) + write_u30(index)
|
|
741
|
+
@staticmethod
|
|
742
|
+
def op_debugline(line: int) -> bytes:
|
|
743
|
+
return bytes([OP_debugline]) + write_u30(line)
|
|
744
|
+
|
|
745
|
+
# Opcodes with u30 u30 operands
|
|
746
|
+
@staticmethod
|
|
747
|
+
def op_callproperty(index: int, arg_count: int) -> bytes:
|
|
748
|
+
return bytes([OP_callproperty]) + write_u30(index) + write_u30(arg_count)
|
|
749
|
+
@staticmethod
|
|
750
|
+
def op_callpropvoid(index: int, arg_count: int) -> bytes:
|
|
751
|
+
return bytes([OP_callpropvoid]) + write_u30(index) + write_u30(arg_count)
|
|
752
|
+
@staticmethod
|
|
753
|
+
def op_constructprop(index: int, arg_count: int) -> bytes:
|
|
754
|
+
return bytes([OP_constructprop]) + write_u30(index) + write_u30(arg_count)
|
|
755
|
+
|
|
756
|
+
# Branch opcodes (s24 operand)
|
|
757
|
+
@staticmethod
|
|
758
|
+
def op_jump(offset: int) -> bytes:
|
|
759
|
+
return bytes([OP_jump]) + _encode_s24(offset)
|
|
760
|
+
@staticmethod
|
|
761
|
+
def op_iftrue(offset: int) -> bytes:
|
|
762
|
+
return bytes([OP_iftrue]) + _encode_s24(offset)
|
|
763
|
+
@staticmethod
|
|
764
|
+
def op_iffalse(offset: int) -> bytes:
|
|
765
|
+
return bytes([OP_iffalse]) + _encode_s24(offset)
|
|
766
|
+
|
|
767
|
+
# ── Convenience ────────────────────────────────────────────────────
|
|
768
|
+
|
|
769
|
+
def simple_class(
|
|
770
|
+
self,
|
|
771
|
+
name: str,
|
|
772
|
+
package: str = "",
|
|
773
|
+
super_name: str | None = "Object",
|
|
774
|
+
fields: list[tuple[str, str]] | None = None,
|
|
775
|
+
is_interface: bool = False,
|
|
776
|
+
) -> int:
|
|
777
|
+
"""Define a class with minimal boilerplate.
|
|
778
|
+
|
|
779
|
+
Creates namespaces, multinames, and traits automatically from
|
|
780
|
+
simple string arguments. For full control, use ``define_class()``.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
name: Class name string.
|
|
784
|
+
package: Package name string (empty for default package).
|
|
785
|
+
super_name: Superclass name string, or None for no super.
|
|
786
|
+
fields: List of (field_name, type_name) tuples for instance fields.
|
|
787
|
+
is_interface: Whether this is an interface definition.
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
Class index.
|
|
791
|
+
"""
|
|
792
|
+
ns = self.package_namespace(package)
|
|
793
|
+
pub = self.package_namespace("")
|
|
794
|
+
priv = self.private_namespace()
|
|
795
|
+
cls_mn = self.qname(ns, name)
|
|
796
|
+
|
|
797
|
+
super_mn = 0
|
|
798
|
+
if super_name:
|
|
799
|
+
super_mn = self.qname(pub, super_name)
|
|
800
|
+
|
|
801
|
+
instance_traits = []
|
|
802
|
+
if fields:
|
|
803
|
+
for i, (fname, ftype) in enumerate(fields):
|
|
804
|
+
type_mn = self.qname(pub, ftype) if ftype else 0
|
|
805
|
+
field_mn = self.qname(priv, fname)
|
|
806
|
+
instance_traits.append(
|
|
807
|
+
self.trait_slot(field_mn, type_mn=type_mn,
|
|
808
|
+
slot_id=i + 1))
|
|
809
|
+
|
|
810
|
+
flags = INSTANCE_Interface if is_interface else INSTANCE_Sealed
|
|
811
|
+
|
|
812
|
+
return self.define_class(
|
|
813
|
+
name=cls_mn, super_name=super_mn, flags=flags,
|
|
814
|
+
instance_traits=instance_traits,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
# ── Build ──────────────────────────────────────────────────────────
|
|
818
|
+
|
|
819
|
+
def build(self) -> AbcFile:
|
|
820
|
+
"""Build the final AbcFile from all added components.
|
|
821
|
+
|
|
822
|
+
If no scripts have been added, a default empty script is created.
|
|
823
|
+
|
|
824
|
+
Returns:
|
|
825
|
+
Complete AbcFile ready for serialization.
|
|
826
|
+
"""
|
|
827
|
+
# Ensure at least one script exists
|
|
828
|
+
if not self._scripts:
|
|
829
|
+
self.script()
|
|
830
|
+
|
|
831
|
+
abc = AbcFile()
|
|
832
|
+
abc.int_pool = list(self._int_pool)
|
|
833
|
+
abc.uint_pool = list(self._uint_pool)
|
|
834
|
+
abc._int_pool_raw = [b""] * len(self._int_pool)
|
|
835
|
+
abc._uint_pool_raw = [b""] * len(self._uint_pool)
|
|
836
|
+
abc.double_pool = list(self._double_pool)
|
|
837
|
+
abc.string_pool = list(self._string_pool)
|
|
838
|
+
abc.namespace_pool = list(self._namespace_pool)
|
|
839
|
+
abc.ns_set_pool = list(self._ns_set_pool)
|
|
840
|
+
abc.multiname_pool = list(self._multiname_pool)
|
|
841
|
+
abc.methods = list(self._methods)
|
|
842
|
+
abc.metadata = list(self._metadata)
|
|
843
|
+
abc.instances = list(self._instances)
|
|
844
|
+
abc.classes = list(self._classes)
|
|
845
|
+
abc.scripts = list(self._scripts)
|
|
846
|
+
abc.method_bodies = list(self._method_bodies)
|
|
847
|
+
return abc
|