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.
Files changed (48) hide show
  1. flashkit/__init__.py +54 -0
  2. flashkit/abc/__init__.py +79 -0
  3. flashkit/abc/builder.py +847 -0
  4. flashkit/abc/constants.py +198 -0
  5. flashkit/abc/disasm.py +364 -0
  6. flashkit/abc/parser.py +434 -0
  7. flashkit/abc/types.py +275 -0
  8. flashkit/abc/writer.py +230 -0
  9. flashkit/analysis/__init__.py +28 -0
  10. flashkit/analysis/call_graph.py +317 -0
  11. flashkit/analysis/inheritance.py +267 -0
  12. flashkit/analysis/references.py +371 -0
  13. flashkit/analysis/strings.py +299 -0
  14. flashkit/cli/__init__.py +75 -0
  15. flashkit/cli/_util.py +52 -0
  16. flashkit/cli/build.py +36 -0
  17. flashkit/cli/callees.py +30 -0
  18. flashkit/cli/callers.py +30 -0
  19. flashkit/cli/class_cmd.py +83 -0
  20. flashkit/cli/classes.py +71 -0
  21. flashkit/cli/disasm.py +77 -0
  22. flashkit/cli/extract.py +36 -0
  23. flashkit/cli/info.py +41 -0
  24. flashkit/cli/packages.py +30 -0
  25. flashkit/cli/refs.py +31 -0
  26. flashkit/cli/strings.py +58 -0
  27. flashkit/cli/tags.py +32 -0
  28. flashkit/cli/tree.py +52 -0
  29. flashkit/errors.py +33 -0
  30. flashkit/info/__init__.py +31 -0
  31. flashkit/info/class_info.py +176 -0
  32. flashkit/info/member_info.py +275 -0
  33. flashkit/info/package_info.py +60 -0
  34. flashkit/search/__init__.py +16 -0
  35. flashkit/search/search.py +456 -0
  36. flashkit/swf/__init__.py +66 -0
  37. flashkit/swf/builder.py +283 -0
  38. flashkit/swf/parser.py +164 -0
  39. flashkit/swf/tags.py +120 -0
  40. flashkit/workspace/__init__.py +20 -0
  41. flashkit/workspace/resource.py +189 -0
  42. flashkit/workspace/workspace.py +232 -0
  43. pyflashkit-1.0.0.dist-info/METADATA +281 -0
  44. pyflashkit-1.0.0.dist-info/RECORD +48 -0
  45. pyflashkit-1.0.0.dist-info/WHEEL +5 -0
  46. pyflashkit-1.0.0.dist-info/entry_points.txt +2 -0
  47. pyflashkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  48. 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)