def sub_operations(jar, cf, classes, instruction, operand, arg_names=[""]): """Gets the instructions of a different class""" invoked_class = operand.c + ".class" name = operand.name descriptor = operand.descriptor args = method_descriptor(descriptor).args_descriptor cache_key = "%s/%s/%s/%s" % (invoked_class, name, descriptor, _PIT.join(arg_names, ",")) if cache_key in _PIT.CACHE: cache = _PIT.CACHE[cache_key] operations = [op.clone() for op in cache] else: operations = _PIT.operations(jar, invoked_class, classes, args, name, arg_names) position = 0 for operation in _PIT.ordered_operations(operations): position += 0.01 operation.position = instruction.pos + (position) _PIT.CACHE[cache_key] = operations return operations
def sub_operations(classloader, cf, classes, instruction, verbose, operand, arg_names=[""]): """Gets the instructions of a different class""" invoked_class = operand.c + ".class" name = operand.name descriptor = operand.descriptor args = method_descriptor(descriptor).args_descriptor cache_key = "%s/%s/%s/%s" % (invoked_class, name, descriptor, _PIT.join(arg_names, ",")) if cache_key in _PIT.CACHE: cache = _PIT.CACHE[cache_key] operations = [op.clone() for op in cache] else: operations = _PIT.operations(classloader, invoked_class, classes, verbose, args, name, arg_names) position = 0 for operation in _PIT.ordered_operations(operations): position += 0.01 operation.position = instruction.pos + (position) _PIT.CACHE[cache_key] = operations return operations
def parse_srg_line(line): if line.startswith("PK"): # package s = pk_regex.search(line) return ("PK", (s.group(1), s.group(2))) elif line.startswith("CL"): # class s = cl_regex.search(line) return ("CL", (s.group(1), s.group(2))) elif line.startswith("FD"): # field s = fd_regex.search(line) return ("FD", (s.group(1), s.group(2))) elif line.startswith("MD"): # method s = md_regex.search(line) return ("MD", ( (s.group(1), method_descriptor(s.group(2))), (s.group(3), method_descriptor(s.group(4))))) else: raise RuntimeError("Unknown field in srg file: %s" % line)
def on_invoke(self, ins, const, obj, args): method_name = const.name_and_type.name.value method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) if ins.mnemonic == "invokestatic": if const.class_.name.value == superclass: # Call to the static register method. text_id = args[0] current_block = args[1] current_block["text_id"] = text_id current_block["numeric_id"] = self.cur_id self.cur_id += 1 lang_key = "minecraft.%s" % text_id if language != None and lang_key in language: current_block["display_name"] = language[lang_key] block[text_id] = current_block ordered_blocks.append(text_id) elif const.class_.name.value == builder_class: if desc.args[0].name == superclass: # Copy constructor copy = dict(args[0]) del copy["text_id"] del copy["numeric_id"] del copy["class"] if "display_name" in copy: del copy["display_name"] return copy else: return {} # Append current block else: if method_name == "hasNext": # We've reached the end of block registration # (and have started iterating over registry keys) raise StopIteration() if method_name == hardness_setter.name.value and method_desc == hardness_setter.descriptor.value: obj["hardness"] = args[0] # resistance is args[1] elif method_name == hardness_setter_2.name.value and method_desc == hardness_setter_2.descriptor.value: obj["hardness"] = args[0] # resistance is args[0] elif method_name == hardness_setter_3.name.value and method_desc == hardness_setter_3.descriptor.value: obj["hardness"] = 0.0 # resistance is 0.0 elif method_name == "<init>": # Call to the constructor for the block # We can't hardcode index 0 because sand has an extra parameter, so use the last one # There are also cases where it's an arg-less constructor; we don't want to do anything there. if len(args) > 0: obj.update(args[-1]) if desc.returns.name == builder_class: return obj elif desc.returns.name == aggregate["classes"]["identifier"]: # Probably getting the air identifier from the registry return "air" elif desc.returns.name != "void": return object()
def _process_113_classes_new(aggregate, classloader, verbose): # After 18w16a, biomes used a builder again. The name is now also translatable. for biome in six.itervalues(aggregate["biomes"]["biome"]): biome["name"] = aggregate["language"]["biome"]["minecraft." + biome["text_id"]] cf = classloader[biome["class"]] method = cf.methods.find_one(name="<init>") stack = [] for ins in method.code.disassemble(): if ins == "invokespecial": const = ins.operands[0] name = const.name_and_type.name.value if const.class_.name.value == cf.super_.name.value and name == "<init>": # Calling biome init; we're done break elif ins == "invokevirtual": const = ins.operands[0] name = const.name_and_type.name.value desc = method_descriptor( const.name_and_type.descriptor.value) if len(desc.args) == 1: if desc.args[0].name == "float": # Ugly portion - different methods with different names # Hopefully the order doesn't change if name == "a": biome["height"][0] = stack.pop() elif name == "b": biome["height"][1] = stack.pop() elif name == "c": biome["temperature"] = stack.pop() elif name == "d": biome["rainfall"] = stack.pop() elif desc.args[0].name == "java/lang/String": val = stack.pop() if val is not None: biome["mutated_from"] = val stack = [] # Constants elif ins in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, String): stack.append(const.string.value) if isinstance(const, (Integer, Float)): stack.append(const.value) elif ins.mnemonic.startswith("fconst"): stack.append(float(ins.mnemonic[-1])) elif ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins == "aconst_null": stack.append(None)
def _process_113_classes_new(aggregate, classloader, verbose): # After 18w16a, biomes used a builder again. The name is now also translatable. for biome in six.itervalues(aggregate["biomes"]["biome"]): biome["name"] = aggregate["language"]["biome"]["minecraft." + biome["text_id"]] cf = classloader[biome["class"]] method = cf.methods.find_one(name="<init>") stack = [] for ins in method.code.disassemble(): if ins == "invokespecial": const = ins.operands[0] name = const.name_and_type.name.value if const.class_.name.value == cf.super_.name.value and name == "<init>": # Calling biome init; we're done break elif ins == "invokevirtual": const = ins.operands[0] name = const.name_and_type.name.value desc = method_descriptor(const.name_and_type.descriptor.value) if len(desc.args) == 1: if desc.args[0].name == "float": # Ugly portion - different methods with different names # Hopefully the order doesn't change if name == "a": biome["height"][0] = stack.pop() elif name == "b": biome["height"][1] = stack.pop() elif name == "c": biome["temperature"] = stack.pop() elif name == "d": biome["rainfall"] = stack.pop() elif desc.args[0].name == "java/lang/String": val = stack.pop() if val is not None: biome["mutated_from"] = val stack = [] # Constants elif ins in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, String): stack.append(const.string.value) if isinstance(const, (Integer, Float)): stack.append(const.value) elif ins.mnemonic.startswith("fconst"): stack.append(float(ins.mnemonic[-1])) elif ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins == "aconst_null": stack.append(None)
def try_eval_lambda(ins, args, cf): """ Attempts to call a lambda function that returns a constant value. May throw; this code is very hacky. """ const = ins.operands[0] bootstrap = cf.bootstrap_methods[const.method_attr_index] method = cf.constants.get(bootstrap.method_ref) # Make sure this is a reference to LambdaMetafactory assert method.reference_kind == 6 # REF_invokeStatic assert method.reference.class_.name == "java/lang/invoke/LambdaMetafactory" assert method.reference.name_and_type.name == "metafactory" assert len(bootstrap.bootstrap_args) == 3 # Num arguments methodhandle = cf.constants.get(bootstrap.bootstrap_args[1]) assert methodhandle.reference_kind == 6 # REF_invokeStatic # We only want to deal with lambdas in the same class assert methodhandle.reference.class_.name == cf.this.name name2 = methodhandle.reference.name_and_type.name.value desc2 = method_descriptor( methodhandle.reference.name_and_type.descriptor.value) lambda_method = cf.methods.find_one(name=name2, args=desc2.args_descriptor, returns=desc2.returns_descriptor) assert lambda_method class Callback(WalkerCallback): def on_new(self, ins, const): raise Exception("Illegal new") def on_invoke(self, ins, const, obj, args): raise Exception("Illegal invoke") def on_get_field(self, ins, const, obj): raise Exception("Illegal getfield") def on_put_field(self, ins, const, obj, value): raise Exception("Illegal putfield") # Set verbose to false because we don't want lots of output if this errors # (since it is expected to for more complex methods) return walk_method(cf, lambda_method, Callback(), False, args)
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()
def __init__(self, ins, cf, const): super().__init__(ins, cf, const) bootstrap = cf.bootstrap_methods[const.method_attr_index] method = cf.constants.get(bootstrap.method_ref) # Make sure this is a reference to StringConcatFactory.makeConcatWithConstants assert method.reference_kind == REF_invokeStatic assert method.reference.class_.name == "java/lang/invoke/StringConcatFactory" assert method.reference.name_and_type.name == "makeConcatWithConstants" assert len(bootstrap.bootstrap_args) == 1 # Num arguments - may change with constants # Now check the arguments. Note that StringConcatFactory has some # arguments automatically filled in. The bootstrap arguments are: # args[0] is recipe, format string # Further arguments presumably go into the constants varargs array, but I haven't seen this # (and I'm not sure how you get a constant that can't be converted to a string at compile time) self.recipe = cf.constants.get(bootstrap.bootstrap_args[0]).string.value assert '\u0002' not in self.recipe self.dynamic_name = const.name_and_type.name.value self.dynamic_desc = method_descriptor(const.name_and_type.descriptor.value) assert self.dynamic_desc.returns.name == "java/lang/String"
def walk_method(cf, method, callback, verbose): assert isinstance(callback, WalkerCallback) stack = [] locals = {} # TODO: Allow passing argument values in or something like that cur_index = 0 if not method.access_flags.acc_static: locals[cur_index] = object() cur_index += 1 for arg in method.args: locals[cur_index] = object() cur_index += 1 for ins in method.code.disassemble(): if ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith("dconst"): stack.append(float(ins.mnemonic[-1])) elif ins == "aconst_null": stack.append(None) elif ins in ("ldc", "ldc_w", "ldc2_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins == "new": const = ins.operands[0] try: stack.append(callback.on_new(ins, const)) except StopIteration: break elif ins in ("getfield", "getstatic"): const = ins.operands[0] if ins.mnemonic != "getstatic": obj = stack.pop() else: obj = None try: stack.append(callback.on_get_field(ins, const, obj)) except StopIteration: break elif ins in ("putfield", "putstatic"): const = ins.operands[0] value = stack.pop() if ins.mnemonic != "putstatic": obj = stack.pop() else: obj = None try: callback.on_put_field(ins, const, obj, value) except StopIteration: break elif ins in ("invokevirtual", "invokespecial", "invokeinterface", "invokestatic"): const = ins.operands[0] method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) if ins.mnemonic != "invokestatic": obj = stack.pop() else: obj = None try: ret = callback.on_invoke(ins, const, obj, args) except StopIteration: break if desc.returns.name != "void": stack.append(ret) elif ins == "astore": locals[ins.operands[0].value] = stack.pop() elif ins == "aload": stack.append(locals[ins.operands[0].value]) elif ins == "dup": stack.append(stack[-1]) elif ins == "pop": stack.pop() elif ins == "anewarray": stack.append([None] * stack.pop()) elif ins == "newarray": stack.append([0] * stack.pop()) elif ins in ("aastore", "iastore", "fastore"): value = stack.pop() index = stack.pop() array = stack.pop() if isinstance(array, list) and isinstance(index, int): array[index] = value elif verbose: print("Failed to execute %s: array %s index %s value %s" % (ins, array, index, value)) elif ins == "invokedynamic": stack.append(callback.on_invokedynamic(ins, ins.operands[0])) elif ins in ("checkcast", "return"): pass elif verbose: print("Unknown instruction %s: stack is %s" % (ins, stack))
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_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 process_class(name): """ Gets the properties for the given block class, checking the parent class if none are defined. Returns the properties, and also adds them to properties_by_class """ if name in properties_by_class: # Caching - avoid reading the same class multiple times return properties_by_class[name] cf = classloader[name] method = cf.methods.find_one(f=matches) if not method: properties = process_class(cf.super_.name.value) properties_by_class[name] = properties return properties properties = None if_pos = None stack = [] for ins in method.code.disassemble(): # This could _almost_ just be checking for getstatic, but # brewing stands use an array of properties as the field, # so we need some stupid extra logic. if ins == "new": assert not is_18w19a # In 18w19a this should be a parameter const = ins.operands[0] type_name = const.name.value assert type_name == blockstatecontainer stack.append(object()) elif ins == "aload" and ins.operands[0].value == 1: assert is_18w19a # The parameter is only used in 18w19a and above stack.append(object()) elif ins in ("sipush", "bipush"): stack.append(ins.operands[0].value) elif ins in ("anewarray", "newarray"): length = stack.pop() val = [None] * length stack.append(val) elif ins == "getstatic": const = ins.operands[0] prop = { "field_name": const.name_and_type.name.value } desc = field_descriptor(const.name_and_type.descriptor.value) _property_types.add(desc.name) stack.append(prop) elif ins == "aaload": index = stack.pop() array = stack.pop() prop = array.copy() prop["array_index"] = index stack.append(prop) elif ins == "aastore": value = stack.pop() index = stack.pop() array = stack.pop() array[index] = value elif ins == "dup": stack.append(stack[-1]) elif ins == "invokespecial": const = ins.operands[0] assert const.name_and_type.name == "<init>" desc = method_descriptor(const.name_and_type.descriptor.value) assert len(desc.args) == 2 # Normally this constructor call would return nothing, but # in this case we'd rather remove the object it's called on # and keep the properties array (its parameter) arg = stack.pop() stack.pop() # Block stack.pop() # Invocation target stack.append(arg) elif ins == "invokevirtual": # Two possibilities (both only present pre-flattening): # 1. It's isDouble() for a slab. Two different sets of # properties in that case. # 2. It's getTypeProperty() for flowers. Only one # set of properties, but other hacking is needed. # We can differentiate these cases based off of the return # type. # There is a third option post 18w19a: # 3. It's calling the state container's register method. # We can check this just by the type. const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) if const.class_.name == blockstatecontainer: # Case 3. assert properties == None properties = stack.pop() assert desc.returns.name == blockstatecontainer # Don't pop anything, since we'd just pop and re-add the builder elif desc.returns.name == "boolean": # Case 2. properties = [None, None] stack.pop() # Target object # XXX shouldn't something be returned here? else: # Case 1. # Assume that the return type is the base interface # for properties stack.pop() # Target object stack.append(None) elif ins == "ifeq": assert if_pos is None if_pos = ins.pos + ins.operands[0].value elif ins == "pop": stack.pop() elif ins == "areturn": assert not is_18w19a # In 18w19a we don't return a container if if_pos == None: assert properties == None properties = stack.pop() else: assert isinstance(properties, list) index = 0 if ins.pos < if_pos else 1 assert properties[index] == None properties[index] = stack.pop() elif ins == "return": assert is_18w19a # We only return void in 18w19a elif ins == "aload": assert ins.operands[0].value == 0 # Should be aload_0 (this) stack.append(object()) elif verbose: print("%s createBlockState contains unimplemented ins %s" % (name, ins)) if properties is None: # If we never set properties, warn; however, this is normal for # the base implementation in Block in 18w19a if verbose and name != aggregate["classes"]["block.superclass"]: print("Didn't find anything that set properties for %s" % name) properties = [] properties_by_class[name] = properties return properties
def args(self) -> List[JVMType]: """ A list of :class:`~jawa.util.descriptor.JVMType` representing the method's argument list. """ return method_descriptor(self.descriptor.value).args
def on_invoke(self, ins, const, obj, args): method_name = const.name_and_type.name.value method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) if ins == "invokestatic": if const.class_.name == superclass: current_item = {} text_id = None idx = 0 for arg in desc.args: if arg.name == blockclass: block = args[idx] text_id = block["text_id"] if "name" in block: current_item["name"] = block["name"] if "display_name" in block: current_item["display_name"] = block["display_name"] elif arg.name == superclass: current_item.update(args[idx]) elif arg.name == item_block_class: current_item.update(args[idx]) text_id = current_item["text_id"] elif arg.name == "java/lang/String": text_id = args[idx] idx += 1 if current_item == {} and not text_id: if verbose: print("Couldn't find any identifying information for the call to %s with %s" % (method_desc, args)) return if not text_id: if verbose: print("Could not find text_id for call to %s with %s" % (method_desc, args)) return # Call to the static register method. current_item["text_id"] = text_id current_item["numeric_id"] = self.cur_id self.cur_id += 1 lang_key = "minecraft.%s" % text_id if language != None and lang_key in language: current_item["display_name"] = language[lang_key] if "max_stack_size" not in current_item: current_item["max_stack_size"] = 64 item_list[text_id] = current_item else: if method_name == "<init>": # Call to a constructor. Check if the builder is in the args, # and if so update the item with it idx = 0 for arg in desc.args: if arg.name == builder_class: # Update from the builder if "max_stack_size" in args[idx]: obj["max_stack_size"] = args[idx]["max_stack_size"] elif arg.name == blockclass and "text_id" not in obj: block = args[idx] obj["text_id"] = block["text_id"] if "name" in block: obj["name"] = block["name"] if "display_name" in block: obj["display_name"] = block["display_name"] idx += 1 elif method_name == max_stack_method.name.value and method_desc == max_stack_method.descriptor.value: obj["max_stack_size"] = args[0] if desc.returns.name != "void": if desc.returns.name == builder_class or is_item_class(desc.returns.name): if ins == "invokestatic": # Probably returning itself, but through a synthetic method return args[0] else: # Probably returning itself return obj else: return object()
def __init__(self, ins, cf, const): super().__init__(ins, cf, const) self.generated_cf = None self.generated_method = None bootstrap = cf.bootstrap_methods[const.method_attr_index] method = cf.constants.get(bootstrap.method_ref) # Make sure this is a reference to LambdaMetafactory.metafactory assert method.reference_kind == REF_invokeStatic assert method.reference.class_.name == "java/lang/invoke/LambdaMetafactory" assert method.reference.name_and_type.name == "metafactory" assert len(bootstrap.bootstrap_args) == 3 # Num arguments # It could also be a reference to LambdaMetafactory.altMetafactory. # This is used for intersection types, which I don't think I've ever seen # used in the wild, and maybe for some other things. Here's an example: """ class Example { interface A { default int foo() { return 1; } } interface B { int bar(); } public Object test() { return (A & B)() -> 1; } } """ # See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.9 # and https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8-200-D # for details. Minecraft doesn't use this, so let's just pretend it doesn't exist. # Now check the arguments. Note that LambdaMetafactory has some # arguments automatically filled in. The bootstrap arguments are: # args[0] is samMethodType, signature of the implemented method # args[1] is implMethod, the method handle that is used # args[2] is instantiatedMethodType, narrower signature of the implemented method # We only really care about the method handle, and just assume that the # method handle satisfies instantiatedMethodType, and that that also # satisfies samMethodType. instantiatedMethodType could maybe be used # to get the type of object created by the returned function, but I'm not # sure if there's a reason to do that over just looking at the handle. methodhandle = cf.constants.get(bootstrap.bootstrap_args[1]) self.ref_kind = methodhandle.reference_kind # instantiatedMethodType does have a use when executing the created # object, so store it for later. instantiated = cf.constants.get(bootstrap.bootstrap_args[2]) self.instantiated_desc = method_descriptor(instantiated.descriptor.value) assert self.ref_kind >= REF_getField and self.ref_kind <= REF_invokeInterface # Javac does not appear to use REF_getField, REF_getStatic, # REF_putField, or REF_putStatic, so don't bother handling fields here. assert self.ref_kind not in FIELD_REFS self.method_class = methodhandle.reference.class_.name.value self.method_name = methodhandle.reference.name_and_type.name.value self.method_desc = method_descriptor(methodhandle.reference.name_and_type.descriptor.value) if self.ref_kind == REF_newInvokeSpecial: # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.8-200-C.2 assert self.method_name == "<init>" else: # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.8-200-C.1 assert self.method_name not in ("<init>", "<clinit>") # Although invokeinterface won't cause problems here, other code likely # will break with it, so bail out early for now (if it's eventually used, # it can be fixed later) assert self.ref_kind != REF_invokeInterface # As for stack changes, consider the following: """ public Supplier<String> foo() { return this::toString; } public Function<Object, String> bar() { return Object::toString; } public static Supplier<String> baz(String a, String b, String c) { return () -> a + b + c; } public Supplier<Object> quux() { return Object::new; } """ # Which disassembles (tidied to remove java.lang and java.util) to: """ Constant pool: #2 = InvokeDynamic #0:#38 // #0:get:(LClassName;)LSupplier; #3 = InvokeDynamic #1:#41 // #1:apply:()LFunction; #4 = InvokeDynamic #2:#43 // #2:get:(LString;LString;LString;)LSupplier; #5 = InvokeDynamic #3:#45 // #3:get:()LSupplier; public Supplier<String> foo(); Code: 0: aload_0 1: invokedynamic #2, 0 6: areturn public Function<Object, String> bar(); Code: 0: invokedynamic #3, 0 5: areturn public static Supplier<String> baz(String, String, String); Code: 0: aload_0 1: aload_1 2: aload_2 3: invokedynamic #4, 0 8: areturn public Supplier<java.lang.Object> quux(); Code: 0: invokedynamic #5, 0 5: areturn private static synthetic String lambda$baz$0(String, String, String); -snip- BootstrapMethods: 0: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip- Method arguments: #35 ()LObject; #36 invokevirtual Object.toString:()LString; #37 ()LString; 1: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip- Method arguments: #39 (LObject;)LObject; #36 invokevirtual Object.toString:()LString; #40 (LObject;)LString; 2: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip- Method arguments: #35 ()LObject; #42 invokestatic ClassName.lambda$baz$0:(LString;LString;LString;)LString; #37 ()LString; 3: #34 invokestatic -snip- LambdaMetafactory.metafactory -snip- Method arguments: #35 ()LObject; #44 newinvokespecial Object."<init>":()V #35 ()LObject; """ # Note that both foo and bar have invokevirtual in the method handle, # but `this` is added to the stack in foo(). # Similarly, baz pushes 3 arguments to the stack. Unfortunately the JVM # spec doesn't make it super clear how to decide how many items to # pop from the stack for invokedynamic. My guess, looking at the # constant pool, is that it's the name_and_type member of InvokeDynamic, # specifically the descriptor, that determines stack changes. # https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.9.invokedynamic # kinda confirms this without explicitly stating it. self.dynamic_name = const.name_and_type.name.value self.dynamic_desc = method_descriptor(const.name_and_type.descriptor.value) assert self.dynamic_desc.returns.name != "void" self.implemented_iface = self.dynamic_desc.returns.name # created_type is the type returned by the function we return. if self.ref_kind == REF_newInvokeSpecial: self.created_type = self.method_class else: self.created_type = self.method_desc.returns.name
def on_invoke(self, ins, const, obj, args): method_name = const.name_and_type.name.value method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) if ins == "invokestatic": if const.class_.name == superclass: current_item = {} text_id = None idx = 0 for arg in desc.args: if arg.name == blockclass: block = args[idx] text_id = block["text_id"] if "name" in block: current_item["name"] = block["name"] if "display_name" in block: current_item["display_name"] = block[ "display_name"] elif arg.name == superclass: current_item.update(args[idx]) elif arg.name == item_block_class: current_item.update(args[idx]) text_id = current_item["text_id"] elif arg.name == "java/lang/String": text_id = args[idx] idx += 1 if current_item == {} and not text_id: if verbose: print( "Couldn't find any identifying information for the call to %s with %s" % (method_desc, args)) return if not text_id: if verbose: print( "Could not find text_id for call to %s with %s" % (method_desc, args)) return # Call to the static register method. current_item["text_id"] = text_id current_item["numeric_id"] = self.cur_id self.cur_id += 1 lang_key = "minecraft.%s" % text_id if language != None and lang_key in language: current_item["display_name"] = language[lang_key] if "max_stack_size" not in current_item: current_item["max_stack_size"] = 64 item_list[text_id] = current_item else: if method_name == "<init>": # Call to a constructor. Check if the builder is in the args, # and if so update the item with it idx = 0 for arg in desc.args: if arg.name == builder_class: # Update from the builder if "max_stack_size" in args[idx]: obj["max_stack_size"] = args[idx][ "max_stack_size"] elif arg.name == blockclass and "text_id" not in obj: block = args[idx] obj["text_id"] = block["text_id"] if "name" in block: obj["name"] = block["name"] if "display_name" in block: obj["display_name"] = block["display_name"] idx += 1 elif method_name == max_stack_method.name.value and method_desc == max_stack_method.descriptor.value: obj["max_stack_size"] = args[0] if desc.returns.name != "void": if desc.returns.name == builder_class or is_item_class( desc.returns.name): if ins == "invokestatic": # Probably returning itself, but through a synthetic method return args[0] else: # Probably returning itself return obj else: return object()
def operations(jar, classname, classes, args=None, methodname=None, arg_names=("this", "packetbuffer")): """Gets the instructions of the specified method""" # Find the writing method cf = ClassFile(StringIO(jar.read(classname))) if methodname is None and args is None: methods = list(cf.methods.find(f=lambda x: len(x.args) == 1 and x.args[0].name == classes["packet.packetbuffer"])) if len(methods) == 2: method = methods[1] else: if cf.super_: return _PIT.operations(jar, cf.super_.name + ".class", classes) else: raise Exception("Failed to find method in class or superclass") elif methodname is None: method = cf.methods.find_one(args=args) else: method = cf.methods.find_one(name=methodname, args=args) # Decode the instructions operations = [] stack = [] skip_until = -1 shortif_pos = -1 shortif_cond = '' for instruction in method.code.disassemble(): if skip_until != -1: if instruction.pos == skip_until: skip_until = -1 else: continue opcode = instruction.opcode operands = [InstructionField(operand, instruction, cf.constants) for operand in instruction.operands] # Shortcut if if instruction.pos == shortif_pos: category = stack[-1].category stack.append(Operand("((%(cond)s) ? %(sec)s : %(first)s)" % { "cond": shortif_cond, "first": stack.pop(), "sec": stack.pop() }, category)) # Method calls if opcode >= 0xb6 and opcode <= 0xb9: name = operands[0].name desc = operands[0].descriptor descriptor = method_descriptor(desc) num_arguments = len(descriptor.args) if name in _PIT.TYPES: operations.append(Operation(instruction.pos, "write", type=_PIT.TYPES[name], field=stack.pop())) elif name == "writeBytes": # Directly write to the buffer (no length info) if len(desc.args) == 3: # Method that takes indexes within the arrays - we don't really care about the indexes. stack.pop() stack.pop() operations.append(Operation(instruction.pos, "write", type="byte[]", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == "byte" and descriptor.args[0].dimensions == 1 and len(name) == 1: # Write byte array - this method prefixes the length. field = stack.pop() operations.append(Operation(instruction.pos, "write", type="varint", field="%s.length" % field)) operations.append(Operation(instruction.pos, "write", type="byte[]", field=field)) elif num_arguments == 1 and descriptor.args[0].name == "int" and descriptor.args[0].dimensions == 1 and len(name) == 1: field = stack.pop() operations.append(Operation(instruction.pos, "write", type="varint", field="%s.length" % field)) operations.append(Operation(instruction.pos, "write", type="varint[]", field=field)) elif num_arguments == 1 and descriptor.args[0].name == "long" and descriptor.args[0].dimensions == 1 and len(name) == 1: field = stack.pop() operations.append(Operation(instruction.pos, "write", type="varint", field="%s.length" % field)) operations.append(Operation(instruction.pos, "write", type="long[]", field=field)) elif num_arguments == 1 and descriptor.args[0].name == "java/lang/String": operations.append(Operation(instruction.pos, "write", type="string16", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == "java/util/UUID": operations.append(Operation(instruction.pos, "write", type="uuid", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == "int" and len(name) == 1: # We need to check the return type to distinguish it from # other methods, including the normal netty writeint method # that writes 4 full bytes. The netty method returns a # ByteBuf, but varint returns void. operations.append(Operation(instruction.pos, "write", type="varint", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == "long" and len(name) == 1: operations.append(Operation(instruction.pos, "write", type="varlong", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == "java/lang/Enum": # If we were using the read method instead of the write method, then we could get the class for this enum... operations.append(Operation(instruction.pos, "write", type="enum", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == classes["nbtcompound"]: operations.append(Operation(instruction.pos, "write", type="nbtcompound", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == classes["itemstack"]: operations.append(Operation(instruction.pos, "write", type="itemstack", field=stack.pop())) elif num_arguments == 1 and descriptor.args[0].name == classes["chatcomponent"]: operations.append(Operation(instruction.pos, "write", type="chatcomponent", field=stack.pop())) else: if num_arguments > 0: arguments = stack[-len(descriptor.args):] else: arguments = [] for i in range(num_arguments): stack.pop() obj = "static" if opcode == 0xb8 else stack.pop() if descriptor.returns.name != "void": stack.append(Operand( "%s.%s(%s)" % ( obj, name, _PIT.join(arguments) ), 2 if descriptor.returns.name in ("long", "double") else 1) ) if isinstance(obj, Operand) and obj.value == "packetbuffer": # Right now there isn't a good way to identify the # class for positions, so we assume that any calls to # a packetbuffer method that we haven't yet handled is # actually writing a position. operations.append(Operation(instruction.pos, "write", type="position", field=arguments[0])) else: for arg in descriptor.args: if arg.name == classes["packet.packetbuffer"]: if operands[0].c == classes["metadata"]: # Special case - metadata is a complex type but # well documented; we don't want to include its # exact writing but just want to instead say # 'metadata'. # There are two cases - one is calling an # instance method of metadata that writes # out the instance, and the other is a # static method that takes a list and then # writes that list. operations.append(Operation(instruction.pos, "write", type="metadata", field=obj if obj != "static" else arguments[0])) break # If calling a sub method that takes a packetbuffer # as a parameter, it's possible that it's a sub # method that writes to the buffer, so we need to # check it. operations += _PIT.sub_operations( jar, cf, classes, instruction, operands[0], [obj] + arguments if obj != "static" else arguments ) break # Conditional statements and loops elif opcode in [0xc7, 0xc6] or opcode >= 0x99 and opcode <= 0xa6: if opcode == 0xc7: condition = "%s == null" % stack.pop() elif opcode == 0xc6: condition = "%s != null" % stack.pop() else: if opcode <= 0x9e: # if comperation = opcode - 0x99 fields = [0, stack.pop()] elif opcode <= 0xa4: # if_icmp comperation = opcode - 0x9f fields = [stack.pop(), stack.pop()] else: # if_acmp comperation = opcode - 0xa5 fields = [stack.pop(), stack.pop()] if comperation == 0 and fields[0] == 0: condition = fields[1] else: condition = "%s %s %s" % ( fields[1], _PIT.CONDITIONS[comperation], fields[0] ) operations.append(Operation(instruction.pos, "if", condition=condition)) operations.append(Operation(operands[0].target, "endif")) shortif_cond = condition elif opcode == 0xaa: # tableswitch operations.append(Operation(instruction.pos, "switch", field=stack.pop())) default = operands[0].target low = operands[1].value high = operands[2].value for opr in range(3, len(operands)): target = operands[opr].target operations.append(Operation(target, "case", value=low + opr - 3)) # TODO: Default might not be the right place for endswitch, # though it seems like default isn't used in any other way # in the normal code. operations.append(Operation(default, "endswitch")) elif opcode == 0xab: # lookupswitch operations.append(Operation(instruction.pos, "switch", field=stack.pop())) for opr in range(1, len(operands)): target = operands[opr].find_target(1) operations.append(Operation(target, "case", value=operands[opr].value[0])) operations.append(Operation(operands[0].target, "endswitch")) elif opcode == 0xa7: # goto target = operands[0].target endif = _PIT.find_next(operations, instruction.pos, "endif") case = _PIT.find_next(operations, instruction.pos, "case") if case is not None and target > case.position: operations.append(Operation(instruction.pos, "break")) elif endif is not None: if target > instruction.pos: endif.operation = "else" operations.append(Operation(target, "endif")) if len(stack) != 0: shortif_pos = target else: endif.operation = "endloop" _PIT.find_next( operations, target, "if" ).operation = "loop" elif target > instruction.pos: skip_until = target # Math elif opcode >= 0x74 and opcode <= 0x77: # Tneg category = stack[-1].category stack.append(Operand("(- %s)" % (stack.pop), category)) elif opcode >= 0x60 and opcode <= 0x83: lookup_opcode = opcode while not lookup_opcode in _PIT.MATH: lookup_opcode -= 1 category = stack[-1].category value2 = stack.pop() stack.append(Operand( "(%s %s %s)" % ( stack.pop(), _PIT.MATH[lookup_opcode], value2 ), category )) elif opcode == 0x84: # iinc operations.append(Operation(instruction.pos, "increment", field="var%s" % operands[0], amount=operands[1])) # Other manually handled opcodes elif opcode == 0xc5: # multianewarray operand = "" for i in range(operands[1].value): operand = "[%s]%s" % (stack.pop(), operand) stack.append(Operand( "new %s%s" % (operands[0].type, operand))) elif opcode == 0x58: # pop2 if stack.pop().category != 2: stack.pop() elif opcode == 0x5f: # swap stack += [stack.pop(), stack.pop()] elif opcode == 0x59: # dup stack.append(stack[-1]) elif opcode == 0x5a: # dup_x1 stack.insert(-2, stack[-1]) elif opcode == 0x5b: # dup_x2 stack.insert(-2 if stack[-2].category == 2 else -3, stack[-1]) elif opcode == 0x5c: # dup2 if stack[-1].category == 2: stack.append(stack[-1]) else: stack += stack[-2:] elif opcode == 0x5d: # dup2_x1 if stack[-1].category == 2: stack.insert(-2, stack[-1]) else: stack.insert(-3, stack[-2]) stack.insert(-3, stack[-1]) elif opcode == 0x5e: # dup2_x2 if stack[-1].category == 2: stack.insert( -2 if stack[-2].category == 2 else -3, stack[-1] ) else: stack.insert( -3 if stack[-3].category == 2 else -4, stack[-2] ) stack.insert( -3 if stack[-3].category == 2 else -4, stack[-1] ) # Unhandled opcodes elif opcode in [0xc8, 0xa8, 0xc9]: raise Exception("unhandled opcode 0x%x" % opcode) # Default handlers else: lookup_opcode = opcode while not lookup_opcode in _PIT.OPCODES: lookup_opcode -= 1 handler = _PIT.OPCODES[lookup_opcode] index = 0 if isinstance(handler, int): handler = [handler] assert len(stack) >= handler[index] for i in range(handler[index]): operands.insert(0, stack.pop()) index += 1 if len(handler) > index: format = handler[index] index += 1 if (len(handler) > index and isinstance(handler[index], LambdaType)): value = handler[index](opcode) operands.append(value) operands.append(arg_names[value] if value < len(arg_names) else "var%s" % value) index += 1 elif len(operands) >= 1: value = operands[0].value operands.append(arg_names[value] if value < len(arg_names) else "var%s" % value) if (len(handler) > index and isinstance(handler[index], int)): category = handler[index] else: category = 1 stack.append(Operand( StringFormatter.format(format, *operands), category) ) if "store" in instruction.mnemonic: if instruction.mnemonic[1] == 'a': # Array store: Doesn't seem to be used when writing # packets; let's ignore it. raise Exception("Unhandled array store instruction: " + str(instruction)) # Keep track of what is being stored, for clarity if "_" in instruction.mnemonic: # Tstore_<index> arg = instruction.mnemonic[-1] else: arg = operands.pop().value type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]] var = arg_names[arg] if arg < len(arg_names) else "var%s" % arg operations.append(Operation(instruction.pos, "store", type=type, var=var, value=operands.pop())) return operations
def operations(classloader, classname, classes, verbose, args=None, methodname=None, arg_names=("this", "packetbuffer")): """Gets the instructions of the specified method""" # Find the writing method cf = classloader[classname[:-len(".class")]] # XXX triming a .class that has no reason to exist anyways if methodname is None and args is None: methods = list(cf.methods.find(returns="V", args="L" + classes["packet.packetbuffer"] + ";")) if len(methods) == 2: method = methods[1] else: if cf.super_.name.value != "java/lang/Object": return _PIT.operations(classloader, cf.super_.name.value + ".class", classes, verbose) else: raise Exception("Failed to find method in class or superclass") elif methodname is None: method = cf.methods.find_one(args=args) else: method = cf.methods.find_one(name=methodname, args=args) if method.access_flags.acc_abstract: # Abstract method call -- just log that, since we can't view it return [Operation(instruction.pos, "interfacecall", type="abstract", target=operands[0].c, name=name, method=name + desc, field=obj, args=_PIT.join(arguments))] # Decode the instructions operations = [] stack = [] skip_until = -1 shortif_pos = None shortif_cond = None # NOTE: we only use the simple_swap transform here due to the # expand_constants transform making it hard to use InstructionField # InstructionField should probably be cleaned up first for instruction in method.code.disassemble(transforms=[simple_swap]): if skip_until != -1: if instruction.pos == skip_until: skip_until = -1 else: continue mnemonic = instruction.mnemonic operands = [InstructionField(operand, instruction, cf.constants) for operand in instruction.operands] # Shortcut if if instruction.pos == shortif_pos: # Check to make sure that this actually is a ternary if assert len(operations) >= 3 assert operations[-1].operation == "endif" assert operations[-2].operation == "else" assert operations[-3].operation == "if" # Now get rid of the unneeded if's operations.pop() operations.pop() operations.pop() category = stack[-1].category stack.append(StackOperand("((%(cond)s) ? %(sec)s : %(first)s)" % { "cond": shortif_cond, "first": stack.pop(), "sec": stack.pop() }, category)) shortif_cond = None shortif_pos = None # Method calls if mnemonic in ("invokevirtual", "invokespecial", "invokestatic", "invokeinterface"): name = operands[0].name desc = operands[0].descriptor descriptor = method_descriptor(desc) num_arguments = len(descriptor.args) if num_arguments > 0: arguments = stack[-len(descriptor.args):] else: arguments = [] for i in six.moves.range(num_arguments): stack.pop() is_static = (mnemonic == "invokestatic") obj = operands[0].classname if is_static else stack.pop() if name in _PIT.TYPES: # Builtin netty buffer methods assert num_arguments == 1 operations.append(Operation(instruction.pos, "write", type=_PIT.TYPES[name], field=arguments[0])) stack.append(obj) elif len(name) == 1 and isinstance(obj, StackOperand) and obj.value == "packetbuffer": # Checking len(name) == 1 is used to see if it's a Minecraft # method (due to obfuscation). Netty methods have real # (and thus longer) names. assert num_arguments >= 1 arg_type = descriptor.args[0].name field = arguments[0] if descriptor.args[0].dimensions == 1 and num_arguments == 1: # Array methods, which prefix a length operations.append(Operation(instruction.pos, "write", type="varint", field="%s.length" % field)) if arg_type == "byte": operations.append(Operation(instruction.pos, "write", type="byte[]", field=field)) elif arg_type == "int": operations.append(Operation(instruction.pos, "write", type="varint[]", field=field)) elif arg_type == "long": operations.append(Operation(instruction.pos, "write", type="long[]", field=field)) else: raise Exception("Unexpected array type: " + arg_type) elif num_arguments == 1: assert descriptor.args[0].dimensions == 0 if arg_type == "java/lang/String": max_length = 32767 # not using this at the time operations.append(Operation(instruction.pos, "write", type="string", field=field)) elif arg_type == "java/util/UUID": operations.append(Operation(instruction.pos, "write", type="uuid", field=field)) elif arg_type == "java/util/Date": operations.append(Operation(instruction.pos, "write", type="long", field="%s.getTime()" % field)) elif arg_type == "int": operations.append(Operation(instruction.pos, "write", type="varint", field=field)) elif arg_type == "long": operations.append(Operation(instruction.pos, "write", type="varlong", field=field)) elif arg_type == "java/lang/Enum": # If we were using the read method instead of the write method, then we could get the class for this enum... operations.append(Operation(instruction.pos, "write", type="enum", field=field)) elif arg_type == classes["nbtcompound"]: operations.append(Operation(instruction.pos, "write", type="nbtcompound", field=field)) elif arg_type == classes["itemstack"]: operations.append(Operation(instruction.pos, "write", type="itemstack", field=field)) elif arg_type == classes["chatcomponent"]: operations.append(Operation(instruction.pos, "write", type="chatcomponent", field=field)) elif arg_type == classes["identifier"]: operations.append(Operation(instruction.pos, "write", type="identifier", field=field)) elif "position" not in classes or arg_type == classes["position"]: if "position" not in classes: classes["position"] = arg_type if verbose: print("Assuming", arg_type, "is the position class") operations.append(Operation(instruction.pos, "write", type="position", field=field)) else: # Unknown type in packetbuffer; try inlining it as well # (on the assumption that it's something made of a few calls, # and not e.g. writeVarInt) if verbose: print("Inlining code for", arg_type) operations += _PIT.sub_operations( classloader, cf, classes, instruction, verbose, operands[0], [obj] + arguments if not is_static else arguments ) elif num_arguments == 2: if arg_type == "java/lang/String" and descriptor.args[1].name == "int": max_length = arguments[1] # not using this at the time operations.append(Operation(instruction.pos, "write", type="string", field=field)) else: raise Exception("Unexpected descriptor " + desc) else: raise Exception("Unexpected num_arguments: " + str(num_arguments) + " - desc " + desc) # Return the buffer back to the stack, if needed if descriptor.returns.name == classes["packet.packetbuffer"]: stack.append(obj) elif name == "<init>": # Constructor call. Should have the instance right # on the stack as well (due to constructors returning void). # Add the arguments to that object. assert stack[-1] is obj obj.value += "(" + _PIT.join(arguments) + ")"; else: if descriptor.returns.name != "void": stack.append(StackOperand( "%s.%s(%s)" % ( obj, name, _PIT.join(arguments) ), 2 if descriptor.returns.name in ("long", "double") else 1) ) else: for arg in descriptor.args: if arg.name == classes["packet.packetbuffer"]: if operands[0].c == classes["metadata"]: # Special case - metadata is a complex type but # well documented; we don't want to include its # exact writing but just want to instead say # 'metadata'. # There are two cases - one is calling an # instance method of metadata that writes # out the instance, and the other is a # static method that takes a list and then # writes that list. operations.append(Operation(instruction.pos, "write", type="metadata", field=obj if not is_static else arguments[0])) break if mnemonic != "invokeinterface": # If calling a sub method that takes a packetbuffer # as a parameter, it's possible that it's a sub # method that writes to the buffer, so we need to # check it. operations += _PIT.sub_operations( classloader, cf, classes, instruction, verbose, operands[0], [obj] + arguments if not is_static else arguments ) else: # However, for interface method calls, we can't # check its code -- so just note that it's a call operations.append(Operation(instruction.pos, "interfacecall", type="interface", target=operands[0].c, name=name, method=name + desc, field=obj, args=_PIT.join(arguments))) break # Conditional statements and loops elif mnemonic.startswith("if"): if "icmp" in mnemonic or "acmp" in mnemonic: value2 = stack.pop() value1 = stack.pop() elif "null" in mnemonic: value1 = stack.pop() value2 = "null" else: value1 = stack.pop() value2 = 0 # All conditions are reversed: if the condition in the mnemonic # passes, then we'd jump; thus, to execute the following code, # the condition must _not_ pass if mnemonic in ("ifeq", "if_icmpeq", "if_acmpeq", "ifnull"): comparison = "!=" elif mnemonic in ("ifne", "if_icmpne", "if_acmpne", "ifnonnull"): comparison = "==" elif mnemonic in ("iflt", "if_icmplt"): comparison = ">=" elif mnemonic in ("ifge", "if_icmpge"): comparison = "<" elif mnemonic in ("ifgt", "if_icmpgt"): comparison = "<=" elif mnemonic in ("ifle", "if_icmple"): comparison = ">" else: raise Exception("Unknown if mnemonic %s (0x%x)" % (mnemonic, instruction.opcode)) if comparison == "!=" and value2 == 0: # if (something != 0) -> if (something) condition = value1 else: condition = "%s %s %s" % (value1, comparison, value2) operations.append(Operation(instruction.pos, "if", condition=condition)) operations.append(Operation(operands[0].target, "endif")) if shortif_pos is not None: # Clearly not a ternary-if if we have another nested if # (assuming that it's not a nested ternary, which we # already don't handle for other reasons) # If we don't do this, then the following code can have # problems: # if (a) { # if (b) { # // ... # } # } else if (c) { # // ... # } # as there would be a goto instruction to skip the # `else if (c)` portion that would be parsed as a shortif shortif_pos = None shortif_cond = condition elif mnemonic == "tableswitch": operations.append(Operation(instruction.pos, "switch", field=stack.pop())) default = operands[0].target low = operands[1].value high = operands[2].value for opr in six.moves.range(3, len(operands)): target = operands[opr].target operations.append(Operation(target, "case", value=low + opr - 3)) # TODO: Default might not be the right place for endswitch, # though it seems like default isn't used in any other way # in the normal code. operations.append(Operation(default, "endswitch")) elif mnemonic == "lookupswitch": raise Exception("lookupswitch is not supported") # operations.append(Operation(instruction.pos, "switch", # field=stack.pop())) # for opr in six.moves.range(1, len(operands)): # target = operands[opr].find_target(1) # operations.append(Operation(target, "case", # value=operands[opr].value[0])) # operations.append(Operation(operands[0].target, "endswitch")) elif mnemonic == "goto": target = operands[0].target endif = _PIT.find_next(operations, instruction.pos, "endif") case = _PIT.find_next(operations, instruction.pos, "case") if case is not None and target > case.position: operations.append(Operation(instruction.pos, "break")) elif endif is not None: if target > instruction.pos: endif.operation = "else" operations.append(Operation(target, "endif")) if len(stack) != 0: shortif_pos = target else: endif.operation = "endloop" _PIT.find_next( operations, target, "if" ).operation = "loop" elif target > instruction.pos: skip_until = target elif mnemonic == "iinc": operations.append(Operation(instruction.pos, "increment", field="var%s" % operands[0], amount=operands[1])) # Other manually handled instructions elif mnemonic == "multianewarray": operand = "" for i in six.moves.range(operands[1].value): operand = "[%s]%s" % (stack.pop(), operand) stack.append(StackOperand( "new %s%s" % (operands[0].type, operand))) elif mnemonic == "pop": stack.pop() elif mnemonic == "pop2": if stack.pop().category != 2: stack.pop() elif mnemonic == "swap": stack[-2], stack[-1] = stack[-1], stack[-2] elif mnemonic == "dup": stack.append(stack[-1]) elif mnemonic == "dup_x1": stack.insert(-2, stack[-1]) elif mnemonic == "dup_x2": stack.insert(-2 if stack[-2].category == 2 else -3, stack[-1]) elif mnemonic == "dup2": if stack[-1].category == 2: stack.append(stack[-1]) else: stack += stack[-2:] elif mnemonic == "dup2_x1": if stack[-1].category == 2: stack.insert(-2, stack[-1]) else: stack.insert(-3, stack[-2]) stack.insert(-3, stack[-1]) elif mnemonic == "dup2_x2": if stack[-1].category == 2: stack.insert( -2 if stack[-2].category == 2 else -3, stack[-1] ) else: stack.insert( -3 if stack[-3].category == 2 else -4, stack[-2] ) stack.insert( -3 if stack[-3].category == 2 else -4, stack[-1] ) elif mnemonic == "return": # Don't attempt to lookup the instruction in the handler pass elif instruction in ("istore", "lstore", "fstore", "dstore", "astore"): # Keep track of what is being stored, for clarity type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]] arg = operands.pop().value var = arg_names[arg] if arg < len(arg_names) else "var%s" % arg operations.append(Operation(instruction.pos, "store", type=type, var=var, value=stack.pop())) elif instruction in ("iastore", "lastore", "fastore", "dastore", "aastore", "bastore", "castore", "sastore"): type = _PIT.INSTRUCTION_TYPES[instruction.mnemonic[0]] # Array store value = stack.pop() index = stack.pop() array = stack.pop() operations.append(Operation(instruction.pos, "arraystore", type=type, index=index, var=array, value=value)) # Default handlers else: if mnemonic not in _PIT.OPCODES: raise Exception("Unhandled instruction opcode %s (0x%x)" % (mnemonic, instruction.opcode)) handler = _PIT.OPCODES[mnemonic] ins_stack = [] assert len(stack) >= handler["stack_count"] for _ in six.moves.range(handler["stack_count"]): ins_stack.insert(0, stack.pop()) ctx = { "operands": operands, "stack": ins_stack, "ins": instruction, "arg_names": arg_names } if handler["extra_method"]: ctx["extra"] = handler["extra_method"](ctx) category = handler["category"] try: formatted = handler["template"].format(**ctx) except Exception as ex: raise Exception("Failed to format info for %s (0x%x) with template %s and ctx %s: %s" % (mnemonic, instruction.opcode, handler["template"], ctx, ex)) stack.append(StackOperand(formatted, handler["category"])) return operations
def walk_method(cf, method, callback, verbose): assert isinstance(callback, WalkerCallback) stack = [] locals = {} # TODO: Allow passing argument values in or something like that cur_index = 0 if not method.access_flags.acc_static: locals[cur_index] = object() cur_index += 1 for arg in method.args: locals[cur_index] = object() cur_index += 1 for ins in method.code.disassemble(): if ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith( "dconst"): stack.append(float(ins.mnemonic[-1])) elif ins == "aconst_null": stack.append(None) elif ins in ("ldc", "ldc_w", "ldc2_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins == "new": const = ins.operands[0] try: stack.append(callback.on_new(ins, const)) except StopIteration: break elif ins in ("getfield", "getstatic"): const = ins.operands[0] if ins.mnemonic != "getstatic": obj = stack.pop() else: obj = None try: stack.append(callback.on_get_field(ins, const, obj)) except StopIteration: break elif ins in ("putfield", "putstatic"): const = ins.operands[0] value = stack.pop() if ins.mnemonic != "putstatic": obj = stack.pop() else: obj = None try: callback.on_put_field(ins, const, obj, value) except StopIteration: break elif ins in ("invokevirtual", "invokespecial", "invokeinterface", "invokestatic"): const = ins.operands[0] method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) if ins.mnemonic != "invokestatic": obj = stack.pop() else: obj = None try: ret = callback.on_invoke(ins, const, obj, args) except StopIteration: break if desc.returns.name != "void": stack.append(ret) elif ins in ("astore", "istore"): locals[ins.operands[0].value] = stack.pop() elif ins in ("aload", "iload"): stack.append(locals[ins.operands[0].value]) elif ins == "dup": stack.append(stack[-1]) elif ins == "pop": stack.pop() elif ins == "anewarray": stack.append([None] * stack.pop()) elif ins == "newarray": stack.append([0] * stack.pop()) elif ins in ("aastore", "iastore", "fastore"): value = stack.pop() index = stack.pop() array = stack.pop() if isinstance(array, list) and isinstance(index, int): array[index] = value elif verbose: print("Failed to execute %s: array %s index %s value %s" % (ins, array, index, value)) elif ins == "invokedynamic": stack.append(callback.on_invokedynamic(ins, ins.operands[0])) elif ins in ("checkcast", "return"): pass elif verbose: print("Unknown instruction %s: stack is %s" % (ins, stack))
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 walk_method(cf, method, callback, verbose): assert isinstance(callback, WalkerCallback) stack = [] locals = {} for ins in method.code.disassemble(): if ins.mnemonic in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins.mnemonic.startswith("fconst"): stack.append(float(ins.mnemonic[-1])) elif ins.mnemonic == "aconst_null": stack.append(None) elif ins.mnemonic in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins.mnemonic == "new": const = ins.operands[0] try: stack.append(callback.on_new(ins, const)) except StopIteration: break elif ins.mnemonic in ("getfield", "getstatic"): const = ins.operands[0] if ins.mnemonic != "getstatic": obj = stack.pop() else: obj = None try: stack.append(callback.on_get_field(ins, const, obj)) except StopIteration: break elif ins.mnemonic in ("putfield", "putstatic"): const = ins.operands[0] value = stack.pop() if ins.mnemonic != "putstatic": obj = stack.pop() else: obj = None try: callback.on_put_field(ins, const, obj, value) except StopIteration: break elif ins.mnemonic in ("invokevirtual", "invokespecial", "invokeinterface", "invokestatic"): const = ins.operands[0] method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) if ins.mnemonic != "invokestatic": obj = stack.pop() else: obj = None try: ret = callback.on_invoke(ins, const, obj, args) except StopIteration: break if desc.returns.name != "void": stack.append(ret) elif ins.mnemonic == "astore": locals[ins.operands[0].value] = stack.pop() elif ins.mnemonic == "aload": stack.append(locals[ins.operands[0].value]) elif ins.mnemonic == "dup": stack.append(stack[-1]) elif ins.mnemonic in ("checkcast", "return"): pass elif verbose: print("Unknown instruction %s: stack is %s" % (ins, stack))
def _process_19(aggregate, classloader, verbose): # Processes biomes for Minecraft 1.9 through 1.12 biomes_base = aggregate.setdefault("biomes", {}) biomes = biomes_base.setdefault("biome", {}) biome_fields = biomes_base.setdefault("biome_fields", {}) superclass = aggregate["classes"]["biome.superclass"] cf = classloader[superclass] method = cf.methods.find_one(returns="V", args="", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static) heights_by_field = {} first_new = True biome = None stack = [] # OK, start running through the initializer for biomes. for ins in method.code.disassemble(): if ins.mnemonic == "anewarray": # End of biome initialization; now creating the list of biomes # for the explore all biomes achievement but we don't need # that info. break if ins.mnemonic == "new": if first_new: # There are two 'new's in biome initialization - the first # one is for the biome generator itself and the second one # is the biome properties. There's some info that is only # stored on the first new (well, actually, beforehand) # that we want to save. const = ins.operands[0] text_id = stack.pop() numeric_id = stack.pop() biome = { "id": numeric_id, "text_id": text_id, "rainfall": 0.5, "height": [0.1, 0.2], "temperature": 0.5, "class": const.name.value } stack = [] first_new = not(first_new) elif ins.mnemonic == "invokestatic": # Call to the static registration method # We already saved its parameters at the constructor, so we # only need to store the biome now. biomes[biome["text_id"]] = biome elif ins.mnemonic == "invokespecial": # Possibly the constructor for biome properties, which takes # the name as a string. if len(stack) > 0 and not "name" in biome: biome["name"] = stack.pop() stack = [] elif ins.mnemonic == "invokevirtual": const = ins.operands[0] name = const.name_and_type.name.value desc = method_descriptor(const.name_and_type.descriptor.value) if len(desc.args) == 1: if desc.args[0].name == "float": # Ugly portion - different methods with different names # Hopefully the order doesn't change if name == "a": biome["temperature"] = stack.pop() elif name == "b": biome["rainfall"] = stack.pop() elif name == "c": biome["height"][0] = stack.pop() elif name == "d": biome["height"][1] = stack.pop() elif desc.args[0].name == "java/lang/String": # setBaseBiome biome["mutated_from"] = stack.pop() # numeric values & constants elif ins.mnemonic in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, String): stack.append(const.string.value) if isinstance(const, (Integer, Float)): stack.append(const.value) elif ins.mnemonic.startswith("fconst"): stack.append(float(ins.mnemonic[-1])) elif ins.mnemonic in ("bipush", "sipush"): stack.append(ins.operands[0].value) # Go through the biome list and add the field info. list = aggregate["classes"]["biome.list"] lcf = classloader[list] # Find the static block, and load the fields for each. method = lcf.methods.find_one(name="<clinit>") biome_name = "" for ins in method.code.disassemble(): if ins.mnemonic in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, String): biome_name = const.string.value elif ins.mnemonic == "putstatic": if biome_name is None or biome_name == "Accessed Biomes before Bootstrap!": continue const = ins.operands[0] field = const.name_and_type.name.value biomes[biome_name]["field"] = field biome_fields[field] = biome_name
def walk_method(cf, method, callback, verbose, input_args=None): """ Walks through a method, evaluating instructions and using the callback for side-effects. The method is assumed to not have any conditionals, and to only return at the very end. """ assert isinstance(callback, WalkerCallback) stack = [] locals = {} cur_index = 0 if not method.access_flags.acc_static: # TODO: allow specifying this locals[cur_index] = object() cur_index += 1 if input_args != None: assert len(input_args) == len(method.args) for arg in input_args: locals[cur_index] = arg cur_index += 1 else: for arg in method.args: locals[cur_index] = object() cur_index += 1 ins_list = list(method.code.disassemble()) for ins in ins_list[:-1]: if ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins.mnemonic.startswith("fconst") or ins.mnemonic.startswith( "dconst"): stack.append(float(ins.mnemonic[-1])) elif ins == "aconst_null": stack.append(None) elif ins in ("ldc", "ldc_w", "ldc2_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins == "new": const = ins.operands[0] try: stack.append(callback.on_new(ins, const)) except StopIteration: break elif ins in ("getfield", "getstatic"): const = ins.operands[0] if ins.mnemonic != "getstatic": obj = stack.pop() else: obj = None try: stack.append(callback.on_get_field(ins, const, obj)) except StopIteration: break elif ins in ("putfield", "putstatic"): const = ins.operands[0] value = stack.pop() if ins.mnemonic != "putstatic": obj = stack.pop() else: obj = None try: callback.on_put_field(ins, const, obj, value) except StopIteration: break elif ins in ("invokevirtual", "invokespecial", "invokeinterface", "invokestatic"): const = ins.operands[0] method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) if ins.mnemonic != "invokestatic": obj = stack.pop() else: obj = None try: ret = callback.on_invoke(ins, const, obj, args) except StopIteration: break if desc.returns.name != "void": stack.append(ret) elif ins in ("astore", "istore", "lstore", "fstore", "dstore"): locals[ins.operands[0].value] = stack.pop() elif ins in ("aload", "iload", "lload", "fload", "dload"): stack.append(locals[ins.operands[0].value]) elif ins == "dup": stack.append(stack[-1]) elif ins == "pop": stack.pop() elif ins == "anewarray": stack.append([None] * stack.pop()) elif ins == "newarray": stack.append([0] * stack.pop()) elif ins in ("aastore", "bastore", "castore", "sastore", "iastore", "lastore", "fastore", "dastore"): value = stack.pop() index = stack.pop() array = stack.pop() if isinstance(array, list) and isinstance(index, int): array[index] = value elif verbose: print("Failed to execute %s: array %s index %s value %s" % (ins, array, index, value)) elif ins in ("aaload", "baload", "caload", "saload", "iaload", "laload", "faload", "daload"): index = stack.pop() array = stack.pop() if isinstance(array, list) and isinstance(index, int): stack.push(array[index]) elif verbose: print("Failed to execute %s: array %s index %s" % (ins, array, index)) elif ins == "invokedynamic": const = ins.operands[0] method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) stack.append(callback.on_invokedynamic(ins, ins.operands[0], args)) elif ins == "checkcast": pass elif verbose: print("Unknown instruction %s: stack is %s" % (ins, stack)) last_ins = ins_list[-1] if last_ins.mnemonic in ("ireturn", "lreturn", "freturn", "dreturn", "areturn"): # Non-void method returning return stack.pop() elif last_ins.mnemonic == "return": # Void method returning pass elif verbose: print("Unexpected final instruction %s: stack is %s" % (ins, stack))
def returns(self): return method_descriptor(self.descriptor.value).returns
def returns(self) -> JVMType: """ A :class:`~jawa.util.descriptor.JVMType` representing the method's return type. """ return method_descriptor(self.descriptor.value).returns
def operations(classloader, classname, classes, verbose, args=None, methodname=None, arg_names=("this", "packetbuffer")): """Gets the instructions of the specified method""" # Find the writing method cf = classloader[classname[:-len( ".class" )]] # XXX triming a .class that has no reason to exist anyways if methodname is None and args is None: methods = list( cf.methods.find(returns="V", args="L" + classes["packet.packetbuffer"] + ";")) if len(methods) == 2: method = methods[1] else: if cf.super_.name.value != "java/lang/Object": return _PIT.operations(classloader, cf.super_.name.value + ".class", classes, verbose) else: raise Exception( "Failed to find method in class or superclass") elif methodname is None: method = cf.methods.find_one(args=args) else: method = cf.methods.find_one(name=methodname, args=args) if method.access_flags.acc_abstract: # Abstract method call -- just log that, since we can't view it return [ Operation(instruction.pos, "interfacecall", type="abstract", target=operands[0].c, name=name, method=name + desc, field=obj, args=_PIT.join(arguments)) ] # Decode the instructions operations = [] stack = [] skip_until = -1 shortif_pos = None shortif_cond = None # NOTE: we only use the simple_swap transform here due to the # expand_constants transform making it hard to use InstructionField # InstructionField should probably be cleaned up first for instruction in method.code.disassemble(transforms=[simple_swap]): if skip_until != -1: if instruction.pos == skip_until: skip_until = -1 else: continue mnemonic = instruction.mnemonic operands = [ InstructionField(operand, instruction, cf.constants) for operand in instruction.operands ] # Shortcut if if instruction.pos == shortif_pos: # Check to make sure that this actually is a ternary if assert len(operations) >= 3 assert operations[-1].operation == "endif" assert operations[-2].operation == "else" assert operations[-3].operation == "if" # Now get rid of the unneeded if's operations.pop() operations.pop() operations.pop() category = stack[-1].category stack.append( StackOperand( "((%(cond)s) ? %(sec)s : %(first)s)" % { "cond": shortif_cond, "first": stack.pop(), "sec": stack.pop() }, category)) shortif_cond = None shortif_pos = None # Method calls if mnemonic in ("invokevirtual", "invokespecial", "invokestatic", "invokeinterface"): name = operands[0].name desc = operands[0].descriptor descriptor = method_descriptor(desc) num_arguments = len(descriptor.args) if num_arguments > 0: arguments = stack[-len(descriptor.args):] else: arguments = [] for i in six.moves.range(num_arguments): stack.pop() is_static = (mnemonic == "invokestatic") obj = operands[0].classname if is_static else stack.pop() if name in _PIT.TYPES: # Builtin netty buffer methods assert num_arguments == 1 operations.append( Operation(instruction.pos, "write", type=_PIT.TYPES[name], field=arguments[0])) stack.append(obj) elif len(name) == 1 and isinstance( obj, StackOperand) and obj.value == "packetbuffer": # Checking len(name) == 1 is used to see if it's a Minecraft # method (due to obfuscation). Netty methods have real # (and thus longer) names. assert num_arguments >= 1 arg_type = descriptor.args[0].name field = arguments[0] if descriptor.args[ 0].dimensions == 1 and num_arguments == 1: # Array methods, which prefix a length operations.append( Operation(instruction.pos, "write", type="varint", field="%s.length" % field)) if arg_type == "byte": operations.append( Operation(instruction.pos, "write", type="byte[]", field=field)) elif arg_type == "int": operations.append( Operation(instruction.pos, "write", type="varint[]", field=field)) elif arg_type == "long": operations.append( Operation(instruction.pos, "write", type="long[]", field=field)) else: raise Exception("Unexpected array type: " + arg_type) elif num_arguments == 1: assert descriptor.args[0].dimensions == 0 if arg_type == "java/lang/String": max_length = 32767 # not using this at the time operations.append( Operation(instruction.pos, "write", type="string", field=field)) elif arg_type == "java/util/UUID": operations.append( Operation(instruction.pos, "write", type="uuid", field=field)) elif arg_type == "java/util/Date": operations.append( Operation(instruction.pos, "write", type="long", field="%s.getTime()" % field)) elif arg_type == "int": operations.append( Operation(instruction.pos, "write", type="varint", field=field)) elif arg_type == "long": operations.append( Operation(instruction.pos, "write", type="varlong", field=field)) elif arg_type == "java/lang/Enum": # If we were using the read method instead of the write method, then we could get the class for this enum... operations.append( Operation(instruction.pos, "write", type="enum", field=field)) elif arg_type == classes["nbtcompound"]: operations.append( Operation(instruction.pos, "write", type="nbtcompound", field=field)) elif arg_type == classes["itemstack"]: operations.append( Operation(instruction.pos, "write", type="itemstack", field=field)) elif arg_type == classes["chatcomponent"]: operations.append( Operation(instruction.pos, "write", type="chatcomponent", field=field)) elif arg_type == classes["identifier"]: operations.append( Operation(instruction.pos, "write", type="identifier", field=field)) elif "position" not in classes or arg_type == classes[ "position"]: if "position" not in classes: classes["position"] = arg_type if verbose: print("Assuming", arg_type, "is the position class") operations.append( Operation(instruction.pos, "write", type="position", field=field)) else: # Unknown type in packetbuffer; try inlining it as well # (on the assumption that it's something made of a few calls, # and not e.g. writeVarInt) if verbose: print("Inlining code for", arg_type) operations += _PIT.sub_operations( classloader, cf, classes, instruction, verbose, operands[0], [obj] + arguments if not is_static else arguments) elif num_arguments == 2: if arg_type == "java/lang/String" and descriptor.args[ 1].name == "int": max_length = arguments[ 1] # not using this at this time operations.append( Operation(instruction.pos, "write", type="string", field=field)) elif arg_type == "com/mojang/serialization/Codec": codec = arguments[0] value = arguments[1] # This isn't the exact syntax used by DataFixerUpper, # but it's close enough for our purposes field = "%s.encode(%s)" % (codec, value) operations.append( Operation(instruction.pos, "write", type="nbtcompound", field=field)) else: raise Exception("Unexpected descriptor " + desc) else: raise Exception("Unexpected num_arguments: " + str(num_arguments) + " - desc " + desc) # Return the buffer back to the stack, if needed if descriptor.returns.name == classes[ "packet.packetbuffer"]: stack.append(obj) elif name == "<init>": # Constructor call. Should have the instance right # on the stack as well (due to constructors returning void). # Add the arguments to that object. assert stack[-1] is obj obj.value += "(" + _PIT.join(arguments) + ")" else: if descriptor.returns.name != "void": stack.append( StackOperand( "%s.%s(%s)" % (obj, name, _PIT.join(arguments)), 2 if descriptor.returns.name in ("long", "double") else 1)) else: for arg in descriptor.args: if arg.name == classes["packet.packetbuffer"]: if operands[0].c == classes["metadata"]: # Special case - metadata is a complex type but # well documented; we don't want to include its # exact writing but just want to instead say # 'metadata'. # There are two cases - one is calling an # instance method of metadata that writes # out the instance, and the other is a # static method that takes a list and then # writes that list. operations.append( Operation(instruction.pos, "write", type="metadata", field=obj if not is_static else arguments[0])) break if mnemonic != "invokeinterface": # If calling a sub method that takes a packetbuffer # as a parameter, it's possible that it's a sub # method that writes to the buffer, so we need to # check it. operations += _PIT.sub_operations( classloader, cf, classes, instruction, verbose, operands[0], [obj] + arguments if not is_static else arguments) else: # However, for interface method calls, we can't # check its code -- so just note that it's a call operations.append( Operation(instruction.pos, "interfacecall", type="interface", target=operands[0].c, name=name, method=name + desc, field=obj, args=_PIT.join(arguments))) break elif mnemonic == "invokedynamic": stack.append( stringify_invokedynamic(stack.pop(), instruction, cf)) # Conditional statements and loops elif mnemonic.startswith("if"): if "icmp" in mnemonic or "acmp" in mnemonic: value2 = stack.pop() value1 = stack.pop() elif "null" in mnemonic: value1 = stack.pop() value2 = "null" else: value1 = stack.pop() value2 = 0 # All conditions are reversed: if the condition in the mnemonic # passes, then we'd jump; thus, to execute the following code, # the condition must _not_ pass if mnemonic in ("ifeq", "if_icmpeq", "if_acmpeq", "ifnull"): comparison = "!=" elif mnemonic in ("ifne", "if_icmpne", "if_acmpne", "ifnonnull"): comparison = "==" elif mnemonic in ("iflt", "if_icmplt"): comparison = ">=" elif mnemonic in ("ifge", "if_icmpge"): comparison = "<" elif mnemonic in ("ifgt", "if_icmpgt"): comparison = "<=" elif mnemonic in ("ifle", "if_icmple"): comparison = ">" else: raise Exception("Unknown if mnemonic %s (0x%x)" % (mnemonic, instruction.opcode)) if comparison == "!=" and value2 == 0: # if (something != 0) -> if (something) condition = value1 else: condition = "%s %s %s" % (value1, comparison, value2) operations.append( Operation(instruction.pos, "if", condition=condition)) operations.append(Operation(operands[0].target, "endif")) if shortif_pos is not None: # Clearly not a ternary-if if we have another nested if # (assuming that it's not a nested ternary, which we # already don't handle for other reasons) # If we don't do this, then the following code can have # problems: # if (a) { # if (b) { # // ... # } # } else if (c) { # // ... # } # as there would be a goto instruction to skip the # `else if (c)` portion that would be parsed as a shortif shortif_pos = None shortif_cond = condition elif mnemonic == "tableswitch": operations.append( Operation(instruction.pos, "switch", field=stack.pop())) default = operands[0].target low = operands[1].value high = operands[2].value for opr in six.moves.range(3, len(operands)): target = operands[opr].target operations.append( Operation(target, "case", value=low + opr - 3)) # TODO: Default might not be the right place for endswitch, # though it seems like default isn't used in any other way # in the normal code. operations.append(Operation(default, "endswitch")) elif mnemonic == "lookupswitch": raise Exception("lookupswitch is not supported") # operations.append(Operation(instruction.pos, "switch", # field=stack.pop())) # for opr in six.moves.range(1, len(operands)): # target = operands[opr].find_target(1) # operations.append(Operation(target, "case", # value=operands[opr].value[0])) # operations.append(Operation(operands[0].target, "endswitch")) elif mnemonic == "goto": target = operands[0].target endif = _PIT.find_next(operations, instruction.pos, "endif") case = _PIT.find_next(operations, instruction.pos, "case") if case is not None and target > case.position: operations.append(Operation(instruction.pos, "break")) elif endif is not None: if target > instruction.pos: endif.operation = "else" operations.append(Operation(target, "endif")) if len(stack) != 0: shortif_pos = target else: endif.operation = "endloop" _PIT.find_next(operations, target, "if").operation = "loop" elif target > instruction.pos: skip_until = target elif mnemonic == "iinc": operations.append( Operation(instruction.pos, "increment", field="var%s" % operands[0], amount=operands[1]))
def args(self): return method_descriptor(self.descriptor.value).args
def process_19(aggregate, jar, verbose): """Processes biomes for Minecraft 1.9""" biomes_base = aggregate.setdefault("biomes", {}) biomes = biomes_base.setdefault("biome", {}) biome_fields = biomes_base.setdefault("biome_fields", {}) superclass = aggregate["classes"]["biome.superclass"] cf = ClassFile(StringIO(jar.read(superclass + ".class"))) method = cf.methods.find_one(returns="V", args="", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static) heights_by_field = {} first_new = True biome = None stack = [] # OK, start running through the initializer for biomes. for ins in method.code.disassemble(): if ins.mnemonic == "anewarray": # End of biome initialization; now creating the list of biomes # for the explore all biomes achievement but we don't need # that info. break if ins.mnemonic == "new": if first_new: # There are two 'new's in biome initialization - the first # one is for the biome generator itself and the second one # is the biome properties. There's some info that is only # stored on the first new (well, actually, beforehand) # that we want to save. const = cf.constants.get(ins.operands[0].value) text_id = stack.pop() numeric_id = stack.pop() biome = { "id": numeric_id, "text_id": text_id, "rainfall": 0.5, "height": [0.1, 0.2], "temperature": 0.5, "class": const.name.value } stack = [] first_new = not(first_new) elif ins.mnemonic == "invokestatic": # Call to the static registration method # We already saved its parameters at the constructor, so we # only need to store the biome now. biomes[biome["text_id"]] = biome elif ins.mnemonic == "invokespecial": # Possibly the constructor for biome properties, which takes # the name as a string. if len(stack) > 0 and not "name" in biome: biome["name"] = stack.pop() stack = [] elif ins.mnemonic == "invokevirtual": const = cf.constants.get(ins.operands[0].value) name = const.name_and_type.name.value desc = method_descriptor(const.name_and_type.descriptor.value) if len(desc.args) == 1: if desc.args[0].name == "float": # Ugly portion - different methods with different names # Hopefully the order doesn't change if name == "a": biome["temperature"] = stack.pop() elif name == "b": biome["rainfall"] = stack.pop() elif name == "c": biome["height"][0] = stack.pop() elif name == "d": biome["height"][1] = stack.pop() elif desc.args[0].name == "java/lang/String": # setBaseBiome biome["mutated_from"] = stack.pop() # numeric values & constants elif ins.mnemonic in ("ldc", "ldc_w"): const = cf.constants.get(ins.operands[0].value) if isinstance(const, ConstantString): stack.append(const.string.value) if isinstance(const, (ConstantInteger, ConstantFloat)): stack.append(const.value) elif ins.opcode <= 8 and ins.opcode >= 2: # iconst stack.append(ins.opcode - 3) elif ins.opcode >= 0xb and ins.opcode <= 0xd: # fconst stack.append(ins.opcode - 0xb) elif ins.mnemonic == "bipush": stack.append(ins.operands[0].value) elif ins.mnemonic == "sipush": stack.append(ins.operands[0].value) # Go through the block list and add the field info. list = aggregate["classes"]["biome.list"] lcf = ClassFile(StringIO(jar.read(list + ".class"))) # Find the static block, and load the fields for each. method = lcf.methods.find_one(name="<clinit>") biome_name = "" for ins in method.code.disassemble(): if ins.mnemonic in ("ldc", "ldc_w"): const = lcf.constants.get(ins.operands[0].value) if isinstance(const, ConstantString): biome_name = const.string.value elif ins.mnemonic == "putstatic": if biome_name is None or biome_name == "Accessed Biomes before Bootstrap!": continue const = lcf.constants.get(ins.operands[0].value) field = const.name_and_type.name.value biomes[biome_name]["field"] = field biome_fields[field] = biome_name
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 _process_1point12(aggregate, classloader, verbose): # Handles versions prior to 1.13 superclass = aggregate["classes"]["block.superclass"] cf = classloader[superclass] is_flattened = aggregate["version"]["is_flattened"] individual_textures = True #aggregate["version"]["protocol"] >= 52 # assume >1.5 http://wiki.vg/Protocol_History#1.5.x since don't read packets TODO if "tile" in aggregate["language"]: language = aggregate["language"]["tile"] elif "block" in aggregate["language"]: language = aggregate["language"]["block"] else: language = None # Find the static block registration method method = cf.methods.find_one(args='', returns="V", f=lambda m: m.access_flags.acc_public and m.access_flags.acc_static) blocks = aggregate.setdefault("blocks", {}) block = blocks.setdefault("block", {}) ordered_blocks = blocks.setdefault("ordered_blocks", []) tmp = [] stack = [] locals = {} for ins in method.code.disassemble(): if ins.mnemonic == "new": # The beginning of a new block definition const = ins.operands[0] class_name = const.name.value current_block = {"class": class_name, "calls": {}} stack.append(current_block) elif ins.mnemonic.startswith("fconst"): stack.append(float(ins.mnemonic[-1])) elif ins.mnemonic == "aconst_null": stack.append(None) elif ins.mnemonic in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins.mnemonic == "fdiv": den = stack.pop() num = stack.pop() if isinstance(den, (float, int)) and isinstance( num, dict) and "scale" in num: num["scale"] /= den stack.append(num) else: stack.append({"numerator": num, "denominator": den}) elif ins.mnemonic in ("ldc", "ldc_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins.mnemonic == "getstatic": const = ins.operands[0] if const.class_.name.value == superclass: # Probably getting the static AIR resource location stack.append("air") else: stack.append({"obj": None, "field": repr(const)}) elif ins.mnemonic == "getfield": const = ins.operands[0] obj = stack.pop() if "text_id" in obj: stack.append({ "block": obj["text_id"], "field": const.name_and_type.name.value, "scale": 1 }) else: stack.append({"obj": obj, "field": repr(const)}) elif ins.mnemonic in ("invokevirtual", "invokespecial", "invokeinterface"): # A method invocation const = ins.operands[0] method_name = const.name_and_type.name.value method_desc = const.name_and_type.descriptor.value desc = method_descriptor(method_desc) num_args = len(desc.args) if method_name == "hasNext": # We've reached the end of block registration # (and have started iterating over registry keys) break args = [] for i in six.moves.range(num_args): args.insert(0, stack.pop()) obj = stack.pop() if "calls" in obj: obj["calls"][method_name + method_desc] = args if desc.returns.name != "void": if desc.returns.name == superclass: stack.append(obj) else: stack.append({ "obj": obj, "method": const, "args": args }) elif ins.mnemonic == "invokestatic": # Call to the registration method const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) num_args = len(desc.args) if num_args == 3: current_block = stack.pop() current_block["text_id"] = stack.pop() current_block["numeric_id"] = stack.pop() else: assert num_args == 2 current_block = stack.pop() current_block["text_id"] = stack.pop() tmp.append(current_block) elif ins.mnemonic == "astore": locals[ins.operands[0].value] = stack.pop() elif ins.mnemonic == "aload": stack.append(locals[ins.operands[0].value]) elif ins.mnemonic == "dup": stack.append(stack[-1]) elif ins.mnemonic == "checkcast": pass elif verbose: print("Unknown instruction %s: stack is %s" % (ins, stack)) # Now that we have all of the blocks, we need a few more things # to make sense of what it all means. So, # 1. Find the function that returns 'this' and accepts a string. # This is the name or texture setting function. # 2. Find the function that returns 'this' and accepts a float. # This is the function that sets the hardness. string_setter = cf.methods.find_one( returns="L" + superclass + ";", args="Ljava/lang/String;", f=lambda x: not x.access_flags.acc_static) if string_setter: name_setter = string_setter.name.value + cf.constants.get( string_setter.descriptor.index).value else: name_setter = None float_setters = list( cf.methods.find(returns="L" + superclass + ";", args="F", f=lambda x: x.access_flags.acc_protected)) for method in float_setters: fld = None for ins in method.code.disassemble(): if ins.mnemonic == "putfield": const = ins.operands[0] fld = const.name_and_type.name.value elif ins.mnemonic == "ifge": hardness_setter = method.name.value + method.descriptor.value hardness_field = fld break for method in float_setters: # Look for the resistance setter, which multiplies by 3. is_resistance = False for ins in method.code.disassemble(): if ins.mnemonic in ("ldc", "ldc_w"): is_resistance = (ins.operands[0].value == 3.0) elif ins.mnemonic == "fmul" and is_resistance: resistance_setter = method.name.value + method.descriptor.value elif ins.mnemonic == "putfield" and is_resistance: const = ins.operands[0] resistance_field = const.name_and_type.name.value break else: is_resistance = False for method in float_setters: # Look for the light setter, which multiplies by 15, but 15 is the first value (15 * val) is_light = False for ins in method.code.disassemble(): if ins.mnemonic in ("ldc", "ldc_w"): is_light = (ins.operands[0].value == 15.0) elif ins.mnemonic.startswith("fload"): pass elif ins.mnemonic == "fmul" and is_light: light_setter = method.name.value + method.descriptor.value break else: is_light = False if is_flattened: # Current IDs are incremental, manually track them cur_id = 0 for blk in tmp: if not "text_id" in blk: if verbose: print("Dropping nameless block:", blk) continue final = {} if "numeric_id" in blk: assert not is_flattened final["numeric_id"] = blk["numeric_id"] else: assert is_flattened final["numeric_id"] = cur_id cur_id += 1 if "text_id" in blk: final["text_id"] = blk["text_id"] final["class"] = blk["class"] if name_setter in blk["calls"]: final["name"] = blk["calls"][name_setter][0] if "name" in final: lang_key = "%s.name" % final["name"] else: # 17w43a (1.13) and above - no specific translation string, only the id lang_key = "minecraft.%s" % final["text_id"] if language and lang_key in language: final["display_name"] = language[lang_key] if hardness_setter not in blk["calls"]: final["hardness"] = 0.0 final["resistance"] = 0.0 else: stack = blk["calls"][hardness_setter] if len(stack) == 0: if verbose: print("%s: Broken hardness value" % final["text_id"]) final["hardness"] = 0.0 final["resistance"] = 0.0 else: hardness = blk["calls"][hardness_setter][0] if isinstance(hardness, dict) and "field" in hardness: # Repair field info assert hardness["field"] == hardness_field assert "block" in hardness assert hardness["block"] in block hardness = block[ hardness["block"]]["hardness"] * hardness["scale"] final["hardness"] = hardness # NOTE: vanilla multiples this value by 5, but then divides by 5 later # Just ignore that multiplication to avoid confusion. final["resistance"] = hardness if resistance_setter in blk["calls"]: resistance = blk["calls"][resistance_setter][0] if isinstance(resistance, dict) and "field" in resistance: # Repair field info assert resistance["field"] == resistance_field assert "block" in resistance assert resistance["block"] in block resistance = block[resistance["block"]][ "resistance"] * resistance["scale"] # The * 3 is also present in vanilla, strange logic # Division to normalize for the multiplication/division by 5. final["resistance"] = resistance * 3.0 / 5.0 # Already set in the hardness area, so no need for an else clause if light_setter in blk["calls"]: final["light"] = int(blk["calls"][light_setter][0] * 15) ordered_blocks.append(final["text_id"]) block[final["text_id"]] = final
def find_field(cls, field_name): """ cls: name of the class field_name: name of the field to find. If None, returns all fields """ if cls in fields_by_class: if field_name is not None: if field_name not in fields_by_class[cls] and verbose: print("Requested field %s.%s but that wasn't found last time" % (cls, field_name)) return fields_by_class[cls][field_name] else: return fields_by_class[cls] elif cls == aggregate["classes"]["sounds.list"]: # Another scary case. We don't want to parse all of the sound events. return object() cf = classloader[cls] fields_by_class[cls] = {} super_name = cf.super_.name.value if not super_name.startswith("java/lang"): # Add fields from superclass fields_by_class[cls].update(find_field(super_name, None)) init = cf.methods.find_one(name="<clinit>") if not init: if field_name is not None: return fields_by_class[cls][field_name] else: return fields_by_class[cls] stack = [] locals = {} # After certain calls, we're no longer storing properties. # But, we still want to assign values for remaining fields; # go through and put None in, only looking at putstatic. ignore_remaining = False for ins in init.code.disassemble(): if ins == "putstatic": const = ins.operands[0] name = const.name_and_type.name.value if ignore_remaining: value = None else: value = stack.pop() if isinstance(value, dict): if "declared_in" not in value: # If there's already a declared_in, this is a field # loaded with getstatic, and we don't want to change # the true location of it value["declared_in"] = cls if value["class"] == plane: # Convert to an instance of Plane # Now is the easiest time to do this, and for # Plane itself it doesn't matter since it's never # used on the stack assert "enum_name" in value assert value["enum_name"] in PLANES value = PLANES[value["enum_name"]] fields_by_class[cls][name] = value elif ignore_remaining: continue elif ins == "getstatic": const = ins.operands[0] target = const.class_.name.value type = field_descriptor(const.name_and_type.descriptor.value).name name = const.name_and_type.name.value if not target.startswith("java/"): stack.append(find_field(target, name)) else: stack.append(object()) elif ins in ("ldc", "ldc_w", "ldc2_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins.mnemonic.startswith("dconst"): stack.append(float(ins.mnemonic[-1])) elif ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins == "aconst_null": stack.append(None) elif ins in ("anewarray", "newarray"): length = stack.pop() stack.append([None] * length) elif ins in ("aaload", "iaload"): index = stack.pop() array = stack.pop() prop = array[index].copy() prop["array_index"] = index stack.append(prop) elif ins in ("aastore", "iastore"): value = stack.pop() index = stack.pop() array = stack.pop() array[index] = value elif ins == "arraylength": array = stack.pop() stack.append(len(array)) elif ins == "dup": stack.append(stack[-1]) elif ins == "invokedynamic": # Try to get the class that's being created const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) stack.append({"dynamic_class": desc.returns.name, "class": cls}) elif ins.mnemonic.startswith("invoke"): const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) num_args = len(desc.args) args = [stack.pop() for _ in six.moves.range(num_args)] args.reverse() if ins == "invokestatic": if const.class_.name.value.startswith("com/google/"): # Call to e.g. Maps.newHashMap, beyond what we # care about ignore_remaining = True continue obj = None else: obj = stack.pop() if desc.returns.name in property_types: prop = { "class": desc.returns.name, "type": property_types[desc.returns.name], "args": args } stack.append(prop) elif const.name_and_type.name == "<init>": if obj["is_enum"]: obj["enum_name"] = args[0] obj["enum_ordinal"] = args[1] else: obj["args"] = args elif const.name_and_type.name == "values": # Enum values fields = find_field(const.class_.name.value, None) stack.append([fld for fld in fields if isinstance(fld, dict) and fld["is_enum"]]) elif desc.returns.name != "void": if isinstance(obj, Plane): # One special case, where EnumFacing.Plane is used # to get a list of directions stack.append(obj.directions) elif (isinstance(obj, dict) and obj["is_enum"] and desc.returns.name == "int"): # Assume it's the enum ordinal, even if it really # isn't stack.append(obj["enum_ordinal"]) else: o = object() stack.append(o) elif ins in ("istore", "lstore", "fstore", "dstore", "astore"): # Store other than array store locals[ins.operands[0].value] = stack.pop() elif ins in ("iload", "lload", "fload", "dload", "aload"): # Load other than array load stack.append(locals[ins.operands[0].value]) elif ins == "new": const = ins.operands[0] type_name = const.name.value obj = { "class": type_name, "is_enum": is_enum(type_name) } stack.append(obj) elif ins == "checkcast": # We don't have type information, so no checking or casting pass elif ins == "return": break elif ins == "if_icmpge": # Code in stairs that loops over state combinations for hitboxes break elif verbose: print("%s initializer contains unimplemented ins %s" % (cls, ins)) if field_name is not None: return fields_by_class[cls][field_name] else: return fields_by_class[cls]
def process_class(name): """ Gets the properties for the given block class, checking the parent class if none are defined. Returns the properties, and also adds them to properties_by_class """ if name in properties_by_class: # Caching - avoid reading the same class multiple times return properties_by_class[name] cf = classloader[name] method = cf.methods.find_one(f=matches) if not method: properties = process_class(cf.super_.name.value) properties_by_class[name] = properties return properties properties = None if_pos = None stack = [] for ins in method.code.disassemble(): # This could _almost_ just be checking for getstatic, but # brewing stands use an array of properties as the field, # so we need some stupid extra logic. if ins == "new": assert not is_18w19a # In 18w19a this should be a parameter const = ins.operands[0] type_name = const.name.value assert type_name == blockstatecontainer stack.append(object()) elif ins == "aload" and ins.operands[0].value == 1: assert is_18w19a # The parameter is only used in 18w19a and above stack.append(object()) elif ins in ("sipush", "bipush"): stack.append(ins.operands[0].value) elif ins in ("anewarray", "newarray"): length = stack.pop() val = [None] * length stack.append(val) elif ins == "getstatic": const = ins.operands[0] prop = { "field_name": const.name_and_type.name.value } desc = field_descriptor(const.name_and_type.descriptor.value) _property_types.add(desc.name) stack.append(prop) elif ins == "aaload": index = stack.pop() array = stack.pop() prop = array.copy() prop["array_index"] = index stack.append(prop) elif ins == "aastore": value = stack.pop() index = stack.pop() array = stack.pop() array[index] = value elif ins == "dup": stack.append(stack[-1]) elif ins == "invokespecial": const = ins.operands[0] assert const.name_and_type.name == "<init>" desc = method_descriptor(const.name_and_type.descriptor.value) assert len(desc.args) == 2 # Normally this constructor call would return nothing, but # in this case we'd rather remove the object it's called on # and keep the properties array (its parameter) arg = stack.pop() stack.pop() # Block stack.pop() # Invocation target stack.append(arg) elif ins == "invokevirtual": # Two possibilities (both only present pre-flattening): # 1. It's isDouble() for a slab. Two different sets of # properties in that case. # 2. It's getTypeProperty() for flowers. Only one # set of properties, but other hacking is needed. # We can differentiate these cases based off of the return # type. # There is a third option post 18w19a: # 3. It's calling the state container's register method. # We can check this just by the type. const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) if const.class_.name == blockstatecontainer: # Case 3. assert properties == None properties = stack.pop() assert desc.returns.name == blockstatecontainer # Don't pop anything, since we'd just pop and re-add the builder elif desc.returns.name == "boolean": # Case 2. properties = [None, None] stack.pop() # Target object # XXX shouldn't something be returned here? else: # Case 1. # Assume that the return type is the base interface # for properties stack.pop() # Target object stack.append(None) elif ins == "ifeq": assert if_pos is None if_pos = ins.pos + ins.operands[0].value elif ins == "pop": stack.pop() elif ins == "areturn": assert not is_18w19a # In 18w19a we don't return a container if if_pos == None: assert properties == None properties = stack.pop() else: assert isinstance(properties, list) index = 0 if ins.pos < if_pos else 1 assert properties[index] == None properties[index] = stack.pop() elif ins == "return": assert is_18w19a # We only return void in 18w19a elif ins == "aload": assert ins.operands[0].value == 0 # Should be aload_0 (this) stack.append(object()) elif verbose: print("%s createBlockState contains unimplemented ins %s" % (name, ins)) if properties is None: # If we never set properties, warn; however, this is normal for # the base implementation in Block in 18w19a if verbose and name != aggregate["classes"]["block.superclass"]: print("Didn't find anything that set properties for %s" % name) properties = [] properties_by_class[name] = properties return properties
def find_field(cls, field_name): """ cls: name of the class field_name: name of the field to find. If None, returns all fields """ if cls in fields_by_class: if field_name is not None: if field_name not in fields_by_class[cls] and verbose: print("Requested field %s.%s but that wasn't found last time" % (cls, field_name)) return fields_by_class[cls][field_name] else: return fields_by_class[cls] elif cls == aggregate["classes"].get("sounds.list"): # If we already know what the sounds list class is, just ignore it # as going through it would take a while for no reason return object() cf = classloader[cls] fields_by_class[cls] = {} super_name = cf.super_.name.value if not super_name.startswith("java/lang"): # Add fields from superclass fields_by_class[cls].update(find_field(super_name, None)) init = cf.methods.find_one(name="<clinit>") if not init: if field_name is not None: return fields_by_class[cls][field_name] else: return fields_by_class[cls] stack = [] locals = {} # After certain calls, we're no longer storing properties. # But, we still want to assign values for remaining fields; # go through and put None in, only looking at putstatic. ignore_remaining = False for ins in init.code.disassemble(): if ins == "putstatic": const = ins.operands[0] name = const.name_and_type.name.value if ignore_remaining: value = None else: value = stack.pop() if isinstance(value, dict): if "declared_in" not in value: # If there's already a declared_in, this is a field # loaded with getstatic, and we don't want to change # the true location of it value["declared_in"] = cls if value["class"] == plane: # Convert to an instance of Plane # Now is the easiest time to do this, and for # Plane itself it doesn't matter since it's never # used on the stack assert "enum_name" in value assert value["enum_name"] in PLANES value = PLANES[value["enum_name"]] fields_by_class[cls][name] = value elif ignore_remaining: continue elif ins == "getstatic": const = ins.operands[0] target = const.class_.name.value type = field_descriptor(const.name_and_type.descriptor.value).name name = const.name_and_type.name.value if not target.startswith("java/"): stack.append(find_field(target, name)) else: stack.append(object()) elif ins in ("ldc", "ldc_w", "ldc2_w"): const = ins.operands[0] if isinstance(const, ConstantClass): stack.append("%s.class" % const.name.value) elif isinstance(const, String): stack.append(const.string.value) else: stack.append(const.value) elif ins.mnemonic.startswith("dconst"): stack.append(float(ins.mnemonic[-1])) elif ins in ("bipush", "sipush"): stack.append(ins.operands[0].value) elif ins == "aconst_null": stack.append(None) elif ins in ("anewarray", "newarray"): length = stack.pop() stack.append([None] * length) elif ins in ("aaload", "iaload"): index = stack.pop() array = stack.pop() prop = array[index].copy() prop["array_index"] = index stack.append(prop) elif ins in ("aastore", "iastore"): value = stack.pop() index = stack.pop() array = stack.pop() array[index] = value elif ins == "arraylength": array = stack.pop() stack.append(len(array)) elif ins == "dup": stack.append(stack[-1]) elif ins == "invokedynamic": # Try to get the class that's being created const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) stack.append({"dynamic_class": desc.returns.name, "class": cls}) elif ins.mnemonic.startswith("invoke"): const = ins.operands[0] desc = method_descriptor(const.name_and_type.descriptor.value) num_args = len(desc.args) args = [stack.pop() for _ in six.moves.range(num_args)] args.reverse() if ins == "invokestatic": if const.class_.name.value.startswith("com/google/"): # Call to e.g. Maps.newHashMap, beyond what we # care about ignore_remaining = True continue obj = None else: obj = stack.pop() if desc.returns.name in property_types: prop = { "class": desc.returns.name, "type": property_types[desc.returns.name], "args": args } stack.append(prop) elif const.name_and_type.name == "<init>": if obj["is_enum"]: obj["enum_name"] = args[0] obj["enum_ordinal"] = args[1] else: obj["args"] = args elif const.name_and_type.name == "values": # Enum values fields = find_field(const.class_.name.value, None) stack.append([fld for fld in fields if isinstance(fld, dict) and fld["is_enum"]]) elif desc.returns.name != "void": if isinstance(obj, Plane): # One special case, where EnumFacing.Plane is used # to get a list of directions stack.append(obj.directions) elif (isinstance(obj, dict) and obj["is_enum"] and desc.returns.name == "int"): # Assume it's the enum ordinal, even if it really # isn't stack.append(obj["enum_ordinal"]) else: o = object() stack.append(o) elif ins in ("istore", "lstore", "fstore", "dstore", "astore"): # Store other than array store locals[ins.operands[0].value] = stack.pop() elif ins in ("iload", "lload", "fload", "dload", "aload"): # Load other than array load stack.append(locals[ins.operands[0].value]) elif ins == "new": const = ins.operands[0] type_name = const.name.value obj = { "class": type_name, "is_enum": is_enum(type_name) } stack.append(obj) elif ins == "checkcast": # We don't have type information, so no checking or casting pass elif ins == "return": break elif ins == "if_icmpge": # Code in stairs that loops over state combinations for hitboxes break elif verbose: print("%s initializer contains unimplemented ins %s" % (cls, ins)) if field_name is not None: return fields_by_class[cls][field_name] else: return fields_by_class[cls]