X-Git-Url: http://git.bitcoin.ninja/index.cgi?p=youtube-dl;a=blobdiff_plain;f=youtube_dl%2Fswfinterp.py;h=0c71585753134e93fba8d8de5cee003d31f050c9;hp=f7a3889a0d99c0297d61850da58d7899b531151d;hb=6cd452acffe8d79c895a2ebd0346e2ba7f9e112f;hpb=70f767dc65189df0118d319d62385e54bd9bb03e diff --git a/youtube_dl/swfinterp.py b/youtube_dl/swfinterp.py index f7a3889a0..0c7158575 100644 --- a/youtube_dl/swfinterp.py +++ b/youtube_dl/swfinterp.py @@ -2,11 +2,13 @@ from __future__ import unicode_literals import collections import io -import struct import zlib -from .utils import ( +from .compat import ( compat_str, + compat_struct_unpack, +) +from .utils import ( ExtractorError, ) @@ -23,17 +25,17 @@ def _extract_tags(file_contents): file_contents[:1]) # Determine number of bits in framesize rectangle - framesize_nbits = struct.unpack('!B', content[:1])[0] >> 3 + framesize_nbits = compat_struct_unpack('!B', content[:1])[0] >> 3 framesize_len = (5 + 4 * framesize_nbits + 7) // 8 pos = framesize_len + 2 + 2 while pos < len(content): - header16 = struct.unpack('> 6 tag_len = header16 & 0x3f if tag_len == 0x3f: - tag_len = struct.unpack('= 0x80) else b'\x00' - return struct.unpack('> 4 methods = {} - if kind in [0x00, 0x06]: # Slot or Const + constants = None + if kind == 0x00: # Slot u30() # Slot id u30() # type_name_idx vindex = u30() if vindex != 0: read_byte() # vkind - elif kind in [0x01, 0x02, 0x03]: # Method / Getter / Setter + elif kind == 0x06: # Const + u30() # Slot id + u30() # type_name_idx + vindex = u30() + vkind = 'any' + if vindex != 0: + vkind = read_byte() + if vkind == 0x03: # Constant_Int + value = self.constant_ints[vindex] + elif vkind == 0x04: # Constant_UInt + value = self.constant_uints[vindex] + else: + return {}, None # Ignore silently for now + constants = {self.multinames[trait_name_idx]: value} + elif kind in (0x01, 0x02, 0x03): # Method / Getter / Setter u30() # disp_id method_idx = u30() methods[self.multinames[trait_name_idx]] = method_idx @@ -283,14 +341,18 @@ class SWFInterpreter(object): for _c3 in range(metadata_count): u30() # metadata index - return methods + return methods, constants # Classes class_count = u30() classes = [] for class_id in range(class_count): name_idx = u30() - classes.append(_AVMClass(name_idx, self.multinames[name_idx])) + + cname = self.multinames[name_idx] + avm_class = _AVMClass(name_idx, cname) + classes.append(avm_class) + u30() # super_name idx flags = read_byte() if flags & 0x08 != 0: # Protected namespace is present @@ -301,19 +363,22 @@ class SWFInterpreter(object): u30() # iinit trait_count = u30() for _c2 in range(trait_count): - parse_traits_info() + trait_methods, trait_constants = parse_traits_info() + avm_class.register_methods(trait_methods) + if trait_constants: + avm_class.constants.update(trait_constants) + assert len(classes) == class_count self._classes_by_name = dict((c.name, c) for c in classes) for avm_class in classes: - u30() # cinit + avm_class.cinit_idx = u30() trait_count = u30() for _c2 in range(trait_count): - trait_methods = parse_traits_info() - avm_class.method_names.update(trait_methods.items()) - avm_class.method_idxs.update(dict( - (idx, name) - for name, idx in trait_methods.items())) + trait_methods, trait_constants = parse_traits_info() + avm_class.register_methods(trait_methods) + if trait_constants: + avm_class.constants.update(trait_constants) # Scripts script_count = u30() @@ -326,6 +391,7 @@ class SWFInterpreter(object): # Method bodies method_body_count = u30() Method = collections.namedtuple('Method', ['code', 'local_count']) + self._all_methods = [] for _c in range(method_body_count): method_idx = u30() u30() # max_stack @@ -334,9 +400,10 @@ class SWFInterpreter(object): u30() # max_scope_depth code_length = u30() code = read_bytes(code_length) + m = Method(code, local_count) + self._all_methods.append(m) for avm_class in classes: if method_idx in avm_class.method_idxs: - m = Method(code, local_count) avm_class.methods[avm_class.method_idxs[method_idx]] = m exception_count = u30() for _c2 in range(exception_count): @@ -351,19 +418,34 @@ class SWFInterpreter(object): assert p + code_reader.tell() == len(code_tag) - def extract_class(self, class_name): + def patch_function(self, avm_class, func_name, f): + self._patched_functions[(avm_class, func_name)] = f + + def extract_class(self, class_name, call_cinit=True): try: - return self._classes_by_name[class_name] + res = self._classes_by_name[class_name] except KeyError: raise ExtractorError('Class %r not found' % class_name) + if call_cinit and hasattr(res, 'cinit_idx'): + res.register_methods({'$cinit': res.cinit_idx}) + res.methods['$cinit'] = self._all_methods[res.cinit_idx] + cinit = self.extract_function(res, '$cinit') + cinit([]) + + return res + def extract_function(self, avm_class, func_name): + p = self._patched_functions.get((avm_class, func_name)) + if p: + return p if func_name in avm_class.method_pyfunctions: return avm_class.method_pyfunctions[func_name] if func_name in self._classes_by_name: return self._classes_by_name[func_name].make_object() if func_name not in avm_class.methods: - raise ExtractorError('Cannot find function %r' % func_name) + raise ExtractorError('Cannot find function %s.%s' % ( + avm_class.name, func_name)) m = avm_class.methods[func_name] def resfunc(args): @@ -372,14 +454,18 @@ class SWFInterpreter(object): s24 = lambda: _s24(coder) u30 = lambda: _u30(coder) - print('Invoking %s.%s(%r)' % (avm_class.name, func_name, tuple(args))) registers = [avm_class.variables] + list(args) + [None] * m.local_count stack = [] - scopes = collections.deque([avm_class.variables]) + scopes = collections.deque([ + self._classes_by_name, avm_class.constants, avm_class.variables]) while True: opcode = _read_byte(coder) - print('opcode: %r, stack(%d): %r' % (opcode, len(stack), stack)) - if opcode == 17: # iftrue + if opcode == 9: # label + pass # Spec says: "Do nothing." + elif opcode == 16: # jump + offset = s24() + coder.seek(coder.tell() + offset) + elif opcode == 17: # iftrue offset = s24() value = stack.pop() if value: @@ -389,9 +475,40 @@ class SWFInterpreter(object): value = stack.pop() if not value: coder.seek(coder.tell() + offset) + elif opcode == 19: # ifeq + offset = s24() + value2 = stack.pop() + value1 = stack.pop() + if value2 == value1: + coder.seek(coder.tell() + offset) + elif opcode == 20: # ifne + offset = s24() + value2 = stack.pop() + value1 = stack.pop() + if value2 != value1: + coder.seek(coder.tell() + offset) + elif opcode == 21: # iflt + offset = s24() + value2 = stack.pop() + value1 = stack.pop() + if value1 < value2: + coder.seek(coder.tell() + offset) + elif opcode == 32: # pushnull + stack.append(None) + elif opcode == 33: # pushundefined + stack.append(undefined) elif opcode == 36: # pushbyte v = _read_byte(coder) stack.append(v) + elif opcode == 37: # pushshort + v = u30() + stack.append(v) + elif opcode == 38: # pushtrue + stack.append(True) + elif opcode == 39: # pushfalse + stack.append(False) + elif opcode == 40: # pushnan + stack.append(float('NaN')) elif opcode == 42: # dup value = stack[-1] stack.append(value) @@ -401,6 +518,13 @@ class SWFInterpreter(object): elif opcode == 48: # pushscope new_scope = stack.pop() scopes.append(new_scope) + elif opcode == 66: # construct + arg_count = u30() + args = list(reversed( + [stack.pop() for _ in range(arg_count)])) + obj = stack.pop() + res = obj.avm_class.make_object() + stack.append(res) elif opcode == 70: # callproperty index = u30() mname = self.multinames[index] @@ -408,36 +532,85 @@ class SWFInterpreter(object): args = list(reversed( [stack.pop() for _ in range(arg_count)])) obj = stack.pop() - if mname == 'split': - assert len(args) == 1 - assert isinstance(args[0], compat_str) - assert isinstance(obj, compat_str) - if args[0] == '': - res = list(obj) + + if obj == StringClass: + if mname == 'String': + assert len(args) == 1 + assert isinstance(args[0], ( + int, compat_str, _Undefined)) + if args[0] == undefined: + res = 'undefined' + else: + res = compat_str(args[0]) + stack.append(res) + continue else: - res = obj.split(args[0]) + raise NotImplementedError( + 'Function String.%s is not yet implemented' + % mname) + elif isinstance(obj, _AVMClass_Object): + func = self.extract_function(obj.avm_class, mname) + res = func(args) stack.append(res) - elif mname == 'slice': - assert len(args) == 1 - assert isinstance(args[0], int) - assert isinstance(obj, list) - res = obj[args[0]:] + continue + elif isinstance(obj, _AVMClass): + func = self.extract_function(obj, mname) + res = func(args) stack.append(res) - elif mname == 'join': - assert len(args) == 1 - assert isinstance(args[0], compat_str) - assert isinstance(obj, list) - res = args[0].join(obj) + continue + elif isinstance(obj, _ScopeDict): + if mname in obj.avm_class.method_names: + func = self.extract_function(obj.avm_class, mname) + res = func(args) + else: + res = obj[mname] stack.append(res) - elif mname in avm_class.method_pyfunctions: - stack.append(avm_class.method_pyfunctions[mname](args)) - else: - raise NotImplementedError( - 'Unsupported property %r on %r' - % (mname, obj)) + continue + elif isinstance(obj, compat_str): + if mname == 'split': + assert len(args) == 1 + assert isinstance(args[0], compat_str) + if args[0] == '': + res = list(obj) + else: + res = obj.split(args[0]) + stack.append(res) + continue + elif mname == 'charCodeAt': + assert len(args) <= 1 + idx = 0 if len(args) == 0 else args[0] + assert isinstance(idx, int) + res = ord(obj[idx]) + stack.append(res) + continue + elif isinstance(obj, list): + if mname == 'slice': + assert len(args) == 1 + assert isinstance(args[0], int) + res = obj[args[0]:] + stack.append(res) + continue + elif mname == 'join': + assert len(args) == 1 + assert isinstance(args[0], compat_str) + res = args[0].join(obj) + stack.append(res) + continue + raise NotImplementedError( + 'Unsupported property %r on %r' + % (mname, obj)) + elif opcode == 71: # returnvoid + res = undefined + return res elif opcode == 72: # returnvalue res = stack.pop() return res + elif opcode == 73: # constructsuper + # Not yet implemented, just hope it works without it + arg_count = u30() + args = list(reversed( + [stack.pop() for _ in range(arg_count)])) + obj = stack.pop() elif opcode == 74: # constructproperty index = u30() arg_count = u30() @@ -446,11 +619,11 @@ class SWFInterpreter(object): obj = stack.pop() mname = self.multinames[index] - construct_method = self.extract_function( - obj.avm_class, mname) + assert isinstance(obj, _AVMClass) + # We do not actually call the constructor for now; # we just pretend it does nothing - stack.append(obj) + stack.append(obj.make_object()) elif opcode == 79: # callpropvoid index = u30() mname = self.multinames[index] @@ -458,6 +631,17 @@ class SWFInterpreter(object): args = list(reversed( [stack.pop() for _ in range(arg_count)])) obj = stack.pop() + if isinstance(obj, _AVMClass_Object): + func = self.extract_function(obj.avm_class, mname) + res = func(args) + assert res is undefined + continue + if isinstance(obj, _ScopeDict): + assert mname in obj.avm_class.method_names + func = self.extract_function(obj.avm_class, mname) + res = func(args) + assert res is undefined + continue if mname == 'reverse': assert isinstance(obj, list) obj.reverse() @@ -481,7 +665,10 @@ class SWFInterpreter(object): break else: res = scopes[0] - stack.append(res) + if mname not in res and mname in _builtin_classes: + stack.append(_builtin_classes[mname]) + else: + stack.append(res[mname]) elif opcode == 94: # findproperty index = u30() mname = self.multinames[index] @@ -490,7 +677,7 @@ class SWFInterpreter(object): res = s break else: - res = scopes[0] + res = avm_class.variables stack.append(res) elif opcode == 96: # getlex index = u30() @@ -500,15 +687,23 @@ class SWFInterpreter(object): scope = s break else: - scope = scopes[0] - # I cannot find where static variables are initialized - # so let's just return None - res = scope.get(mname) + scope = avm_class.variables + + if mname in scope: + res = scope[mname] + elif mname in _builtin_classes: + res = _builtin_classes[mname] + else: + # Assume uninitialized + # TODO warn here + res = undefined stack.append(res) elif opcode == 97: # setproperty index = u30() value = stack.pop() idx = self.multinames[index] + if isinstance(idx, _Multiname): + idx = stack.pop() obj = stack.pop() obj[idx] = value elif opcode == 98: # getlocal @@ -523,22 +718,57 @@ class SWFInterpreter(object): pname = self.multinames[index] if pname == 'length': obj = stack.pop() - assert isinstance(obj, list) + assert isinstance(obj, (compat_str, list)) stack.append(len(obj)) + elif isinstance(pname, compat_str): # Member access + obj = stack.pop() + if isinstance(obj, _AVMClass): + res = obj.static_properties[pname] + stack.append(res) + continue + + assert isinstance(obj, (dict, _ScopeDict)),\ + 'Accessing member %r on %r' % (pname, obj) + res = obj.get(pname, undefined) + stack.append(res) else: # Assume attribute access idx = stack.pop() assert isinstance(idx, int) obj = stack.pop() assert isinstance(obj, list) stack.append(obj[idx]) + elif opcode == 104: # initproperty + index = u30() + value = stack.pop() + idx = self.multinames[index] + if isinstance(idx, _Multiname): + idx = stack.pop() + obj = stack.pop() + obj[idx] = value elif opcode == 115: # convert_ value = stack.pop() intvalue = int(value) stack.append(intvalue) elif opcode == 128: # coerce u30() + elif opcode == 130: # coerce_a + value = stack.pop() + # um, yes, it's any value + stack.append(value) elif opcode == 133: # coerce_s assert isinstance(stack[-1], (type(None), compat_str)) + elif opcode == 147: # decrement + value = stack.pop() + assert isinstance(value, int) + stack.append(value - 1) + elif opcode == 149: # typeof + value = stack.pop() + return { + _Undefined: 'undefined', + compat_str: 'String', + int: 'Number', + float: 'Number', + }[type(value)] elif opcode == 160: # add value2 = stack.pop() value1 = stack.pop() @@ -549,16 +779,37 @@ class SWFInterpreter(object): value1 = stack.pop() res = value1 - value2 stack.append(res) + elif opcode == 162: # multiply + value2 = stack.pop() + value1 = stack.pop() + res = value1 * value2 + stack.append(res) elif opcode == 164: # modulo value2 = stack.pop() value1 = stack.pop() res = value1 % value2 stack.append(res) + elif opcode == 168: # bitand + value2 = stack.pop() + value1 = stack.pop() + assert isinstance(value1, int) + assert isinstance(value2, int) + res = value1 & value2 + stack.append(res) + elif opcode == 171: # equals + value2 = stack.pop() + value1 = stack.pop() + result = value1 == value2 + stack.append(result) elif opcode == 175: # greaterequals value2 = stack.pop() value1 = stack.pop() result = value1 >= value2 stack.append(result) + elif opcode == 192: # increment_i + value = stack.pop() + assert isinstance(value, int) + stack.append(value + 1) elif opcode == 208: # getlocal_0 stack.append(registers[0]) elif opcode == 209: # getlocal_1 @@ -581,4 +832,3 @@ class SWFInterpreter(object): avm_class.method_pyfunctions[func_name] = resfunc return resfunc -