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