Beispiel #1
0
    def sub_operations(jar, cf, classes, instruction,
                       operand, arg_names=[""]):
        """Gets the instructions of a different class"""
        invoked_class = operand.c + ".class"
        name = operand.name
        descriptor = operand.descriptor
        args = method_descriptor(descriptor).args_descriptor
        cache_key = "%s/%s/%s/%s" % (invoked_class, name,
                                     descriptor, _PIT.join(arg_names, ","))

        if cache_key in _PIT.CACHE:
            cache = _PIT.CACHE[cache_key]
            operations = [op.clone() for op in cache]
        else:
            operations = _PIT.operations(jar, invoked_class, classes,
                                         args, name, arg_names)

        position = 0
        for operation in _PIT.ordered_operations(operations):
            position += 0.01
            operation.position = instruction.pos + (position)

        _PIT.CACHE[cache_key] = operations

        return operations
Beispiel #2
0
    def sub_operations(classloader,
                       cf,
                       classes,
                       instruction,
                       verbose,
                       operand,
                       arg_names=[""]):
        """Gets the instructions of a different class"""
        invoked_class = operand.c + ".class"
        name = operand.name
        descriptor = operand.descriptor
        args = method_descriptor(descriptor).args_descriptor
        cache_key = "%s/%s/%s/%s" % (invoked_class, name, descriptor,
                                     _PIT.join(arg_names, ","))

        if cache_key in _PIT.CACHE:
            cache = _PIT.CACHE[cache_key]
            operations = [op.clone() for op in cache]
        else:
            operations = _PIT.operations(classloader, invoked_class, classes,
                                         verbose, args, name, arg_names)

        position = 0
        for operation in _PIT.ordered_operations(operations):
            position += 0.01
            operation.position = instruction.pos + (position)

        _PIT.CACHE[cache_key] = operations

        return operations
Beispiel #3
0
def parse_srg_line(line):
    if line.startswith("PK"): # package
        s = pk_regex.search(line)
        return ("PK", (s.group(1), s.group(2)))
    elif line.startswith("CL"): # class
        s = cl_regex.search(line)
        return ("CL", (s.group(1), s.group(2)))
    elif line.startswith("FD"): # field
        s = fd_regex.search(line)
        return ("FD", (s.group(1), s.group(2)))
    elif line.startswith("MD"): # method
        s = md_regex.search(line)
        return ("MD", (
                (s.group(1), method_descriptor(s.group(2))), 
                (s.group(3), method_descriptor(s.group(4)))))
    else:
        raise RuntimeError("Unknown field in srg file: %s" % line)
Beispiel #4
0
            def on_invoke(self, ins, const, obj, args):
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)

                if ins.mnemonic == "invokestatic":
                    if const.class_.name.value == superclass:
                        # Call to the static register method.
                        text_id = args[0]
                        current_block = args[1]
                        current_block["text_id"] = text_id
                        current_block["numeric_id"] = self.cur_id
                        self.cur_id += 1
                        lang_key = "minecraft.%s" % text_id
                        if language != None and lang_key in language:
                            current_block["display_name"] = language[lang_key]
                        block[text_id] = current_block
                        ordered_blocks.append(text_id)
                    elif const.class_.name.value == builder_class:
                        if desc.args[0].name == superclass: # Copy constructor
                            copy = dict(args[0])
                            del copy["text_id"]
                            del copy["numeric_id"]
                            del copy["class"]
                            if "display_name" in copy:
                                del copy["display_name"]
                            return copy
                        else:
                            return {} # Append current block
                else:
                    if method_name == "hasNext":
                        # We've reached the end of block registration
                        # (and have started iterating over registry keys)
                        raise StopIteration()

                    if method_name == hardness_setter.name.value and method_desc == hardness_setter.descriptor.value:
                        obj["hardness"] = args[0]
                        # resistance is args[1]
                    elif method_name == hardness_setter_2.name.value and method_desc == hardness_setter_2.descriptor.value:
                        obj["hardness"] = args[0]
                        # resistance is args[0]
                    elif method_name == hardness_setter_3.name.value and method_desc == hardness_setter_3.descriptor.value:
                        obj["hardness"] = 0.0
                        # resistance is 0.0
                    elif method_name == "<init>":
                        # Call to the constructor for the block
                        # We can't hardcode index 0 because sand has an extra parameter, so use the last one
                        # There are also cases where it's an arg-less constructor; we don't want to do anything there.
                        if len(args) > 0:
                            obj.update(args[-1])

                    if desc.returns.name == builder_class:
                        return obj
                    elif desc.returns.name == aggregate["classes"]["identifier"]:
                        # Probably getting the air identifier from the registry
                        return "air"
                    elif desc.returns.name != "void":
                        return object()
Beispiel #5
0
    def _process_113_classes_new(aggregate, classloader, verbose):
        # After 18w16a, biomes used a builder again.  The name is now also translatable.

        for biome in six.itervalues(aggregate["biomes"]["biome"]):
            biome["name"] = aggregate["language"]["biome"]["minecraft." +
                                                           biome["text_id"]]

            cf = classloader[biome["class"]]
            method = cf.methods.find_one(name="<init>")
            stack = []
            for ins in method.code.disassemble():
                if ins == "invokespecial":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    if const.class_.name.value == cf.super_.name.value and name == "<init>":
                        # Calling biome init; we're done
                        break
                elif ins == "invokevirtual":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    desc = method_descriptor(
                        const.name_and_type.descriptor.value)

                    if len(desc.args) == 1:
                        if desc.args[0].name == "float":
                            # Ugly portion - different methods with different names
                            # Hopefully the order doesn't change
                            if name == "a":
                                biome["height"][0] = stack.pop()
                            elif name == "b":
                                biome["height"][1] = stack.pop()
                            elif name == "c":
                                biome["temperature"] = stack.pop()
                            elif name == "d":
                                biome["rainfall"] = stack.pop()
                        elif desc.args[0].name == "java/lang/String":
                            val = stack.pop()
                            if val is not None:
                                biome["mutated_from"] = val

                    stack = []
                # Constants
                elif ins in ("ldc", "ldc_w"):
                    const = ins.operands[0]
                    if isinstance(const, String):
                        stack.append(const.string.value)
                    if isinstance(const, (Integer, Float)):
                        stack.append(const.value)

                elif ins.mnemonic.startswith("fconst"):
                    stack.append(float(ins.mnemonic[-1]))
                elif ins in ("bipush", "sipush"):
                    stack.append(ins.operands[0].value)
                elif ins == "aconst_null":
                    stack.append(None)
Beispiel #6
0
    def _process_113_classes_new(aggregate, classloader, verbose):
        # After 18w16a, biomes used a builder again.  The name is now also translatable.

        for biome in six.itervalues(aggregate["biomes"]["biome"]):
            biome["name"] = aggregate["language"]["biome"]["minecraft." + biome["text_id"]]

            cf = classloader[biome["class"]]
            method = cf.methods.find_one(name="<init>")
            stack = []
            for ins in method.code.disassemble():
                if ins == "invokespecial":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    if const.class_.name.value == cf.super_.name.value and name == "<init>":
                        # Calling biome init; we're done
                        break
                elif ins == "invokevirtual":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    desc = method_descriptor(const.name_and_type.descriptor.value)

                    if len(desc.args) == 1:
                        if desc.args[0].name == "float":
                            # Ugly portion - different methods with different names
                            # Hopefully the order doesn't change
                            if name == "a":
                                biome["height"][0] = stack.pop()
                            elif name == "b":
                                biome["height"][1] = stack.pop()
                            elif name == "c":
                                biome["temperature"] = stack.pop()
                            elif name == "d":
                                biome["rainfall"] = stack.pop()
                        elif desc.args[0].name == "java/lang/String":
                            val = stack.pop()
                            if val is not None:
                                biome["mutated_from"] = val

                    stack = []
                # Constants
                elif ins in ("ldc", "ldc_w"):
                    const = ins.operands[0]
                    if isinstance(const, String):
                        stack.append(const.string.value)
                    if isinstance(const, (Integer, Float)):
                        stack.append(const.value)

                elif ins.mnemonic.startswith("fconst"):
                    stack.append(float(ins.mnemonic[-1]))
                elif ins in ("bipush", "sipush"):
                    stack.append(ins.operands[0].value)
                elif ins == "aconst_null":
                    stack.append(None)
Beispiel #7
0
def try_eval_lambda(ins, args, cf):
    """
    Attempts to call a lambda function that returns a constant value.
    May throw; this code is very hacky.
    """
    const = ins.operands[0]
    bootstrap = cf.bootstrap_methods[const.method_attr_index]
    method = cf.constants.get(bootstrap.method_ref)
    # Make sure this is a reference to LambdaMetafactory
    assert method.reference_kind == 6  # REF_invokeStatic
    assert method.reference.class_.name == "java/lang/invoke/LambdaMetafactory"
    assert method.reference.name_and_type.name == "metafactory"
    assert len(bootstrap.bootstrap_args) == 3  # Num arguments
    methodhandle = cf.constants.get(bootstrap.bootstrap_args[1])
    assert methodhandle.reference_kind == 6  # REF_invokeStatic
    # We only want to deal with lambdas in the same class
    assert methodhandle.reference.class_.name == cf.this.name

    name2 = methodhandle.reference.name_and_type.name.value
    desc2 = method_descriptor(
        methodhandle.reference.name_and_type.descriptor.value)

    lambda_method = cf.methods.find_one(name=name2,
                                        args=desc2.args_descriptor,
                                        returns=desc2.returns_descriptor)
    assert lambda_method

    class Callback(WalkerCallback):
        def on_new(self, ins, const):
            raise Exception("Illegal new")

        def on_invoke(self, ins, const, obj, args):
            raise Exception("Illegal invoke")

        def on_get_field(self, ins, const, obj):
            raise Exception("Illegal getfield")

        def on_put_field(self, ins, const, obj, value):
            raise Exception("Illegal putfield")

    # Set verbose to false because we don't want lots of output if this errors
    # (since it is expected to for more complex methods)
    return walk_method(cf, lambda_method, Callback(), False, args)
Beispiel #8
0
            def on_invokedynamic(self, ins, const, args):
                # 1.15-pre2 introduced a Supplier<BlockEntityType> parameter,
                # and while most blocks handled it in their own constructor,
                # chests put it directly in initialization.  We don't care about
                # the value (we get block entities in a different way), but
                # we still need to override this as the default implementation
                # raises an exception

                # 20w12a changed light levels to use a lambda, and we do
                # care about those.  The light level is a ToIntFunction<BlockState>.
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)
                if desc.returns.name == "java/util/function/ToIntFunction":
                    # Try to invoke the function.
                    try:
                        args.append(object())  # The state that the lambda gets
                        return try_eval_lambda(ins, args, lcf)
                    except Exception as ex:
                        if verbose:
                            print("Failed to call lambda for light data:", ex)
                        return None
                else:
                    return object()
Beispiel #9
0
    def __init__(self, ins, cf, const):
        super().__init__(ins, cf, const)

        bootstrap = cf.bootstrap_methods[const.method_attr_index]
        method = cf.constants.get(bootstrap.method_ref)
        # Make sure this is a reference to StringConcatFactory.makeConcatWithConstants
        assert method.reference_kind == REF_invokeStatic
        assert method.reference.class_.name == "java/lang/invoke/StringConcatFactory"
        assert method.reference.name_and_type.name == "makeConcatWithConstants"
        assert len(bootstrap.bootstrap_args) == 1 # Num arguments - may change with constants

        # Now check the arguments.  Note that StringConcatFactory has some
        # arguments automatically filled in.  The bootstrap arguments are:
        # args[0] is recipe, format string
        # Further arguments presumably go into the constants varargs array, but I haven't seen this
        # (and I'm not sure how you get a constant that can't be converted to a string at compile time)
        self.recipe = cf.constants.get(bootstrap.bootstrap_args[0]).string.value
        assert '\u0002' not in self.recipe

        self.dynamic_name = const.name_and_type.name.value
        self.dynamic_desc = method_descriptor(const.name_and_type.descriptor.value)

        assert self.dynamic_desc.returns.name == "java/lang/String"
Beispiel #10
0
def walk_method(cf, method, callback, verbose):
    assert isinstance(callback, WalkerCallback)

    stack = []
    locals = {}
    # TODO: Allow passing argument values in or something like that
    cur_index = 0
    if not method.access_flags.acc_static:
        locals[cur_index] = object()
        cur_index += 1
    for arg in method.args:
        locals[cur_index] = object()
        cur_index += 1

    for ins in method.code.disassemble():
        if ins in ("bipush", "sipush"):
            stack.append(ins.operands[0].value)
        elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith("dconst"):
            stack.append(float(ins.mnemonic[-1]))
        elif ins == "aconst_null":
            stack.append(None)
        elif ins in ("ldc", "ldc_w", "ldc2_w"):
            const = ins.operands[0]

            if isinstance(const, ConstantClass):
                stack.append("%s.class" % const.name.value)
            elif isinstance(const, String):
                stack.append(const.string.value)
            else:
                stack.append(const.value)
        elif ins == "new":
            const = ins.operands[0]

            try:
                stack.append(callback.on_new(ins, const))
            except StopIteration:
                break
        elif ins in ("getfield", "getstatic"):
            const = ins.operands[0]
            if ins.mnemonic != "getstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                stack.append(callback.on_get_field(ins, const, obj))
            except StopIteration:
                break
        elif ins in ("putfield", "putstatic"):
            const = ins.operands[0]
            value = stack.pop()
            if ins.mnemonic != "putstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                callback.on_put_field(ins, const, obj, value)
            except StopIteration:
                break
        elif ins in ("invokevirtual", "invokespecial", "invokeinterface", "invokestatic"):
            const = ins.operands[0]
            method_desc = const.name_and_type.descriptor.value
            desc = method_descriptor(method_desc)
            num_args = len(desc.args)

            args = []

            for i in six.moves.range(num_args):
                args.insert(0, stack.pop())
            if ins.mnemonic != "invokestatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                ret = callback.on_invoke(ins, const, obj, args)
            except StopIteration:
                break
            if desc.returns.name != "void":
                stack.append(ret)
        elif ins == "astore":
            locals[ins.operands[0].value] = stack.pop()
        elif ins == "aload":
            stack.append(locals[ins.operands[0].value])
        elif ins == "dup":
            stack.append(stack[-1])
        elif ins == "pop":
            stack.pop()
        elif ins == "anewarray":
            stack.append([None] * stack.pop())
        elif ins == "newarray":
            stack.append([0] * stack.pop())
        elif ins in ("aastore", "iastore", "fastore"):
            value = stack.pop()
            index = stack.pop()
            array = stack.pop()
            if isinstance(array, list) and isinstance(index, int):
                array[index] = value
            elif verbose:
                print("Failed to execute %s: array %s index %s value %s" % (ins, array, index, value))
        elif ins == "invokedynamic":
            stack.append(callback.on_invokedynamic(ins, ins.operands[0]))
        elif ins in ("checkcast", "return"):
            pass
        elif verbose:
            print("Unknown instruction %s: stack is %s" % (ins, stack))
