Example #1
0
    def _entities_1point11(aggregate, classloader, verbose):
        # 1.11 logic
        if verbose:
            print("Using 1.11 entity format")

        listclass = aggregate["classes"]["entity.list"]
        cf = classloader[listclass]

        entities = aggregate.setdefault("entities", {})
        entity = entities.setdefault("entity", {})

        method = cf.methods.find_one(args='', returns="V", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static)

        minecart_info = {}
        class EntityContext(WalkerCallback):
            def on_get_field(self, ins, const, obj):
                # Minecarts use an enum for their data - assume that this is that enum
                const = ins.operands[0]
                if not "types_by_field" in minecart_info:
                    EntityTopping._load_minecart_enum(classloader, const.class_.name.value, minecart_info)
                minecart_name = minecart_info["types_by_field"][const.name_and_type.name.value]
                return minecart_info["types"][minecart_name]

            def on_invoke(self, ins, const, obj, args):
                if const.class_.name == listclass:
                    if len(args) == 4:
                        # Initial registration
                        name = args[1]
                        old_name = args[3]
                        entity[name] = {
                            "id": args[0],
                            "name": name,
                            "class": args[2][:-len(".class")],
                            "old_name": old_name
                        }

                        if old_name + ".name" in aggregate["language"]["entity"]:
                            entity[name]["display_name"] = aggregate["language"]["entity"][old_name + ".name"]
                    elif len(args) == 3:
                        # Spawn egg registration
                        name = args[0]
                        if name in entity:
                            entity[name]["egg_primary"] = args[1]
                            entity[name]["egg_secondary"] = args[2]
                        elif verbose:
                            print("Missing entity during egg registration: %s" % name)
                elif const.class_.name == minecart_info["class"]:
                    # Assume that obj is the minecart info, and the method being called is the one that gets the name
                    return obj["entitytype"]

            def on_new(self, ins, const):
                raise Exception("unexpected new: %s" % ins)

            def on_put_field(self, ins, const, obj, value):
                raise Exception("unexpected putfield: %s" % ins)

        walk_method(cf, method, EntityContext(), verbose)
Example #2
0
    def _process_1point13(aggregate, classloader, verbose):
        # Handles versions after 1.13 (specifically >= 18w02a)
        superclass = aggregate["classes"]["block.superclass"]
        cf = classloader[superclass]

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

        # Figure out what the builder class is
        ctor = cf.methods.find_one(name="<init>")
        builder_class = ctor.args[0].name

        builder_cf = classloader[builder_class]
        # Sets hardness and resistance
        hardness_setter = builder_cf.methods.find_one(args='FF')
        # There's also one that sets both to the same value
        hardness_setter_2 = None
        for method in builder_cf.methods.find(args='F'):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter.descriptor.value):
                        hardness_setter_2 = method
                        break
        assert hardness_setter_2 != None
        # ... and one that sets them both to 0
        hardness_setter_3 = None
        for method in builder_cf.methods.find(args=''):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter_2.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter_2.descriptor.value):
                        hardness_setter_3 = method
                        break
        assert hardness_setter_3 != None

        light_setter = builder_cf.methods.find_one(args='I')

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

        # 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)

        class Walker(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_new(self, ins, const):
                class_name = const.name.value
                return {"class": class_name}

            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]
                        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
                        # 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()

            def on_get_field(self, ins, const, obj):
                if const.class_.name.value == superclass:
                    # Probably getting the static AIR resource location
                    return "air"
                else:
                    return object()

            def on_put_field(self, ins, const, obj, value):
                raise Exception("unexpected putfield: %s" % ins)

        walk_method(cf, method, Walker(), verbose)
