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/parser.py
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ABC bytecode parser and LEB128 codec.
|
|
3
|
+
|
|
4
|
+
Parses raw ABC (ActionScript Byte Code) binary data into an ``AbcFile``
|
|
5
|
+
structure. Also provides the LEB128 variable-length integer encoding
|
|
6
|
+
functions used throughout AVM2 bytecode.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from flashkit.abc.parser import parse_abc
|
|
11
|
+
abc = parse_abc(raw_bytes)
|
|
12
|
+
print(f"Classes: {len(abc.instances)}")
|
|
13
|
+
print(f"Strings: {len(abc.string_pool)}")
|
|
14
|
+
|
|
15
|
+
The LEB128 functions (``read_u30``, ``write_u30``, etc.) are also public
|
|
16
|
+
and useful for manual bytecode manipulation.
|
|
17
|
+
|
|
18
|
+
Reference: Adobe AVM2 Overview, Chapter 4 (abc file format).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import struct
|
|
24
|
+
|
|
25
|
+
from ..errors import ABCParseError
|
|
26
|
+
from .types import (
|
|
27
|
+
AbcFile, NamespaceInfo, NsSetInfo, MultinameInfo,
|
|
28
|
+
MethodInfo, MetadataInfo, TraitInfo, InstanceInfo,
|
|
29
|
+
ClassInfo, ScriptInfo, ExceptionInfo, MethodBodyInfo,
|
|
30
|
+
)
|
|
31
|
+
from .constants import (
|
|
32
|
+
CONSTANT_QName, CONSTANT_QNameA,
|
|
33
|
+
CONSTANT_RTQName, CONSTANT_RTQNameA,
|
|
34
|
+
CONSTANT_RTQNameL, CONSTANT_RTQNameLA,
|
|
35
|
+
CONSTANT_Multiname, CONSTANT_MultinameA,
|
|
36
|
+
CONSTANT_MultinameL, CONSTANT_MultinameLA,
|
|
37
|
+
CONSTANT_TypeName,
|
|
38
|
+
TRAIT_Slot, TRAIT_Const, TRAIT_Method, TRAIT_Getter, TRAIT_Setter,
|
|
39
|
+
TRAIT_Class, TRAIT_Function,
|
|
40
|
+
ATTR_Metadata,
|
|
41
|
+
METHOD_HasOptional, METHOD_HasParamNames,
|
|
42
|
+
INSTANCE_ProtectedNs,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ── LEB128 encoding/decoding ───────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
def read_u30(data: bytes, offset: int) -> tuple[int, int]:
|
|
49
|
+
"""Read a u30 (unsigned LEB128, max 30 bits).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Tuple of (value, new_offset).
|
|
53
|
+
"""
|
|
54
|
+
result = 0
|
|
55
|
+
shift = 0
|
|
56
|
+
for _ in range(5):
|
|
57
|
+
b = data[offset]
|
|
58
|
+
offset += 1
|
|
59
|
+
result |= (b & 0x7F) << shift
|
|
60
|
+
if (b & 0x80) == 0:
|
|
61
|
+
break
|
|
62
|
+
shift += 7
|
|
63
|
+
return result, offset
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def read_s32(data: bytes, offset: int) -> tuple[int, int]:
|
|
67
|
+
"""Read an s32 (signed LEB128).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Tuple of (value, new_offset).
|
|
71
|
+
"""
|
|
72
|
+
result = 0
|
|
73
|
+
shift = 0
|
|
74
|
+
for _ in range(5):
|
|
75
|
+
b = data[offset]
|
|
76
|
+
offset += 1
|
|
77
|
+
result |= (b & 0x7F) << shift
|
|
78
|
+
shift += 7
|
|
79
|
+
if (b & 0x80) == 0:
|
|
80
|
+
break
|
|
81
|
+
# Sign extend
|
|
82
|
+
if shift < 32 and (b & 0x40):
|
|
83
|
+
result |= -(1 << shift)
|
|
84
|
+
return result, offset
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def write_u30(value: int) -> bytes:
|
|
88
|
+
"""Encode a u30 value as unsigned LEB128 bytes."""
|
|
89
|
+
value &= 0x3FFFFFFF # 30-bit unsigned
|
|
90
|
+
result = bytearray()
|
|
91
|
+
while True:
|
|
92
|
+
byte = value & 0x7F
|
|
93
|
+
value >>= 7
|
|
94
|
+
if value:
|
|
95
|
+
byte |= 0x80
|
|
96
|
+
result.append(byte)
|
|
97
|
+
if not value:
|
|
98
|
+
break
|
|
99
|
+
return bytes(result)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def write_s32(value: int) -> bytes:
|
|
103
|
+
"""Encode an s32 value as signed LEB128 bytes."""
|
|
104
|
+
result = bytearray()
|
|
105
|
+
more = True
|
|
106
|
+
while more:
|
|
107
|
+
byte = value & 0x7F
|
|
108
|
+
value >>= 7
|
|
109
|
+
if (value == 0 and (byte & 0x40) == 0) or (value == -1 and (byte & 0x40)):
|
|
110
|
+
more = False
|
|
111
|
+
else:
|
|
112
|
+
byte |= 0x80
|
|
113
|
+
result.append(byte)
|
|
114
|
+
return bytes(result)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def s24(value: int) -> bytes:
|
|
118
|
+
"""Encode a signed 24-bit offset (little-endian).
|
|
119
|
+
|
|
120
|
+
Used for branch instruction offsets in AVM2 bytecode.
|
|
121
|
+
"""
|
|
122
|
+
if value < 0:
|
|
123
|
+
value = value + (1 << 24)
|
|
124
|
+
return bytes([value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF])
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def read_u8(data: bytes, offset: int) -> tuple[int, int]:
|
|
128
|
+
"""Read a single unsigned byte."""
|
|
129
|
+
return data[offset], offset + 1
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def read_u16(data: bytes, offset: int) -> tuple[int, int]:
|
|
133
|
+
"""Read a 16-bit unsigned integer (little-endian)."""
|
|
134
|
+
return struct.unpack_from("<H", data, offset)[0], offset + 2
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def read_u32(data: bytes, offset: int) -> tuple[int, int]:
|
|
138
|
+
"""Read a 32-bit unsigned integer (little-endian)."""
|
|
139
|
+
return struct.unpack_from("<I", data, offset)[0], offset + 4
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def read_d64(data: bytes, offset: int) -> tuple[float, int]:
|
|
143
|
+
"""Read a 64-bit IEEE 754 double (little-endian)."""
|
|
144
|
+
return struct.unpack_from("<d", data, offset)[0], offset + 8
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ── Internal helpers ────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
def _read_traits(data: bytes, offset: int) -> tuple[list[TraitInfo], int]:
|
|
150
|
+
"""Read a traits_info array.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Tuple of (list of TraitInfo, new_offset).
|
|
154
|
+
"""
|
|
155
|
+
count, offset = read_u30(data, offset)
|
|
156
|
+
traits = []
|
|
157
|
+
for _ in range(count):
|
|
158
|
+
start = offset
|
|
159
|
+
name, offset = read_u30(data, offset)
|
|
160
|
+
kind_byte, offset = read_u8(data, offset)
|
|
161
|
+
kind = kind_byte & 0x0F
|
|
162
|
+
attr = (kind_byte >> 4) & 0x0F
|
|
163
|
+
|
|
164
|
+
if kind in (TRAIT_Slot, TRAIT_Const):
|
|
165
|
+
_slot_id, offset = read_u30(data, offset)
|
|
166
|
+
_type_name, offset = read_u30(data, offset)
|
|
167
|
+
vindex, offset = read_u30(data, offset)
|
|
168
|
+
if vindex:
|
|
169
|
+
_vkind, offset = read_u8(data, offset)
|
|
170
|
+
elif kind in (TRAIT_Method, TRAIT_Getter, TRAIT_Setter):
|
|
171
|
+
_disp_id, offset = read_u30(data, offset)
|
|
172
|
+
_method_idx, offset = read_u30(data, offset)
|
|
173
|
+
elif kind == TRAIT_Class:
|
|
174
|
+
_slot_id, offset = read_u30(data, offset)
|
|
175
|
+
_class_idx, offset = read_u30(data, offset)
|
|
176
|
+
elif kind == TRAIT_Function:
|
|
177
|
+
_slot_id, offset = read_u30(data, offset)
|
|
178
|
+
_func_idx, offset = read_u30(data, offset)
|
|
179
|
+
|
|
180
|
+
if attr & ATTR_Metadata:
|
|
181
|
+
md_count, offset = read_u30(data, offset)
|
|
182
|
+
for _ in range(md_count):
|
|
183
|
+
_, offset = read_u30(data, offset)
|
|
184
|
+
|
|
185
|
+
raw = data[start:offset]
|
|
186
|
+
traits.append(TraitInfo(name=name, kind=kind, data=raw))
|
|
187
|
+
|
|
188
|
+
return traits, offset
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ── Main parser ─────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def parse_abc(data: bytes) -> AbcFile:
|
|
194
|
+
"""Parse raw ABC bytecode into an AbcFile structure.
|
|
195
|
+
|
|
196
|
+
This is the primary entry point for loading ABC data. The returned
|
|
197
|
+
``AbcFile`` can be inspected, modified, and serialized back to bytes
|
|
198
|
+
with ``flashkit.abc.writer.serialize_abc()``.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
data: Raw ABC bytecode bytes.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Parsed AbcFile with all constant pools, methods, classes, and bodies.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
ABCParseError: If the data is not valid ABC bytecode.
|
|
208
|
+
"""
|
|
209
|
+
if not data:
|
|
210
|
+
raise ABCParseError("ABC data is empty")
|
|
211
|
+
if len(data) < 4:
|
|
212
|
+
raise ABCParseError(
|
|
213
|
+
f"ABC data too short ({len(data)} bytes, minimum 4)")
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
return _parse_abc_inner(data)
|
|
217
|
+
except ABCParseError:
|
|
218
|
+
raise
|
|
219
|
+
except (IndexError, struct.error, ValueError, OverflowError) as e:
|
|
220
|
+
raise ABCParseError(f"Corrupted ABC data: {e}") from e
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _parse_abc_inner(data: bytes) -> AbcFile:
|
|
224
|
+
"""Internal ABC parser (no error wrapping)."""
|
|
225
|
+
abc = AbcFile()
|
|
226
|
+
off = 0
|
|
227
|
+
|
|
228
|
+
abc.minor_version, off = read_u16(data, off)
|
|
229
|
+
abc.major_version, off = read_u16(data, off)
|
|
230
|
+
|
|
231
|
+
# ── Constant pool ────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
# Integers
|
|
234
|
+
count, off = read_u30(data, off)
|
|
235
|
+
abc.int_pool = [0]
|
|
236
|
+
abc._int_pool_raw = [b""]
|
|
237
|
+
for _ in range(max(0, count - 1)):
|
|
238
|
+
raw_start = off
|
|
239
|
+
val, off = read_s32(data, off)
|
|
240
|
+
abc.int_pool.append(val)
|
|
241
|
+
abc._int_pool_raw.append(data[raw_start:off])
|
|
242
|
+
|
|
243
|
+
# Unsigned integers
|
|
244
|
+
count, off = read_u30(data, off)
|
|
245
|
+
abc.uint_pool = [0]
|
|
246
|
+
abc._uint_pool_raw = [b""]
|
|
247
|
+
for _ in range(max(0, count - 1)):
|
|
248
|
+
raw_start = off
|
|
249
|
+
val, off = read_u30(data, off)
|
|
250
|
+
abc.uint_pool.append(val)
|
|
251
|
+
abc._uint_pool_raw.append(data[raw_start:off])
|
|
252
|
+
|
|
253
|
+
# Doubles
|
|
254
|
+
count, off = read_u30(data, off)
|
|
255
|
+
abc.double_pool = [0.0]
|
|
256
|
+
for _ in range(max(0, count - 1)):
|
|
257
|
+
val, off = read_d64(data, off)
|
|
258
|
+
abc.double_pool.append(val)
|
|
259
|
+
|
|
260
|
+
# Strings
|
|
261
|
+
count, off = read_u30(data, off)
|
|
262
|
+
abc.string_pool = [""]
|
|
263
|
+
for _ in range(max(0, count - 1)):
|
|
264
|
+
slen, off = read_u30(data, off)
|
|
265
|
+
s = data[off:off + slen].decode("utf-8", errors="replace")
|
|
266
|
+
off += slen
|
|
267
|
+
abc.string_pool.append(s)
|
|
268
|
+
|
|
269
|
+
# Namespaces
|
|
270
|
+
count, off = read_u30(data, off)
|
|
271
|
+
abc.namespace_pool = [NamespaceInfo(0, 0)]
|
|
272
|
+
for _ in range(max(0, count - 1)):
|
|
273
|
+
kind, off = read_u8(data, off)
|
|
274
|
+
name, off = read_u30(data, off)
|
|
275
|
+
abc.namespace_pool.append(NamespaceInfo(kind, name))
|
|
276
|
+
|
|
277
|
+
# Namespace sets
|
|
278
|
+
count, off = read_u30(data, off)
|
|
279
|
+
abc.ns_set_pool = [NsSetInfo([])]
|
|
280
|
+
for _ in range(max(0, count - 1)):
|
|
281
|
+
ns_count, off = read_u30(data, off)
|
|
282
|
+
nss = []
|
|
283
|
+
for __ in range(ns_count):
|
|
284
|
+
ns, off = read_u30(data, off)
|
|
285
|
+
nss.append(ns)
|
|
286
|
+
abc.ns_set_pool.append(NsSetInfo(nss))
|
|
287
|
+
|
|
288
|
+
# Multinames
|
|
289
|
+
count, off = read_u30(data, off)
|
|
290
|
+
abc.multiname_pool = [MultinameInfo(0)]
|
|
291
|
+
for _ in range(max(0, count - 1)):
|
|
292
|
+
kind, off = read_u8(data, off)
|
|
293
|
+
mn = MultinameInfo(kind=kind)
|
|
294
|
+
if kind in (CONSTANT_QName, CONSTANT_QNameA):
|
|
295
|
+
mn.ns, off = read_u30(data, off)
|
|
296
|
+
mn.name, off = read_u30(data, off)
|
|
297
|
+
elif kind in (CONSTANT_RTQName, CONSTANT_RTQNameA):
|
|
298
|
+
mn.name, off = read_u30(data, off)
|
|
299
|
+
elif kind in (CONSTANT_RTQNameL, CONSTANT_RTQNameLA):
|
|
300
|
+
pass
|
|
301
|
+
elif kind in (CONSTANT_Multiname, CONSTANT_MultinameA):
|
|
302
|
+
mn.name, off = read_u30(data, off)
|
|
303
|
+
mn.ns_set, off = read_u30(data, off)
|
|
304
|
+
elif kind in (CONSTANT_MultinameL, CONSTANT_MultinameLA):
|
|
305
|
+
mn.ns_set, off = read_u30(data, off)
|
|
306
|
+
elif kind == CONSTANT_TypeName:
|
|
307
|
+
mn.ns, off = read_u30(data, off) # base type multiname index
|
|
308
|
+
param_count, off = read_u30(data, off)
|
|
309
|
+
params = []
|
|
310
|
+
for __ in range(param_count):
|
|
311
|
+
p, off = read_u30(data, off)
|
|
312
|
+
params.append(p)
|
|
313
|
+
# Store params as serialized u30 bytes for round-trip fidelity
|
|
314
|
+
param_bytes = bytearray()
|
|
315
|
+
for p in params:
|
|
316
|
+
param_bytes += write_u30(p)
|
|
317
|
+
mn.data = bytes(param_bytes)
|
|
318
|
+
mn.name = param_count # stash param count in name field
|
|
319
|
+
else:
|
|
320
|
+
raise ABCParseError(
|
|
321
|
+
f"Unknown multiname kind: 0x{kind:02X} at offset {off}")
|
|
322
|
+
abc.multiname_pool.append(mn)
|
|
323
|
+
|
|
324
|
+
# ── Methods ──────────────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
count, off = read_u30(data, off)
|
|
327
|
+
for _ in range(count):
|
|
328
|
+
param_count, off = read_u30(data, off)
|
|
329
|
+
return_type, off = read_u30(data, off)
|
|
330
|
+
param_types = []
|
|
331
|
+
for __ in range(param_count):
|
|
332
|
+
pt, off = read_u30(data, off)
|
|
333
|
+
param_types.append(pt)
|
|
334
|
+
name, off = read_u30(data, off)
|
|
335
|
+
flags, off = read_u8(data, off)
|
|
336
|
+
|
|
337
|
+
mi = MethodInfo(
|
|
338
|
+
param_count=param_count, return_type=return_type,
|
|
339
|
+
param_types=param_types, name=name, flags=flags)
|
|
340
|
+
|
|
341
|
+
if flags & METHOD_HasOptional:
|
|
342
|
+
opt_count, off = read_u30(data, off)
|
|
343
|
+
for __ in range(opt_count):
|
|
344
|
+
val, off = read_u30(data, off)
|
|
345
|
+
vkind, off = read_u8(data, off)
|
|
346
|
+
mi.options.append((val, vkind))
|
|
347
|
+
|
|
348
|
+
if flags & METHOD_HasParamNames:
|
|
349
|
+
for __ in range(param_count):
|
|
350
|
+
pn, off = read_u30(data, off)
|
|
351
|
+
mi.param_names.append(pn)
|
|
352
|
+
|
|
353
|
+
abc.methods.append(mi)
|
|
354
|
+
|
|
355
|
+
# ── Metadata ─────────────────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
count, off = read_u30(data, off)
|
|
358
|
+
for _ in range(count):
|
|
359
|
+
name, off = read_u30(data, off)
|
|
360
|
+
item_count, off = read_u30(data, off)
|
|
361
|
+
items = []
|
|
362
|
+
for __ in range(item_count):
|
|
363
|
+
k, off = read_u30(data, off)
|
|
364
|
+
v, off = read_u30(data, off)
|
|
365
|
+
items.append((k, v))
|
|
366
|
+
abc.metadata.append(MetadataInfo(name=name, items=items))
|
|
367
|
+
|
|
368
|
+
# ── Instances + Classes ──────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
count, off = read_u30(data, off)
|
|
371
|
+
for _ in range(count):
|
|
372
|
+
inst = InstanceInfo(name=0, super_name=0, flags=0)
|
|
373
|
+
inst.name, off = read_u30(data, off)
|
|
374
|
+
inst.super_name, off = read_u30(data, off)
|
|
375
|
+
inst.flags, off = read_u8(data, off)
|
|
376
|
+
|
|
377
|
+
if inst.flags & INSTANCE_ProtectedNs:
|
|
378
|
+
inst.protectedNs, off = read_u30(data, off)
|
|
379
|
+
|
|
380
|
+
iface_count, off = read_u30(data, off)
|
|
381
|
+
for __ in range(iface_count):
|
|
382
|
+
ifc, off = read_u30(data, off)
|
|
383
|
+
inst.interfaces.append(ifc)
|
|
384
|
+
|
|
385
|
+
inst.iinit, off = read_u30(data, off)
|
|
386
|
+
inst.traits, off = _read_traits(data, off)
|
|
387
|
+
abc.instances.append(inst)
|
|
388
|
+
|
|
389
|
+
for _ in range(count):
|
|
390
|
+
ci = ClassInfo(cinit=0)
|
|
391
|
+
ci.cinit, off = read_u30(data, off)
|
|
392
|
+
ci.traits, off = _read_traits(data, off)
|
|
393
|
+
abc.classes.append(ci)
|
|
394
|
+
|
|
395
|
+
# ── Scripts ──────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
count, off = read_u30(data, off)
|
|
398
|
+
for _ in range(count):
|
|
399
|
+
si = ScriptInfo(init=0)
|
|
400
|
+
si.init, off = read_u30(data, off)
|
|
401
|
+
si.traits, off = _read_traits(data, off)
|
|
402
|
+
abc.scripts.append(si)
|
|
403
|
+
|
|
404
|
+
# ── Method bodies ────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
count, off = read_u30(data, off)
|
|
407
|
+
for _ in range(count):
|
|
408
|
+
mb = MethodBodyInfo(
|
|
409
|
+
method=0, max_stack=0, local_count=0,
|
|
410
|
+
init_scope_depth=0, max_scope_depth=0, code=b"")
|
|
411
|
+
mb.method, off = read_u30(data, off)
|
|
412
|
+
mb.max_stack, off = read_u30(data, off)
|
|
413
|
+
mb.local_count, off = read_u30(data, off)
|
|
414
|
+
mb.init_scope_depth, off = read_u30(data, off)
|
|
415
|
+
mb.max_scope_depth, off = read_u30(data, off)
|
|
416
|
+
code_len, off = read_u30(data, off)
|
|
417
|
+
mb.code = data[off:off + code_len]
|
|
418
|
+
off += code_len
|
|
419
|
+
|
|
420
|
+
# Exceptions
|
|
421
|
+
exc_count, off = read_u30(data, off)
|
|
422
|
+
for __ in range(exc_count):
|
|
423
|
+
ei = ExceptionInfo(0, 0, 0, 0, 0)
|
|
424
|
+
ei.from_offset, off = read_u30(data, off)
|
|
425
|
+
ei.to_offset, off = read_u30(data, off)
|
|
426
|
+
ei.target, off = read_u30(data, off)
|
|
427
|
+
ei.exc_type, off = read_u30(data, off)
|
|
428
|
+
ei.var_name, off = read_u30(data, off)
|
|
429
|
+
mb.exceptions.append(ei)
|
|
430
|
+
|
|
431
|
+
mb.traits, off = _read_traits(data, off)
|
|
432
|
+
abc.method_bodies.append(mb)
|
|
433
|
+
|
|
434
|
+
return abc
|
flashkit/abc/types.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AVM2 bytecode data structures.
|
|
3
|
+
|
|
4
|
+
These dataclasses mirror the structures defined in the AVM2 specification
|
|
5
|
+
(avm2overview.pdf). They represent the parsed contents of an ABC (ActionScript
|
|
6
|
+
Byte Code) block as found inside SWF DoABC/DoABC2 tags.
|
|
7
|
+
|
|
8
|
+
All pool indices (string, namespace, multiname, method) are zero-based.
|
|
9
|
+
Index 0 in each pool is the implicit default entry and is always present.
|
|
10
|
+
|
|
11
|
+
Reference: Adobe AVM2 Overview, Chapter 4 (abc file format).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class NamespaceInfo:
|
|
21
|
+
"""A namespace entry in the constant pool.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
kind: Namespace kind constant (CONSTANT_Namespace, CONSTANT_PackageNamespace, etc.).
|
|
25
|
+
name: Index into the string pool for the namespace name.
|
|
26
|
+
"""
|
|
27
|
+
kind: int
|
|
28
|
+
name: int
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class NsSetInfo:
|
|
33
|
+
"""A namespace set — an unordered collection of namespaces.
|
|
34
|
+
|
|
35
|
+
Used by Multiname and MultinameL to search across multiple namespaces.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
namespaces: List of indices into the namespace pool.
|
|
39
|
+
"""
|
|
40
|
+
namespaces: list[int]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class MultinameInfo:
|
|
45
|
+
"""A multiname entry in the constant pool.
|
|
46
|
+
|
|
47
|
+
Multinames are the primary name-resolution mechanism in AVM2. The
|
|
48
|
+
interpretation of fields depends on ``kind``:
|
|
49
|
+
|
|
50
|
+
- QName/QNameA: ``ns`` = namespace index, ``name`` = string index.
|
|
51
|
+
- RTQName/RTQNameA: ``name`` = string index (namespace from runtime stack).
|
|
52
|
+
- RTQNameL/RTQNameLA: no fields (both name and namespace from stack).
|
|
53
|
+
- Multiname/MultinameA: ``name`` = string index, ``ns_set`` = ns-set index.
|
|
54
|
+
- MultinameL/MultinameLA: ``ns_set`` = ns-set index (name from stack).
|
|
55
|
+
- TypeName: ``ns`` = base type multiname index (repurposed field),
|
|
56
|
+
``name`` = parameter count (repurposed), ``data`` = serialized
|
|
57
|
+
parameter multiname indices as u30 bytes. This encoding is intentional
|
|
58
|
+
for round-trip fidelity — a future version may add dedicated fields.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
kind: Multiname kind constant (CONSTANT_QName, etc.).
|
|
62
|
+
data: Raw serialized parameter bytes (TypeName only).
|
|
63
|
+
ns: Namespace index, or base type index for TypeName.
|
|
64
|
+
name: String index, or parameter count for TypeName.
|
|
65
|
+
ns_set: Namespace set index.
|
|
66
|
+
"""
|
|
67
|
+
kind: int
|
|
68
|
+
data: bytes = b""
|
|
69
|
+
ns: int = 0
|
|
70
|
+
name: int = 0
|
|
71
|
+
ns_set: int = 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class MethodInfo:
|
|
76
|
+
"""A method signature (not the body — see MethodBodyInfo).
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
param_count: Number of formal parameters.
|
|
80
|
+
return_type: Multiname index of the return type (0 = any/void).
|
|
81
|
+
param_types: List of multiname indices for each parameter type.
|
|
82
|
+
name: String index for the method name (0 = anonymous).
|
|
83
|
+
flags: Bitmask — 0x08 = HAS_OPTIONAL, 0x80 = HAS_PARAM_NAMES, etc.
|
|
84
|
+
options: Default parameter values as (value_index, value_kind) pairs.
|
|
85
|
+
param_names: String indices for parameter names (debug info).
|
|
86
|
+
"""
|
|
87
|
+
param_count: int
|
|
88
|
+
return_type: int
|
|
89
|
+
param_types: list[int]
|
|
90
|
+
name: int
|
|
91
|
+
flags: int
|
|
92
|
+
options: list[tuple] = field(default_factory=list)
|
|
93
|
+
param_names: list[int] = field(default_factory=list)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class MetadataInfo:
|
|
98
|
+
"""Metadata attached to traits (e.g. [SWF(width=800)]).
|
|
99
|
+
|
|
100
|
+
Attributes:
|
|
101
|
+
name: String index for the metadata tag name.
|
|
102
|
+
items: List of (key_string_index, value_string_index) pairs.
|
|
103
|
+
"""
|
|
104
|
+
name: int
|
|
105
|
+
items: list[tuple]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class TraitInfo:
|
|
110
|
+
"""A trait (field, method, getter, setter, class, or const) on a class or script.
|
|
111
|
+
|
|
112
|
+
Traits are stored with their raw binary data to guarantee perfect
|
|
113
|
+
round-trip serialization. The ``name`` and ``kind`` fields are parsed
|
|
114
|
+
for easy inspection, but the full trait data (including slot IDs,
|
|
115
|
+
type references, method indices, and metadata) is in ``data``.
|
|
116
|
+
|
|
117
|
+
To inspect trait contents beyond name/kind, use the trait resolution
|
|
118
|
+
utilities in ``flashkit.info``.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
name: Multiname index for the trait name.
|
|
122
|
+
kind: Trait kind (TRAIT_Slot, TRAIT_Method, TRAIT_Getter, etc.).
|
|
123
|
+
data: Complete raw binary of this trait entry (includes name and kind bytes).
|
|
124
|
+
"""
|
|
125
|
+
name: int
|
|
126
|
+
kind: int
|
|
127
|
+
data: bytes
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class InstanceInfo:
|
|
132
|
+
"""An instance (non-static side) of a class definition.
|
|
133
|
+
|
|
134
|
+
Each InstanceInfo is paired with a ClassInfo at the same array index.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
name: Multiname index for the class name.
|
|
138
|
+
super_name: Multiname index for the superclass (0 = Object).
|
|
139
|
+
flags: Bitmask — 0x01 = sealed, 0x02 = final, 0x04 = interface,
|
|
140
|
+
0x08 = has protected namespace.
|
|
141
|
+
protectedNs: Namespace index for the protected namespace (if flag 0x08 set).
|
|
142
|
+
interfaces: List of multiname indices for implemented interfaces.
|
|
143
|
+
iinit: Method index for the instance initializer (constructor).
|
|
144
|
+
traits: Instance traits (fields, methods, getters, setters).
|
|
145
|
+
"""
|
|
146
|
+
name: int
|
|
147
|
+
super_name: int
|
|
148
|
+
flags: int
|
|
149
|
+
protectedNs: int = 0
|
|
150
|
+
interfaces: list[int] = field(default_factory=list)
|
|
151
|
+
iinit: int = 0
|
|
152
|
+
traits: list[TraitInfo] = field(default_factory=list)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class ClassInfo:
|
|
157
|
+
"""The static side of a class definition.
|
|
158
|
+
|
|
159
|
+
Paired with InstanceInfo at the same array index.
|
|
160
|
+
|
|
161
|
+
Attributes:
|
|
162
|
+
cinit: Method index for the static initializer.
|
|
163
|
+
traits: Static traits (static fields, static methods).
|
|
164
|
+
"""
|
|
165
|
+
cinit: int
|
|
166
|
+
traits: list[TraitInfo] = field(default_factory=list)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class ScriptInfo:
|
|
171
|
+
"""A script entry point.
|
|
172
|
+
|
|
173
|
+
Each ABC file has one or more scripts. The last script is the entry point.
|
|
174
|
+
|
|
175
|
+
Attributes:
|
|
176
|
+
init: Method index for the script initializer.
|
|
177
|
+
traits: Script-level traits (top-level classes, functions, variables).
|
|
178
|
+
"""
|
|
179
|
+
init: int
|
|
180
|
+
traits: list[TraitInfo] = field(default_factory=list)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass
|
|
184
|
+
class ExceptionInfo:
|
|
185
|
+
"""An exception handler within a method body.
|
|
186
|
+
|
|
187
|
+
Attributes:
|
|
188
|
+
from_offset: Bytecode offset where the try block starts.
|
|
189
|
+
to_offset: Bytecode offset where the try block ends.
|
|
190
|
+
target: Bytecode offset of the catch handler.
|
|
191
|
+
exc_type: Multiname index for the exception type (0 = catch-all).
|
|
192
|
+
var_name: Multiname index for the catch variable name.
|
|
193
|
+
"""
|
|
194
|
+
from_offset: int
|
|
195
|
+
to_offset: int
|
|
196
|
+
target: int
|
|
197
|
+
exc_type: int
|
|
198
|
+
var_name: int
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass
|
|
202
|
+
class MethodBodyInfo:
|
|
203
|
+
"""The bytecode body of a method.
|
|
204
|
+
|
|
205
|
+
Attributes:
|
|
206
|
+
method: Index into the method array this body belongs to.
|
|
207
|
+
max_stack: Maximum operand stack depth.
|
|
208
|
+
local_count: Number of local registers (including 'this' at register 0).
|
|
209
|
+
init_scope_depth: Initial scope stack depth.
|
|
210
|
+
max_scope_depth: Maximum scope stack depth.
|
|
211
|
+
code: Raw AVM2 bytecode bytes.
|
|
212
|
+
exceptions: Exception handler table.
|
|
213
|
+
traits: Activation traits (rare — used for method-level closures).
|
|
214
|
+
"""
|
|
215
|
+
method: int
|
|
216
|
+
max_stack: int
|
|
217
|
+
local_count: int
|
|
218
|
+
init_scope_depth: int
|
|
219
|
+
max_scope_depth: int
|
|
220
|
+
code: bytes
|
|
221
|
+
exceptions: list[ExceptionInfo] = field(default_factory=list)
|
|
222
|
+
traits: list[TraitInfo] = field(default_factory=list)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class AbcFile:
|
|
227
|
+
"""A complete ABC (ActionScript Byte Code) file.
|
|
228
|
+
|
|
229
|
+
Contains the constant pools, method signatures, class definitions,
|
|
230
|
+
scripts, and method bodies that make up one compilation unit.
|
|
231
|
+
|
|
232
|
+
Constant pools always have an implicit entry at index 0:
|
|
233
|
+
- int_pool[0] = 0
|
|
234
|
+
- uint_pool[0] = 0
|
|
235
|
+
- double_pool[0] = 0.0
|
|
236
|
+
- string_pool[0] = ""
|
|
237
|
+
- namespace_pool[0] = NamespaceInfo(0, 0)
|
|
238
|
+
- ns_set_pool[0] = NsSetInfo([])
|
|
239
|
+
- multiname_pool[0] = MultinameInfo(0)
|
|
240
|
+
|
|
241
|
+
Attributes:
|
|
242
|
+
minor_version: ABC minor version (typically 16).
|
|
243
|
+
major_version: ABC major version (typically 46).
|
|
244
|
+
"""
|
|
245
|
+
minor_version: int = 16
|
|
246
|
+
major_version: int = 46
|
|
247
|
+
|
|
248
|
+
# Constant pools (index 0 is always the implicit default)
|
|
249
|
+
int_pool: list[int] = field(default_factory=lambda: [0])
|
|
250
|
+
uint_pool: list[int] = field(default_factory=lambda: [0])
|
|
251
|
+
|
|
252
|
+
# Raw LEB128 bytes for constant pool entries (for round-trip fidelity).
|
|
253
|
+
# The AVM2 spec allows non-minimal LEB128 encodings (e.g. -1 encoded
|
|
254
|
+
# as 4 bytes instead of 1) and uint values that exceed 30 bits.
|
|
255
|
+
# We preserve the original encoding so serialize_abc() produces
|
|
256
|
+
# byte-identical output.
|
|
257
|
+
# Index 0 is empty (the implicit default). Populated by parse_abc().
|
|
258
|
+
_int_pool_raw: list[bytes] = field(default_factory=lambda: [b""])
|
|
259
|
+
_uint_pool_raw: list[bytes] = field(default_factory=lambda: [b""])
|
|
260
|
+
double_pool: list[float] = field(default_factory=lambda: [0.0])
|
|
261
|
+
string_pool: list[str] = field(default_factory=lambda: [""])
|
|
262
|
+
namespace_pool: list[NamespaceInfo] = field(
|
|
263
|
+
default_factory=lambda: [NamespaceInfo(0, 0)])
|
|
264
|
+
ns_set_pool: list[NsSetInfo] = field(
|
|
265
|
+
default_factory=lambda: [NsSetInfo([])])
|
|
266
|
+
multiname_pool: list[MultinameInfo] = field(
|
|
267
|
+
default_factory=lambda: [MultinameInfo(0)])
|
|
268
|
+
|
|
269
|
+
# Definitions
|
|
270
|
+
methods: list[MethodInfo] = field(default_factory=list)
|
|
271
|
+
metadata: list[MetadataInfo] = field(default_factory=list)
|
|
272
|
+
instances: list[InstanceInfo] = field(default_factory=list)
|
|
273
|
+
classes: list[ClassInfo] = field(default_factory=list)
|
|
274
|
+
scripts: list[ScriptInfo] = field(default_factory=list)
|
|
275
|
+
method_bodies: list[MethodBodyInfo] = field(default_factory=list)
|