Beispiel #11
0
            def on_invoke(self, ins, const, obj, args):
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)

                if ins.mnemonic == "invokestatic":
                    if const.class_.name.value == listclass:
                        if len(desc.args) == 2 and desc.args[
                                0].name == "java/lang/String" and desc.args[
                                    1].name == superclass:
                            # Call to the static register method.
                            text_id = args[0]
                            current_block = args[1]
                            current_block["text_id"] = text_id
                            current_block["numeric_id"] = self.cur_id
                            self.cur_id += 1
                            lang_key = "minecraft.%s" % text_id
                            if language != None and lang_key in language:
                                current_block["display_name"] = language[
                                    lang_key]
                            block[text_id] = current_block
                            ordered_blocks.append(text_id)
                            return current_block
                        elif len(desc.args) == 1 and desc.args[
                                0].name == "int" and desc.returns.name == "java/util/function/ToIntFunction":
                            # 20w12a+: a method that takes a light level and returns a function
                            # that checks if the current block state has the lit state set,
                            # using light level 0 if not and the given light level if so.
                            # For our purposes, just simplify it to always be the given light level.
                            return args[0]
                        else:
                            # In 20w12a+ (1.16), some blocks (e.g. logs) use a separate method
                            # for initialization.  Call them.
                            sub_method = lcf.methods.find_one(
                                name=method_name,
                                args=desc.args_descriptor,
                                returns=desc.returns_descriptor)
                            return walk_method(lcf, sub_method, self, verbose,
                                               args)
                    elif const.class_.name.value == builder_class:
                        if desc.args[0].name == superclass:  # Copy constructor
                            copy = dict(args[0])
                            del copy["text_id"]
                            del copy["numeric_id"]
                            del copy["class"]
                            if "display_name" in copy:
                                del copy["display_name"]
                            return copy
                        else:
                            return {}  # Append current block
                else:
                    if method_name == "hasNext":
                        # We've reached the end of block registration
                        # (and have started iterating over registry keys)
                        raise StopIteration()

                    if method_name == hardness_setter.name.value and method_desc == hardness_setter.descriptor.value:
                        obj["hardness"] = args[0]
                        obj["resistance"] = args[1]
                    elif method_name == hardness_setter_2.name.value and method_desc == hardness_setter_2.descriptor.value:
                        obj["hardness"] = args[0]
                        obj["resistance"] = args[0]
                    elif method_name == hardness_setter_3.name.value and method_desc == hardness_setter_3.descriptor.value:
                        obj["hardness"] = 0.0
                        obj["resistance"] = 0.0
                    elif method_name == light_setter.name.value and method_desc == light_setter.descriptor.value:
                        if args[0] != None:
                            obj["light"] = args[0]
                    elif method_name == "<init>":
                        # Call to the constructor for the block
                        # The majority of blocks have a 1-arg constructor simply taking the builder.
                        # However, sand has public BlockSand(int color, Block.Builder builder), and
                        # signs (as of 1.15-pre1) have public BlockSign(Block.builder builder, WoodType type)
                        # (Prior to that 1.15-pre1, we were able to assume that the last argument was the builder)
                        # There are also cases of arg-less constructors, which we just ignore as they are presumably not blocks.
                        for idx, arg in enumerate(desc.args):
                            if arg.name == builder_class:
                                obj.update(args[idx])
                                break

                    if desc.returns.name == builder_class or desc.returns.name == superclass:
                        return obj
                    elif desc.returns.name == aggregate["classes"][
                            "identifier"]:
                        # Probably getting the air identifier from the registry
                        return "air"
                    elif desc.returns.name != "void":
                        return object()
Beispiel #12
0
            def on_invoke(self, ins, const, obj, args):
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)

                if ins.mnemonic == "invokestatic":
                    if const.class_.name.value == listclass:
                        # Call to the static register method.
                        text_id = args[0]
                        current_block = args[1]
                        current_block["text_id"] = text_id
                        current_block["numeric_id"] = self.cur_id
                        self.cur_id += 1
                        lang_key = "minecraft.%s" % text_id
                        if language != None and lang_key in language:
                            current_block["display_name"] = language[lang_key]
                        block[text_id] = current_block
                        ordered_blocks.append(text_id)
                        return current_block
                    elif const.class_.name.value == builder_class:
                        if desc.args[0].name == superclass:  # Copy constructor
                            copy = dict(args[0])
                            del copy["text_id"]
                            del copy["numeric_id"]
                            del copy["class"]
                            if "display_name" in copy:
                                del copy["display_name"]
                            return copy
                        else:
                            return {}  # Append current block
                else:
                    if method_name == "hasNext":
                        # We've reached the end of block registration
                        # (and have started iterating over registry keys)
                        raise StopIteration()

                    if method_name == hardness_setter.name.value and method_desc == hardness_setter.descriptor.value:
                        obj["hardness"] = args[0]
                        obj["resistance"] = args[1]
                    elif method_name == hardness_setter_2.name.value and method_desc == hardness_setter_2.descriptor.value:
                        obj["hardness"] = args[0]
                        obj["resistance"] = args[0]
                    elif method_name == hardness_setter_3.name.value and method_desc == hardness_setter_3.descriptor.value:
                        obj["hardness"] = 0.0
                        obj["resistance"] = 0.0
                    elif method_name == light_setter.name.value and method_desc == light_setter.descriptor.value:
                        obj["light"] = args[0]
                    elif method_name == "<init>":
                        # Call to the constructor for the block
                        # The majority of blocks have a 1-arg constructor simply taking the builder.
                        # However, sand has public BlockSand(int color, Block.Builder builder), and
                        # signs (as of 1.15-pre1) have public BlockSign(Block.builder builder, WoodType type)
                        # (Prior to that 1.15-pre1, we were able to assume that the last argument was the builder)
                        # There are also cases of arg-less constructors, which we just ignore as they are presumably not blocks.
                        for idx, arg in enumerate(desc.args):
                            if arg.name == builder_class:
                                obj.update(args[idx])
                                break

                    if desc.returns.name == builder_class or desc.returns.name == superclass:
                        return obj
                    elif desc.returns.name == aggregate["classes"][
                            "identifier"]:
                        # Probably getting the air identifier from the registry
                        return "air"
                    elif desc.returns.name != "void":
                        return object()
Beispiel #13
0
        def process_class(name):
            """
            Gets the properties for the given block class, checking the parent
            class if none are defined.  Returns the properties, and also adds
            them to properties_by_class
            """
            if name in properties_by_class:
                # Caching - avoid reading the same class multiple times
                return properties_by_class[name]

            cf = classloader[name]
            method = cf.methods.find_one(f=matches)

            if not method:
                properties = process_class(cf.super_.name.value)
                properties_by_class[name] = properties
                return properties

            properties = None
            if_pos = None
            stack = []
            for ins in method.code.disassemble():
                # This could _almost_ just be checking for getstatic, but
                # brewing stands use an array of properties as the field,
                # so we need some stupid extra logic.
                if ins == "new":
                    assert not is_18w19a # In 18w19a this should be a parameter
                    const = ins.operands[0]
                    type_name = const.name.value
                    assert type_name == blockstatecontainer
                    stack.append(object())
                elif ins == "aload" and ins.operands[0].value == 1:
                    assert is_18w19a # The parameter is only used in 18w19a and above
                    stack.append(object())
                elif ins in ("sipush", "bipush"):
                    stack.append(ins.operands[0].value)
                elif ins in ("anewarray", "newarray"):
                    length = stack.pop()
                    val = [None] * length
                    stack.append(val)
                elif ins == "getstatic":
                    const = ins.operands[0]
                    prop = {
                        "field_name": const.name_and_type.name.value
                    }
                    desc = field_descriptor(const.name_and_type.descriptor.value)
                    _property_types.add(desc.name)
                    stack.append(prop)
                elif ins == "aaload":
                    index = stack.pop()
                    array = stack.pop()
                    prop = array.copy()
                    prop["array_index"] = index
                    stack.append(prop)
                elif ins == "aastore":
                    value = stack.pop()
                    index = stack.pop()
                    array = stack.pop()
                    array[index] = value
                elif ins == "dup":
                    stack.append(stack[-1])
                elif ins == "invokespecial":
                    const = ins.operands[0]
                    assert const.name_and_type.name == "<init>"
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    assert len(desc.args) == 2

                    # Normally this constructor call would return nothing, but
                    # in this case we'd rather remove the object it's called on
                    # and keep the properties array (its parameter)
                    arg = stack.pop()
                    stack.pop() # Block
                    stack.pop() # Invocation target
                    stack.append(arg)
                elif ins == "invokevirtual":
                    # Two possibilities (both only present pre-flattening):
                    # 1. It's isDouble() for a slab.  Two different sets of
                    #    properties in that case.
                    # 2. It's getTypeProperty() for flowers.  Only one
                    #    set of properties, but other hacking is needed.
                    # We can differentiate these cases based off of the return
                    # type.
                    # There is a third option post 18w19a:
                    # 3. It's calling the state container's register method.
                    # We can check this just by the type.
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)

                    if const.class_.name == blockstatecontainer:
                        # Case 3.
                        assert properties == None
                        properties = stack.pop()
                        assert desc.returns.name == blockstatecontainer
                        # Don't pop anything, since we'd just pop and re-add the builder
                    elif desc.returns.name == "boolean":
                        # Case 2.
                        properties = [None, None]
                        stack.pop() # Target object
                        # XXX shouldn't something be returned here?
                    else:
                        # Case 1.
                        # Assume that the return type is the base interface
                        # for properties
                        stack.pop() # Target object
                        stack.append(None)
                elif ins == "ifeq":
                    assert if_pos is None
                    if_pos = ins.pos + ins.operands[0].value
                elif ins == "pop":
                    stack.pop()
                elif ins == "areturn":
                    assert not is_18w19a # In 18w19a we don't return a container
                    if if_pos == None:
                        assert properties == None
                        properties = stack.pop()
                    else:
                        assert isinstance(properties, list)
                        index = 0 if ins.pos < if_pos else 1
                        assert properties[index] == None
                        properties[index] = stack.pop()
                elif ins == "return":
                    assert is_18w19a # We only return void in 18w19a
                elif ins == "aload":
                    assert ins.operands[0].value == 0 # Should be aload_0 (this)
                    stack.append(object())
                elif verbose:
                    print("%s createBlockState contains unimplemented ins %s" % (name, ins))

            if properties is None:
                # If we never set properties, warn; however, this is normal for
                # the base implementation in Block in 18w19a
                if verbose and name != aggregate["classes"]["block.superclass"]:
                    print("Didn't find anything that set properties for %s" % name)
                properties = []
            properties_by_class[name] = properties
            return properties
Beispiel #14
0
 def args(self) -> List[JVMType]:
     """
     A list of :class:`~jawa.util.descriptor.JVMType` representing the
     method's argument list.
     """
     return method_descriptor(self.descriptor.value).args
Beispiel #15
0
            def on_invoke(self, ins, const, obj, args):
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)

                if ins == "invokestatic":
                    if const.class_.name == superclass:
                        current_item = {}

                        text_id = None
                        idx = 0
                        for arg in desc.args:
                            if arg.name == blockclass:
                                block = args[idx]
                                text_id = block["text_id"]
                                if "name" in block:
                                    current_item["name"] = block["name"]
                                if "display_name" in block:
                                    current_item["display_name"] = block["display_name"]
                            elif arg.name == superclass:
                                current_item.update(args[idx])
                            elif arg.name == item_block_class:
                                current_item.update(args[idx])
                                text_id = current_item["text_id"]
                            elif arg.name == "java/lang/String":
                                text_id = args[idx]
                            idx += 1

                        if current_item == {} and not text_id:
                            if verbose:
                                print("Couldn't find any identifying information for the call to %s with %s" % (method_desc, args))
                            return

                        if not text_id:
                            if verbose:
                                print("Could not find text_id for call to %s with %s" % (method_desc, args))
                            return

                        # Call to the static register method.
                        current_item["text_id"] = text_id
                        current_item["numeric_id"] = self.cur_id
                        self.cur_id += 1
                        lang_key = "minecraft.%s" % text_id
                        if language != None and lang_key in language:
                            current_item["display_name"] = language[lang_key]
                        if "max_stack_size" not in current_item:
                            current_item["max_stack_size"] = 64
                        item_list[text_id] = current_item
                else:
                    if method_name == "<init>":
                        # Call to a constructor.  Check if the builder is in the args,
                        # and if so update the item with it
                        idx = 0
                        for arg in desc.args:
                            if arg.name == builder_class:
                                # Update from the builder
                                if "max_stack_size" in args[idx]:
                                    obj["max_stack_size"] = args[idx]["max_stack_size"]
                            elif arg.name == blockclass and "text_id" not in obj:
                                block = args[idx]
                                obj["text_id"] = block["text_id"]
                                if "name" in block:
                                    obj["name"] = block["name"]
                                if "display_name" in block:
                                    obj["display_name"] = block["display_name"]
                            idx += 1
                    elif method_name == max_stack_method.name.value and method_desc == max_stack_method.descriptor.value:
                        obj["max_stack_size"] = args[0]

                if desc.returns.name != "void":
                    if desc.returns.name == builder_class or is_item_class(desc.returns.name):
                        if ins == "invokestatic":
                            # Probably returning itself, but through a synthetic method
                            return args[0]
                        else:
                            # Probably returning itself
                            return obj
                    else:
                        return object()
Beispiel #16
0
    def __init__(self, ins, cf, const):
        super().__init__(ins, cf, const)
        self.generated_cf = None
        self.generated_method = None

        bootstrap = cf.bootstrap_methods[const.method_attr_index]
        method = cf.constants.get(bootstrap.method_ref)
        # Make sure this is a reference to LambdaMetafactory.metafactory
        assert method.reference_kind == REF_invokeStatic
        assert method.reference.class_.name == "java/lang/invoke/LambdaMetafactory"
        assert method.reference.name_and_type.name == "metafactory"
        assert len(bootstrap.bootstrap_args) == 3 # Num arguments
        # It could also be a reference to LambdaMetafactory.altMetafactory.
        # This is used for intersection types, which I don't think I've ever seen
        # used in the wild, and maybe for some other things.  Here's an example:
        """
        class Example {
            interface A { default int foo() { return 1; } }
            interface B { int bar(); }
            public Object test() {
                return (A & B)() -> 1;
            }
        }
        """
        # See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.9
        # and https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8-200-D
        # for details.  Minecraft doesn't use this, so let's just pretend it doesn't exist.

        # Now check the arguments.  Note that LambdaMetafactory has some
        # arguments automatically filled in.  The bootstrap arguments are:
        # args[0] is samMethodType, signature of the implemented method
        # args[1] is implMethod, the method handle that is used
        # args[2] is instantiatedMethodType, narrower signature of the implemented method
        # We only really care about the method handle, and just assume that the
        # method handle satisfies instantiatedMethodType, and that that also
        # satisfies samMethodType.  instantiatedMethodType could maybe be used
        # to get the type of object created by the returned function, but I'm not
        # sure if there's a reason to do that over just looking at the handle.
        methodhandle = cf.constants.get(bootstrap.bootstrap_args[1])
        self.ref_kind = methodhandle.reference_kind

        # instantiatedMethodType does have a use when executing the created
        # object, so store it for later.
        instantiated = cf.constants.get(bootstrap.bootstrap_args[2])
        self.instantiated_desc = method_descriptor(instantiated.descriptor.value)

        assert self.ref_kind >= REF_getField and self.ref_kind <= REF_invokeInterface
        # Javac does not appear to use REF_getField, REF_getStatic,
        # REF_putField, or REF_putStatic, so don't bother handling fields here.
        assert self.ref_kind not in FIELD_REFS

        self.method_class = methodhandle.reference.class_.name.value
        self.method_name = methodhandle.reference.name_and_type.name.value
        self.method_desc = method_descriptor(methodhandle.reference.name_and_type.descriptor.value)

        if self.ref_kind == REF_newInvokeSpecial:
            # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.8-200-C.2
            assert self.method_name == "<init>"
        else:
            # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.8-200-C.1
            assert self.method_name not in ("<init>", "<clinit>")

        # Although invokeinterface won't cause problems here, other code likely
        # will break with it, so bail out early for now (if it's eventually used,
        # it can be fixed later)
        assert self.ref_kind != REF_invokeInterface

        # As for stack changes, consider the following:
        """
        public Supplier<String> foo() {
          return this::toString;
        }
        public Function<Object, String> bar() {
          return Object::toString;
        }
        public static Supplier<String> baz(String a, String b, String c) {
          return () -> a + b + c;
        }
        public Supplier<Object> quux() {
          return Object::new;
        }
        """
        # Which disassembles (tidied to remove java.lang and java.util) to:
        """
        Constant pool:
          #2 = InvokeDynamic      #0:#38         // #0:get:(LClassName;)LSupplier;
          #3 = InvokeDynamic      #1:#41         // #1:apply:()LFunction;
          #4 = InvokeDynamic      #2:#43         // #2:get:(LString;LString;LString;)LSupplier;
          #5 = InvokeDynamic      #3:#45         // #3:get:()LSupplier;
        public Supplier<String> foo();
          Code:
            0: aload_0
            1: invokedynamic #2,  0
            6: areturn
        public Function<Object, String> bar();
          Code:
            0: invokedynamic #3,  0
            5: areturn
        public static Supplier<String> baz(String, String, String);
          Code:
            0: aload_0
            1: aload_1
            2: aload_2
            3: invokedynamic #4,  0
            8: areturn
        public Supplier<java.lang.Object> quux();
          Code:
            0: invokedynamic #5,  0
            5: areturn
        private static synthetic String lambda$baz$0(String, String, String);
          -snip-
        BootstrapMethods:
          0: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip-
            Method arguments:
              #35 ()LObject;
              #36 invokevirtual Object.toString:()LString;
              #37 ()LString;
          1: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip-
            Method arguments:
              #39 (LObject;)LObject;
              #36 invokevirtual Object.toString:()LString;
              #40 (LObject;)LString;
          2: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip-
            Method arguments:
              #35 ()LObject;
              #42 invokestatic ClassName.lambda$baz$0:(LString;LString;LString;)LString;
              #37 ()LString;
          3: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip-
            Method arguments:
              #35 ()LObject;
              #44 newinvokespecial Object."<init>":()V
              #35 ()LObject;
        """
        # Note that both foo and bar have invokevirtual in the method handle,
        # but `this` is added to the stack in foo().
        # Similarly, baz pushes 3 arguments to the stack.  Unfortunately the JVM
        # spec doesn't make it super clear how to decide how many items to
        # pop from the stack for invokedynamic.  My guess, looking at the
        # constant pool, is that it's the name_and_type member of InvokeDynamic,
        # specifically the descriptor, that determines stack changes.
        # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.9.invokedynamic
        # kinda confirms this without explicitly stating it.
        self.dynamic_name = const.name_and_type.name.value
        self.dynamic_desc = method_descriptor(const.name_and_type.descriptor.value)

        assert self.dynamic_desc.returns.name != "void"
        self.implemented_iface = self.dynamic_desc.returns.name

        # created_type is the type returned by the function we return.
        if self.ref_kind == REF_newInvokeSpecial:
            self.created_type = self.method_class
        else:
            self.created_type = self.method_desc.returns.name