Example #3
0
        def fill_class(cls):
            # Returns the starting index for metadata in subclasses of cls
            if cls == "java/lang/Object":
                return 0
            if cls in metadata_by_class:
                return len(metadata_by_class[cls]) + fill_class(
                    parent_by_class[cls])

            cf = classloader[cls]
            super = cf.super_.name.value
            parent_by_class[cls] = super
            index = fill_class(super)

            metadata = []

            class MetadataFieldContext(WalkerCallback):
                def __init__(self):
                    self.cur_index = index

                def on_invoke(self, ins, const, obj, args):
                    if const.class_.name == datamanager_class and const.name_and_type.name == create_key_method.name and const.name_and_type.descriptor == create_key_method.descriptor:
                        # Call to createKey.
                        # Sanity check: entities should only register metadata for themselves
                        if args[0] != cls + ".class":
                            # ... but in some versions, mojang messed this up with potions... hence why the sanity check exists in vanilla now.
                            if verbose:
                                other_class = args[0][:-len(".class")]
                                name = entity_classes.get(cls, "Unknown")
                                other_name = entity_classes.get(
                                    other_class, "Unknown")
                                print(
                                    "An entity tried to register metadata for another entity: %s (%s) from %s (%s)"
                                    % (other_name, other_class, name, cls))

                        serializer = args[1]
                        index = self.cur_index
                        self.cur_index += 1

                        metadata_entry = {
                            "serializer_id":
                            serializer["id"],
                            "serializer":
                            serializer["name"]
                            if "name" in serializer else serializer["id"],
                            "index":
                            index
                        }
                        metadata.append(metadata_entry)
                        return metadata_entry

                def on_put_field(self, ins, const, obj, value):
                    if isinstance(value, dict):
                        value["field"] = const.name_and_type.name.value

                def on_get_field(self, ins, const, obj):
                    if const.class_.name == dataserializers_class:
                        return dataserializers_by_field[
                            const.name_and_type.name.value]

                def on_invokedynamic(self, ins, const, args):
                    return object()

                def on_new(self, ins, const):
                    return object()

            init = cf.methods.find_one(name="<clinit>")
            if init:
                ctx = MetadataFieldContext()
                walk_method(cf, init, ctx, verbose)
                index = ctx.cur_index

            class MetadataDefaultsContext(WalkerCallback):
                def __init__(self, wait_for_putfield=False):
                    self.textcomponentstring = None
                    # True whlie waiting for "this.dataManager = new EntityDataManager(this);" when going through the entity constructor
                    self.waiting_for_putfield = wait_for_putfield

                def on_invoke(self, ins, const, obj, args):
                    if self.waiting_for_putfield:
                        return

                    if "Optional" in const.class_.name.value:
                        if const.name_and_type.name in ("absent", "empty"):
                            return "Empty"
                        elif len(args) == 1:
                            # Assume "of" or similar
                            return args[0]
                    elif const.name_and_type.name == "valueOf":
                        # Boxing methods
                        if const.class_.name == "java/lang/Boolean":
                            return bool(args[0])
                        else:
                            return args[0]
                    elif const.name_and_type.name == "<init>":
                        if const.class_.name == self.textcomponentstring:
                            obj["text"] = args[0]

                        return
                    elif const.class_.name == datamanager_class:
                        assert const.name_and_type.name == register_method.name
                        assert const.name_and_type.descriptor == register_method.descriptor

                        # args[0] is the metadata entry, and args[1] is the default value
                        if args[0] is not None and args[1] is not None:
                            args[0]["default"] = args[1]

                        return
                    elif const.name_and_type.descriptor.value.endswith(
                            "L" + datamanager_class + ";"):
                        # getDataManager, which doesn't really have a reason to exist given that the data manager field is accessible
                        return None
                    elif const.name_and_type.name == register_data_method_name and const.name_and_type.descriptor == register_data_method_desc:
                        # Call to super.registerData()
                        return

                def on_put_field(self, ins, const, obj, value):
                    if const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        if not self.waiting_for_putfield:
                            raise Exception("Unexpected putfield: %s" %
                                            (ins, ))
                        self.waiting_for_putfield = False

                def on_get_field(self, ins, const, obj):
                    if self.waiting_for_putfield:
                        return

                    if const.name_and_type.descriptor == "L" + dataparameter_class + ";":
                        # Definitely shouldn't be registering something declared elsewhere
                        assert const.class_.name == cls
                        for metadata_entry in metadata:
                            if const.name_and_type.name == metadata_entry.get(
                                    "field"):
                                return metadata_entry
                        else:
                            if verbose:
                                print(
                                    "Can't figure out metadata entry for field %s; default will not be set."
                                    % (const, ))
                            return None

                    if const.class_.name == aggregate["classes"]["position"]:
                        # Assume BlockPos.ORIGIN
                        return "(0, 0, 0)"
                    elif const.class_.name == aggregate["classes"][
                            "itemstack"]:
                        # Assume ItemStack.EMPTY
                        return "Empty"
                    elif const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        return
                    else:
                        return None

                def on_new(self, ins, const):
                    if self.waiting_for_putfield:
                        return

                    if self.textcomponentstring == None:
                        # Check if this is TextComponentString
                        temp_cf = classloader[const.name.value]
                        for str in temp_cf.constants.find(type_=String):
                            if "TextComponent{text=" in str.string.value:
                                self.textcomponentstring = const.name.value
                                break

                    if const.name == aggregate["classes"]["nbtcompound"]:
                        return "Empty"
                    elif const.name == self.textcomponentstring:
                        return {'text': None}

            register = cf.methods.find_one(
                name=register_data_method_name,
                f=lambda m: m.descriptor == register_data_method_desc)
            if register and not register.access_flags.acc_abstract:
                walk_method(cf, register, MetadataDefaultsContext(), verbose)
            elif cls == base_entity_class:
                walk_method(cf, cf.methods.find_one(name="<init>"),
                            MetadataDefaultsContext(True), verbose)

            metadata_by_class[cls] = metadata

            return index
