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
@@ -0,0 +1,198 @@
1
+ """
2
+ AVM2 constants — multiname kinds, namespace kinds, trait kinds, flags, and opcodes.
3
+
4
+ All constants follow the naming convention from the AVM2 specification.
5
+ Opcode constants use the ``OP_`` prefix and match the mnemonics from
6
+ avm2overview.pdf Chapter 5 (AVM2 instructions).
7
+
8
+ Reference: Adobe AVM2 Overview, Chapters 4.4–4.8, Chapter 5.
9
+ """
10
+
11
+ # ── Multiname kinds ─────────────────────────────────────────────────────────
12
+ # Used in MultinameInfo.kind to determine which fields are valid.
13
+
14
+ CONSTANT_QName = 0x07 # Qualified name: namespace + name
15
+ CONSTANT_QNameA = 0x0D # Qualified name (attribute)
16
+ CONSTANT_RTQName = 0x0F # Runtime qualified name: name only, ns from stack
17
+ CONSTANT_RTQNameA = 0x10 # Runtime qualified name (attribute)
18
+ CONSTANT_RTQNameL = 0x11 # Runtime qualified name (late-bound): both from stack
19
+ CONSTANT_RTQNameLA = 0x12 # Runtime qualified name (late-bound, attribute)
20
+ CONSTANT_Multiname = 0x09 # Multiname: name + namespace set
21
+ CONSTANT_MultinameA = 0x0E # Multiname (attribute)
22
+ CONSTANT_MultinameL = 0x1B # Late-bound multiname: name from stack + ns set
23
+ CONSTANT_MultinameLA = 0x1C # Late-bound multiname (attribute)
24
+ CONSTANT_TypeName = 0x1D # Parameterized type: Vector.<T>
25
+
26
+ # ── Namespace kinds ─────────────────────────────────────────────────────────
27
+ # Used in NamespaceInfo.kind.
28
+
29
+ CONSTANT_Namespace = 0x08 # Regular namespace
30
+ CONSTANT_PackageNamespace = 0x16 # Public package namespace
31
+ CONSTANT_PackageInternalNs = 0x17 # Package-internal namespace
32
+ CONSTANT_ProtectedNamespace = 0x18 # Protected namespace (class hierarchy)
33
+ CONSTANT_ExplicitNamespace = 0x19 # Explicit namespace (user-defined)
34
+ CONSTANT_StaticProtectedNs = 0x1A # Static protected namespace
35
+ CONSTANT_PrivateNs = 0x05 # Private namespace (class-scoped)
36
+
37
+ # ── Trait kinds ─────────────────────────────────────────────────────────────
38
+ # Used in TraitInfo.kind (lower 4 bits of the kind byte).
39
+ # Upper 4 bits are trait attributes (ATTR_Final=0x01, ATTR_Override=0x02, ATTR_Metadata=0x04).
40
+
41
+ TRAIT_Slot = 0 # Instance variable (field)
42
+ TRAIT_Method = 1 # Method
43
+ TRAIT_Getter = 2 # Getter property
44
+ TRAIT_Setter = 3 # Setter property
45
+ TRAIT_Class = 4 # Class definition
46
+ TRAIT_Function = 5 # Function (closure)
47
+ TRAIT_Const = 6 # Constant (final field)
48
+
49
+ # Trait attribute flags (upper 4 bits of kind byte)
50
+ ATTR_Final = 0x01
51
+ ATTR_Override = 0x02
52
+ ATTR_Metadata = 0x04
53
+
54
+ # ── Method flags ────────────────────────────────────────────────────────────
55
+ # Bitmask flags in MethodInfo.flags.
56
+
57
+ METHOD_NeedArguments = 0x01 # Method uses 'arguments' object
58
+ METHOD_NeedActivation = 0x02 # Method needs an activation object
59
+ METHOD_NeedRest = 0x04 # Method uses ...rest parameter
60
+ METHOD_HasOptional = 0x08 # Method has optional parameters
61
+ METHOD_SetDxns = 0x40 # Method sets default XML namespace
62
+ METHOD_HasParamNames = 0x80 # Method has debug parameter names
63
+
64
+ # ── Instance flags ──────────────────────────────────────────────────────────
65
+ # Bitmask flags in InstanceInfo.flags.
66
+
67
+ INSTANCE_Sealed = 0x01 # Class is sealed (no dynamic properties)
68
+ INSTANCE_Final = 0x02 # Class is final (cannot be subclassed)
69
+ INSTANCE_Interface = 0x04 # Class is an interface
70
+ INSTANCE_ProtectedNs = 0x08 # Class has a protected namespace
71
+
72
+ # ── AVM2 opcodes ────────────────────────────────────────────────────────────
73
+ # Instruction opcodes for AVM2 bytecode (MethodBodyInfo.code).
74
+ # Organized by functional group.
75
+
76
+ # Control flow
77
+ OP_nop = 0x02
78
+ OP_throw = 0x03
79
+ OP_label = 0x09
80
+ OP_jump = 0x10
81
+ OP_iftrue = 0x11
82
+ OP_iffalse = 0x12
83
+ OP_ifeq = 0x13
84
+ OP_ifne = 0x14
85
+ OP_iflt = 0x15
86
+ OP_ifle = 0x16
87
+ OP_ifgt = 0x17
88
+ OP_ifge = 0x18
89
+ OP_ifstricteq = 0x19
90
+ OP_ifstrictne = 0x1A
91
+ OP_lookupswitch = 0x1B
92
+
93
+ # Scope management
94
+ OP_pushwith = 0x1C
95
+ OP_popscope = 0x1D
96
+ OP_pushscope = 0x30
97
+ OP_getscopeobject = 0x65
98
+
99
+ # Stack operations
100
+ OP_pop = 0x29
101
+ OP_dup = 0x2A
102
+ OP_swap = 0x2B
103
+
104
+ # Push constants
105
+ OP_pushnull = 0x20
106
+ OP_pushundefined = 0x21
107
+ OP_pushtrue = 0x26
108
+ OP_pushfalse = 0x27
109
+ OP_pushnan = 0x28
110
+ OP_pushbyte = 0x24
111
+ OP_pushshort = 0x25
112
+ OP_pushstring = 0x2C
113
+ OP_pushint = 0x2D
114
+ OP_pushuint = 0x2E
115
+ OP_pushdouble = 0x2F
116
+
117
+ # Iteration
118
+ OP_nextname = 0x1E
119
+ OP_hasnext = 0x1F
120
+ OP_nextvalue = 0x23
121
+ OP_hasnext2 = 0x32
122
+
123
+ # Locals
124
+ OP_getlocal = 0x62
125
+ OP_setlocal = 0x63
126
+ OP_getlocal_0 = 0xD0
127
+ OP_getlocal_1 = 0xD1
128
+ OP_getlocal_2 = 0xD2
129
+ OP_getlocal_3 = 0xD3
130
+ OP_setlocal_0 = 0xD4
131
+ OP_setlocal_1 = 0xD5
132
+ OP_setlocal_2 = 0xD6
133
+ OP_setlocal_3 = 0xD7
134
+
135
+ # Properties
136
+ OP_getproperty = 0x66
137
+ OP_setproperty = 0x61
138
+ OP_initproperty = 0x68
139
+ OP_getlex = 0x60
140
+ OP_findpropstrict = 0x5D
141
+
142
+ # Calls
143
+ OP_call = 0x41
144
+ OP_construct = 0x42
145
+ OP_callproperty = 0x46
146
+ OP_returnvoid = 0x47
147
+ OP_returnvalue = 0x48
148
+ OP_constructsuper = 0x49
149
+ OP_constructprop = 0x4A
150
+ OP_callpropvoid = 0x4F
151
+
152
+ # Object creation
153
+ OP_newfunction = 0x40
154
+ OP_newarray = 0x56
155
+ OP_newclass = 0x58
156
+
157
+ # Type conversion
158
+ OP_convert_s = 0x70
159
+ OP_convert_i = 0x73
160
+ OP_convert_d = 0x75
161
+ OP_coerce = 0x80
162
+ OP_coerce_a = 0x82
163
+ OP_coerce_s = 0x85
164
+
165
+ # Comparison & logic
166
+ OP_typeof = 0x95
167
+ OP_not = 0x96
168
+ OP_equals = 0xAB
169
+ OP_strictequals = 0xAC
170
+ OP_lessthan = 0xAD
171
+ OP_lessequals = 0xAE
172
+ OP_greaterthan = 0xAF
173
+ OP_greaterequals = 0xB0
174
+
175
+ # Arithmetic
176
+ OP_increment = 0x91
177
+ OP_decrement = 0x93
178
+ OP_add = 0xA0
179
+ OP_subtract = 0xA1
180
+ OP_multiply = 0xA2
181
+ OP_divide = 0xA3
182
+ OP_modulo = 0xA4
183
+ OP_increment_i = 0xC0
184
+ OP_decrement_i = 0xC1
185
+
186
+ # Bitwise
187
+ OP_bitor = 0xA9
188
+ OP_bitand = 0xA8
189
+ OP_bitxor = 0xAA
190
+ OP_lshift = 0xA5
191
+ OP_rshift = 0xA6
192
+ OP_urshift = 0xA7
193
+ OP_bitnot = 0x97
194
+
195
+ # Debugging
196
+ OP_debug = 0xEF
197
+ OP_debugline = 0xF0
198
+ OP_debugfile = 0xF1
flashkit/abc/disasm.py ADDED
@@ -0,0 +1,364 @@
1
+ """
2
+ AVM2 bytecode disassembler / instruction decoder.
3
+
4
+ Walks the raw bytecode in ``MethodBodyInfo.code`` and yields structured
5
+ ``Instruction`` objects. This is the foundation for call graph analysis,
6
+ cross-reference indexing, and string constant discovery.
7
+
8
+ Usage::
9
+
10
+ from flashkit.abc.disasm import decode_instructions
11
+
12
+ for instr in decode_instructions(method_body.code):
13
+ print(f"0x{instr.offset:04X} {instr.mnemonic} {instr.operands}")
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from dataclasses import dataclass, field
20
+
21
+ from ..errors import ABCParseError
22
+ from .parser import read_u30, read_u8
23
+ from .constants import *
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class Instruction:
30
+ """A single decoded AVM2 instruction.
31
+
32
+ Attributes:
33
+ offset: Byte offset of this instruction in the method body code.
34
+ opcode: Opcode byte value.
35
+ mnemonic: Human-readable opcode name.
36
+ operands: List of decoded operand values.
37
+ size: Total size in bytes (opcode + operands).
38
+ """
39
+ offset: int
40
+ opcode: int
41
+ mnemonic: str
42
+ operands: list[int] = field(default_factory=list)
43
+ size: int = 1
44
+
45
+
46
+ # ── Opcode table ────────────────────────────────────────────────────────────
47
+ # Maps opcode → (mnemonic, operand_format)
48
+ # Operand formats:
49
+ # "" = no operands
50
+ # "u30" = one u30
51
+ # "u30u30" = two u30s
52
+ # "u8" = one byte
53
+ # "s24" = signed 24-bit offset
54
+ # "u30u8" = u30 + byte (hasnext2 uses this differently, but close enough)
55
+ # "special" = handled individually (lookupswitch, debug)
56
+
57
+ _OPCODE_TABLE: dict[int, tuple[str, str]] = {
58
+ # Control flow
59
+ OP_nop: ("nop", ""),
60
+ OP_throw: ("throw", ""),
61
+ OP_label: ("label", ""),
62
+ OP_jump: ("jump", "s24"),
63
+ OP_iftrue: ("iftrue", "s24"),
64
+ OP_iffalse: ("iffalse", "s24"),
65
+ OP_ifeq: ("ifeq", "s24"),
66
+ OP_ifne: ("ifne", "s24"),
67
+ OP_iflt: ("iflt", "s24"),
68
+ OP_ifle: ("ifle", "s24"),
69
+ OP_ifgt: ("ifgt", "s24"),
70
+ OP_ifge: ("ifge", "s24"),
71
+ OP_ifstricteq: ("ifstricteq", "s24"),
72
+ OP_ifstrictne: ("ifstrictne", "s24"),
73
+ OP_lookupswitch: ("lookupswitch", "special"),
74
+
75
+ # Scope
76
+ OP_pushwith: ("pushwith", ""),
77
+ OP_popscope: ("popscope", ""),
78
+ OP_pushscope: ("pushscope", ""),
79
+ OP_getscopeobject: ("getscopeobject", "u30"),
80
+
81
+ # Stack
82
+ OP_pop: ("pop", ""),
83
+ OP_dup: ("dup", ""),
84
+ OP_swap: ("swap", ""),
85
+
86
+ # Push constants
87
+ OP_pushnull: ("pushnull", ""),
88
+ OP_pushundefined: ("pushundefined", ""),
89
+ OP_pushtrue: ("pushtrue", ""),
90
+ OP_pushfalse: ("pushfalse", ""),
91
+ OP_pushnan: ("pushnan", ""),
92
+ OP_pushbyte: ("pushbyte", "u8"),
93
+ OP_pushshort: ("pushshort", "u30"),
94
+ OP_pushstring: ("pushstring", "u30"),
95
+ OP_pushint: ("pushint", "u30"),
96
+ OP_pushuint: ("pushuint", "u30"),
97
+ OP_pushdouble: ("pushdouble", "u30"),
98
+
99
+ # Iteration
100
+ OP_nextname: ("nextname", ""),
101
+ OP_hasnext: ("hasnext", ""),
102
+ OP_nextvalue: ("nextvalue", ""),
103
+ OP_hasnext2: ("hasnext2", "u30u30"),
104
+
105
+ # Locals
106
+ OP_getlocal: ("getlocal", "u30"),
107
+ OP_setlocal: ("setlocal", "u30"),
108
+ OP_getlocal_0: ("getlocal_0", ""),
109
+ OP_getlocal_1: ("getlocal_1", ""),
110
+ OP_getlocal_2: ("getlocal_2", ""),
111
+ OP_getlocal_3: ("getlocal_3", ""),
112
+ OP_setlocal_0: ("setlocal_0", ""),
113
+ OP_setlocal_1: ("setlocal_1", ""),
114
+ OP_setlocal_2: ("setlocal_2", ""),
115
+ OP_setlocal_3: ("setlocal_3", ""),
116
+
117
+ # Properties
118
+ OP_getproperty: ("getproperty", "u30"),
119
+ OP_setproperty: ("setproperty", "u30"),
120
+ OP_initproperty: ("initproperty", "u30"),
121
+ OP_getlex: ("getlex", "u30"),
122
+ OP_findpropstrict: ("findpropstrict", "u30"),
123
+
124
+ # Calls
125
+ OP_call: ("call", "u30"),
126
+ OP_construct: ("construct", "u30"),
127
+ OP_callproperty: ("callproperty", "u30u30"),
128
+ OP_returnvoid: ("returnvoid", ""),
129
+ OP_returnvalue: ("returnvalue", ""),
130
+ OP_constructsuper: ("constructsuper", "u30"),
131
+ OP_constructprop: ("constructprop", "u30u30"),
132
+ OP_callpropvoid: ("callpropvoid", "u30u30"),
133
+
134
+ # Object creation
135
+ OP_newfunction: ("newfunction", "u30"),
136
+ OP_newarray: ("newarray", "u30"),
137
+ OP_newclass: ("newclass", "u30"),
138
+
139
+ # Type conversion
140
+ OP_convert_s: ("convert_s", ""),
141
+ OP_convert_i: ("convert_i", ""),
142
+ OP_convert_d: ("convert_d", ""),
143
+ OP_coerce: ("coerce", "u30"),
144
+ OP_coerce_a: ("coerce_a", ""),
145
+ OP_coerce_s: ("coerce_s", ""),
146
+
147
+ # Comparison & logic
148
+ OP_typeof: ("typeof", ""),
149
+ OP_not: ("not", ""),
150
+ OP_equals: ("equals", ""),
151
+ OP_strictequals: ("strictequals", ""),
152
+ OP_lessthan: ("lessthan", ""),
153
+ OP_lessequals: ("lessequals", ""),
154
+ OP_greaterthan: ("greaterthan", ""),
155
+ OP_greaterequals: ("greaterequals", ""),
156
+
157
+ # Arithmetic
158
+ OP_increment: ("increment", ""),
159
+ OP_decrement: ("decrement", ""),
160
+ OP_add: ("add", ""),
161
+ OP_subtract: ("subtract", ""),
162
+ OP_multiply: ("multiply", ""),
163
+ OP_divide: ("divide", ""),
164
+ OP_modulo: ("modulo", ""),
165
+ OP_increment_i: ("increment_i", ""),
166
+ OP_decrement_i: ("decrement_i", ""),
167
+
168
+ # Bitwise
169
+ OP_bitor: ("bitor", ""),
170
+ OP_bitand: ("bitand", ""),
171
+ OP_bitxor: ("bitxor", ""),
172
+ OP_lshift: ("lshift", ""),
173
+ OP_rshift: ("rshift", ""),
174
+ OP_urshift: ("urshift", ""),
175
+ OP_bitnot: ("bitnot", ""),
176
+
177
+ # Debugging
178
+ OP_debug: ("debug", "special"),
179
+ OP_debugline: ("debugline", "u30"),
180
+ OP_debugfile: ("debugfile", "u30"),
181
+ }
182
+
183
+ # Additional opcodes not in our OP_ constants but valid AVM2
184
+ _EXTRA_OPCODES: dict[int, tuple[str, str]] = {
185
+ 0x04: ("getsuper", "u30"),
186
+ 0x05: ("setsuper", "u30"),
187
+ 0x06: ("dxns", "u30"),
188
+ 0x07: ("dxnslate", ""),
189
+ 0x08: ("kill", "u30"),
190
+ 0x0C: ("ifnlt", "s24"),
191
+ 0x0D: ("ifnle", "s24"),
192
+ 0x0E: ("ifngt", "s24"),
193
+ 0x0F: ("ifnge", "s24"),
194
+ 0x1E: ("nextname", ""),
195
+ 0x30: ("pushscope", ""),
196
+ 0x43: ("callmethod", "u30u30"),
197
+ 0x44: ("callstatic", "u30u30"),
198
+ 0x45: ("callsuper", "u30u30"),
199
+ 0x4C: ("callproplex", "u30u30"),
200
+ 0x4E: ("callsupervoid", "u30u30"),
201
+ 0x53: ("applytype", "u30"),
202
+ 0x55: ("newobject", "u30"),
203
+ 0x57: ("newactivation", ""),
204
+ 0x59: ("getdescendants", "u30"),
205
+ 0x5A: ("newcatch", "u30"),
206
+ 0x5E: ("findproperty", "u30"),
207
+ 0x64: ("getglobalscope", ""),
208
+ 0x6A: ("deleteproperty", "u30"),
209
+ 0x6C: ("getslot", "u30"),
210
+ 0x6D: ("setslot", "u30"),
211
+ 0x6E: ("getglobalslot", "u30"),
212
+ 0x6F: ("setglobalslot", "u30"),
213
+ 0x70: ("convert_s", ""),
214
+ 0x71: ("esc_xelem", ""),
215
+ 0x72: ("esc_xattr", ""),
216
+ 0x73: ("convert_i", ""),
217
+ 0x74: ("convert_u", ""),
218
+ 0x75: ("convert_d", ""),
219
+ 0x76: ("convert_b", ""),
220
+ 0x77: ("convert_o", ""),
221
+ 0x78: ("checkfilter", ""),
222
+ 0x80: ("coerce", "u30"),
223
+ 0x81: ("coerce_b", ""),
224
+ 0x83: ("coerce_i", ""),
225
+ 0x84: ("coerce_d", ""),
226
+ 0x86: ("astype", "u30"),
227
+ 0x87: ("astypelate", ""),
228
+ 0x88: ("coerce_u", ""),
229
+ 0x89: ("coerce_o", ""),
230
+ 0x90: ("negate", ""),
231
+ 0x92: ("inclocal", "u30"),
232
+ 0x94: ("declocal", "u30"),
233
+ 0x96: ("not", ""),
234
+ 0x97: ("bitnot", ""),
235
+ 0x9A: ("concat", ""),
236
+ 0x9B: ("add_d", ""),
237
+ 0xA0: ("add", ""),
238
+ 0xA5: ("lshift", ""),
239
+ 0xA6: ("rshift", ""),
240
+ 0xA7: ("urshift", ""),
241
+ 0xA8: ("bitand", ""),
242
+ 0xA9: ("bitor", ""),
243
+ 0xAA: ("bitxor", ""),
244
+ 0xB1: ("instanceof", ""),
245
+ 0xB2: ("istype", "u30"),
246
+ 0xB3: ("istypelate", ""),
247
+ 0xB4: ("in", ""),
248
+ 0xC0: ("increment_i", ""),
249
+ 0xC1: ("decrement_i", ""),
250
+ 0xC2: ("inclocal_i", "u30"),
251
+ 0xC3: ("declocal_i", "u30"),
252
+ 0xC4: ("negate_i", ""),
253
+ 0xC5: ("add_i", ""),
254
+ 0xC6: ("subtract_i", ""),
255
+ 0xC7: ("multiply_i", ""),
256
+ 0xF0: ("debugline", "u30"),
257
+ 0xF1: ("debugfile", "u30"),
258
+ }
259
+
260
+
261
+ def _read_s24(data: bytes, offset: int) -> tuple[int, int]:
262
+ """Read a signed 24-bit integer (little-endian)."""
263
+ val = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16)
264
+ if val & 0x800000:
265
+ val -= 0x1000000
266
+ return val, offset + 3
267
+
268
+
269
+ def _build_lookup() -> dict[int, tuple[str, str]]:
270
+ """Build the combined opcode lookup table."""
271
+ lookup = dict(_EXTRA_OPCODES)
272
+ lookup.update(_OPCODE_TABLE) # primary table takes precedence
273
+ return lookup
274
+
275
+ _LOOKUP = _build_lookup()
276
+
277
+
278
+ def decode_instructions(code: bytes,
279
+ strict: bool = False) -> list[Instruction]:
280
+ """Decode an AVM2 bytecode stream into a list of instructions.
281
+
282
+ Args:
283
+ code: Raw bytecode bytes (from MethodBodyInfo.code).
284
+ strict: If True, raise ``ABCParseError`` on any decode problem
285
+ (unknown opcodes, truncated operands). If False (default),
286
+ log warnings and emit partial instructions.
287
+
288
+ Returns:
289
+ List of decoded Instruction objects.
290
+
291
+ Raises:
292
+ ABCParseError: Only when ``strict=True`` and a problem is found.
293
+ """
294
+ instructions: list[Instruction] = []
295
+ off = 0
296
+ code_len = len(code)
297
+
298
+ while off < code_len:
299
+ start = off
300
+ op = code[off]
301
+ off += 1
302
+
303
+ entry = _LOOKUP.get(op)
304
+ if entry is None:
305
+ msg = f"Unknown opcode 0x{op:02X} at offset 0x{start:04X}"
306
+ if strict:
307
+ raise ABCParseError(msg)
308
+ log.warning(msg)
309
+ instructions.append(Instruction(
310
+ offset=start, opcode=op, mnemonic=f"unknown_0x{op:02X}",
311
+ operands=[], size=1))
312
+ continue
313
+
314
+ mnemonic, fmt = entry
315
+ operands: list[int] = []
316
+
317
+ try:
318
+ if fmt == "":
319
+ pass
320
+ elif fmt == "u8":
321
+ val, off = read_u8(code, off)
322
+ operands.append(val)
323
+ elif fmt == "u30":
324
+ val, off = read_u30(code, off)
325
+ operands.append(val)
326
+ elif fmt == "u30u30":
327
+ val1, off = read_u30(code, off)
328
+ val2, off = read_u30(code, off)
329
+ operands.extend([val1, val2])
330
+ elif fmt == "s24":
331
+ val, off = _read_s24(code, off)
332
+ operands.append(val)
333
+ elif fmt == "special":
334
+ if op == OP_lookupswitch:
335
+ default_off, off = _read_s24(code, off)
336
+ case_count, off = read_u30(code, off)
337
+ operands.append(default_off)
338
+ operands.append(case_count)
339
+ for _ in range(case_count + 1):
340
+ case_off, off = _read_s24(code, off)
341
+ operands.append(case_off)
342
+ elif op == OP_debug:
343
+ debug_type, off = read_u8(code, off)
344
+ index, off = read_u30(code, off)
345
+ reg, off = read_u8(code, off)
346
+ extra, off = read_u30(code, off)
347
+ operands.extend([debug_type, index, reg, extra])
348
+ except (IndexError, ValueError) as e:
349
+ msg = (f"Truncated operand for {mnemonic} at offset "
350
+ f"0x{start:04X}: {e}")
351
+ if strict:
352
+ raise ABCParseError(msg) from e
353
+ log.warning(msg)
354
+ # Emit what we have so far and stop decoding
355
+ instructions.append(Instruction(
356
+ offset=start, opcode=op, mnemonic=mnemonic,
357
+ operands=operands, size=off - start))
358
+ break
359
+
360
+ instructions.append(Instruction(
361
+ offset=start, opcode=op, mnemonic=mnemonic,
362
+ operands=operands, size=off - start))
363
+
364
+ return instructions