Beispiel #17
0
            def on_invoke(self, ins, const, obj, args):
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)

                if ins == "invokestatic":
                    if const.class_.name == superclass:
                        current_item = {}

                        text_id = None
                        idx = 0
                        for arg in desc.args:
                            if arg.name == blockclass:
                                block = args[idx]
                                text_id = block["text_id"]
                                if "name" in block:
                                    current_item["name"] = block["name"]
                                if "display_name" in block:
                                    current_item["display_name"] = block[
                                        "display_name"]
                            elif arg.name == superclass:
                                current_item.update(args[idx])
                            elif arg.name == item_block_class:
                                current_item.update(args[idx])
                                text_id = current_item["text_id"]
                            elif arg.name == "java/lang/String":
                                text_id = args[idx]
                            idx += 1

                        if current_item == {} and not text_id:
                            if verbose:
                                print(
                                    "Couldn't find any identifying information for the call to %s with %s"
                                    % (method_desc, args))
                            return

                        if not text_id:
                            if verbose:
                                print(
                                    "Could not find text_id for call to %s with %s"
                                    % (method_desc, args))
                            return

                        # Call to the static register method.
                        current_item["text_id"] = text_id
                        current_item["numeric_id"] = self.cur_id
                        self.cur_id += 1
                        lang_key = "minecraft.%s" % text_id
                        if language != None and lang_key in language:
                            current_item["display_name"] = language[lang_key]
                        if "max_stack_size" not in current_item:
                            current_item["max_stack_size"] = 64
                        item_list[text_id] = current_item
                else:
                    if method_name == "<init>":
                        # Call to a constructor.  Check if the builder is in the args,
                        # and if so update the item with it
                        idx = 0
                        for arg in desc.args:
                            if arg.name == builder_class:
                                # Update from the builder
                                if "max_stack_size" in args[idx]:
                                    obj["max_stack_size"] = args[idx][
                                        "max_stack_size"]
                            elif arg.name == blockclass and "text_id" not in obj:
                                block = args[idx]
                                obj["text_id"] = block["text_id"]
                                if "name" in block:
                                    obj["name"] = block["name"]
                                if "display_name" in block:
                                    obj["display_name"] = block["display_name"]
                            idx += 1
                    elif method_name == max_stack_method.name.value and method_desc == max_stack_method.descriptor.value:
                        obj["max_stack_size"] = args[0]

                if desc.returns.name != "void":
                    if desc.returns.name == builder_class or is_item_class(
                            desc.returns.name):
                        if ins == "invokestatic":
                            # Probably returning itself, but through a synthetic method
                            return args[0]
                        else:
                            # Probably returning itself
                            return obj
                    else:
                        return object()