Example #4
0
    def _process_1point14(aggregate, classloader, verbose):
        # Handles versions after 1.14 (specifically >= 18w43a)
        # All of the registration happens in the list class in this version.
        listclass = aggregate["classes"]["item.list"]
        lcf = classloader[listclass]
        superclass = next(lcf.fields.find(
        )).type.name  # The first field in the list class is an item
        cf = classloader[superclass]
        aggregate["classes"]["item.superclass"] = superclass
        blockclass = aggregate["classes"]["block.superclass"]
        blocklist = aggregate["classes"]["block.list"]

        cf = classloader[superclass]

        if "item" in aggregate["language"]:
            language = aggregate["language"]["item"]
        else:
            language = None

        # Figure out what the builder class is
        ctor = cf.methods.find_one(name="<init>")
        builder_class = ctor.args[0].name
        builder_cf = classloader[builder_class]

        # Find the max stack size method
        max_stack_method = None
        for method in builder_cf.methods.find(args='I'):
            for ins in method.code.disassemble():
                if ins.mnemonic in ("ldc", "ldc_w"):
                    const = ins.operands[0]
                    if isinstance(
                            const, String
                    ) and const.string.value == "Unable to have damage AND stack.":
                        max_stack_method = method
                        break
        if not max_stack_method:
            raise Exception("Couldn't find max stack size setter in " +
                            builder_class)

        register_item_block_method = lcf.methods.find_one(
            args='L' + blockclass + ';', returns='L' + superclass + ';')
        item_block_class = None
        # Find the class used that represents an item that is a block
        for ins in register_item_block_method.code.disassemble():
            if ins.mnemonic == "new":
                const = ins.operands[0]
                item_block_class = const.name.value
                break

        items = aggregate.setdefault("items", {})
        item_list = items.setdefault("item", {})
        item_fields = items.setdefault("item_fields", {})

        is_item_class_cache = {superclass: True}

        def is_item_class(name):
            if name in is_item_class_cache:
                return is_item_class_cache
            elif name == 'java/lang/Object':
                return True
            elif '/' in name:
                return False

            cf = classloader[name]
            result = is_item_class(cf.super_.name.value)
            is_item_class_cache[name] = result
            return result

        # Find the static block registration method
        method = lcf.methods.find_one(name='<clinit>')

        class Walker(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_new(self, ins, const):
                class_name = const.name.value
                return {"class": class_name}

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

                        return 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.mnemonic == "invokestatic":
                            # Probably returning itself, but through a synthetic method
                            return args[0]
                        else:
                            # Probably returning itself
                            return obj
                    else:
                        return object()

            def on_get_field(self, ins, const, obj):
                if const.class_.name.value == blocklist:
                    # Getting a block; put it on the stack.
                    block_name = aggregate["blocks"]["block_fields"][
                        const.name_and_type.name.value]
                    if block_name not in aggregate["blocks"]["block"]:
                        if verbose:
                            print(
                                "No information available for item-block for %s/%s"
                                % (const.name_and_type.name.value, block_name))
                        return {}
                    else:
                        return aggregate["blocks"]["block"][block_name]
                elif const.class_.name.value == listclass:
                    return item_list[item_fields[
                        const.name_and_type.name.value]]
                else:
                    return const

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    field = const.name_and_type.name.value
                    value["field"] = field
                    item_fields[
                        const.name_and_type.name.value] = value["text_id"]

        walk_method(cf, method, Walker(), verbose)
Example #5
0
        def fill_class(cls):
            # Returns the starting index for metadata in subclasses of cls
            if cls == "java/lang/Object":
                return 0
            if cls in metadata_by_class:
                return len(metadata_by_class[cls]) + fill_class(parent_by_class[cls])

            cf = classloader[cls]
            super = cf.super_.name.value
            parent_by_class[cls] = super
            index = fill_class(super)

            metadata = []
            class MetadataFieldContext(WalkerCallback):
                def __init__(self):
                    self.cur_index = index

                def on_invoke(self, ins, const, obj, args):
                    if const.class_.name == datamanager_class and const.name_and_type.name == create_key_method.name and const.name_and_type.descriptor == create_key_method.descriptor:
                        # Call to createKey.
                        # Sanity check: entities should only register metadata for themselves
                        if args[0] != cls + ".class":
                            # ... but in some versions, mojang messed this up with potions... hence why the sanity check exists in vanilla now.
                            if verbose:
                                other_class = args[0][:-len(".class")]
                                name = entity_classes.get(cls, "Unknown")
                                other_name = entity_classes.get(other_class, "Unknown")
                                print("An entity tried to register metadata for another entity: %s (%s) from %s (%s)" % (other_name, other_class, name, cls))

                        serializer = args[1]
                        index = self.cur_index
                        self.cur_index += 1

                        metadata_entry = {
                            "serializer_id": serializer["id"],
                            "serializer": serializer["name"] if "name" in serializer else serializer["id"],
                            "index": index
                        }
                        metadata.append(metadata_entry)
                        return metadata_entry

                def on_put_field(self, ins, const, obj, value):
                    if isinstance(value, dict):
                        value["field"] = const.name_and_type.name.value

                def on_get_field(self, ins, const, obj):
                    if const.class_.name == dataserializers_class:
                        return dataserializers_by_field[const.name_and_type.name.value]

                def on_invokedynamic(self, ins, const):
                    return object()

                def on_new(self, ins, const):
                    return object()

            init = cf.methods.find_one(name="<clinit>")
            if init:
                ctx = MetadataFieldContext()
                walk_method(cf, init, ctx, verbose)
                index = ctx.cur_index

            class MetadataDefaultsContext(WalkerCallback):
                def __init__(self, wait_for_putfield=False):
                    self.textcomponentstring = None
                    # True whlie waiting for "this.dataManager = new EntityDataManager(this);" when going through the entity constructor
                    self.waiting_for_putfield = wait_for_putfield

                def on_invoke(self, ins, const, obj, args):
                    if self.waiting_for_putfield:
                        return

                    if "Optional" in const.class_.name.value:
                        if const.name_and_type.name in ("absent", "empty"):
                            return "Empty"
                        elif len(args) == 1:
                            # Assume "of" or similar
                            return args[0]
                    elif const.name_and_type.name == "valueOf":
                        # Boxing methods
                        if const.class_.name == "java/lang/Boolean":
                            return bool(args[0])
                        else:
                            return args[0]
                    elif const.name_and_type.name == "<init>":
                        if const.class_.name == self.textcomponentstring:
                            obj["text"] = args[0]

                        return
                    elif const.class_.name == datamanager_class:
                        assert const.name_and_type.name == register_method.name
                        assert const.name_and_type.descriptor == register_method.descriptor

                        # args[0] is the metadata entry, and args[1] is the default value
                        if args[0] is not None and args[1] is not None:
                            args[0]["default"] = args[1]

                        return
                    elif const.name_and_type.descriptor.value.endswith("L" + datamanager_class + ";"):
                        # getDataManager, which doesn't really have a reason to exist given that the data manager field is accessible
                        return None
                    elif const.name_and_type.name == register_data_method_name and const.name_and_type.descriptor == register_data_method_desc:
                        # Call to super.registerData()
                        return

                def on_put_field(self, ins, const, obj, value):
                    if const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        if not self.waiting_for_putfield:
                            raise Exception("Unexpected putfield: %s" % (ins,))
                        self.waiting_for_putfield = False

                def on_get_field(self, ins, const, obj):
                    if self.waiting_for_putfield:
                        return

                    if const.name_and_type.descriptor == "L" + dataparameter_class + ";":
                        # Definitely shouldn't be registering something declared elsewhere
                        assert const.class_.name == cls
                        for metadata_entry in metadata:
                            if const.name_and_type.name == metadata_entry.get("field"):
                                return metadata_entry
                        else:
                            if verbose:
                                print("Can't figure out metadata entry for field %s; default will not be set." % (const,))
                            return None

                    if const.class_.name == aggregate["classes"]["position"]:
                        # Assume BlockPos.ORIGIN
                        return "(0, 0, 0)"
                    elif const.class_.name == aggregate["classes"]["itemstack"]:
                        # Assume ItemStack.EMPTY
                        return "Empty"
                    elif const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        return
                    else:
                        return None

                def on_new(self, ins, const):
                    if self.waiting_for_putfield:
                        return

                    if self.textcomponentstring == None:
                        # Check if this is TextComponentString
                        temp_cf = classloader[const.name.value]
                        for str in temp_cf.constants.find(type_=String):
                            if "TextComponent{text=" in str.string.value:
                                self.textcomponentstring = const.name.value
                                break

                    if const.name == aggregate["classes"]["nbtcompound"]:
                        return "Empty"
                    elif const.name == self.textcomponentstring:
                        return {'text': None}

            register = cf.methods.find_one(name=register_data_method_name, f=lambda m: m.descriptor == register_data_method_desc)
            if register and not register.access_flags.acc_abstract:
                walk_method(cf, register, MetadataDefaultsContext(), verbose)
            elif cls == base_entity_class:
                walk_method(cf, cf.methods.find_one(name="<init>"), MetadataDefaultsContext(True), verbose)

            metadata_by_class[cls] = metadata

            return index
Example #6
0
        def fill_class(cls):
            # Returns the starting index for metadata in subclasses of cls
            if cls == "java/lang/Object":
                return 0
            if cls in metadata_by_class:
                return len(metadata_by_class[cls]) + fill_class(
                    parent_by_class[cls])

            cf = classloader[cls]
            super = cf.super_.name.value
            parent_by_class[cls] = super
            index = fill_class(super)

            metadata = []

            class MetadataFieldContext(WalkerCallback):
                def __init__(self):
                    self.cur_index = index

                def on_invoke(self, ins, const, obj, args):
                    if const.class_.name == datamanager_class and const.name_and_type.name == create_key_method.name and const.name_and_type.descriptor == create_key_method.descriptor:
                        # Call to createKey.
                        # Sanity check: entities should only register metadata for themselves
                        if args[0] != cls + ".class":
                            # ... but in some versions, mojang messed this up with potions... hence why the sanity check exists in vanilla now.
                            if verbose:
                                other_class = args[0][:-len(".class")]
                                name = entity_classes.get(cls, "Unknown")
                                other_name = entity_classes.get(
                                    other_class, "Unknown")
                                print(
                                    "An entity tried to register metadata for another entity: %s (%s) from %s (%s)"
                                    % (other_name, other_class, name, cls))

                        serializer = args[1]
                        index = self.cur_index
                        self.cur_index += 1

                        metadata_entry = {
                            "serializer_id":
                            serializer["id"],
                            "serializer":
                            serializer["name"]
                            if "name" in serializer else serializer["id"],
                            "index":
                            index
                        }
                        metadata.append(metadata_entry)
                        return metadata_entry

                def on_put_field(self, ins, const, obj, value):
                    if isinstance(value, dict):
                        value["field"] = const.name_and_type.name.value

                def on_get_field(self, ins, const, obj):
                    if const.class_.name == dataserializers_class:
                        return dataserializers_by_field[
                            const.name_and_type.name.value]

                def on_invokedynamic(self, ins, const, args):
                    return object()

                def on_new(self, ins, const):
                    return object()

            init = cf.methods.find_one(name="<clinit>")
            if init:
                ctx = MetadataFieldContext()
                walk_method(cf, init, ctx, verbose)
                index = ctx.cur_index

            class MetadataDefaultsContext(WalkerCallback):
                def __init__(self, wait_for_putfield=False):
                    self.textcomponentstring = None
                    # True whlie waiting for "this.dataManager = new EntityDataManager(this);" when going through the entity constructor
                    self.waiting_for_putfield = wait_for_putfield

                def on_invoke(self, ins, const, obj, args):
                    if self.waiting_for_putfield:
                        return

                    if "Optional" in const.class_.name.value:
                        if const.name_and_type.name in ("absent", "empty"):
                            return "Empty"
                        elif len(args) == 1:
                            # Assume "of" or similar
                            return args[0]
                    elif const.name_and_type.name == "valueOf":
                        # Boxing methods
                        if const.class_.name == "java/lang/Boolean":
                            return bool(args[0])
                        else:
                            return args[0]
                    elif const.name_and_type.name == "<init>":
                        if const.class_.name == self.textcomponentstring:
                            obj["text"] = args[0]

                        return
                    elif const.class_.name == datamanager_class:
                        assert const.name_and_type.name == register_method.name
                        assert const.name_and_type.descriptor == register_method.descriptor

                        # args[0] is the metadata entry, and args[1] is the default value
                        if args[0] is not None and args[1] is not None:
                            args[0]["default"] = args[1]

                        return
                    elif const.name_and_type.descriptor.value.endswith(
                            "L" + datamanager_class + ";"):
                        # getDataManager, which doesn't really have a reason to exist given that the data manager field is accessible
                        return None
                    elif const.name_and_type.name == register_data_method_name and const.name_and_type.descriptor == register_data_method_desc:
                        # Call to super.registerData()
                        return

                def on_put_field(self, ins, const, obj, value):
                    if const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        if not self.waiting_for_putfield:
                            raise Exception("Unexpected putfield: %s" %
                                            (ins, ))
                        self.waiting_for_putfield = False

                def on_get_field(self, ins, const, obj):
                    if self.waiting_for_putfield:
                        return

                    if const.name_and_type.descriptor == "L" + dataparameter_class + ";":
                        # Definitely shouldn't be registering something declared elsewhere
                        assert const.class_.name == cls
                        for metadata_entry in metadata:
                            if const.name_and_type.name == metadata_entry.get(
                                    "field"):
                                return metadata_entry
                        else:
                            if verbose:
                                print(
                                    "Can't figure out metadata entry for field %s; default will not be set."
                                    % (const, ))
                            return None

                    if const.class_.name == aggregate["classes"]["position"]:
                        # Assume BlockPos.ORIGIN
                        return "(0, 0, 0)"
                    elif const.class_.name == aggregate["classes"][
                            "itemstack"]:
                        # Assume ItemStack.EMPTY
                        return "Empty"
                    elif const.name_and_type.descriptor == "L" + datamanager_class + ";":
                        return
                    else:
                        return None

                def on_new(self, ins, const):
                    if self.waiting_for_putfield:
                        return

                    if self.textcomponentstring == None:
                        # Check if this is TextComponentString
                        temp_cf = classloader[const.name.value]
                        for str in temp_cf.constants.find(type_=String):
                            if "TextComponent{text=" in str.string.value:
                                self.textcomponentstring = const.name.value
                                break

                    if const.name == aggregate["classes"]["nbtcompound"]:
                        return "Empty"
                    elif const.name == self.textcomponentstring:
                        return {'text': None}

            register = cf.methods.find_one(
                name=register_data_method_name,
                f=lambda m: m.descriptor == register_data_method_desc)
            if register and not register.access_flags.acc_abstract:
                walk_method(cf, register, MetadataDefaultsContext(), verbose)
            elif cls == base_entity_class:
                walk_method(cf, cf.methods.find_one(name="<init>"),
                            MetadataDefaultsContext(True), verbose)

            get_flag_method = None

            # find if the class has a `boolean getFlag(int)` method
            for method in cf.methods.find(args="I", returns="Z"):
                previous_operators = []
                for ins in method.code.disassemble():
                    if ins.mnemonic == "bipush":
                        # check for a series of operators that looks something like this
                        # `return ((Byte)this.R.a(bo) & var1) != 0;`
                        operator_matcher = [
                            "aload", "getfield", "getstatic", "invokevirtual",
                            "checkcast", "invokevirtual", "iload", "iand",
                            "ifeq", "bipush", "goto"
                        ]
                        previous_operators_match = previous_operators == operator_matcher

                        if previous_operators_match and ins.operands[
                                0].value == 0:
                            # store the method name as the result for later
                            get_flag_method = method.name.value

                    previous_operators.append(ins.mnemonic)

            bitfields = []

            # find the methods that get bit fields
            for method in cf.methods.find(args="", returns="Z"):
                if method.code:
                    bitmask_value = None
                    stack = []
                    for ins in method.code.disassemble():
                        # the method calls getField() or getSharedField()
                        if ins.mnemonic in ("invokevirtual", "invokespecial",
                                            "invokeinterface", "invokestatic"):
                            calling_method = ins.operands[
                                0].name_and_type.name.value

                            has_correct_arguments = ins.operands[
                                0].name_and_type.descriptor.value == "(I)Z"

                            is_getflag_method = has_correct_arguments and calling_method == get_flag_method
                            is_shared_getflag_method = has_correct_arguments and calling_method == shared_get_flag_method

                            # if it's a shared flag, update the bitfields_by_class for abstract_entity
                            if is_shared_getflag_method and stack:
                                bitmask_value = stack.pop()
                                if bitmask_value is not None:
                                    base_entity_cls = base_entity_cf.this.name.value
                                    if base_entity_cls not in bitfields_by_class:
                                        bitfields_by_class[
                                            base_entity_cls] = []
                                    bitfields_by_class[base_entity_cls].append({
                                        # we include the class here so it can be easily figured out from the mappings
                                        "class":
                                        cls,
                                        "method":
                                        method.name.value,
                                        "mask":
                                        1 << bitmask_value
                                    })
                                bitmask_value = None
                            elif is_getflag_method and stack:
                                bitmask_value = stack.pop()
                                break
                        elif ins.mnemonic == "iand":
                            # get the last item in the stack, since it's the bitmask
                            bitmask_value = stack[-1]
                            break
                        elif ins.mnemonic == "bipush":
                            stack.append(ins.operands[0].value)
                    if bitmask_value:
                        bitfields.append({
                            "method": method.name.value,
                            "mask": bitmask_value
                        })

            metadata_by_class[cls] = metadata
            if cls not in bitfields_by_class:
                bitfields_by_class[cls] = bitfields
            else:
                bitfields_by_class[cls].extend(bitfields)
            return index
Example #7
0
    def _process_1point14(aggregate, classloader, verbose):
        # Handles versions after 1.14 (specifically >= 18w43a)
        # All of the registration happens in the list class in this version.
        listclass = aggregate["classes"]["item.list"]
        lcf = classloader[listclass]
        superclass = next(lcf.fields.find()).type.name # The first field in the list class is an item
        cf = classloader[superclass]
        aggregate["classes"]["item.superclass"] = superclass
        blockclass = aggregate["classes"]["block.superclass"]
        blocklist = aggregate["classes"]["block.list"]

        cf = classloader[superclass]

        if "item" in aggregate["language"]:
            language = aggregate["language"]["item"]
        else:
            language = None

        # Figure out what the builder class is
        ctor = cf.methods.find_one(name="<init>")
        builder_class = ctor.args[0].name
        builder_cf = classloader[builder_class]

        # Find the max stack size method
        max_stack_method = None
        for method in builder_cf.methods.find(args='I'):
            for ins in method.code.disassemble():
                if ins.mnemonic in ("ldc", "ldc_w"):
                    const = ins.operands[0]
                    if isinstance(const, String) and const.string.value == "Unable to have damage AND stack.":
                        max_stack_method = method
                        break
        if not max_stack_method:
            raise Exception("Couldn't find max stack size setter in " + builder_class)

        register_item_block_method = lcf.methods.find_one(args='L' + blockclass + ';', returns='L' + superclass + ';')
        item_block_class = None
        # Find the class used that represents an item that is a block
        for ins in register_item_block_method.code.disassemble():
            if ins.mnemonic == "new":
                const = ins.operands[0]
                item_block_class = const.name.value
                break

        items = aggregate.setdefault("items", {})
        item_list = items.setdefault("item", {})
        item_fields = items.setdefault("item_fields", {})

        is_item_class_cache = {superclass: True}
        def is_item_class(name):
            if name in is_item_class_cache:
                return is_item_class_cache
            elif name == 'java/lang/Object':
                return True
            elif '/' in name:
                return False

            cf = classloader[name]
            result = is_item_class(cf.super_.name.value)
            is_item_class_cache[name] = result
            return result
        # Find the static block registration method
        method = lcf.methods.find_one(name='<clinit>')

        class Walker(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_new(self, ins, const):
                class_name = const.name.value
                return {"class": class_name}

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

                        return 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.mnemonic == "invokestatic":
                            # Probably returning itself, but through a synthetic method
                            return args[0]
                        else:
                            # Probably returning itself
                            return obj
                    else:
                        return object()

            def on_get_field(self, ins, const, obj):
                if const.class_.name.value == blocklist:
                    # Getting a block; put it on the stack.
                    block_name = aggregate["blocks"]["block_fields"][const.name_and_type.name.value]
                    if block_name not in aggregate["blocks"]["block"]:
                        if verbose:
                            print("No information available for item-block for %s/%s" % (const.name_and_type.name.value, block_name))
                        return {}
                    else:
                        return aggregate["blocks"]["block"][block_name]
                elif const.class_.name.value == listclass:
                    return item_list[item_fields[const.name_and_type.name.value]]
                else:
                    return const

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    field = const.name_and_type.name.value
                    value["field"] = field
                    item_fields[const.name_and_type.name.value] = value["text_id"]

        walk_method(cf, method, Walker(), verbose)
Example #8
0
    def _entities_1point13(aggregate, classloader, verbose):
        if verbose:
            print("Using 1.13 entity format")

        listclass = aggregate["classes"]["entity.list"]
        cf = classloader[listclass]

        entities = aggregate.setdefault("entities", {})
        entity = entities.setdefault("entity", {})

        # Find the inner builder class
        inner_classes = cf.attributes.find_one(name="InnerClasses").inner_classes
        builderclass = None
        funcclass = None # 19w08a+ - a functional interface for creating new entities
        for entry in inner_classes:
            outer = cf.constants.get(entry.outer_class_info_index)
            if outer.name == listclass:
                inner = cf.constants.get(entry.inner_class_info_index)
                inner_cf = classloader[inner.name.value]
                if inner_cf.access_flags.acc_interface:
                    if funcclass:
                        raise Exception("Unexpected multiple inner interfaces")
                    funcclass = inner.name.value
                else:
                    if builderclass:
                        raise Exception("Unexpected multiple inner classes")
                    builderclass = inner.name.value

        if not builderclass:
            raise Exception("Failed to find inner class for builder in " + str(inner_classes))
        # Note that funcclass might not be found since it didn't always exist

        method = cf.methods.find_one(name="<clinit>")

        # Example of what's being parsed:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud::new, EntityCategory.MISC).setSize(6.0F, 0.5F)); // 19w05a+
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud.class, EntityAreaEffectCloud::new).setSize(6.0F, 0.5F)); // 19w03a+
        # and in older versions:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud.class, EntityAreaEffectCloud::new)); // 18w06a-19w02a
        # and in even older versions:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud::new)); // through 18w05a

        class EntityContext(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_invokedynamic(self, ins, const):
                # MC uses EntityZombie::new, similar; return the created class
                return class_from_invokedynamic(ins, cf)

            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" }

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    # Keep track of the field in the entity list too.
                    value["field"] = const.name_and_type.name.value
                    # Also, if this isn't a serializable entity, get the class from the generic signature of the field
                    if "class" not in value:
                        field = cf.fields.find_one(name=const.name_and_type.name.value)
                        sig = field.attributes.find_one(name="Signature").signature.value # Something like `Laev<Laep;>;`
                        value["class"] = sig[sig.index("<") + 2 : sig.index(">") - 1] # Awful way of getting the actual type

            def on_new(self, ins, const):
                # Done once, for the registry, but we don't care
                return object()

            def on_get_field(self, ins, const, obj):
                # 19w05a+: used to set entity types.
                return object()

        walk_method(cf, method, EntityContext(), verbose)
Example #9
0
    def _process_1point14(aggregate, classloader, verbose):
        # Handles versions after 1.14 (specifically >= 18w43a)
        # All of the registration happens in the list class in this version.
        listclass = aggregate["classes"]["block.list"]
        lcf = classloader[listclass]
        superclass = next(lcf.fields.find(
        )).type.name  # The first field in the list class is a block
        cf = classloader[superclass]
        aggregate["classes"]["block.superclass"] = superclass

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

        # Figure out what the builder class is
        ctor = cf.methods.find_one(name="<init>")
        builder_class = ctor.args[0].name

        builder_cf = classloader[builder_class]
        # Sets hardness and resistance
        hardness_setter = builder_cf.methods.find_one(args='FF')
        # There's also one that sets both to the same value
        hardness_setter_2 = None
        for method in builder_cf.methods.find(args='F'):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter.descriptor.value):
                        hardness_setter_2 = method
                        break
        assert hardness_setter_2 != None
        # ... and one that sets them both to 0
        hardness_setter_3 = None
        for method in builder_cf.methods.find(args=''):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter_2.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter_2.descriptor.value):
                        hardness_setter_3 = method
                        break
        assert hardness_setter_3 != None

        light_setter = builder_cf.methods.find_one(args='I')
        if light_setter == None:
            # 20w12a replaced the simple setter with one that takes a lambda
            # that is called to compute the light level for a given block
            # state.  Most blocks simply return a constant value, but some
            # such as sea pickles have varying light levels by state.
            light_setter = builder_cf.methods.find_one(
                args='Ljava/util/function/ToIntFunction;')
        assert light_setter != None

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

        # Find the static block registration method
        method = lcf.methods.find_one(name='<clinit>')

        class Walker(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_new(self, ins, const):
                class_name = const.name.value
                return {"class": class_name}

            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()

            def on_get_field(self, ins, const, obj):
                if const.class_.name.value == superclass:
                    # Probably getting the static AIR resource location
                    return "air"
                elif const.class_.name.value == listclass:
                    return block[block_fields[const.name_and_type.name.value]]
                else:
                    return object()

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    field = const.name_and_type.name.value
                    value["field"] = field
                    block_fields[field] = value["text_id"]

            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()

        walk_method(lcf, method, Walker(), verbose)
Example #10
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()
Example #11
0
    def _process_1point14(aggregate, classloader, verbose):
        # Handles versions after 1.14 (specifically >= 18w43a)
        # All of the registration happens in the list class in this version.
        listclass = aggregate["classes"]["block.list"]
        lcf = classloader[listclass]
        superclass = next(lcf.fields.find(
        )).type.name  # The first field in the list class is a block
        cf = classloader[superclass]
        aggregate["classes"]["block.superclass"] = superclass

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

        # Figure out what the builder class is
        ctor = cf.methods.find_one(name="<init>")
        builder_class = ctor.args[0].name

        builder_cf = classloader[builder_class]
        # Sets hardness and resistance
        hardness_setter = builder_cf.methods.find_one(args='FF')
        # There's also one that sets both to the same value
        hardness_setter_2 = None
        for method in builder_cf.methods.find(args='F'):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter.descriptor.value):
                        hardness_setter_2 = method
                        break
        assert hardness_setter_2 != None
        # ... and one that sets them both to 0
        hardness_setter_3 = None
        for method in builder_cf.methods.find(args=''):
            for ins in method.code.disassemble():
                if ins.mnemonic == "invokevirtual":
                    const = ins.operands[0]
                    if (const.name_and_type.name.value
                            == hardness_setter_2.name.value
                            and const.name_and_type.descriptor.value
                            == hardness_setter_2.descriptor.value):
                        hardness_setter_3 = method
                        break
        assert hardness_setter_3 != None

        light_setter = builder_cf.methods.find_one(args='I')

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

        # Find the static block registration method
        method = lcf.methods.find_one(name='<clinit>')

        class Walker(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_new(self, ins, const):
                class_name = const.name.value
                return {"class": class_name}

            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()

            def on_get_field(self, ins, const, obj):
                if const.class_.name.value == superclass:
                    # Probably getting the static AIR resource location
                    return "air"
                elif const.class_.name.value == listclass:
                    return block[block_fields[const.name_and_type.name.value]]
                else:
                    return object()

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    field = const.name_and_type.name.value
                    value["field"] = field
                    block_fields[field] = value["text_id"]

            def on_invokedynamic(self, ins, const):
                # 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
                return object()

        walk_method(cf, method, Walker(), verbose)
Example #12
0
    def _entities_1point13(aggregate, classloader, verbose):
        if verbose:
            print("Using 1.13 entity format")

        listclass = aggregate["classes"]["entity.list"]
        cf = classloader[listclass]

        entities = aggregate.setdefault("entities", {})
        entity = entities.setdefault("entity", {})

        # Find the inner builder class
        inner_classes = cf.attributes.find_one(
            name="InnerClasses").inner_classes
        builderclass = None
        funcclass = None  # 19w08a+ - a functional interface for creating new entities
        for entry in inner_classes:
            outer = cf.constants.get(entry.outer_class_info_index)
            if outer.name == listclass:
                inner = cf.constants.get(entry.inner_class_info_index)
                inner_cf = classloader[inner.name.value]
                if inner_cf.access_flags.acc_interface:
                    if funcclass:
                        raise Exception("Unexpected multiple inner interfaces")
                    funcclass = inner.name.value
                else:
                    if builderclass:
                        raise Exception("Unexpected multiple inner classes")
                    builderclass = inner.name.value

        if not builderclass:
            raise Exception("Failed to find inner class for builder in " +
                            str(inner_classes))
        # Note that funcclass might not be found since it didn't always exist

        method = cf.methods.find_one(name="<clinit>")

        # Example of what's being parsed:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud::new, EntityCategory.MISC).setSize(6.0F, 0.5F)); // 19w05a+
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud.class, EntityAreaEffectCloud::new).setSize(6.0F, 0.5F)); // 19w03a+
        # and in older versions:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud.class, EntityAreaEffectCloud::new)); // 18w06a-19w02a
        # and in even older versions:
        # public static final EntityType<EntityAreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.create(EntityAreaEffectCloud::new)); // through 18w05a

        class EntityContext(WalkerCallback):
            def __init__(self):
                self.cur_id = 0

            def on_invokedynamic(self, ins, const, args):
                # MC uses EntityZombie::new, similar; return the created class
                return class_from_invokedynamic(ins, cf)

            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"}

            def on_put_field(self, ins, const, obj, value):
                if isinstance(value, dict):
                    # Keep track of the field in the entity list too.
                    value["field"] = const.name_and_type.name.value
                    # Also, if this isn't a serializable entity, get the class from the generic signature of the field
                    if "class" not in value:
                        field = cf.fields.find_one(
                            name=const.name_and_type.name.value)
                        sig = field.attributes.find_one(
                            name="Signature"
                        ).signature.value  # Something like `Laev<Laep;>;`
                        value["class"] = sig[
                            sig.index("<") + 2:sig.index(">") -
                            1]  # Awful way of getting the actual type

            def on_new(self, ins, const):
                # Done once, for the registry, but we don't care
                return object()

            def on_get_field(self, ins, const, obj):
                # 19w05a+: used to set entity types.
                return object()

        walk_method(cf, method, EntityContext(), verbose)
