[swfinterp] Implement bitand and pushshort operators
[youtube-dl] / youtube_dl / swfinterp.py
index 1e8b26ecc4329282efbd85050f84007ab43ea1e1..fc704603ab233353bf488e01c45cb9a8f14c4b5e 100644 (file)
@@ -149,6 +149,21 @@ def _read_byte(reader):
 
 
 StringClass = _AVMClass('(no name idx)', 'String')
+ByteArrayClass = _AVMClass('(no name idx)', 'ByteArray')
+_builtin_classes = {
+    StringClass.name: StringClass,
+    ByteArrayClass.name: ByteArrayClass,
+}
+
+
+class _Undefined(object):
+    def __boolean__(self):
+        return False
+
+    def __hash__(self):
+        return 0
+
+undefined = _Undefined()
 
 
 class SWFInterpreter(object):
@@ -396,7 +411,9 @@ class SWFInterpreter(object):
                 self._classes_by_name, avm_class.variables])
             while True:
                 opcode = _read_byte(coder)
-                if opcode == 16:  # jump
+                if opcode == 9:  # label
+                    pass  # Spec says: "Do nothing."
+                elif opcode == 16:  # jump
                     offset = s24()
                     coder.seek(coder.tell() + offset)
                 elif opcode == 17:  # iftrue
@@ -421,11 +438,28 @@ class SWFInterpreter(object):
                     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)
@@ -450,11 +484,31 @@ class SWFInterpreter(object):
                         [stack.pop() for _ in range(arg_count)]))
                     obj = stack.pop()
 
-                    if isinstance(obj, _AVMClass_Object):
+                    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:
+                            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)
                         continue
+                    elif isinstance(obj, _AVMClass):
+                        func = self.extract_function(obj, mname)
+                        res = func(args)
+                        stack.append(res)
+                        continue
                     elif isinstance(obj, _ScopeDict):
                         if mname in obj.avm_class.method_names:
                             func = self.extract_function(obj.avm_class, mname)
@@ -473,6 +527,13 @@ class SWFInterpreter(object):
                                 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
@@ -486,20 +547,12 @@ class SWFInterpreter(object):
                             res = args[0].join(obj)
                             stack.append(res)
                             continue
-                    elif obj == StringClass:
-                        if mname == 'String':
-                            assert len(args) == 1
-                            assert isinstance(args[0], (int, compat_str))
-                            res = compat_str(args[0])
-                            stack.append(res)
-                            continue
-                        else:
-                            raise NotImplementedError(
-                                'Function String.%s is not yet implemented'
-                                % mname)
                     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
@@ -523,6 +576,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()
@@ -546,8 +610,8 @@ class SWFInterpreter(object):
                             break
                     else:
                         res = scopes[0]
-                    if mname not in res and mname == 'String':
-                        stack.append(StringClass)
+                    if mname not in res and mname in _builtin_classes:
+                        stack.append(_builtin_classes[mname])
                     else:
                         stack.append(res[mname])
                 elif opcode == 94:  # findproperty
@@ -599,7 +663,8 @@ class SWFInterpreter(object):
                         obj = stack.pop()
                         assert isinstance(obj, (dict, _ScopeDict)), \
                             'Accessing member %r on %r' % (pname, obj)
-                        stack.append(obj[pname])
+                        res = obj.get(pname, undefined)
+                        stack.append(res)
                     else:  # Assume attribute access
                         idx = stack.pop()
                         assert isinstance(idx, int)
@@ -612,8 +677,24 @@ class SWFInterpreter(object):
                     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()
@@ -624,11 +705,23 @@ 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()
@@ -639,6 +732,10 @@ class SWFInterpreter(object):
                     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