Beispiel #18
0
    def operations(jar, classname, classes, args=None,
                   methodname=None, arg_names=("this", "packetbuffer")):
        """Gets the instructions of the specified method"""

        # Find the writing method
        cf = ClassFile(StringIO(jar.read(classname)))

        if methodname is None and args is None:
            methods = list(cf.methods.find(f=lambda x: len(x.args) == 1 and x.args[0].name == classes["packet.packetbuffer"]))

            if len(methods) == 2:
                method = methods[1]
            else:
                if cf.super_:
                    return _PIT.operations(jar, cf.super_.name + ".class", classes)
                else:
                    raise Exception("Failed to find method in class or superclass")
        elif methodname is None:
            method = cf.methods.find_one(args=args)
        else:
            method = cf.methods.find_one(name=methodname, args=args)


        # Decode the instructions
        operations = []
        stack = []
        skip_until = -1
        shortif_pos = -1
        shortif_cond = ''

        for instruction in method.code.disassemble():
            if skip_until != -1:
                if instruction.pos == skip_until:
                    skip_until = -1
                else:
                    continue

            opcode = instruction.opcode
            operands = [InstructionField(operand, instruction, cf.constants)
                        for operand in instruction.operands]

            # Shortcut if
            if instruction.pos == shortif_pos:
                category = stack[-1].category
                stack.append(Operand("((%(cond)s) ? %(sec)s : %(first)s)" % {
                    "cond": shortif_cond,
                    "first": stack.pop(),
                    "sec": stack.pop()
                }, category))

            # Method calls
            if opcode >= 0xb6 and opcode <= 0xb9:
                name = operands[0].name
                desc = operands[0].descriptor

                descriptor = method_descriptor(desc)
                num_arguments = len(descriptor.args)

                if name in _PIT.TYPES:
                    operations.append(Operation(instruction.pos, "write",
                                                type=_PIT.TYPES[name],
                                                field=stack.pop()))
                elif name == "writeBytes":
                    # Directly write to the buffer (no length info)
                    if len(desc.args) == 3:
                        # Method that takes indexes within the arrays - we don't really care about the indexes.
                        stack.pop()
                        stack.pop()
                    operations.append(Operation(instruction.pos, "write",
                                                type="byte[]",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == "byte" and descriptor.args[0].dimensions == 1 and len(name) == 1:
                    # Write byte array - this method prefixes the length.
                    field = stack.pop()
                    operations.append(Operation(instruction.pos, "write",
                                                type="varint",
                                                field="%s.length" % field))
                    operations.append(Operation(instruction.pos, "write",
                                                type="byte[]",
                                                field=field))
                elif num_arguments == 1 and descriptor.args[0].name == "int" and descriptor.args[0].dimensions == 1 and len(name) == 1:
                    field = stack.pop()
                    operations.append(Operation(instruction.pos, "write",
                                                type="varint",
                                                field="%s.length" % field))
                    operations.append(Operation(instruction.pos, "write",
                                                type="varint[]",
                                                field=field))
                elif num_arguments == 1 and descriptor.args[0].name == "long" and descriptor.args[0].dimensions == 1 and len(name) == 1:
                    field = stack.pop()
                    operations.append(Operation(instruction.pos, "write",
                                                type="varint",
                                                field="%s.length" % field))
                    operations.append(Operation(instruction.pos, "write",
                                                type="long[]",
                                                field=field))
                elif num_arguments == 1 and descriptor.args[0].name == "java/lang/String":
                    operations.append(Operation(instruction.pos, "write",
                                                type="string16",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == "java/util/UUID":
                    operations.append(Operation(instruction.pos, "write",
                                                type="uuid",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == "int" and len(name) == 1:
                    # We need to check the return type to distinguish it from
                    # other methods, including the normal netty writeint method
                    # that writes 4 full bytes.  The netty method returns a
                    # ByteBuf, but varint returns void.
                    operations.append(Operation(instruction.pos, "write",
                                                type="varint",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == "long" and len(name) == 1:
                    operations.append(Operation(instruction.pos, "write",
                                                type="varlong",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == "java/lang/Enum":
                    # If we were using the read method instead of the write method, then we could get the class for this enum...
                    operations.append(Operation(instruction.pos, "write",
                                                type="enum",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == classes["nbtcompound"]:
                    operations.append(Operation(instruction.pos, "write",
                                                type="nbtcompound",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == classes["itemstack"]:
                    operations.append(Operation(instruction.pos, "write",
                                                type="itemstack",
                                                field=stack.pop()))
                elif num_arguments == 1 and descriptor.args[0].name == classes["chatcomponent"]:
                    operations.append(Operation(instruction.pos, "write",
                                                type="chatcomponent",
                                                field=stack.pop()))
                else:
                    if num_arguments > 0:
                        arguments = stack[-len(descriptor.args):]
                    else:
                        arguments = []
                    for i in range(num_arguments):
                        stack.pop()
                    obj = "static" if opcode == 0xb8 else stack.pop()
                    if descriptor.returns.name != "void":
                        stack.append(Operand(
                            "%s.%s(%s)" % (
                                obj, name, _PIT.join(arguments)
                            ),
                            2 if descriptor.returns.name in ("long", "double") else 1)
                        )

                    if isinstance(obj, Operand) and obj.value == "packetbuffer":
                        # Right now there isn't a good way to identify the
                        # class for positions, so we assume that any calls to
                        # a packetbuffer method that we haven't yet handled is
                        # actually writing a position.
                        operations.append(Operation(instruction.pos,
                                                    "write", type="position",
                                                    field=arguments[0]))
                    else:
                        for arg in descriptor.args:
                            if arg.name == classes["packet.packetbuffer"]:
                                if operands[0].c == classes["metadata"]:
                                    # Special case - metadata is a complex type but
                                    # well documented; we don't want to include its
                                    # exact writing but just want to instead say
                                    # 'metadata'.

                                    # There are two cases - one is calling an
                                    # instance method of metadata that writes
                                    # out the instance, and the other is a
                                    # static method that takes a list and then
                                    # writes that list.
                                    operations.append(Operation(instruction.pos,
                                                    "write", type="metadata",
                                                    field=obj if obj != "static" else arguments[0]))
                                    break
                                # If calling a sub method that takes a packetbuffer
                                # as a parameter, it's possible that it's a sub
                                # method that writes to the buffer, so we need to
                                # check it.
                                operations += _PIT.sub_operations(
                                    jar, cf, classes, instruction, operands[0],
                                    [obj] + arguments if obj != "static" else arguments
                                )
                                break

            # Conditional statements and loops
            elif opcode in [0xc7, 0xc6] or opcode >= 0x99 and opcode <= 0xa6:
                if opcode == 0xc7:
                    condition = "%s == null" % stack.pop()
                elif opcode == 0xc6:
                    condition = "%s != null" % stack.pop()
                else:
                    if opcode <= 0x9e:                  # if
                        comperation = opcode - 0x99
                        fields = [0, stack.pop()]
                    elif opcode <= 0xa4:                # if_icmp
                        comperation = opcode - 0x9f
                        fields = [stack.pop(), stack.pop()]
                    else:                               # if_acmp
                        comperation = opcode - 0xa5
                        fields = [stack.pop(), stack.pop()]
                    if comperation == 0 and fields[0] == 0:
                        condition = fields[1]
                    else:
                        condition = "%s %s %s" % (
                            fields[1], _PIT.CONDITIONS[comperation], fields[0]
                        )
                operations.append(Operation(instruction.pos, "if",
                                            condition=condition))
                operations.append(Operation(operands[0].target, "endif"))
                shortif_cond = condition

            elif opcode == 0xaa:                        # tableswitch
                operations.append(Operation(instruction.pos, "switch",
                                            field=stack.pop()))

                default = operands[0].target
                low = operands[1].value
                high = operands[2].value
                for opr in range(3, len(operands)):
                    target = operands[opr].target
                    operations.append(Operation(target, "case",
                                                value=low + opr - 3))
                # TODO: Default might not be the right place for endswitch,
                # though it seems like default isn't used in any other way
                # in the normal code.
                operations.append(Operation(default, "endswitch"))

            elif opcode == 0xab:                        # lookupswitch
                operations.append(Operation(instruction.pos, "switch",
                                            field=stack.pop()))
                for opr in range(1, len(operands)):
                    target = operands[opr].find_target(1)
                    operations.append(Operation(target, "case",
                                                value=operands[opr].value[0]))
                operations.append(Operation(operands[0].target, "endswitch"))

            elif opcode == 0xa7:                        # goto
                target = operands[0].target
                endif = _PIT.find_next(operations, instruction.pos, "endif")
                case = _PIT.find_next(operations, instruction.pos, "case")
                if case is not None and target > case.position:
                    operations.append(Operation(instruction.pos, "break"))
                elif endif is not None:
                    if target > instruction.pos:
                        endif.operation = "else"
                        operations.append(Operation(target, "endif"))
                        if len(stack) != 0:
                            shortif_pos = target
                    else:
                        endif.operation = "endloop"
                        _PIT.find_next(
                            operations, target, "if"
                        ).operation = "loop"
                elif target > instruction.pos:
                    skip_until = target

            # Math
            elif opcode >= 0x74 and opcode <= 0x77: # Tneg
                category = stack[-1].category
                stack.append(Operand("(- %s)" % (stack.pop), category))
            elif opcode >= 0x60 and opcode <= 0x83:
                lookup_opcode = opcode
                while not lookup_opcode in _PIT.MATH:
                    lookup_opcode -= 1
                category = stack[-1].category
                value2 = stack.pop()
                stack.append(Operand(
                    "(%s %s %s)" % (
                        stack.pop(), _PIT.MATH[lookup_opcode], value2
                    ), category
                ))
            elif opcode == 0x84:                        # iinc
                operations.append(Operation(instruction.pos, "increment",
                                            field="var%s" % operands[0],
                                            amount=operands[1]))

            # Other manually handled opcodes
            elif opcode == 0xc5:                        # multianewarray
                operand = ""
                for i in range(operands[1].value):
                    operand = "[%s]%s" % (stack.pop(), operand)
                stack.append(Operand(
                    "new %s%s" % (operands[0].type, operand)))
            elif opcode == 0x58:                        # pop2
                if stack.pop().category != 2:
                    stack.pop()
            elif opcode == 0x5f:                        # swap
                stack += [stack.pop(), stack.pop()]
            elif opcode == 0x59:                        # dup
                stack.append(stack[-1])
            elif opcode == 0x5a:                        # dup_x1
                stack.insert(-2, stack[-1])
            elif opcode == 0x5b:                        # dup_x2
                stack.insert(-2 if stack[-2].category == 2 else -3, stack[-1])
            elif opcode == 0x5c:                        # dup2
                if stack[-1].category == 2:
                    stack.append(stack[-1])
                else:
                    stack += stack[-2:]
            elif opcode == 0x5d:                        # dup2_x1
                if stack[-1].category == 2:
                    stack.insert(-2, stack[-1])
                else:
                    stack.insert(-3, stack[-2])
                    stack.insert(-3, stack[-1])
            elif opcode == 0x5e:                        # dup2_x2
                if stack[-1].category == 2:
                    stack.insert(
                        -2 if stack[-2].category == 2 else -3, stack[-1]
                    )
                else:
                    stack.insert(
                        -3 if stack[-3].category == 2 else -4, stack[-2]
                    )
                    stack.insert(
                        -3 if stack[-3].category == 2 else -4, stack[-1]
                    )

            # Unhandled opcodes
            elif opcode in [0xc8, 0xa8, 0xc9]:
                raise Exception("unhandled opcode 0x%x" % opcode)

            # Default handlers
            else:
                lookup_opcode = opcode
                while not lookup_opcode in _PIT.OPCODES:
                    lookup_opcode -= 1

                handler = _PIT.OPCODES[lookup_opcode]
                index = 0

                if isinstance(handler, int):
                    handler = [handler]

                assert len(stack) >= handler[index]

                for i in range(handler[index]):
                    operands.insert(0, stack.pop())

                index += 1

                if len(handler) > index:
                    format = handler[index]
                    index += 1

                    if (len(handler) > index and
                            isinstance(handler[index], LambdaType)):
                        value = handler[index](opcode)
                        operands.append(value)
                        operands.append(arg_names[value]
                                        if value < len(arg_names)
                                        else "var%s" % value)
                        index += 1
                    elif len(operands) >= 1:
                        value = operands[0].value
                        operands.append(arg_names[value]
                                        if value < len(arg_names)
                                        else "var%s" % value)

                    if (len(handler) > index and
                            isinstance(handler[index], int)):
                        category = handler[index]
                    else:
                        category = 1

                    stack.append(Operand(
                        StringFormatter.format(format, *operands),
                        category)
                    )

                if "store" in instruction.mnemonic:
                    if instruction.mnemonic[1] == 'a':
                        # Array store: Doesn't seem to be used when writing
                        # packets; let's ignore it.
                        raise Exception("Unhandled array store instruction: " + str(instruction))

                    # Keep track of what is being stored, for clarity
                    if "_" in instruction.mnemonic:
                        # Tstore_<index>
                        arg = instruction.mnemonic[-1]
                    else:
                        arg = operands.pop().value
                    
                    type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]]

                    var = arg_names[arg] if arg < len(arg_names) else "var%s" % arg
                    operations.append(Operation(instruction.pos, "store",
                                                type=type,
                                                var=var,
                                                value=operands.pop()))

        return operations
Beispiel #19
0
    def operations(classloader, classname, classes, verbose, args=None,
                   methodname=None, arg_names=("this", "packetbuffer")):
        """Gets the instructions of the specified method"""

        # Find the writing method
        cf = classloader[classname[:-len(".class")]] # XXX triming a .class that has no reason to exist anyways

        if methodname is None and args is None:
            methods = list(cf.methods.find(returns="V", args="L" + classes["packet.packetbuffer"] + ";"))

            if len(methods) == 2:
                method = methods[1]
            else:
                if cf.super_.name.value != "java/lang/Object":
                    return _PIT.operations(classloader, cf.super_.name.value + ".class", classes, verbose)
                else:
                    raise Exception("Failed to find method in class or superclass")
        elif methodname is None:
            method = cf.methods.find_one(args=args)
        else:
            method = cf.methods.find_one(name=methodname, args=args)

        if method.access_flags.acc_abstract:
            # Abstract method call -- just log that, since we can't view it
            return [Operation(instruction.pos, "interfacecall",
                              type="abstract", target=operands[0].c, name=name,
                              method=name + desc, field=obj, args=_PIT.join(arguments))]

        # Decode the instructions
        operations = []
        stack = []
        skip_until = -1
        shortif_pos = None
        shortif_cond = None

        # NOTE: we only use the simple_swap transform here due to the
        # expand_constants transform making it hard to use InstructionField
        # InstructionField should probably be cleaned up first
        for instruction in method.code.disassemble(transforms=[simple_swap]):
            if skip_until != -1:
                if instruction.pos == skip_until:
                    skip_until = -1
                else:
                    continue

            mnemonic = instruction.mnemonic
            operands = [InstructionField(operand, instruction, cf.constants)
                        for operand in instruction.operands]

            # Shortcut if
            if instruction.pos == shortif_pos:
                # Check to make sure that this actually is a ternary if
                assert len(operations) >= 3
                assert operations[-1].operation == "endif"
                assert operations[-2].operation == "else"
                assert operations[-3].operation == "if"
                # Now get rid of the unneeded if's
                operations.pop()
                operations.pop()
                operations.pop()
                category = stack[-1].category
                stack.append(StackOperand("((%(cond)s) ? %(sec)s : %(first)s)" % {
                    "cond": shortif_cond,
                    "first": stack.pop(),
                    "sec": stack.pop()
                }, category))
                shortif_cond = None
                shortif_pos = None

            # Method calls
            if mnemonic in ("invokevirtual", "invokespecial", "invokestatic", "invokeinterface"):
                name = operands[0].name
                desc = operands[0].descriptor

                descriptor = method_descriptor(desc)
                num_arguments = len(descriptor.args)

                if num_arguments > 0:
                    arguments = stack[-len(descriptor.args):]
                else:
                    arguments = []
                for i in six.moves.range(num_arguments):
                    stack.pop()

                is_static = (mnemonic == "invokestatic")
                obj = operands[0].classname if is_static else stack.pop()

                if name in _PIT.TYPES:
                    # Builtin netty buffer methods
                    assert num_arguments == 1
                    operations.append(Operation(instruction.pos, "write",
                                                type=_PIT.TYPES[name],
                                                field=arguments[0]))
                    stack.append(obj)
                elif len(name) == 1 and isinstance(obj, StackOperand) and obj.value == "packetbuffer":
                    # Checking len(name) == 1 is used to see if it's a Minecraft
                    # method (due to obfuscation).  Netty methods have real
                    # (and thus longer) names.
                    assert num_arguments >= 1
                    arg_type = descriptor.args[0].name
                    field = arguments[0]

                    if descriptor.args[0].dimensions == 1 and num_arguments == 1:
                        # Array methods, which prefix a length
                        operations.append(Operation(instruction.pos, "write",
                                                    type="varint",
                                                    field="%s.length" % field))
                        if arg_type == "byte":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="byte[]",
                                                        field=field))
                        elif arg_type == "int":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="varint[]",
                                                        field=field))
                        elif arg_type == "long":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="long[]",
                                                        field=field))
                        else:
                            raise Exception("Unexpected array type: " + arg_type)
                    elif num_arguments == 1:
                        assert descriptor.args[0].dimensions == 0
                        if arg_type == "java/lang/String":
                            max_length = 32767 # not using this at the time
                            operations.append(Operation(instruction.pos, "write",
                                                        type="string",
                                                        field=field))
                        elif arg_type == "java/util/UUID":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="uuid",
                                                        field=field))
                        elif arg_type == "java/util/Date":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="long",
                                                        field="%s.getTime()" % field))
                        elif arg_type == "int":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="varint",
                                                        field=field))
                        elif arg_type == "long":
                            operations.append(Operation(instruction.pos, "write",
                                                        type="varlong",
                                                        field=field))
                        elif arg_type == "java/lang/Enum":
                            # If we were using the read method instead of the write method, then we could get the class for this enum...
                            operations.append(Operation(instruction.pos, "write",
                                                        type="enum",
                                                        field=field))
                        elif arg_type == classes["nbtcompound"]:
                            operations.append(Operation(instruction.pos, "write",
                                                        type="nbtcompound",
                                                        field=field))
                        elif arg_type == classes["itemstack"]:
                            operations.append(Operation(instruction.pos, "write",
                                                        type="itemstack",
                                                        field=field))
                        elif arg_type == classes["chatcomponent"]:
                            operations.append(Operation(instruction.pos, "write",
                                                        type="chatcomponent",
                                                        field=field))
                        elif arg_type == classes["identifier"]:
                            operations.append(Operation(instruction.pos, "write",
                                                        type="identifier",
                                                        field=field))
                        elif "position" not in classes or arg_type == classes["position"]:
                            if "position" not in classes:
                                classes["position"] = arg_type
                                if verbose:
                                    print("Assuming", arg_type, "is the position class")
                            operations.append(Operation(instruction.pos,
                                                        "write", type="position",
                                                        field=field))
                        else:
                            # Unknown type in packetbuffer; try inlining it as well
                            # (on the assumption that it's something made of a few calls,
                            # and not e.g. writeVarInt)
                            if verbose:
                                print("Inlining code for", arg_type)
                            operations += _PIT.sub_operations(
                                classloader, cf, classes, instruction, verbose, operands[0],
                                [obj] + arguments if not is_static else arguments
                            )
                    elif num_arguments == 2:
                        if arg_type == "java/lang/String" and descriptor.args[1].name == "int":
                            max_length = arguments[1] # not using this at the time
                            operations.append(Operation(instruction.pos, "write",
                                                        type="string",
                                                        field=field))
                        else:
                            raise Exception("Unexpected descriptor " + desc)
                    else:
                        raise Exception("Unexpected num_arguments: " + str(num_arguments) + " - desc " + desc)
                    # Return the buffer back to the stack, if needed
                    if descriptor.returns.name == classes["packet.packetbuffer"]:
                        stack.append(obj)
                elif name == "<init>":
                    # Constructor call.  Should have the instance right
                    # on the stack as well (due to constructors returning void).
                    # Add the arguments to that object.
                    assert stack[-1] is obj
                    obj.value += "(" + _PIT.join(arguments) + ")";
                else:

                    if descriptor.returns.name != "void":
                        stack.append(StackOperand(
                            "%s.%s(%s)" % (
                                obj, name, _PIT.join(arguments)
                            ),
                            2 if descriptor.returns.name in ("long", "double") else 1)
                        )

                    else:
                        for arg in descriptor.args:
                            if arg.name == classes["packet.packetbuffer"]:
                                if operands[0].c == classes["metadata"]:
                                    # Special case - metadata is a complex type but
                                    # well documented; we don't want to include its
                                    # exact writing but just want to instead say
                                    # 'metadata'.

                                    # There are two cases - one is calling an
                                    # instance method of metadata that writes
                                    # out the instance, and the other is a
                                    # static method that takes a list and then
                                    # writes that list.
                                    operations.append(Operation(instruction.pos,
                                                    "write", type="metadata",
                                                    field=obj if not is_static else arguments[0]))
                                    break
                                if mnemonic != "invokeinterface":
                                    # If calling a sub method that takes a packetbuffer
                                    # as a parameter, it's possible that it's a sub
                                    # method that writes to the buffer, so we need to
                                    # check it.
                                    operations += _PIT.sub_operations(
                                        classloader, cf, classes, instruction, verbose, operands[0],
                                        [obj] + arguments if not is_static else arguments
                                    )
                                else:
                                    # However, for interface method calls, we can't
                                    # check its code -- so just note that it's a call
                                    operations.append(Operation(instruction.pos,
                                                        "interfacecall",
                                                        type="interface",
                                                        target=operands[0].c,
                                                        name=name,
                                                        method=name + desc,
                                                        field=obj,
                                                        args=_PIT.join(arguments)))
                                break

            # Conditional statements and loops
            elif mnemonic.startswith("if"):
                if "icmp" in mnemonic or "acmp" in mnemonic:
                    value2 = stack.pop()
                    value1 = stack.pop()
                elif "null" in mnemonic:
                    value1 = stack.pop()
                    value2 = "null"
                else:
                    value1 = stack.pop()
                    value2 = 0

                # All conditions are reversed: if the condition in the mnemonic
                # passes, then we'd jump; thus, to execute the following code,
                # the condition must _not_ pass
                if mnemonic in ("ifeq", "if_icmpeq", "if_acmpeq", "ifnull"):
                    comparison = "!="
                elif mnemonic in ("ifne", "if_icmpne", "if_acmpne", "ifnonnull"):
                    comparison = "=="
                elif mnemonic in ("iflt", "if_icmplt"):
                    comparison = ">="
                elif mnemonic in ("ifge", "if_icmpge"):
                    comparison = "<"
                elif mnemonic in ("ifgt", "if_icmpgt"):
                    comparison = "<="
                elif mnemonic in ("ifle", "if_icmple"):
                    comparison = ">"
                else:
                    raise Exception("Unknown if mnemonic %s (0x%x)" % (mnemonic, instruction.opcode))

                if comparison == "!=" and value2 == 0:
                    # if (something != 0) -> if (something)
                    condition = value1
                else:
                    condition = "%s %s %s" % (value1, comparison, value2)

                operations.append(Operation(instruction.pos, "if",
                                            condition=condition))
                operations.append(Operation(operands[0].target, "endif"))
                if shortif_pos is not None:
                    # Clearly not a ternary-if if we have another nested if
                    # (assuming that it's not a nested ternary, which we
                    # already don't handle for other reasons)
                    # If we don't do this, then the following code can have
                    # problems:
                    # if (a) {
                    #     if (b) {
                    #         // ...
                    #     }
                    # } else if (c) {
                    #     // ...
                    # }
                    # as there would be a goto instruction to skip the
                    # `else if (c)` portion that would be parsed as a shortif
                    shortif_pos = None
                shortif_cond = condition

            elif mnemonic == "tableswitch":
                operations.append(Operation(instruction.pos, "switch",
                                            field=stack.pop()))

                default = operands[0].target
                low = operands[1].value
                high = operands[2].value
                for opr in six.moves.range(3, len(operands)):
                    target = operands[opr].target
                    operations.append(Operation(target, "case",
                                                value=low + opr - 3))
                # TODO: Default might not be the right place for endswitch,
                # though it seems like default isn't used in any other way
                # in the normal code.
                operations.append(Operation(default, "endswitch"))

            elif mnemonic == "lookupswitch":
                raise Exception("lookupswitch is not supported")
                # operations.append(Operation(instruction.pos, "switch",
                #                             field=stack.pop()))
                # for opr in six.moves.range(1, len(operands)):
                #     target = operands[opr].find_target(1)
                #     operations.append(Operation(target, "case",
                #                                 value=operands[opr].value[0]))
                # operations.append(Operation(operands[0].target, "endswitch"))

            elif mnemonic == "goto":
                target = operands[0].target
                endif = _PIT.find_next(operations, instruction.pos, "endif")
                case = _PIT.find_next(operations, instruction.pos, "case")
                if case is not None and target > case.position:
                    operations.append(Operation(instruction.pos, "break"))
                elif endif is not None:
                    if target > instruction.pos:
                        endif.operation = "else"
                        operations.append(Operation(target, "endif"))
                        if len(stack) != 0:
                            shortif_pos = target
                    else:
                        endif.operation = "endloop"
                        _PIT.find_next(
                            operations, target, "if"
                        ).operation = "loop"
                elif target > instruction.pos:
                    skip_until = target

            elif mnemonic == "iinc":
                operations.append(Operation(instruction.pos, "increment",
                                            field="var%s" % operands[0],
                                            amount=operands[1]))

            # Other manually handled instructions
            elif mnemonic == "multianewarray":
                operand = ""
                for i in six.moves.range(operands[1].value):
                    operand = "[%s]%s" % (stack.pop(), operand)
                stack.append(StackOperand(
                    "new %s%s" % (operands[0].type, operand)))
            elif mnemonic == "pop":
                stack.pop()
            elif mnemonic == "pop2":
                if stack.pop().category != 2:
                    stack.pop()
            elif mnemonic == "swap":
                stack[-2], stack[-1] = stack[-1], stack[-2]
            elif mnemonic == "dup":
                stack.append(stack[-1])
            elif mnemonic == "dup_x1":
                stack.insert(-2, stack[-1])
            elif mnemonic == "dup_x2":
                stack.insert(-2 if stack[-2].category == 2 else -3, stack[-1])
            elif mnemonic == "dup2":
                if stack[-1].category == 2:
                    stack.append(stack[-1])
                else:
                    stack += stack[-2:]
            elif mnemonic == "dup2_x1":
                if stack[-1].category == 2:
                    stack.insert(-2, stack[-1])
                else:
                    stack.insert(-3, stack[-2])
                    stack.insert(-3, stack[-1])
            elif mnemonic == "dup2_x2":
                if stack[-1].category == 2:
                    stack.insert(
                        -2 if stack[-2].category == 2 else -3, stack[-1]
                    )
                else:
                    stack.insert(
                        -3 if stack[-3].category == 2 else -4, stack[-2]
                    )
                    stack.insert(
                        -3 if stack[-3].category == 2 else -4, stack[-1]
                    )
            elif mnemonic == "return":
                # Don't attempt to lookup the instruction in the handler
                pass

            elif instruction in ("istore", "lstore", "fstore", "dstore", "astore"):
                # Keep track of what is being stored, for clarity
                type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]]
                arg = operands.pop().value

                var = arg_names[arg] if arg < len(arg_names) else "var%s" % arg
                operations.append(Operation(instruction.pos, "store",
                                            type=type,
                                            var=var,
                                            value=stack.pop()))

            elif instruction in ("iastore", "lastore", "fastore", "dastore", "aastore", "bastore", "castore", "sastore"):
                type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]]

                # Array store
                value = stack.pop()
                index = stack.pop()
                array = stack.pop()
                operations.append(Operation(instruction.pos, "arraystore",
                                        type=type,
                                        index=index,
                                        var=array,
                                        value=value))

            # Default handlers
            else:
                if mnemonic not in _PIT.OPCODES:
                    raise Exception("Unhandled instruction opcode %s (0x%x)" % (mnemonic, instruction.opcode))

                handler = _PIT.OPCODES[mnemonic]

                ins_stack = []
                assert len(stack) >= handler["stack_count"]

                for _ in six.moves.range(handler["stack_count"]):
                    ins_stack.insert(0, stack.pop())

                ctx = {
                    "operands": operands,
                    "stack": ins_stack,
                    "ins": instruction,
                    "arg_names": arg_names
                }

                if handler["extra_method"]:
                    ctx["extra"] = handler["extra_method"](ctx)

                category = handler["category"]
                try:
                    formatted = handler["template"].format(**ctx)
                except Exception as ex:
                    raise Exception("Failed to format info for %s (0x%x) with template %s and ctx %s: %s" %
                        (mnemonic, instruction.opcode, handler["template"], ctx, ex))

                stack.append(StackOperand(formatted, handler["category"]))

        return operations