Example #13
0
    def _entities_1point11(aggregate, classloader, verbose):
        # 1.11 logic
        if verbose:
            print("Using 1.11 entity format")

        listclass = aggregate["classes"]["entity.list"]
        cf = classloader[listclass]

        entities = aggregate.setdefault("entities", {})
        entity = entities.setdefault("entity", {})

        method = cf.methods.find_one(args='',
                                     returns="V",
                                     f=lambda m: m.access_flags.acc_public and
                                     m.access_flags.acc_static)

        minecart_info = {}

        class EntityContext(WalkerCallback):
            def on_get_field(self, ins, const, obj):
                # Minecarts use an enum for their data - assume that this is that enum
                const = ins.operands[0]
                if not "types_by_field" in minecart_info:
                    EntityTopping._load_minecart_enum(classloader,
                                                      const.class_.name.value,
                                                      minecart_info)
                minecart_name = minecart_info["types_by_field"][
                    const.name_and_type.name.value]
                return minecart_info["types"][minecart_name]

            def on_invoke(self, ins, const, obj, args):
                if const.class_.name == listclass:
                    if len(args) == 4:
                        # Initial registration
                        name = args[1]
                        old_name = args[3]
                        entity[name] = {
                            "id": args[0],
                            "name": name,
                            "class": args[2][:-len(".class")],
                            "old_name": old_name
                        }

                        if old_name + ".name" in aggregate["language"][
                                "entity"]:
                            entity[name]["display_name"] = aggregate[
                                "language"]["entity"][old_name + ".name"]
                    elif len(args) == 3:
                        # Spawn egg registration
                        name = args[0]
                        if name in entity:
                            entity[name]["egg_primary"] = args[1]
                            entity[name]["egg_secondary"] = args[2]
                        elif verbose:
                            print(
                                "Missing entity during egg registration: %s" %
                                name)
                elif const.class_.name == minecart_info["class"]:
                    # Assume that obj is the minecart info, and the method being called is the one that gets the name
                    return obj["entitytype"]

            def on_new(self, ins, const):
                raise Exception("unexpected new: %s" % ins)

            def on_put_field(self, ins, const, obj, value):
                raise Exception("unexpected putfield: %s" % ins)

        walk_method(cf, method, EntityContext(), verbose)