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
|
@@ -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
|