Beispiel #20
0
def walk_method(cf, method, callback, verbose):
    assert isinstance(callback, WalkerCallback)

    stack = []
    locals = {}
    # TODO: Allow passing argument values in or something like that
    cur_index = 0
    if not method.access_flags.acc_static:
        locals[cur_index] = object()
        cur_index += 1
    for arg in method.args:
        locals[cur_index] = object()
        cur_index += 1

    for ins in method.code.disassemble():
        if ins in ("bipush", "sipush"):
            stack.append(ins.operands[0].value)
        elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith(
                "dconst"):
            stack.append(float(ins.mnemonic[-1]))
        elif ins == "aconst_null":
            stack.append(None)
        elif ins in ("ldc", "ldc_w", "ldc2_w"):
            const = ins.operands[0]

            if isinstance(const, ConstantClass):
                stack.append("%s.class" % const.name.value)
            elif isinstance(const, String):
                stack.append(const.string.value)
            else:
                stack.append(const.value)
        elif ins == "new":
            const = ins.operands[0]

            try:
                stack.append(callback.on_new(ins, const))
            except StopIteration:
                break
        elif ins in ("getfield", "getstatic"):
            const = ins.operands[0]
            if ins.mnemonic != "getstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                stack.append(callback.on_get_field(ins, const, obj))
            except StopIteration:
                break
        elif ins in ("putfield", "putstatic"):
            const = ins.operands[0]
            value = stack.pop()
            if ins.mnemonic != "putstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                callback.on_put_field(ins, const, obj, value)
            except StopIteration:
                break
        elif ins in ("invokevirtual", "invokespecial", "invokeinterface",
                     "invokestatic"):
            const = ins.operands[0]
            method_desc = const.name_and_type.descriptor.value
            desc = method_descriptor(method_desc)
            num_args = len(desc.args)

            args = []

            for i in six.moves.range(num_args):
                args.insert(0, stack.pop())
            if ins.mnemonic != "invokestatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                ret = callback.on_invoke(ins, const, obj, args)
            except StopIteration:
                break
            if desc.returns.name != "void":
                stack.append(ret)
        elif ins in ("astore", "istore"):
            locals[ins.operands[0].value] = stack.pop()
        elif ins in ("aload", "iload"):
            stack.append(locals[ins.operands[0].value])
        elif ins == "dup":
            stack.append(stack[-1])
        elif ins == "pop":
            stack.pop()
        elif ins == "anewarray":
            stack.append([None] * stack.pop())
        elif ins == "newarray":
            stack.append([0] * stack.pop())
        elif ins in ("aastore", "iastore", "fastore"):
            value = stack.pop()
            index = stack.pop()
            array = stack.pop()
            if isinstance(array, list) and isinstance(index, int):
                array[index] = value
            elif verbose:
                print("Failed to execute %s: array %s index %s value %s" %
                      (ins, array, index, value))
        elif ins == "invokedynamic":
            stack.append(callback.on_invokedynamic(ins, ins.operands[0]))
        elif ins in ("checkcast", "return"):
            pass
        elif verbose:
            print("Unknown instruction %s: stack is %s" % (ins, stack))
Beispiel #21
0
            def on_invoke(self, ins, const, obj, args):
                if const.class_.name == listclass:
                    assert len(args) == 2
                    # Call to register
                    name = args[0]
                    new_entity = args[1]
                    new_entity["name"] = name
                    new_entity["id"] = self.cur_id
                    if "minecraft." + name in aggregate["language"]["entity"]:
                        new_entity["display_name"] = aggregate["language"]["entity"]["minecraft." + name]
                    self.cur_id += 1

                    entity[name] = new_entity
                    return new_entity
                elif const.class_.name == builderclass:
                    if ins.mnemonic != "invokestatic":
                        if len(args) == 2 and const.name_and_type.descriptor.value.startswith("(FF)"):
                            # Entity size in 19w03a and newer
                            obj["width"] = args[0]
                            obj["height"] = args[1]

                        # There are other properties on the builder (related to whether the entity can be created)
                        # We don't care about these
                        return obj

                    method_desc = const.name_and_type.descriptor.value
                    desc = method_descriptor(method_desc)

                    if len(args) == 2:
                        if desc.args[0].name == "java/lang/Class" and desc.args[1].name == "java/util/function/Function":
                            # Builder.create(Class, Function), 18w06a+
                            # In 18w06a, they added a parameter for the entity class; check consistency
                            assert args[0] == args[1] + ".class"
                            cls = args[1]
                        elif desc.args[0].name == "java/util/function/Function" or desc.args[0].name == funcclass:
                            # Builder.create(Function, EntityCategory), 19w05a+
                            cls = args[0]
                        else:
                            if verbose:
                                print("Unknown entity type builder creation method", method_desc)
                            cls = None
                    elif len(args) == 1:
                        # There is also a format that creates an entity that cannot be serialized.
                        # This might be just with a single argument (its class), in 18w06a+.
                        # Otherwise, in 18w05a and below, it's just the function to build.
                        if desc.args[0].name == "java/lang/Function":
                            # Builder.create(Function), 18w05a-
                            # Just the function, which was converted into a class name earlier
                            cls = args[0]
                        elif desc.args[0].name == "java/lang/Class":
                            # Builder.create(Class), 18w06a+
                            # The type that represents something that cannot be serialized
                            cls = None
                        else:
                            # Assume Builder.create(EntityCategory) in 19w05a+,
                            # though it could be hit for other unknown signatures
                            cls = None
                    else:
                        # Assume Builder.create(), though this could be hit for other unknown signatures
                        # In 18w05a and below, nonserializable entities
                        cls = None

                    return { "class": cls } if cls else { "serializable": "false" }
Beispiel #22
0
def walk_method(cf, method, callback, verbose):
    assert isinstance(callback, WalkerCallback)

    stack = []
    locals = {}
    for ins in method.code.disassemble():
        if ins.mnemonic in ("bipush", "sipush"):
            stack.append(ins.operands[0].value)
        elif ins.mnemonic.startswith("fconst"):
            stack.append(float(ins.mnemonic[-1]))
        elif ins.mnemonic == "aconst_null":
            stack.append(None)
        elif ins.mnemonic in ("ldc", "ldc_w"):
            const = ins.operands[0]

            if isinstance(const, ConstantClass):
                stack.append("%s.class" % const.name.value)
            elif isinstance(const, String):
                stack.append(const.string.value)
            else:
                stack.append(const.value)
        elif ins.mnemonic == "new":
            const = ins.operands[0]

            try:
                stack.append(callback.on_new(ins, const))
            except StopIteration:
                break
        elif ins.mnemonic in ("getfield", "getstatic"):
            const = ins.operands[0]
            if ins.mnemonic != "getstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                stack.append(callback.on_get_field(ins, const, obj))
            except StopIteration:
                break
        elif ins.mnemonic in ("putfield", "putstatic"):
            const = ins.operands[0]
            value = stack.pop()
            if ins.mnemonic != "putstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                callback.on_put_field(ins, const, obj, value)
            except StopIteration:
                break
        elif ins.mnemonic in ("invokevirtual", "invokespecial",
                              "invokeinterface", "invokestatic"):
            const = ins.operands[0]
            method_desc = const.name_and_type.descriptor.value
            desc = method_descriptor(method_desc)
            num_args = len(desc.args)

            args = []

            for i in six.moves.range(num_args):
                args.insert(0, stack.pop())
            if ins.mnemonic != "invokestatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                ret = callback.on_invoke(ins, const, obj, args)
            except StopIteration:
                break
            if desc.returns.name != "void":
                stack.append(ret)
        elif ins.mnemonic == "astore":
            locals[ins.operands[0].value] = stack.pop()
        elif ins.mnemonic == "aload":
            stack.append(locals[ins.operands[0].value])
        elif ins.mnemonic == "dup":
            stack.append(stack[-1])
        elif ins.mnemonic in ("checkcast", "return"):
            pass
        elif verbose:
            print("Unknown instruction %s: stack is %s" % (ins, stack))
Beispiel #23
0
    def _process_19(aggregate, classloader, verbose):
        # Processes biomes for Minecraft 1.9 through 1.12
        biomes_base = aggregate.setdefault("biomes", {})
        biomes = biomes_base.setdefault("biome", {})
        biome_fields = biomes_base.setdefault("biome_fields", {})

        superclass = aggregate["classes"]["biome.superclass"]
        cf = classloader[superclass]

        method = cf.methods.find_one(returns="V", args="", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static)
        heights_by_field = {}
        first_new = True
        biome = None
        stack = []

        # OK, start running through the initializer for biomes.
        for ins in method.code.disassemble():
            if ins.mnemonic == "anewarray":
                # End of biome initialization; now creating the list of biomes
                # for the explore all biomes achievement but we don't need
                # that info.
                break

            if ins.mnemonic == "new":
                if first_new:
                    # There are two 'new's in biome initialization - the first
                    # one is for the biome generator itself and the second one
                    # is the biome properties.  There's some info that is only
                    # stored on the first new (well, actually, beforehand)
                    # that we want to save.
                    const = ins.operands[0]

                    text_id = stack.pop()
                    numeric_id = stack.pop()

                    biome = {
                        "id": numeric_id,
                        "text_id": text_id,
                        "rainfall": 0.5,
                        "height": [0.1, 0.2],
                        "temperature": 0.5,
                        "class": const.name.value
                    }
                    stack = []

                first_new = not(first_new)
            elif ins.mnemonic == "invokestatic":
                # Call to the static registration method
                # We already saved its parameters at the constructor, so we
                # only need to store the biome now.
                biomes[biome["text_id"]] = biome
            elif ins.mnemonic == "invokespecial":
                # Possibly the constructor for biome properties, which takes
                # the name as a string.
                if len(stack) > 0 and not "name" in biome:
                    biome["name"] = stack.pop()

                stack = []
            elif ins.mnemonic == "invokevirtual":
                const = ins.operands[0]
                name = const.name_and_type.name.value
                desc = method_descriptor(const.name_and_type.descriptor.value)

                if len(desc.args) == 1:
                    if desc.args[0].name == "float":
                        # Ugly portion - different methods with different names
                        # Hopefully the order doesn't change
                        if name == "a":
                            biome["temperature"] = stack.pop()
                        elif name == "b":
                            biome["rainfall"] = stack.pop()
                        elif name == "c":
                            biome["height"][0] = stack.pop()
                        elif name == "d":
                            biome["height"][1] = stack.pop()
                    elif desc.args[0].name == "java/lang/String":
                        # setBaseBiome
                        biome["mutated_from"] = stack.pop()
            # numeric values & constants
            elif ins.mnemonic in ("ldc", "ldc_w"):
                const = ins.operands[0]
                if isinstance(const, String):
                    stack.append(const.string.value)
                if isinstance(const, (Integer, Float)):
                    stack.append(const.value)

            elif ins.mnemonic.startswith("fconst"):
                stack.append(float(ins.mnemonic[-1]))
            elif ins.mnemonic in ("bipush", "sipush"):
                stack.append(ins.operands[0].value)

        # Go through the biome list and add the field info.
        list = aggregate["classes"]["biome.list"]
        lcf = classloader[list]

        # Find the static block, and load the fields for each.
        method = lcf.methods.find_one(name="<clinit>")
        biome_name = ""
        for ins in method.code.disassemble():
            if ins.mnemonic in ("ldc", "ldc_w"):
                const = ins.operands[0]
                if isinstance(const, String):
                    biome_name = const.string.value
            elif ins.mnemonic == "putstatic":
                if biome_name is None or biome_name == "Accessed Biomes before Bootstrap!":
                    continue
                const = ins.operands[0]
                field = const.name_and_type.name.value
                biomes[biome_name]["field"] = field
                biome_fields[field] = biome_name
Beispiel #24
0
def walk_method(cf, method, callback, verbose, input_args=None):
    """
    Walks through a method, evaluating instructions and using the callback
    for side-effects.

    The method is assumed to not have any conditionals, and to only return
    at the very end.
    """
    assert isinstance(callback, WalkerCallback)

    stack = []
    locals = {}
    cur_index = 0

    if not method.access_flags.acc_static:
        # TODO: allow specifying this
        locals[cur_index] = object()
        cur_index += 1

    if input_args != None:
        assert len(input_args) == len(method.args)
        for arg in input_args:
            locals[cur_index] = arg
            cur_index += 1
    else:
        for arg in method.args:
            locals[cur_index] = object()
            cur_index += 1

    ins_list = list(method.code.disassemble())
    for ins in ins_list[:-1]:
        if ins in ("bipush", "sipush"):
            stack.append(ins.operands[0].value)
        elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith(
                "dconst"):
            stack.append(float(ins.mnemonic[-1]))
        elif ins == "aconst_null":
            stack.append(None)
        elif ins in ("ldc", "ldc_w", "ldc2_w"):
            const = ins.operands[0]

            if isinstance(const, ConstantClass):
                stack.append("%s.class" % const.name.value)
            elif isinstance(const, String):
                stack.append(const.string.value)
            else:
                stack.append(const.value)
        elif ins == "new":
            const = ins.operands[0]

            try:
                stack.append(callback.on_new(ins, const))
            except StopIteration:
                break
        elif ins in ("getfield", "getstatic"):
            const = ins.operands[0]
            if ins.mnemonic != "getstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                stack.append(callback.on_get_field(ins, const, obj))
            except StopIteration:
                break
        elif ins in ("putfield", "putstatic"):
            const = ins.operands[0]
            value = stack.pop()
            if ins.mnemonic != "putstatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                callback.on_put_field(ins, const, obj, value)
            except StopIteration:
                break
        elif ins in ("invokevirtual", "invokespecial", "invokeinterface",
                     "invokestatic"):
            const = ins.operands[0]
            method_desc = const.name_and_type.descriptor.value
            desc = method_descriptor(method_desc)
            num_args = len(desc.args)

            args = []

            for i in six.moves.range(num_args):
                args.insert(0, stack.pop())
            if ins.mnemonic != "invokestatic":
                obj = stack.pop()
            else:
                obj = None

            try:
                ret = callback.on_invoke(ins, const, obj, args)
            except StopIteration:
                break
            if desc.returns.name != "void":
                stack.append(ret)
        elif ins in ("astore", "istore", "lstore", "fstore", "dstore"):
            locals[ins.operands[0].value] = stack.pop()
        elif ins in ("aload", "iload", "lload", "fload", "dload"):
            stack.append(locals[ins.operands[0].value])
        elif ins == "dup":
            stack.append(stack[-1])
        elif ins == "pop":
            stack.pop()
        elif ins == "anewarray":
            stack.append([None] * stack.pop())
        elif ins == "newarray":
            stack.append([0] * stack.pop())
        elif ins in ("aastore", "bastore", "castore", "sastore", "iastore",
                     "lastore", "fastore", "dastore"):
            value = stack.pop()
            index = stack.pop()
            array = stack.pop()
            if isinstance(array, list) and isinstance(index, int):
                array[index] = value
            elif verbose:
                print("Failed to execute %s: array %s index %s value %s" %
                      (ins, array, index, value))
        elif ins in ("aaload", "baload", "caload", "saload", "iaload",
                     "laload", "faload", "daload"):
            index = stack.pop()
            array = stack.pop()
            if isinstance(array, list) and isinstance(index, int):
                stack.push(array[index])
            elif verbose:
                print("Failed to execute %s: array %s index %s" %
                      (ins, array, index))
        elif ins == "invokedynamic":
            const = ins.operands[0]
            method_desc = const.name_and_type.descriptor.value
            desc = method_descriptor(method_desc)
            num_args = len(desc.args)

            args = []

            for i in six.moves.range(num_args):
                args.insert(0, stack.pop())

            stack.append(callback.on_invokedynamic(ins, ins.operands[0], args))
        elif ins == "checkcast":
            pass
        elif verbose:
            print("Unknown instruction %s: stack is %s" % (ins, stack))

    last_ins = ins_list[-1]
    if last_ins.mnemonic in ("ireturn", "lreturn", "freturn", "dreturn",
                             "areturn"):
        # Non-void method returning
        return stack.pop()
    elif last_ins.mnemonic == "return":
        # Void method returning
        pass
    elif verbose:
        print("Unexpected final instruction %s: stack is %s" % (ins, stack))
