]> jfr.im git - yt-dlp.git/blame - youtube_dl/swfinterp.py
[swfinterp] Formalize built-in classes
[yt-dlp.git] / youtube_dl / swfinterp.py
CommitLineData
54256267
PH
1from __future__ import unicode_literals
2
3import collections
4import io
54256267
PH
5import zlib
6
70f767dc
PH
7from .utils import (
8 compat_str,
9 ExtractorError,
c13bf7c8 10 struct_unpack,
70f767dc 11)
54256267
PH
12
13
0cb20563
PH
14def _extract_tags(file_contents):
15 if file_contents[1:3] != b'WS':
16 raise ExtractorError(
17 'Not an SWF file; header is %r' % file_contents[:3])
18 if file_contents[:1] == b'C':
19 content = zlib.decompress(file_contents[8:])
20 else:
21 raise NotImplementedError(
22 'Unsupported compression format %r' %
23 file_contents[:1])
24
25 # Determine number of bits in framesize rectangle
c13bf7c8 26 framesize_nbits = struct_unpack('!B', content[:1])[0] >> 3
0cb20563
PH
27 framesize_len = (5 + 4 * framesize_nbits + 7) // 8
28
29 pos = framesize_len + 2 + 2
54256267 30 while pos < len(content):
c13bf7c8 31 header16 = struct_unpack('<H', content[pos:pos + 2])[0]
54256267
PH
32 pos += 2
33 tag_code = header16 >> 6
34 tag_len = header16 & 0x3f
35 if tag_len == 0x3f:
c13bf7c8 36 tag_len = struct_unpack('<I', content[pos:pos + 4])[0]
54256267 37 pos += 4
0cb20563
PH
38 assert pos + tag_len <= len(content), \
39 ('Tag %d ends at %d+%d - that\'s longer than the file (%d)'
40 % (tag_code, pos, tag_len, len(content)))
54256267
PH
41 yield (tag_code, content[pos:pos + tag_len])
42 pos += tag_len
43
44
45class _AVMClass_Object(object):
46 def __init__(self, avm_class):
47 self.avm_class = avm_class
48
49 def __repr__(self):
50 return '%s#%x' % (self.avm_class.name, id(self))
51
52
0d989011
PH
53class _ScopeDict(dict):
54 def __init__(self, avm_class):
55 super(_ScopeDict, self).__init__()
56 self.avm_class = avm_class
57
58 def __repr__(self):
59 return '%s__Scope(%s)' % (
60 self.avm_class.name,
61 super(_ScopeDict, self).__repr__())
62
63
54256267
PH
64class _AVMClass(object):
65 def __init__(self, name_idx, name):
66 self.name_idx = name_idx
67 self.name = name
68 self.method_names = {}
69 self.method_idxs = {}
70 self.methods = {}
71 self.method_pyfunctions = {}
70f767dc 72
0d989011 73 self.variables = _ScopeDict(self)
54256267
PH
74
75 def make_object(self):
76 return _AVMClass_Object(self)
77
01b4b745
PH
78 def __repr__(self):
79 return '_AVMClass(%s)' % (self.name)
80
81 def register_methods(self, methods):
82 self.method_names.update(methods.items())
83 self.method_idxs.update(dict(
84 (idx, name)
85 for name, idx in methods.items()))
86
54256267 87
decf2ae4
PH
88class _Multiname(object):
89 def __init__(self, kind):
90 self.kind = kind
91
92 def __repr__(self):
93 return '[MULTINAME kind: 0x%x]' % self.kind
94
95
54256267
PH
96def _read_int(reader):
97 res = 0
98 shift = 0
99 for _ in range(5):
100 buf = reader.read(1)
101 assert len(buf) == 1
c13bf7c8 102 b = struct_unpack('<B', buf)[0]
54256267
PH
103 res = res | ((b & 0x7f) << shift)
104 if b & 0x80 == 0:
105 break
106 shift += 7
107 return res
108
109
110def _u30(reader):
111 res = _read_int(reader)
112 assert res & 0xf0000000 == 0
113 return res
351f3738 114_u32 = _read_int
54256267
PH
115
116
117def _s32(reader):
118 v = _read_int(reader)
119 if v & 0x80000000 != 0:
120 v = - ((v ^ 0xffffffff) + 1)
121 return v
122
123
124def _s24(reader):
125 bs = reader.read(3)
126 assert len(bs) == 3
e75c24e8 127 last_byte = b'\xff' if (ord(bs[2:3]) >= 0x80) else b'\x00'
c13bf7c8 128 return struct_unpack('<i', bs + last_byte)[0]
54256267
PH
129
130
131def _read_string(reader):
132 slen = _u30(reader)
133 resb = reader.read(slen)
134 assert len(resb) == slen
135 return resb.decode('utf-8')
136
137
138def _read_bytes(count, reader):
0cb20563 139 assert count >= 0
54256267
PH
140 resb = reader.read(count)
141 assert len(resb) == count
142 return resb
143
144
145def _read_byte(reader):
146 resb = _read_bytes(1, reader=reader)
c13bf7c8 147 res = struct_unpack('<B', resb)[0]
54256267
PH
148 return res
149
150
3cbcff8a 151StringClass = _AVMClass('(no name idx)', 'String')
6b592d93
PH
152ByteArrayClass = _AVMClass('(no name idx)', 'ByteArray')
153_builtin_classes = {
154 StringClass.name: StringClass,
155 ByteArrayClass.name: ByteArrayClass,
156}
3cbcff8a
PH
157
158
4686ae4b
PH
159class _Undefined(object):
160 def __boolean__(self):
161 return False
162
163 def __hash__(self):
164 return 0
165
166undefined = _Undefined()
167
168
54256267
PH
169class SWFInterpreter(object):
170 def __init__(self, file_contents):
b7558d98 171 self._patched_functions = {}
54256267 172 code_tag = next(tag
0cb20563 173 for tag_code, tag in _extract_tags(file_contents)
54256267
PH
174 if tag_code == 82)
175 p = code_tag.index(b'\0', 4) + 1
176 code_reader = io.BytesIO(code_tag[p:])
177
178 # Parse ABC (AVM2 ByteCode)
179
180 # Define a couple convenience methods
181 u30 = lambda *args: _u30(*args, reader=code_reader)
182 s32 = lambda *args: _s32(*args, reader=code_reader)
183 u32 = lambda *args: _u32(*args, reader=code_reader)
184 read_bytes = lambda *args: _read_bytes(*args, reader=code_reader)
185 read_byte = lambda *args: _read_byte(*args, reader=code_reader)
186
187 # minor_version + major_version
188 read_bytes(2 + 2)
189
190 # Constant pool
191 int_count = u30()
192 for _c in range(1, int_count):
193 s32()
194 uint_count = u30()
195 for _c in range(1, uint_count):
196 u32()
197 double_count = u30()
0cb20563 198 read_bytes(max(0, (double_count - 1)) * 8)
54256267 199 string_count = u30()
70f767dc 200 self.constant_strings = ['']
54256267
PH
201 for _c in range(1, string_count):
202 s = _read_string(code_reader)
70f767dc 203 self.constant_strings.append(s)
54256267
PH
204 namespace_count = u30()
205 for _c in range(1, namespace_count):
206 read_bytes(1) # kind
207 u30() # name
208 ns_set_count = u30()
209 for _c in range(1, ns_set_count):
210 count = u30()
211 for _c2 in range(count):
212 u30()
213 multiname_count = u30()
214 MULTINAME_SIZES = {
215 0x07: 2, # QName
216 0x0d: 2, # QNameA
217 0x0f: 1, # RTQName
218 0x10: 1, # RTQNameA
219 0x11: 0, # RTQNameL
220 0x12: 0, # RTQNameLA
221 0x09: 2, # Multiname
222 0x0e: 2, # MultinameA
223 0x1b: 1, # MultinameL
224 0x1c: 1, # MultinameLA
225 }
226 self.multinames = ['']
227 for _c in range(1, multiname_count):
228 kind = u30()
229 assert kind in MULTINAME_SIZES, 'Invalid multiname kind %r' % kind
230 if kind == 0x07:
231 u30() # namespace_idx
232 name_idx = u30()
70f767dc 233 self.multinames.append(self.constant_strings[name_idx])
4baafa22
PH
234 elif kind == 0x09:
235 name_idx = u30()
236 u30()
237 self.multinames.append(self.constant_strings[name_idx])
54256267 238 else:
decf2ae4 239 self.multinames.append(_Multiname(kind))
54256267
PH
240 for _c2 in range(MULTINAME_SIZES[kind]):
241 u30()
242
243 # Methods
244 method_count = u30()
245 MethodInfo = collections.namedtuple(
246 'MethodInfo',
247 ['NEED_ARGUMENTS', 'NEED_REST'])
248 method_infos = []
249 for method_id in range(method_count):
250 param_count = u30()
251 u30() # return type
252 for _ in range(param_count):
253 u30() # param type
254 u30() # name index (always 0 for youtube)
255 flags = read_byte()
256 if flags & 0x08 != 0:
257 # Options present
258 option_count = u30()
259 for c in range(option_count):
260 u30() # val
261 read_bytes(1) # kind
262 if flags & 0x80 != 0:
263 # Param names present
264 for _ in range(param_count):
265 u30() # param name
266 mi = MethodInfo(flags & 0x01 != 0, flags & 0x04 != 0)
267 method_infos.append(mi)
268
269 # Metadata
270 metadata_count = u30()
271 for _c in range(metadata_count):
272 u30() # name
273 item_count = u30()
274 for _c2 in range(item_count):
275 u30() # key
276 u30() # value
277
278 def parse_traits_info():
279 trait_name_idx = u30()
280 kind_full = read_byte()
281 kind = kind_full & 0x0f
282 attrs = kind_full >> 4
283 methods = {}
284 if kind in [0x00, 0x06]: # Slot or Const
285 u30() # Slot id
286 u30() # type_name_idx
287 vindex = u30()
288 if vindex != 0:
289 read_byte() # vkind
290 elif kind in [0x01, 0x02, 0x03]: # Method / Getter / Setter
291 u30() # disp_id
292 method_idx = u30()
293 methods[self.multinames[trait_name_idx]] = method_idx
294 elif kind == 0x04: # Class
295 u30() # slot_id
296 u30() # classi
297 elif kind == 0x05: # Function
298 u30() # slot_id
299 function_idx = u30()
300 methods[function_idx] = self.multinames[trait_name_idx]
301 else:
302 raise ExtractorError('Unsupported trait kind %d' % kind)
303
304 if attrs & 0x4 != 0: # Metadata present
305 metadata_count = u30()
306 for _c3 in range(metadata_count):
307 u30() # metadata index
308
309 return methods
310
311 # Classes
312 class_count = u30()
313 classes = []
314 for class_id in range(class_count):
315 name_idx = u30()
01b4b745
PH
316
317 cname = self.multinames[name_idx]
318 avm_class = _AVMClass(name_idx, cname)
319 classes.append(avm_class)
320
54256267
PH
321 u30() # super_name idx
322 flags = read_byte()
323 if flags & 0x08 != 0: # Protected namespace is present
324 u30() # protected_ns_idx
325 intrf_count = u30()
326 for _c2 in range(intrf_count):
327 u30()
328 u30() # iinit
329 trait_count = u30()
330 for _c2 in range(trait_count):
01b4b745
PH
331 trait_methods = parse_traits_info()
332 avm_class.register_methods(trait_methods)
333
54256267
PH
334 assert len(classes) == class_count
335 self._classes_by_name = dict((c.name, c) for c in classes)
336
337 for avm_class in classes:
338 u30() # cinit
339 trait_count = u30()
340 for _c2 in range(trait_count):
341 trait_methods = parse_traits_info()
01b4b745 342 avm_class.register_methods(trait_methods)
54256267
PH
343
344 # Scripts
345 script_count = u30()
346 for _c in range(script_count):
347 u30() # init
348 trait_count = u30()
349 for _c2 in range(trait_count):
350 parse_traits_info()
351
352 # Method bodies
353 method_body_count = u30()
354 Method = collections.namedtuple('Method', ['code', 'local_count'])
355 for _c in range(method_body_count):
356 method_idx = u30()
357 u30() # max_stack
358 local_count = u30()
359 u30() # init_scope_depth
360 u30() # max_scope_depth
361 code_length = u30()
362 code = read_bytes(code_length)
363 for avm_class in classes:
364 if method_idx in avm_class.method_idxs:
365 m = Method(code, local_count)
366 avm_class.methods[avm_class.method_idxs[method_idx]] = m
367 exception_count = u30()
368 for _c2 in range(exception_count):
369 u30() # from
370 u30() # to
371 u30() # target
372 u30() # exc_type
373 u30() # var_name
374 trait_count = u30()
375 for _c2 in range(trait_count):
376 parse_traits_info()
377
378 assert p + code_reader.tell() == len(code_tag)
379
b7558d98
PH
380 def patch_function(self, avm_class, func_name, f):
381 self._patched_functions[(avm_class, func_name)] = f
382
54256267
PH
383 def extract_class(self, class_name):
384 try:
385 return self._classes_by_name[class_name]
386 except KeyError:
387 raise ExtractorError('Class %r not found' % class_name)
388
389 def extract_function(self, avm_class, func_name):
b7558d98
PH
390 p = self._patched_functions.get((avm_class, func_name))
391 if p:
392 return p
54256267
PH
393 if func_name in avm_class.method_pyfunctions:
394 return avm_class.method_pyfunctions[func_name]
395 if func_name in self._classes_by_name:
396 return self._classes_by_name[func_name].make_object()
397 if func_name not in avm_class.methods:
01b4b745
PH
398 raise ExtractorError('Cannot find function %s.%s' % (
399 avm_class.name, func_name))
54256267
PH
400 m = avm_class.methods[func_name]
401
402 def resfunc(args):
403 # Helper functions
404 coder = io.BytesIO(m.code)
405 s24 = lambda: _s24(coder)
406 u30 = lambda: _u30(coder)
407
e75c24e8 408 registers = [avm_class.variables] + list(args) + [None] * m.local_count
54256267 409 stack = []
01b4b745
PH
410 scopes = collections.deque([
411 self._classes_by_name, avm_class.variables])
54256267
PH
412 while True:
413 opcode = _read_byte(coder)
e983cf52
PH
414 if opcode == 16: # jump
415 offset = s24()
416 coder.seek(coder.tell() + offset)
417 elif opcode == 17: # iftrue
54256267
PH
418 offset = s24()
419 value = stack.pop()
420 if value:
421 coder.seek(coder.tell() + offset)
e75c24e8
PH
422 elif opcode == 18: # iffalse
423 offset = s24()
424 value = stack.pop()
425 if not value:
426 coder.seek(coder.tell() + offset)
e983cf52
PH
427 elif opcode == 19: # ifeq
428 offset = s24()
429 value2 = stack.pop()
430 value1 = stack.pop()
431 if value2 == value1:
432 coder.seek(coder.tell() + offset)
433 elif opcode == 20: # ifne
434 offset = s24()
435 value2 = stack.pop()
436 value1 = stack.pop()
437 if value2 != value1:
438 coder.seek(coder.tell() + offset)
439 elif opcode == 32: # pushnull
440 stack.append(None)
4686ae4b
PH
441 elif opcode == 33: # pushundefined
442 stack.append(undefined)
54256267
PH
443 elif opcode == 36: # pushbyte
444 v = _read_byte(coder)
445 stack.append(v)
a4bb8395
PH
446 elif opcode == 38: # pushtrue
447 stack.append(True)
448 elif opcode == 39: # pushfalse
449 stack.append(False)
4686ae4b
PH
450 elif opcode == 40: # pushnan
451 stack.append(float('NaN'))
0cb20563
PH
452 elif opcode == 42: # dup
453 value = stack[-1]
454 stack.append(value)
54256267
PH
455 elif opcode == 44: # pushstring
456 idx = u30()
70f767dc 457 stack.append(self.constant_strings[idx])
54256267 458 elif opcode == 48: # pushscope
54256267 459 new_scope = stack.pop()
e75c24e8 460 scopes.append(new_scope)
decf2ae4
PH
461 elif opcode == 66: # construct
462 arg_count = u30()
463 args = list(reversed(
464 [stack.pop() for _ in range(arg_count)]))
465 obj = stack.pop()
466 res = obj.avm_class.make_object()
467 stack.append(res)
54256267
PH
468 elif opcode == 70: # callproperty
469 index = u30()
470 mname = self.multinames[index]
471 arg_count = u30()
472 args = list(reversed(
473 [stack.pop() for _ in range(arg_count)]))
474 obj = stack.pop()
01b4b745 475
6b592d93
PH
476 if obj == StringClass:
477 if mname == 'String':
478 assert len(args) == 1
479 assert isinstance(args[0], (
480 int, compat_str, _Undefined))
481 if args[0] == undefined:
482 res = 'undefined'
483 else:
484 res = compat_str(args[0])
485 stack.append(res)
486 continue
487 else:
488 raise NotImplementedError(
489 'Function String.%s is not yet implemented'
490 % mname)
491 elif isinstance(obj, _AVMClass_Object):
01b4b745
PH
492 func = self.extract_function(obj.avm_class, mname)
493 res = func(args)
54256267 494 stack.append(res)
01b4b745 495 continue
6b592d93
PH
496 elif isinstance(obj, _AVMClass):
497 func = self.extract_function(obj, mname)
498 res = func(args)
499 stack.append(res)
500 continue
0d989011
PH
501 elif isinstance(obj, _ScopeDict):
502 if mname in obj.avm_class.method_names:
503 func = self.extract_function(obj.avm_class, mname)
504 res = func(args)
505 else:
506 res = obj[mname]
507 stack.append(res)
508 continue
01b4b745
PH
509 elif isinstance(obj, compat_str):
510 if mname == 'split':
511 assert len(args) == 1
512 assert isinstance(args[0], compat_str)
513 if args[0] == '':
514 res = list(obj)
515 else:
516 res = obj.split(args[0])
517 stack.append(res)
518 continue
519 elif isinstance(obj, list):
520 if mname == 'slice':
521 assert len(args) == 1
522 assert isinstance(args[0], int)
523 res = obj[args[0]:]
524 stack.append(res)
525 continue
526 elif mname == 'join':
527 assert len(args) == 1
528 assert isinstance(args[0], compat_str)
529 res = args[0].join(obj)
530 stack.append(res)
531 continue
532 raise NotImplementedError(
533 'Unsupported property %r on %r'
534 % (mname, obj))
8d05f2c1 535 elif opcode == 71: # returnvoid
4686ae4b 536 res = undefined
8d05f2c1 537 return res
54256267
PH
538 elif opcode == 72: # returnvalue
539 res = stack.pop()
540 return res
541 elif opcode == 74: # constructproperty
542 index = u30()
543 arg_count = u30()
544 args = list(reversed(
545 [stack.pop() for _ in range(arg_count)]))
546 obj = stack.pop()
547
548 mname = self.multinames[index]
01b4b745 549 assert isinstance(obj, _AVMClass)
7fbf54dc 550
54256267
PH
551 # We do not actually call the constructor for now;
552 # we just pretend it does nothing
01b4b745 553 stack.append(obj.make_object())
54256267
PH
554 elif opcode == 79: # callpropvoid
555 index = u30()
556 mname = self.multinames[index]
557 arg_count = u30()
558 args = list(reversed(
559 [stack.pop() for _ in range(arg_count)]))
560 obj = stack.pop()
8d05f2c1
PH
561 if isinstance(obj, _AVMClass_Object):
562 func = self.extract_function(obj.avm_class, mname)
563 res = func(args)
4686ae4b 564 assert res is undefined
8d05f2c1
PH
565 continue
566 if isinstance(obj, _ScopeDict):
567 assert mname in obj.avm_class.method_names
568 func = self.extract_function(obj.avm_class, mname)
569 res = func(args)
4686ae4b 570 assert res is undefined
8d05f2c1 571 continue
54256267
PH
572 if mname == 'reverse':
573 assert isinstance(obj, list)
574 obj.reverse()
575 else:
576 raise NotImplementedError(
577 'Unsupported (void) property %r on %r'
578 % (mname, obj))
579 elif opcode == 86: # newarray
580 arg_count = u30()
581 arr = []
582 for i in range(arg_count):
583 arr.append(stack.pop())
584 arr = arr[::-1]
585 stack.append(arr)
70f767dc
PH
586 elif opcode == 93: # findpropstrict
587 index = u30()
588 mname = self.multinames[index]
589 for s in reversed(scopes):
590 if mname in s:
591 res = s
592 break
593 else:
594 res = scopes[0]
6b592d93
PH
595 if mname not in res and mname in _builtin_classes:
596 stack.append(_builtin_classes[mname])
3cbcff8a
PH
597 else:
598 stack.append(res[mname])
54256267
PH
599 elif opcode == 94: # findproperty
600 index = u30()
601 mname = self.multinames[index]
e75c24e8
PH
602 for s in reversed(scopes):
603 if mname in s:
604 res = s
605 break
606 else:
01b4b745 607 res = avm_class.variables
54256267
PH
608 stack.append(res)
609 elif opcode == 96: # getlex
610 index = u30()
611 mname = self.multinames[index]
e75c24e8
PH
612 for s in reversed(scopes):
613 if mname in s:
614 scope = s
615 break
616 else:
01b4b745 617 scope = avm_class.variables
e75c24e8
PH
618 # I cannot find where static variables are initialized
619 # so let's just return None
620 res = scope.get(mname)
54256267
PH
621 stack.append(res)
622 elif opcode == 97: # setproperty
623 index = u30()
624 value = stack.pop()
625 idx = self.multinames[index]
decf2ae4
PH
626 if isinstance(idx, _Multiname):
627 idx = stack.pop()
54256267
PH
628 obj = stack.pop()
629 obj[idx] = value
630 elif opcode == 98: # getlocal
631 index = u30()
632 stack.append(registers[index])
633 elif opcode == 99: # setlocal
634 index = u30()
635 value = stack.pop()
636 registers[index] = value
637 elif opcode == 102: # getproperty
638 index = u30()
639 pname = self.multinames[index]
640 if pname == 'length':
641 obj = stack.pop()
3cbcff8a 642 assert isinstance(obj, (compat_str, list))
54256267 643 stack.append(len(obj))
4baafa22
PH
644 elif isinstance(pname, compat_str): # Member access
645 obj = stack.pop()
646 assert isinstance(obj, (dict, _ScopeDict)), \
0ab1ca55 647 'Accessing member %r on %r' % (pname, obj)
4686ae4b 648 res = obj.get(pname, undefined)
8d05f2c1 649 stack.append(res)
54256267
PH
650 else: # Assume attribute access
651 idx = stack.pop()
652 assert isinstance(idx, int)
653 obj = stack.pop()
654 assert isinstance(obj, list)
655 stack.append(obj[idx])
0cb20563
PH
656 elif opcode == 115: # convert_
657 value = stack.pop()
658 intvalue = int(value)
659 stack.append(intvalue)
54256267
PH
660 elif opcode == 128: # coerce
661 u30()
4686ae4b
PH
662 elif opcode == 130: # coerce_a
663 value = stack.pop()
664 # um, yes, it's any value
665 stack.append(value)
54256267
PH
666 elif opcode == 133: # coerce_s
667 assert isinstance(stack[-1], (type(None), compat_str))
4686ae4b
PH
668 elif opcode == 147: # decrement
669 value = stack.pop()
670 assert isinstance(value, int)
671 stack.append(value - 1)
672 elif opcode == 149: # typeof
673 value = stack.pop()
674 return {
675 _Undefined: 'undefined',
676 compat_str: 'String',
677 int: 'Number',
678 float: 'Number',
679 }[type(value)]
0cb20563
PH
680 elif opcode == 160: # add
681 value2 = stack.pop()
682 value1 = stack.pop()
683 res = value1 + value2
684 stack.append(res)
685 elif opcode == 161: # subtract
686 value2 = stack.pop()
687 value1 = stack.pop()
688 res = value1 - value2
689 stack.append(res)
54256267
PH
690 elif opcode == 164: # modulo
691 value2 = stack.pop()
692 value1 = stack.pop()
693 res = value1 % value2
694 stack.append(res)
eb537604
PH
695 elif opcode == 171: # equals
696 value2 = stack.pop()
697 value1 = stack.pop()
698 result = value1 == value2
699 stack.append(result)
54256267
PH
700 elif opcode == 175: # greaterequals
701 value2 = stack.pop()
702 value1 = stack.pop()
703 result = value1 >= value2
704 stack.append(result)
705 elif opcode == 208: # getlocal_0
706 stack.append(registers[0])
707 elif opcode == 209: # getlocal_1
708 stack.append(registers[1])
709 elif opcode == 210: # getlocal_2
710 stack.append(registers[2])
711 elif opcode == 211: # getlocal_3
712 stack.append(registers[3])
70f767dc
PH
713 elif opcode == 212: # setlocal_0
714 registers[0] = stack.pop()
715 elif opcode == 213: # setlocal_1
716 registers[1] = stack.pop()
54256267
PH
717 elif opcode == 214: # setlocal_2
718 registers[2] = stack.pop()
719 elif opcode == 215: # setlocal_3
720 registers[3] = stack.pop()
721 else:
722 raise NotImplementedError(
723 'Unsupported opcode %d' % opcode)
724
725 avm_class.method_pyfunctions[func_name] = resfunc
726 return resfunc
727