Beispiel #25
0
 def returns(self):
     return method_descriptor(self.descriptor.value).returns
Beispiel #26
0
 def returns(self) -> JVMType:
     """
     A :class:`~jawa.util.descriptor.JVMType` representing the method's
     return type.
     """
     return method_descriptor(self.descriptor.value).returns
Beispiel #27
0
    def operations(classloader,
                   classname,
                   classes,
                   verbose,
                   args=None,
                   methodname=None,
                   arg_names=("this", "packetbuffer")):
        """Gets the instructions of the specified method"""

        # Find the writing method
        cf = classloader[classname[:-len(
            ".class"
        )]]  # XXX triming a .class that has no reason to exist anyways

        if methodname is None and args is None:
            methods = list(
                cf.methods.find(returns="V",
                                args="L" + classes["packet.packetbuffer"] +
                                ";"))

            if len(methods) == 2:
                method = methods[1]
            else:
                if cf.super_.name.value != "java/lang/Object":
                    return _PIT.operations(classloader,
                                           cf.super_.name.value + ".class",
                                           classes, verbose)
                else:
                    raise Exception(
                        "Failed to find method in class or superclass")
        elif methodname is None:
            method = cf.methods.find_one(args=args)
        else:
            method = cf.methods.find_one(name=methodname, args=args)

        if method.access_flags.acc_abstract:
            # Abstract method call -- just log that, since we can't view it
            return [
                Operation(instruction.pos,
                          "interfacecall",
                          type="abstract",
                          target=operands[0].c,
                          name=name,
                          method=name + desc,
                          field=obj,
                          args=_PIT.join(arguments))
            ]

        # Decode the instructions
        operations = []
        stack = []
        skip_until = -1
        shortif_pos = None
        shortif_cond = None

        # NOTE: we only use the simple_swap transform here due to the
        # expand_constants transform making it hard to use InstructionField
        # InstructionField should probably be cleaned up first
        for instruction in method.code.disassemble(transforms=[simple_swap]):
            if skip_until != -1:
                if instruction.pos == skip_until:
                    skip_until = -1
                else:
                    continue

            mnemonic = instruction.mnemonic
            operands = [
                InstructionField(operand, instruction, cf.constants)
                for operand in instruction.operands
            ]

            # Shortcut if
            if instruction.pos == shortif_pos:
                # Check to make sure that this actually is a ternary if
                assert len(operations) >= 3
                assert operations[-1].operation == "endif"
                assert operations[-2].operation == "else"
                assert operations[-3].operation == "if"
                # Now get rid of the unneeded if's
                operations.pop()
                operations.pop()
                operations.pop()
                category = stack[-1].category
                stack.append(
                    StackOperand(
                        "((%(cond)s) ? %(sec)s : %(first)s)" % {
                            "cond": shortif_cond,
                            "first": stack.pop(),
                            "sec": stack.pop()
                        }, category))
                shortif_cond = None
                shortif_pos = None

            # Method calls
            if mnemonic in ("invokevirtual", "invokespecial", "invokestatic",
                            "invokeinterface"):
                name = operands[0].name
                desc = operands[0].descriptor

                descriptor = method_descriptor(desc)
                num_arguments = len(descriptor.args)

                if num_arguments > 0:
                    arguments = stack[-len(descriptor.args):]
                else:
                    arguments = []
                for i in six.moves.range(num_arguments):
                    stack.pop()

                is_static = (mnemonic == "invokestatic")
                obj = operands[0].classname if is_static else stack.pop()

                if name in _PIT.TYPES:
                    # Builtin netty buffer methods
                    assert num_arguments == 1
                    operations.append(
                        Operation(instruction.pos,
                                  "write",
                                  type=_PIT.TYPES[name],
                                  field=arguments[0]))
                    stack.append(obj)
                elif len(name) == 1 and isinstance(
                        obj, StackOperand) and obj.value == "packetbuffer":
                    # Checking len(name) == 1 is used to see if it's a Minecraft
                    # method (due to obfuscation).  Netty methods have real
                    # (and thus longer) names.
                    assert num_arguments >= 1
                    arg_type = descriptor.args[0].name
                    field = arguments[0]

                    if descriptor.args[
                            0].dimensions == 1 and num_arguments == 1:
                        # Array methods, which prefix a length
                        operations.append(
                            Operation(instruction.pos,
                                      "write",
                                      type="varint",
                                      field="%s.length" % field))
                        if arg_type == "byte":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="byte[]",
                                          field=field))
                        elif arg_type == "int":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="varint[]",
                                          field=field))
                        elif arg_type == "long":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="long[]",
                                          field=field))
                        else:
                            raise Exception("Unexpected array type: " +
                                            arg_type)
                    elif num_arguments == 1:
                        assert descriptor.args[0].dimensions == 0
                        if arg_type == "java/lang/String":
                            max_length = 32767  # not using this at the time
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="string",
                                          field=field))
                        elif arg_type == "java/util/UUID":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="uuid",
                                          field=field))
                        elif arg_type == "java/util/Date":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="long",
                                          field="%s.getTime()" % field))
                        elif arg_type == "int":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="varint",
                                          field=field))
                        elif arg_type == "long":
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="varlong",
                                          field=field))
                        elif arg_type == "java/lang/Enum":
                            # If we were using the read method instead of the write method, then we could get the class for this enum...
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="enum",
                                          field=field))
                        elif arg_type == classes["nbtcompound"]:
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="nbtcompound",
                                          field=field))
                        elif arg_type == classes["itemstack"]:
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="itemstack",
                                          field=field))
                        elif arg_type == classes["chatcomponent"]:
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="chatcomponent",
                                          field=field))
                        elif arg_type == classes["identifier"]:
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="identifier",
                                          field=field))
                        elif "position" not in classes or arg_type == classes[
                                "position"]:
                            if "position" not in classes:
                                classes["position"] = arg_type
                                if verbose:
                                    print("Assuming", arg_type,
                                          "is the position class")
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="position",
                                          field=field))
                        else:
                            # Unknown type in packetbuffer; try inlining it as well
                            # (on the assumption that it's something made of a few calls,
                            # and not e.g. writeVarInt)
                            if verbose:
                                print("Inlining code for", arg_type)
                            operations += _PIT.sub_operations(
                                classloader, cf, classes, instruction, verbose,
                                operands[0], [obj] +
                                arguments if not is_static else arguments)
                    elif num_arguments == 2:
                        if arg_type == "java/lang/String" and descriptor.args[
                                1].name == "int":
                            max_length = arguments[
                                1]  # not using this at this time
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="string",
                                          field=field))
                        elif arg_type == "com/mojang/serialization/Codec":
                            codec = arguments[0]
                            value = arguments[1]
                            # This isn't the exact syntax used by DataFixerUpper,
                            # but it's close enough for our purposes
                            field = "%s.encode(%s)" % (codec, value)
                            operations.append(
                                Operation(instruction.pos,
                                          "write",
                                          type="nbtcompound",
                                          field=field))
                        else:
                            raise Exception("Unexpected descriptor " + desc)
                    else:
                        raise Exception("Unexpected num_arguments: " +
                                        str(num_arguments) + " - desc " + desc)
                    # Return the buffer back to the stack, if needed
                    if descriptor.returns.name == classes[
                            "packet.packetbuffer"]:
                        stack.append(obj)
                elif name == "<init>":
                    # Constructor call.  Should have the instance right
                    # on the stack as well (due to constructors returning void).
                    # Add the arguments to that object.
                    assert stack[-1] is obj
                    obj.value += "(" + _PIT.join(arguments) + ")"
                else:

                    if descriptor.returns.name != "void":
                        stack.append(
                            StackOperand(
                                "%s.%s(%s)" %
                                (obj, name, _PIT.join(arguments)),
                                2 if descriptor.returns.name
                                in ("long", "double") else 1))

                    else:
                        for arg in descriptor.args:
                            if arg.name == classes["packet.packetbuffer"]:
                                if operands[0].c == classes["metadata"]:
                                    # Special case - metadata is a complex type but
                                    # well documented; we don't want to include its
                                    # exact writing but just want to instead say
                                    # 'metadata'.

                                    # There are two cases - one is calling an
                                    # instance method of metadata that writes
                                    # out the instance, and the other is a
                                    # static method that takes a list and then
                                    # writes that list.
                                    operations.append(
                                        Operation(instruction.pos,
                                                  "write",
                                                  type="metadata",
                                                  field=obj if not is_static
                                                  else arguments[0]))
                                    break
                                if mnemonic != "invokeinterface":
                                    # If calling a sub method that takes a packetbuffer
                                    # as a parameter, it's possible that it's a sub
                                    # method that writes to the buffer, so we need to
                                    # check it.
                                    operations += _PIT.sub_operations(
                                        classloader, cf, classes, instruction,
                                        verbose, operands[0], [obj] + arguments
                                        if not is_static else arguments)
                                else:
                                    # However, for interface method calls, we can't
                                    # check its code -- so just note that it's a call
                                    operations.append(
                                        Operation(instruction.pos,
                                                  "interfacecall",
                                                  type="interface",
                                                  target=operands[0].c,
                                                  name=name,
                                                  method=name + desc,
                                                  field=obj,
                                                  args=_PIT.join(arguments)))
                                break

            elif mnemonic == "invokedynamic":
                stack.append(
                    stringify_invokedynamic(stack.pop(), instruction, cf))

            # Conditional statements and loops
            elif mnemonic.startswith("if"):
                if "icmp" in mnemonic or "acmp" in mnemonic:
                    value2 = stack.pop()
                    value1 = stack.pop()
                elif "null" in mnemonic:
                    value1 = stack.pop()
                    value2 = "null"
                else:
                    value1 = stack.pop()
                    value2 = 0

                # All conditions are reversed: if the condition in the mnemonic
                # passes, then we'd jump; thus, to execute the following code,
                # the condition must _not_ pass
                if mnemonic in ("ifeq", "if_icmpeq", "if_acmpeq", "ifnull"):
                    comparison = "!="
                elif mnemonic in ("ifne", "if_icmpne", "if_acmpne",
                                  "ifnonnull"):
                    comparison = "=="
                elif mnemonic in ("iflt", "if_icmplt"):
                    comparison = ">="
                elif mnemonic in ("ifge", "if_icmpge"):
                    comparison = "<"
                elif mnemonic in ("ifgt", "if_icmpgt"):
                    comparison = "<="
                elif mnemonic in ("ifle", "if_icmple"):
                    comparison = ">"
                else:
                    raise Exception("Unknown if mnemonic %s (0x%x)" %
                                    (mnemonic, instruction.opcode))

                if comparison == "!=" and value2 == 0:
                    # if (something != 0) -> if (something)
                    condition = value1
                else:
                    condition = "%s %s %s" % (value1, comparison, value2)

                operations.append(
                    Operation(instruction.pos, "if", condition=condition))
                operations.append(Operation(operands[0].target, "endif"))
                if shortif_pos is not None:
                    # Clearly not a ternary-if if we have another nested if
                    # (assuming that it's not a nested ternary, which we
                    # already don't handle for other reasons)
                    # If we don't do this, then the following code can have
                    # problems:
                    # if (a) {
                    #     if (b) {
                    #         // ...
                    #     }
                    # } else if (c) {
                    #     // ...
                    # }
                    # as there would be a goto instruction to skip the
                    # `else if (c)` portion that would be parsed as a shortif
                    shortif_pos = None
                shortif_cond = condition

            elif mnemonic == "tableswitch":
                operations.append(
                    Operation(instruction.pos, "switch", field=stack.pop()))

                default = operands[0].target
                low = operands[1].value
                high = operands[2].value
                for opr in six.moves.range(3, len(operands)):
                    target = operands[opr].target
                    operations.append(
                        Operation(target, "case", value=low + opr - 3))
                # TODO: Default might not be the right place for endswitch,
                # though it seems like default isn't used in any other way
                # in the normal code.
                operations.append(Operation(default, "endswitch"))

            elif mnemonic == "lookupswitch":
                raise Exception("lookupswitch is not supported")
                # operations.append(Operation(instruction.pos, "switch",
                #                             field=stack.pop()))
                # for opr in six.moves.range(1, len(operands)):
                #     target = operands[opr].find_target(1)
                #     operations.append(Operation(target, "case",
                #                                 value=operands[opr].value[0]))
                # operations.append(Operation(operands[0].target, "endswitch"))

            elif mnemonic == "goto":
                target = operands[0].target
                endif = _PIT.find_next(operations, instruction.pos, "endif")
                case = _PIT.find_next(operations, instruction.pos, "case")
                if case is not None and target > case.position:
                    operations.append(Operation(instruction.pos, "break"))
                elif endif is not None:
                    if target > instruction.pos:
                        endif.operation = "else"
                        operations.append(Operation(target, "endif"))
                        if len(stack) != 0:
                            shortif_pos = target
                    else:
                        endif.operation = "endloop"
                        _PIT.find_next(operations, target,
                                       "if").operation = "loop"
                elif target > instruction.pos:
                    skip_until = target

            elif mnemonic == "iinc":
                operations.append(
                    Operation(instruction.pos,
                              "increment",
                              field="var%s" % operands[0],
                              amount=operands[1]))
Beispiel #28
0
 def args(self):
     return method_descriptor(self.descriptor.value).args
Beispiel #29
0
    def process_19(aggregate, jar, verbose):
        """Processes biomes for Minecraft 1.9"""
        biomes_base = aggregate.setdefault("biomes", {})
        biomes = biomes_base.setdefault("biome", {})
        biome_fields = biomes_base.setdefault("biome_fields", {})

        superclass = aggregate["classes"]["biome.superclass"]
        cf = ClassFile(StringIO(jar.read(superclass + ".class")))

        method = cf.methods.find_one(returns="V", args="", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static)
        heights_by_field = {}
        first_new = True
        biome = None
        stack = []

        # OK, start running through the initializer for biomes.
        for ins in method.code.disassemble():
            if ins.mnemonic == "anewarray":
                # End of biome initialization; now creating the list of biomes
                # for the explore all biomes achievement but we don't need
                # that info.
                break

            if ins.mnemonic == "new":
                if first_new:
                    # There are two 'new's in biome initialization - the first
                    # one is for the biome generator itself and the second one
                    # is the biome properties.  There's some info that is only
                    # stored on the first new (well, actually, beforehand)
                    # that we want to save.
                    const = cf.constants.get(ins.operands[0].value)

                    text_id = stack.pop()
                    numeric_id = stack.pop()

                    biome = {
                        "id": numeric_id,
                        "text_id": text_id,
                        "rainfall": 0.5,
                        "height": [0.1, 0.2],
                        "temperature": 0.5,
                        "class": const.name.value
                    }
                    stack = []

                first_new = not(first_new)
            elif ins.mnemonic == "invokestatic":
                # Call to the static registration method
                # We already saved its parameters at the constructor, so we
                # only need to store the biome now.
                biomes[biome["text_id"]] = biome
            elif ins.mnemonic == "invokespecial":
                # Possibly the constructor for biome properties, which takes
                # the name as a string.
                if len(stack) > 0 and not "name" in biome:
                    biome["name"] = stack.pop()

                stack = []
            elif ins.mnemonic == "invokevirtual":
                const = cf.constants.get(ins.operands[0].value)
                name = const.name_and_type.name.value
                desc = method_descriptor(const.name_and_type.descriptor.value)

                if len(desc.args) == 1:
                    if desc.args[0].name == "float":
                        # Ugly portion - different methods with different names
                        # Hopefully the order doesn't change
                        if name == "a":
                            biome["temperature"] = stack.pop()
                        elif name == "b":
                            biome["rainfall"] = stack.pop()
                        elif name == "c":
                            biome["height"][0] = stack.pop()
                        elif name == "d":
                            biome["height"][1] = stack.pop()
                    elif desc.args[0].name == "java/lang/String":
                        # setBaseBiome
                        biome["mutated_from"] = stack.pop()
            # numeric values & constants
            elif ins.mnemonic in ("ldc", "ldc_w"):
                const = cf.constants.get(ins.operands[0].value)
                if isinstance(const, ConstantString):
                    stack.append(const.string.value)
                if isinstance(const, (ConstantInteger, ConstantFloat)):
                    stack.append(const.value)

            elif ins.opcode <= 8 and ins.opcode >= 2:  # iconst
                stack.append(ins.opcode - 3)
            elif ins.opcode >= 0xb and ins.opcode <= 0xd:  # fconst
                stack.append(ins.opcode - 0xb)
            elif ins.mnemonic == "bipush":
                stack.append(ins.operands[0].value)
            elif ins.mnemonic == "sipush":
                stack.append(ins.operands[0].value)

        # Go through the block list and add the field info.
        list = aggregate["classes"]["biome.list"]
        lcf = ClassFile(StringIO(jar.read(list + ".class")))
        
        # Find the static block, and load the fields for each.
        method = lcf.methods.find_one(name="<clinit>")
        biome_name = ""
        for ins in method.code.disassemble():
            if ins.mnemonic in ("ldc", "ldc_w"):
                const = lcf.constants.get(ins.operands[0].value)
                if isinstance(const, ConstantString):
                    biome_name = const.string.value
            elif ins.mnemonic == "putstatic":
                if biome_name is None or biome_name == "Accessed Biomes before Bootstrap!":
                    continue
                const = lcf.constants.get(ins.operands[0].value)
                field = const.name_and_type.name.value
                biomes[biome_name]["field"] = field
                biome_fields[field] = biome_name
Beispiel #30
0
            def on_invoke(self, ins, const, obj, args):
                if const.class_.name == listclass:
                    assert len(args) == 2
                    # Call to register
                    name = args[0]
                    new_entity = args[1]
                    new_entity["name"] = name
                    new_entity["id"] = self.cur_id
                    if "minecraft." + name in aggregate["language"]["entity"]:
                        new_entity["display_name"] = aggregate["language"][
                            "entity"]["minecraft." + name]
                    self.cur_id += 1

                    entity[name] = new_entity
                    return new_entity
                elif const.class_.name == builderclass:
                    if ins.mnemonic != "invokestatic":
                        if len(
                                args
                        ) == 2 and const.name_and_type.descriptor.value.startswith(
                                "(FF)"):
                            # Entity size in 19w03a and newer
                            obj["width"] = args[0]
                            obj["height"] = args[1]

                        # There are other properties on the builder (related to whether the entity can be created)
                        # We don't care about these
                        return obj

                    method_desc = const.name_and_type.descriptor.value
                    desc = method_descriptor(method_desc)

                    if len(args) == 2:
                        if desc.args[0].name == "java/lang/Class" and desc.args[
                                1].name == "java/util/function/Function":
                            # Builder.create(Class, Function), 18w06a+
                            # In 18w06a, they added a parameter for the entity class; check consistency
                            assert args[0] == args[1] + ".class"
                            cls = args[1]
                        elif desc.args[
                                0].name == "java/util/function/Function" or desc.args[
                                    0].name == funcclass:
                            # Builder.create(Function, EntityCategory), 19w05a+
                            cls = args[0]
                        else:
                            if verbose:
                                print(
                                    "Unknown entity type builder creation method",
                                    method_desc)
                            cls = None
                    elif len(args) == 1:
                        # There is also a format that creates an entity that cannot be serialized.
                        # This might be just with a single argument (its class), in 18w06a+.
                        # Otherwise, in 18w05a and below, it's just the function to build.
                        if desc.args[0].name == "java/lang/Function":
                            # Builder.create(Function), 18w05a-
                            # Just the function, which was converted into a class name earlier
                            cls = args[0]
                        elif desc.args[0].name == "java/lang/Class":
                            # Builder.create(Class), 18w06a+
                            # The type that represents something that cannot be serialized
                            cls = None
                        else:
                            # Assume Builder.create(EntityCategory) in 19w05a+,
                            # though it could be hit for other unknown signatures
                            cls = None
                    else:
                        # Assume Builder.create(), though this could be hit for other unknown signatures
                        # In 18w05a and below, nonserializable entities
                        cls = None

                    return {"class": cls} if cls else {"serializable": "false"}
Beispiel #31
0
    def _process_1point12(aggregate, classloader, verbose):
        # Handles versions prior to 1.13
        superclass = aggregate["classes"]["block.superclass"]
        cf = classloader[superclass]

        is_flattened = aggregate["version"]["is_flattened"]
        individual_textures = True  #aggregate["version"]["protocol"] >= 52 # assume >1.5 http://wiki.vg/Protocol_History#1.5.x since don't read packets TODO

        if "tile" in aggregate["language"]:
            language = aggregate["language"]["tile"]
        elif "block" in aggregate["language"]:
            language = aggregate["language"]["block"]
        else:
            language = None

        # Find the static block registration method
        method = cf.methods.find_one(args='',
                                     returns="V",
                                     f=lambda m: m.access_flags.acc_public and
                                     m.access_flags.acc_static)

        blocks = aggregate.setdefault("blocks", {})
        block = blocks.setdefault("block", {})
        ordered_blocks = blocks.setdefault("ordered_blocks", [])
        tmp = []

        stack = []
        locals = {}
        for ins in method.code.disassemble():
            if ins.mnemonic == "new":
                # The beginning of a new block definition
                const = ins.operands[0]
                class_name = const.name.value
                current_block = {"class": class_name, "calls": {}}

                stack.append(current_block)
            elif ins.mnemonic.startswith("fconst"):
                stack.append(float(ins.mnemonic[-1]))
            elif ins.mnemonic == "aconst_null":
                stack.append(None)
            elif ins.mnemonic in ("bipush", "sipush"):
                stack.append(ins.operands[0].value)
            elif ins.mnemonic == "fdiv":
                den = stack.pop()
                num = stack.pop()
                if isinstance(den, (float, int)) and isinstance(
                        num, dict) and "scale" in num:
                    num["scale"] /= den
                    stack.append(num)
                else:
                    stack.append({"numerator": num, "denominator": den})
            elif ins.mnemonic in ("ldc", "ldc_w"):
                const = ins.operands[0]

                if isinstance(const, ConstantClass):
                    stack.append("%s.class" % const.name.value)
                elif isinstance(const, String):
                    stack.append(const.string.value)
                else:
                    stack.append(const.value)
            elif ins.mnemonic == "getstatic":
                const = ins.operands[0]
                if const.class_.name.value == superclass:
                    # Probably getting the static AIR resource location
                    stack.append("air")
                else:
                    stack.append({"obj": None, "field": repr(const)})
            elif ins.mnemonic == "getfield":
                const = ins.operands[0]
                obj = stack.pop()
                if "text_id" in obj:
                    stack.append({
                        "block": obj["text_id"],
                        "field": const.name_and_type.name.value,
                        "scale": 1
                    })
                else:
                    stack.append({"obj": obj, "field": repr(const)})
            elif ins.mnemonic in ("invokevirtual", "invokespecial",
                                  "invokeinterface"):
                # A method invocation
                const = ins.operands[0]
                method_name = const.name_and_type.name.value
                method_desc = const.name_and_type.descriptor.value
                desc = method_descriptor(method_desc)
                num_args = len(desc.args)

                if method_name == "hasNext":
                    # We've reached the end of block registration
                    # (and have started iterating over registry keys)
                    break

                args = []
                for i in six.moves.range(num_args):
                    args.insert(0, stack.pop())
                obj = stack.pop()

                if "calls" in obj:
                    obj["calls"][method_name + method_desc] = args

                if desc.returns.name != "void":
                    if desc.returns.name == superclass:
                        stack.append(obj)
                    else:
                        stack.append({
                            "obj": obj,
                            "method": const,
                            "args": args
                        })
            elif ins.mnemonic == "invokestatic":
                # Call to the registration method
                const = ins.operands[0]
                desc = method_descriptor(const.name_and_type.descriptor.value)
                num_args = len(desc.args)

                if num_args == 3:
                    current_block = stack.pop()
                    current_block["text_id"] = stack.pop()
                    current_block["numeric_id"] = stack.pop()
                else:
                    assert num_args == 2
                    current_block = stack.pop()
                    current_block["text_id"] = stack.pop()

                tmp.append(current_block)
            elif ins.mnemonic == "astore":
                locals[ins.operands[0].value] = stack.pop()
            elif ins.mnemonic == "aload":
                stack.append(locals[ins.operands[0].value])
            elif ins.mnemonic == "dup":
                stack.append(stack[-1])
            elif ins.mnemonic == "checkcast":
                pass
            elif verbose:
                print("Unknown instruction %s: stack is %s" % (ins, stack))

        # Now that we have all of the blocks, we need a few more things
        # to make sense of what it all means. So,
        #   1. Find the function that returns 'this' and accepts a string.
        #      This is the name or texture setting function.
        #   2. Find the function that returns 'this' and accepts a float.
        #      This is the function that sets the hardness.

        string_setter = cf.methods.find_one(
            returns="L" + superclass + ";",
            args="Ljava/lang/String;",
            f=lambda x: not x.access_flags.acc_static)

        if string_setter:
            name_setter = string_setter.name.value + cf.constants.get(
                string_setter.descriptor.index).value
        else:
            name_setter = None

        float_setters = list(
            cf.methods.find(returns="L" + superclass + ";",
                            args="F",
                            f=lambda x: x.access_flags.acc_protected))

        for method in float_setters:
            fld = None
            for ins in method.code.disassemble():
                if ins.mnemonic == "putfield":
                    const = ins.operands[0]
                    fld = const.name_and_type.name.value
                elif ins.mnemonic == "ifge":
                    hardness_setter = method.name.value + method.descriptor.value
                    hardness_field = fld
                    break

        for method in float_setters:
            # Look for the resistance setter, which multiplies by 3.
            is_resistance = False
            for ins in method.code.disassemble():
                if ins.mnemonic in ("ldc", "ldc_w"):
                    is_resistance = (ins.operands[0].value == 3.0)
                elif ins.mnemonic == "fmul" and is_resistance:
                    resistance_setter = method.name.value + method.descriptor.value
                elif ins.mnemonic == "putfield" and is_resistance:
                    const = ins.operands[0]
                    resistance_field = const.name_and_type.name.value
                    break
                else:
                    is_resistance = False

        for method in float_setters:
            # Look for the light setter, which multiplies by 15, but 15 is the first value (15 * val)
            is_light = False
            for ins in method.code.disassemble():
                if ins.mnemonic in ("ldc", "ldc_w"):
                    is_light = (ins.operands[0].value == 15.0)
                elif ins.mnemonic.startswith("fload"):
                    pass
                elif ins.mnemonic == "fmul" and is_light:
                    light_setter = method.name.value + method.descriptor.value
                    break
                else:
                    is_light = False

        if is_flattened:
            # Current IDs are incremental, manually track them
            cur_id = 0

        for blk in tmp:
            if not "text_id" in blk:
                if verbose:
                    print("Dropping nameless block:", blk)
                continue

            final = {}

            if "numeric_id" in blk:
                assert not is_flattened
                final["numeric_id"] = blk["numeric_id"]
            else:
                assert is_flattened
                final["numeric_id"] = cur_id
                cur_id += 1

            if "text_id" in blk:
                final["text_id"] = blk["text_id"]

            final["class"] = blk["class"]

            if name_setter in blk["calls"]:
                final["name"] = blk["calls"][name_setter][0]

            if "name" in final:
                lang_key = "%s.name" % final["name"]
            else:
                # 17w43a (1.13) and above - no specific translation string, only the id
                lang_key = "minecraft.%s" % final["text_id"]
            if language and lang_key in language:
                final["display_name"] = language[lang_key]

            if hardness_setter not in blk["calls"]:
                final["hardness"] = 0.0
                final["resistance"] = 0.0
            else:
                stack = blk["calls"][hardness_setter]
                if len(stack) == 0:
                    if verbose:
                        print("%s: Broken hardness value" % final["text_id"])
                    final["hardness"] = 0.0
                    final["resistance"] = 0.0
                else:
                    hardness = blk["calls"][hardness_setter][0]
                    if isinstance(hardness, dict) and "field" in hardness:
                        # Repair field info
                        assert hardness["field"] == hardness_field
                        assert "block" in hardness
                        assert hardness["block"] in block
                        hardness = block[
                            hardness["block"]]["hardness"] * hardness["scale"]
                    final["hardness"] = hardness
                    # NOTE: vanilla multiples this value by 5, but then divides by 5 later
                    # Just ignore that multiplication to avoid confusion.
                    final["resistance"] = hardness

            if resistance_setter in blk["calls"]:
                resistance = blk["calls"][resistance_setter][0]
                if isinstance(resistance, dict) and "field" in resistance:
                    # Repair field info
                    assert resistance["field"] == resistance_field
                    assert "block" in resistance
                    assert resistance["block"] in block
                    resistance = block[resistance["block"]][
                        "resistance"] * resistance["scale"]
                # The * 3 is also present in vanilla, strange logic
                # Division to normalize for the multiplication/division by 5.
                final["resistance"] = resistance * 3.0 / 5.0
            # Already set in the hardness area, so no need for an else clause

            if light_setter in blk["calls"]:
                final["light"] = int(blk["calls"][light_setter][0] * 15)

            ordered_blocks.append(final["text_id"])
            block[final["text_id"]] = final
Beispiel #32
0
        def find_field(cls, field_name):
            """
            cls: name of the class
            field_name: name of the field to find.  If None, returns all fields
            """
            if cls in fields_by_class:
                if field_name is not None:
                    if field_name not in fields_by_class[cls] and verbose:
                        print("Requested field %s.%s but that wasn't found last time" % (cls, field_name))
                    return fields_by_class[cls][field_name]
                else:
                    return fields_by_class[cls]
            elif cls == aggregate["classes"]["sounds.list"]:
                # Another scary case.  We don't want to parse all of the sound events.
                return object()

            cf = classloader[cls]

            fields_by_class[cls] = {}
            super_name = cf.super_.name.value
            if not super_name.startswith("java/lang"):
                # Add fields from superclass
                fields_by_class[cls].update(find_field(super_name, None))

            init = cf.methods.find_one(name="<clinit>")
            if not init:
                if field_name is not None:
                    return fields_by_class[cls][field_name]
                else:
                    return fields_by_class[cls]

            stack = []
            locals = {}
            # After certain calls, we're no longer storing properties.
            # But, we still want to assign values for remaining fields;
            # go through and put None in, only looking at putstatic.
            ignore_remaining = False

            for ins in init.code.disassemble():
                if ins == "putstatic":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    if ignore_remaining:
                        value = None
                    else:
                        value = stack.pop()

                    if isinstance(value, dict):
                        if "declared_in" not in value:
                            # If there's already a declared_in, this is a field
                            # loaded with getstatic, and we don't want to change
                            # the true location of it
                            value["declared_in"] = cls
                        if value["class"] == plane:
                            # Convert to an instance of Plane
                            # Now is the easiest time to do this, and for
                            # Plane itself it doesn't matter since it's never
                            # used on the stack
                            assert "enum_name" in value
                            assert value["enum_name"] in PLANES
                            value = PLANES[value["enum_name"]]
                    fields_by_class[cls][name] = value
                elif ignore_remaining:
                    continue
                elif ins == "getstatic":
                    const = ins.operands[0]
                    target = const.class_.name.value
                    type = field_descriptor(const.name_and_type.descriptor.value).name
                    name = const.name_and_type.name.value
                    if not target.startswith("java/"):
                        stack.append(find_field(target, name))
                    else:
                        stack.append(object())
                elif ins in ("ldc", "ldc_w", "ldc2_w"):
                    const = ins.operands[0]

                    if isinstance(const, ConstantClass):
                        stack.append("%s.class" % const.name.value)
                    elif isinstance(const, String):
                        stack.append(const.string.value)
                    else:
                        stack.append(const.value)
                elif ins.mnemonic.startswith("dconst"):
                    stack.append(float(ins.mnemonic[-1]))
                elif ins in ("bipush", "sipush"):
                    stack.append(ins.operands[0].value)
                elif ins == "aconst_null":
                    stack.append(None)
                elif ins in ("anewarray", "newarray"):
                    length = stack.pop()
                    stack.append([None] * length)
                elif ins in ("aaload", "iaload"):
                    index = stack.pop()
                    array = stack.pop()
                    prop = array[index].copy()
                    prop["array_index"] = index
                    stack.append(prop)
                elif ins in ("aastore", "iastore"):
                    value = stack.pop()
                    index = stack.pop()
                    array = stack.pop()
                    array[index] = value
                elif ins == "arraylength":
                    array = stack.pop()
                    stack.append(len(array))
                elif ins == "dup":
                    stack.append(stack[-1])
                elif ins == "invokedynamic":
                    # Try to get the class that's being created
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    stack.append({"dynamic_class": desc.returns.name, "class": cls})
                elif ins.mnemonic.startswith("invoke"):
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    num_args = len(desc.args)
                    args = [stack.pop() for _ in six.moves.range(num_args)]
                    args.reverse()

                    if ins == "invokestatic":
                        if const.class_.name.value.startswith("com/google/"):
                            # Call to e.g. Maps.newHashMap, beyond what we
                            # care about
                            ignore_remaining = True
                            continue
                        obj = None
                    else:
                        obj = stack.pop()

                    if desc.returns.name in property_types:
                        prop = {
                            "class": desc.returns.name,
                            "type": property_types[desc.returns.name],
                            "args": args
                        }
                        stack.append(prop)
                    elif const.name_and_type.name == "<init>":
                        if obj["is_enum"]:
                            obj["enum_name"] = args[0]
                            obj["enum_ordinal"] = args[1]
                        else:
                            obj["args"] = args
                    elif const.name_and_type.name == "values":
                        # Enum values
                        fields = find_field(const.class_.name.value, None)
                        stack.append([fld for fld in fields
                                      if isinstance(fld, dict) and fld["is_enum"]])
                    elif desc.returns.name != "void":
                        if isinstance(obj, Plane):
                            # One special case, where EnumFacing.Plane is used
                            # to get a list of directions
                            stack.append(obj.directions)
                        elif (isinstance(obj, dict) and obj["is_enum"] and
                                desc.returns.name == "int"):
                            # Assume it's the enum ordinal, even if it really
                            # isn't
                            stack.append(obj["enum_ordinal"])
                        else:
                            o = object()
                            stack.append(o)
                elif ins in ("istore", "lstore", "fstore", "dstore", "astore"):
                    # Store other than array store
                    locals[ins.operands[0].value] = stack.pop()
                elif ins in ("iload", "lload", "fload", "dload", "aload"):
                    # Load other than array load
                    stack.append(locals[ins.operands[0].value])
                elif ins == "new":
                    const = ins.operands[0]
                    type_name = const.name.value
                    obj = {
                        "class": type_name,
                        "is_enum": is_enum(type_name)
                    }
                    stack.append(obj)
                elif ins == "checkcast":
                    # We don't have type information, so no checking or casting
                    pass
                elif ins == "return":
                    break
                elif ins == "if_icmpge":
                    # Code in stairs that loops over state combinations for hitboxes
                    break
                elif verbose:
                    print("%s initializer contains unimplemented ins %s" % (cls, ins))

            if field_name is not None:
                return fields_by_class[cls][field_name]
            else:
                return fields_by_class[cls]
Beispiel #33
0
        def process_class(name):
            """
            Gets the properties for the given block class, checking the parent
            class if none are defined.  Returns the properties, and also adds
            them to properties_by_class
            """
            if name in properties_by_class:
                # Caching - avoid reading the same class multiple times
                return properties_by_class[name]

            cf = classloader[name]
            method = cf.methods.find_one(f=matches)

            if not method:
                properties = process_class(cf.super_.name.value)
                properties_by_class[name] = properties
                return properties

            properties = None
            if_pos = None
            stack = []
            for ins in method.code.disassemble():
                # This could _almost_ just be checking for getstatic, but
                # brewing stands use an array of properties as the field,
                # so we need some stupid extra logic.
                if ins == "new":
                    assert not is_18w19a # In 18w19a this should be a parameter
                    const = ins.operands[0]
                    type_name = const.name.value
                    assert type_name == blockstatecontainer
                    stack.append(object())
                elif ins == "aload" and ins.operands[0].value == 1:
                    assert is_18w19a # The parameter is only used in 18w19a and above
                    stack.append(object())
                elif ins in ("sipush", "bipush"):
                    stack.append(ins.operands[0].value)
                elif ins in ("anewarray", "newarray"):
                    length = stack.pop()
                    val = [None] * length
                    stack.append(val)
                elif ins == "getstatic":
                    const = ins.operands[0]
                    prop = {
                        "field_name": const.name_and_type.name.value
                    }
                    desc = field_descriptor(const.name_and_type.descriptor.value)
                    _property_types.add(desc.name)
                    stack.append(prop)
                elif ins == "aaload":
                    index = stack.pop()
                    array = stack.pop()
                    prop = array.copy()
                    prop["array_index"] = index
                    stack.append(prop)
                elif ins == "aastore":
                    value = stack.pop()
                    index = stack.pop()
                    array = stack.pop()
                    array[index] = value
                elif ins == "dup":
                    stack.append(stack[-1])
                elif ins == "invokespecial":
                    const = ins.operands[0]
                    assert const.name_and_type.name == "<init>"
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    assert len(desc.args) == 2

                    # Normally this constructor call would return nothing, but
                    # in this case we'd rather remove the object it's called on
                    # and keep the properties array (its parameter)
                    arg = stack.pop()
                    stack.pop() # Block
                    stack.pop() # Invocation target
                    stack.append(arg)
                elif ins == "invokevirtual":
                    # Two possibilities (both only present pre-flattening):
                    # 1. It's isDouble() for a slab.  Two different sets of
                    #    properties in that case.
                    # 2. It's getTypeProperty() for flowers.  Only one
                    #    set of properties, but other hacking is needed.
                    # We can differentiate these cases based off of the return
                    # type.
                    # There is a third option post 18w19a:
                    # 3. It's calling the state container's register method.
                    # We can check this just by the type.
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)

                    if const.class_.name == blockstatecontainer:
                        # Case 3.
                        assert properties == None
                        properties = stack.pop()
                        assert desc.returns.name == blockstatecontainer
                        # Don't pop anything, since we'd just pop and re-add the builder
                    elif desc.returns.name == "boolean":
                        # Case 2.
                        properties = [None, None]
                        stack.pop() # Target object
                        # XXX shouldn't something be returned here?
                    else:
                        # Case 1.
                        # Assume that the return type is the base interface
                        # for properties
                        stack.pop() # Target object
                        stack.append(None)
                elif ins == "ifeq":
                    assert if_pos is None
                    if_pos = ins.pos + ins.operands[0].value
                elif ins == "pop":
                    stack.pop()
                elif ins == "areturn":
                    assert not is_18w19a # In 18w19a we don't return a container
                    if if_pos == None:
                        assert properties == None
                        properties = stack.pop()
                    else:
                        assert isinstance(properties, list)
                        index = 0 if ins.pos < if_pos else 1
                        assert properties[index] == None
                        properties[index] = stack.pop()
                elif ins == "return":
                    assert is_18w19a # We only return void in 18w19a
                elif ins == "aload":
                    assert ins.operands[0].value == 0 # Should be aload_0 (this)
                    stack.append(object())
                elif verbose:
                    print("%s createBlockState contains unimplemented ins %s" % (name, ins))

            if properties is None:
                # If we never set properties, warn; however, this is normal for
                # the base implementation in Block in 18w19a
                if verbose and name != aggregate["classes"]["block.superclass"]:
                    print("Didn't find anything that set properties for %s" % name)
                properties = []
            properties_by_class[name] = properties
            return properties
Beispiel #34
0
        def find_field(cls, field_name):
            """
            cls: name of the class
            field_name: name of the field to find.  If None, returns all fields
            """
            if cls in fields_by_class:
                if field_name is not None:
                    if field_name not in fields_by_class[cls] and verbose:
                        print("Requested field %s.%s but that wasn't found last time" % (cls, field_name))
                    return fields_by_class[cls][field_name]
                else:
                    return fields_by_class[cls]
            elif cls == aggregate["classes"].get("sounds.list"):
                # If we already know what the sounds list class is, just ignore it
                # as going through it would take a while for no reason
                return object()

            cf = classloader[cls]

            fields_by_class[cls] = {}
            super_name = cf.super_.name.value
            if not super_name.startswith("java/lang"):
                # Add fields from superclass
                fields_by_class[cls].update(find_field(super_name, None))

            init = cf.methods.find_one(name="<clinit>")
            if not init:
                if field_name is not None:
                    return fields_by_class[cls][field_name]
                else:
                    return fields_by_class[cls]

            stack = []
            locals = {}
            # After certain calls, we're no longer storing properties.
            # But, we still want to assign values for remaining fields;
            # go through and put None in, only looking at putstatic.
            ignore_remaining = False

            for ins in init.code.disassemble():
                if ins == "putstatic":
                    const = ins.operands[0]
                    name = const.name_and_type.name.value
                    if ignore_remaining:
                        value = None
                    else:
                        value = stack.pop()

                    if isinstance(value, dict):
                        if "declared_in" not in value:
                            # If there's already a declared_in, this is a field
                            # loaded with getstatic, and we don't want to change
                            # the true location of it
                            value["declared_in"] = cls
                        if value["class"] == plane:
                            # Convert to an instance of Plane
                            # Now is the easiest time to do this, and for
                            # Plane itself it doesn't matter since it's never
                            # used on the stack
                            assert "enum_name" in value
                            assert value["enum_name"] in PLANES
                            value = PLANES[value["enum_name"]]
                    fields_by_class[cls][name] = value
                elif ignore_remaining:
                    continue
                elif ins == "getstatic":
                    const = ins.operands[0]
                    target = const.class_.name.value
                    type = field_descriptor(const.name_and_type.descriptor.value).name
                    name = const.name_and_type.name.value
                    if not target.startswith("java/"):
                        stack.append(find_field(target, name))
                    else:
                        stack.append(object())
                elif ins in ("ldc", "ldc_w", "ldc2_w"):
                    const = ins.operands[0]

                    if isinstance(const, ConstantClass):
                        stack.append("%s.class" % const.name.value)
                    elif isinstance(const, String):
                        stack.append(const.string.value)
                    else:
                        stack.append(const.value)
                elif ins.mnemonic.startswith("dconst"):
                    stack.append(float(ins.mnemonic[-1]))
                elif ins in ("bipush", "sipush"):
                    stack.append(ins.operands[0].value)
                elif ins == "aconst_null":
                    stack.append(None)
                elif ins in ("anewarray", "newarray"):
                    length = stack.pop()
                    stack.append([None] * length)
                elif ins in ("aaload", "iaload"):
                    index = stack.pop()
                    array = stack.pop()
                    prop = array[index].copy()
                    prop["array_index"] = index
                    stack.append(prop)
                elif ins in ("aastore", "iastore"):
                    value = stack.pop()
                    index = stack.pop()
                    array = stack.pop()
                    array[index] = value
                elif ins == "arraylength":
                    array = stack.pop()
                    stack.append(len(array))
                elif ins == "dup":
                    stack.append(stack[-1])
                elif ins == "invokedynamic":
                    # Try to get the class that's being created
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    stack.append({"dynamic_class": desc.returns.name, "class": cls})
                elif ins.mnemonic.startswith("invoke"):
                    const = ins.operands[0]
                    desc = method_descriptor(const.name_and_type.descriptor.value)
                    num_args = len(desc.args)
                    args = [stack.pop() for _ in six.moves.range(num_args)]
                    args.reverse()

                    if ins == "invokestatic":
                        if const.class_.name.value.startswith("com/google/"):
                            # Call to e.g. Maps.newHashMap, beyond what we
                            # care about
                            ignore_remaining = True
                            continue
                        obj = None
                    else:
                        obj = stack.pop()

                    if desc.returns.name in property_types:
                        prop = {
                            "class": desc.returns.name,
                            "type": property_types[desc.returns.name],
                            "args": args
                        }
                        stack.append(prop)
                    elif const.name_and_type.name == "<init>":
                        if obj["is_enum"]:
                            obj["enum_name"] = args[0]
                            obj["enum_ordinal"] = args[1]
                        else:
                            obj["args"] = args
                    elif const.name_and_type.name == "values":
                        # Enum values
                        fields = find_field(const.class_.name.value, None)
                        stack.append([fld for fld in fields
                                      if isinstance(fld, dict) and fld["is_enum"]])
                    elif desc.returns.name != "void":
                        if isinstance(obj, Plane):
                            # One special case, where EnumFacing.Plane is used
                            # to get a list of directions
                            stack.append(obj.directions)
                        elif (isinstance(obj, dict) and obj["is_enum"] and
                                desc.returns.name == "int"):
                            # Assume it's the enum ordinal, even if it really
                            # isn't
                            stack.append(obj["enum_ordinal"])
                        else:
                            o = object()
                            stack.append(o)
                elif ins in ("istore", "lstore", "fstore", "dstore", "astore"):
                    # Store other than array store
                    locals[ins.operands[0].value] = stack.pop()
                elif ins in ("iload", "lload", "fload", "dload", "aload"):
                    # Load other than array load
                    stack.append(locals[ins.operands[0].value])
                elif ins == "new":
                    const = ins.operands[0]
                    type_name = const.name.value
                    obj = {
                        "class": type_name,
                        "is_enum": is_enum(type_name)
                    }
                    stack.append(obj)
                elif ins == "checkcast":
                    # We don't have type information, so no checking or casting
                    pass
                elif ins == "return":
                    break
                elif ins == "if_icmpge":
                    # Code in stairs that loops over state combinations for hitboxes
                    break
                elif verbose:
                    print("%s initializer contains unimplemented ins %s" % (cls, ins))

            if field_name is not None:
                return fields_by_class[cls][field_name]
            else:
                return fields_by_class[cls]