package org.stianloader.sml6.starplane.autodeobf; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.InnerClassNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.ParameterNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; import de.geolykt.starloader.deobf.LIFOQueue; import de.geolykt.starloader.deobf.MethodReference; import de.geolykt.starloader.deobf.StackElement; import de.geolykt.starloader.deobf.StackWalker; import de.geolykt.starloader.deobf.StackWalker.StackWalkerConsumer; import de.geolykt.starloader.deobf.remapper.ConflicitingMappingException; import de.geolykt.starloader.deobf.remapper.Remapper; import de.geolykt.starloader.deobf.stack.source.AbstractSource; import de.geolykt.starloader.deobf.stack.source.FieldSource; /** * Automatic specialised deobfuscation for galimulator */ public class Autodeobf502 implements StarmappedNames502, AutodeobfRunner { private static final String ACTOR_CLASS = "snoddasmannen/galimulator/actors/Actor"; private static final String ACTOR_CREATOR_CLASS = "snoddasmannen/galimulator/actors/StateActorCreator"; private static final String ALLIANCE_CLASS = "snoddasmannen/galimulator/Alliance"; private static final String AMBIENT_SNOWFLAKE_EFFECT_CLASS = "snoddasmannen/galimulator/effects/AmbientSnowflakeEffect"; private static final String AMBIENT_STAR_EFFECT_CLASS = "snoddasmannen/galimulator/effects/AmbientStarEffect"; private static final String ARTIFACT_CLASS = "snoddasmannen/galimulator/artifacts/Artifact"; private static final String AUDIO_SAMPLE_CLASS = "snoddasmannen/galimulator/AudioManager$AudioSample"; private static final String BITMAP_STAR_GENERATOR_CLASS = "snoddasmannen/galimulator/BitmapStarGenerator"; private static final String DEBUG_CLASS = "snoddasmannen/galimulator/Debug"; private static final String EMPIRE_CLASS = BASE_PACKAGE + "Empire"; private static final String EMPIRE_SPECIAL_CLASS = BASE_PACKAGE + "EmpireSpecial"; private static final String EMPLOYER_CLASS = BASE_PACKAGE + "Employer"; private static final String EMPLOYMENT_AGENCY_CLASS = BASE_PACKAGE + "EmploymentAgency"; private static final String ENUM_SETTINGS_CLASS = "snoddasmannen/galimulator/Settings$EnumSettings"; private static final String FLOW_LAYOUT_CLASS = "snoddasmannen/galimulator/ui/FlowLayout"; private static final String FRACTAL_STAR_GENERATOR_CLASS = "snoddasmannen/galimulator/FractalStarGenerator"; private static final String GALCOLOR_CLASS = BASE_PACKAGE + "GalColor"; private static final String GALFX_CLASS = "snoddasmannen/galimulator/GalFX"; private static final String GALFX_DRAW_NINEPATCH_DESCRIPTOR = "(Lcom/badlogic/gdx/graphics/g2d/NinePatch;IIIILsnoddasmannen/galimulator/GalColor;Lcom/badlogic/gdx/graphics/Camera;)V"; private static final String GALFX_DRAW_TEXT_DESCRIPTOR = "(FFFLcom/badlogic/gdx/math/Vector3;Ljava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;FLcom/badlogic/gdx/graphics/Camera;)F"; private static final String GALFX_DRAW_TEXT_DESCRIPTOR_2 = "(FFFFLjava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;Lcom/badlogic/gdx/graphics/Camera;I)V"; private static final String GALFX_DRAW_TEXTURE_DESCRIPTOR = "(Lcom/badlogic/gdx/graphics/g2d/TextureRegion;DDDDDLsnoddasmannen/galimulator/GalColor;ZLcom/badlogic/gdx/graphics/Camera;)V"; private static final String GDX_CAMERA_CLASS = "com/badlogic/gdx/graphics/Camera"; private static final String GDX_COLOR_CLASS = "com/badlogic/gdx/graphics/Color"; private static final String GDX_GESTURE_LISTENER_CLASS = "com/badlogic/gdx/input/GestureDetector$GestureListener"; private static final String GDX_INPUT_CLASS = "com/badlogic/gdx/Input"; private static final String GDX_INPUT_PROCESSOR_CLASS = "com/badlogic/gdx/InputProcessor"; private static final String GDX_POLYGON_SPRITE = "com/badlogic/gdx/graphics/g2d/PolygonSprite"; private static final String ITEM_CLASS = BASE_PACKAGE + "Item"; private static final String JOB_CLASS = BASE_PACKAGE + "Job"; private static final String LOCATION_SELECTED_EFFECT_CLASS = BASE_PACKAGE + "effects/LocationSelectedEffect"; private static final String MAIN_ENTRYPOINT_CLASS = "com/example/Main"; // I really want to know the story behind this name private static final String MAP_MODE_CLASS = BASE_PACKAGE + "MapMode"; private static final String MAP_MODE_ENUM_CLASS = MAP_MODE_CLASS + "$MapModes"; private static final String MAPDATA_CLASS = "snoddasmannen/galimulator/MapData"; private static final String MATH_UTILS_CLASS = "com/badlogic/gdx/math/MathUtils"; private static final String NINEPATCH_CLASS = "com/badlogic/gdx/graphics/g2d/NinePatch"; private static final String PERSON_CLASS = BASE_PACKAGE + "Person"; private static final String PLAYER_CLASS = "snoddasmannen/galimulator/Player"; private static final String PROCEDURAL_STAR_GENERATOR_CLASS = BASE_PACKAGE + "ProceduralStarGenerator"; private static final String RELIGION_CLASS = "snoddasmannen/galimulator/Religion"; private static final String RENDER_ITEM_CLASS = RENDERSYSTEM_PACKAGE + "RenderItem"; private static final String SETTINGS_TYPE_CLASS = "snoddasmannen/galimulator/UserSettings$SettingType"; private static final String SPACE_CLASS = "snoddasmannen/galimulator/Space"; private static final String SPACE_OPEN_INPUT_DIALOG_DESCRIPTOR = "(Lcom/badlogic/gdx/Input$TextInputListener;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"; private static final String SPRAWL_CLASS = "snoddasmannen/galimulator/Sprawl"; private static final String STAR_CLASS = "snoddasmannen/galimulator/Star"; private static final String STAR_NATIVES_CLASS = "snoddasmannen/galimulator/Native"; private static final String STATE_ACTOR_CLASS = "snoddasmannen/galimulator/actors/StateActor"; private static final String VANITY_HOLDER_CLASS = BASE_PACKAGE + "VanityHolder"; private static final String WAR_CLASS = BASE_PACKAGE + "War"; private static final String WIDGET_CLASS = "snoddasmannen/galimulator/ui/Widget"; private static final String WIDGET_MESSAGE_CLASS = WIDGET_CLASS + "$WIDGET_MESSAGE"; private static final String WIDGET_POSITIONING_CLASS = WIDGET_CLASS + "$WIDGET_POSITIONING"; private static final String WORDLIST_CLASS = BASE_PACKAGE + "WordList"; private static final AbstractInsnNode[] BITMAP_STAR_GENERATOR_GET_RESOURCES_LIST_METHOD_CONTENTS = new AbstractInsnNode[] { new FieldInsnNode(Opcodes.GETSTATIC, "com/badlogic/gdx/Gdx", "files", "Lcom/badlogic/gdx/Files;"), new VarInsnNode(Opcodes.ALOAD, 0), new FieldInsnNode(Opcodes.GETFIELD, BITMAP_STAR_GENERATOR_CLASS, "bitmapFile", "Ljava/lang/String;"), new MethodInsnNode(Opcodes.INVOKEINTERFACE, "com/badlogic/gdx/Files", "internal", "(Ljava/lang/String;)Lcom/badlogic/gdx/files/FileHandle;"), new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "com/badlogic/gdx/files/FileHandle", "file", "()Ljava/io/File;"), new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/File", "getAbsolutePath", "()Ljava/lang/String;"), new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Collections", "singletonList", "(Ljava/lang/Object;)Ljava/util/List;"), new InsnNode(Opcodes.ARETURN) }; @NotNull private final Map enumSettingsMemberNames = new HashMap<>(); @NotNull private final Map name2Node = new HashMap<>(); private final List nodes; private final Remapper remapper; @NotNull private final Map settingsTypeMemberNames = new HashMap<>(); @Nullable private String textInputDialogWidgetClass = null; @Nullable private String spaceLogicalTickMethodName = null; public Autodeobf502(List nodes, Remapper remapper) { this.nodes = nodes; this.remapper = remapper; for (ClassNode node : nodes) { name2Node.put(node.name, node); } } @Override @NotNull public String getVersion() { return "5.0.2"; } private void assignAsAnonymousClass(ClassNode outerClass, ClassNode innerClass, String outerMethod, String outerMethodDesc) { InnerClassNode icn = new InnerClassNode(innerClass.name, outerClass.name, null, 0); outerClass.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerClass.name)); outerClass.innerClasses.add(icn); innerClass.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerClass.name)); innerClass.innerClasses.add(icn); innerClass.outerClass = outerClass.name; innerClass.outerMethod = outerMethod; innerClass.outerMethodDesc = outerMethodDesc; } private void assignAsAnonymousClass(ClassNode outerClass, MethodNode outerMethod, String innerClass) { InnerClassNode icn = new InnerClassNode(innerClass, outerClass.name, null, 0); outerClass.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerClass)); outerClass.innerClasses.add(icn); ClassNode innerNode = name2Node.get(innerClass); if (innerNode == null) { throw new OutdatedDeobfuscatorException("Unknown", innerClass, "*", "Unresolved node"); } innerNode.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerClass)); innerNode.innerClasses.add(icn); innerNode.outerClass = outerClass.name; innerNode.outerMethod = outerMethod.name; innerNode.outerMethodDesc = outerMethod.desc; } private void assignAsInnerClass(ClassNode outerNode, ClassNode innerNode, String name) { InnerClassNode icn = new InnerClassNode(innerNode.name, outerNode.name, name, innerNode.access & ~Opcodes.ACC_SUPER); outerNode.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerNode.name)); outerNode.innerClasses.add(icn); innerNode.innerClasses.removeIf(innerClassNode -> innerClassNode.name.equals(innerNode.name)); innerNode.innerClasses.add(icn); outerNode.outerClass = null; outerNode.outerMethod = null; outerNode.outerMethodDesc = null; } public boolean contentsEqual(MethodNode method, AbstractInsnNode... instructions) { AbstractInsnNode actualInsn = method.instructions.getFirst(); for (AbstractInsnNode expectedInstruction : instructions) { if (expectedInstruction.getOpcode() == -1) { continue; } while (actualInsn != null && actualInsn.getOpcode() == -1) { actualInsn = actualInsn.getNext(); } if (actualInsn == null) { return false; } if (actualInsn.getOpcode() != expectedInstruction.getOpcode()) { return false; } if (expectedInstruction instanceof MethodInsnNode) { MethodInsnNode expected = (MethodInsnNode) expectedInstruction; MethodInsnNode actual = (MethodInsnNode) actualInsn; if (!expected.owner.equals(actual.owner) || !expected.desc.equals(actual.desc) || (!expected.name.equals("*") && !expected.name.equals(actual.name))) { return false; } } else if (expectedInstruction instanceof InsnNode) { // Nothing to compare - opcode was the most important thing of the instruction } else if (expectedInstruction instanceof FieldInsnNode) { FieldInsnNode expected = (FieldInsnNode) expectedInstruction; FieldInsnNode actual = (FieldInsnNode) actualInsn; if (!expected.owner.equals(actual.owner) || !expected.desc.equals(actual.desc) || (!expected.name.equals("*") && !expected.name.equals(actual.name))) { return false; } } else if (expectedInstruction instanceof VarInsnNode) { if (((VarInsnNode) expectedInstruction).var != ((VarInsnNode) actualInsn).var) { return false; } } else { throw new AssertionError("Cannot compare instances of class " + expectedInstruction.getClass().getName()); } actualInsn = actualInsn.getNext(); } return true; } private AbstractInsnNode getNext(AbstractInsnNode insn) { insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } return insn; } private AbstractInsnNode getNextOrNull(AbstractInsnNode insn) { insn = insn.getNext(); while (insn != null && insn.getOpcode() == -1) { insn = insn.getNext(); } return insn; } @SuppressWarnings("unchecked") private T getNext(AbstractInsnNode insn, int matchOpcode) { insn = insn.getNext(); while (insn.getOpcode() != matchOpcode) { insn = insn.getNext(); } return (T) insn; } @SuppressWarnings("all") private T getNextOrNull(AbstractInsnNode insn, int matchOpcode) { if (insn == null) { return (T) null; } insn = insn.getNext(); while (insn != null && insn.getOpcode() != matchOpcode) { insn = insn.getNext(); } return (T) insn; } @SuppressWarnings("all") private T getPreviousOrNull(@Nullable AbstractInsnNode insn, int matchOpcode) { if (insn == null) { return (T) null; } insn = insn.getPrevious(); while (insn != null && insn.getOpcode() != matchOpcode) { insn = insn.getPrevious(); } return (T) insn; } private boolean isGetter(MethodNode method, String fieldOwner, String fieldName, String fieldType, boolean staticField) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (!staticField) { if (insn.getOpcode() != Opcodes.ALOAD) { return false; } VarInsnNode aloadThis = (VarInsnNode) insn; insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (aloadThis.var != 0 || insn.getOpcode() != Opcodes.GETFIELD) { return false; } } else if (insn.getOpcode() != Opcodes.GETSTATIC) { return false; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (!isReturn(insn.getOpcode())) { return false; } if (fieldInsn.owner.equals(fieldOwner) && fieldInsn.name.equals(fieldName) && fieldInsn.desc.equals(fieldType)) { return true; } return false; } private boolean isInstanceofClass(ClassNode node, String type) { if (node == null) { return false; } if (node.name.equals(type)) { return true; } return isInstanceofClass(name2Node.get(node.superName), type); } private boolean isInstanceofInterface(ClassNode node, String type) { if (node == null) { return false; } if (node.name.equals(type)) { return true; } for (String interfaceName : node.interfaces) { if (isInstanceofClass(name2Node.get(interfaceName), type)) { return true; } } if (node.superName != null) { return isInstanceofInterface(name2Node.get(node.superName), type); } return false; } private boolean isInstanceofWidget(ClassNode node) { // Bit more performant than #isInstanceofClass, probably if (node == null) { return false; } if (node.name.equals(WIDGET_CLASS)) { return true; } return isInstanceofWidget(name2Node.get(node.superName)); } private boolean isReturn(int opcode) { switch (opcode) { case Opcodes.RETURN: case Opcodes.ARETURN: case Opcodes.IRETURN: case Opcodes.DRETURN: case Opcodes.FRETURN: case Opcodes.LRETURN: return true; default: return false; } } private boolean isSetter(MethodNode method, String fieldOwner, String fieldName, String fieldType) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (insn.getOpcode() != Opcodes.ALOAD) { return false; } VarInsnNode aloadThis = (VarInsnNode) insn; insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (aloadThis.var != 0 || insn.getOpcode() != Opcodes.ALOAD) { return false; } VarInsnNode aloadOne = (VarInsnNode) insn; insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (aloadOne.var != 1 || insn.getOpcode() != Opcodes.PUTFIELD) { return false; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (insn.getOpcode() != Opcodes.RETURN) { return false; } if (fieldInsn.owner.equals(fieldOwner) && fieldInsn.name.equals(fieldName) && fieldInsn.desc.equals(fieldType)) { return true; } return false; } public void remapActorClasses(Writer mappingsStream) throws IOException { ClassNode spaceNode = name2Node.get(SPACE_CLASS); if (spaceNode == null) { throw new IllegalStateException(SPACE_CLASS + " not present."); } String spawnActorMethod = null; String initializeActorSpawnPredicatesMethod = null; for (MethodNode method : spaceNode.methods) { if (method.desc.equals("(L" + STAR_CLASS + ";)L" + ACTOR_CLASS + ";")) { boolean isSpawnActorMethod = false; AbstractInsnNode extendedBuildActorCall = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.name.equals("extendedBuildActor") || !methodInsn.desc.equals("(L" + STAR_CLASS + ";)L" + ACTOR_CLASS + ";")) { continue; } if (spawnActorMethod != null) { throw new OutdatedDeobfuscatorException("Actor", "Space", "spawnActor", "Collision"); } isSpawnActorMethod = true; spawnActorMethod = method.name; extendedBuildActorCall = insn; remapClass(mappingsStream, methodInsn.owner, EMPIRE_EXTENSION_CLASS); remapMethod(mappingsStream, SPACE_CLASS, method.name, "spawnActor", "(L" + STAR_CLASS + ";)L" + ACTOR_CLASS + ";"); break; } if (isSpawnActorMethod) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(STAR_CLASS) || !methodInsn.desc.equals("()L" + EMPIRE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Actor", "Star", "getOwningEmpire", "Unexpected nature method call"); } remapMethod(mappingsStream, STAR_CLASS, methodInsn.name, "getOwningEmpire", "()L" + EMPIRE_CLASS + ";"); break; } } if (extendedBuildActorCall == null) { throw new NullPointerException(); } AbstractInsnNode nextInsn = extendedBuildActorCall.getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.GETSTATIC) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("Actor", "Space", "actorSpawnPredicates", "Instructions exhausted"); } FieldInsnNode fieldInsn = (FieldInsnNode) nextInsn; if (!fieldInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Actor", "Space", "actorSpawnPredicates", "Unexpected owner"); } if (!fieldInsn.desc.equals("Ljava/util/ArrayList;")) { throw new OutdatedDeobfuscatorException("Actor", "Space", "actorSpawnPredicates", "Unexpected descriptor"); } remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, "actorSpawnPredicates", "Ljava/util/ArrayList;"); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("Actor", "Space", "initializeActorSpawnPredicates", "Instructions exhausted"); } MethodInsnNode methodInsn = (MethodInsnNode) nextInsn; if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Actor", "Space", "initializeActorSpawnPredicates", "Method call does not have the expected owner or descriptor"); } remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, "initializeActorSpawnPredicates", "()V"); initializeActorSpawnPredicatesMethod = methodInsn.name; } } } if (spawnActorMethod == null) { throw new OutdatedDeobfuscatorException("Actor", "Space", "spawnActor"); } if (initializeActorSpawnPredicatesMethod == null) { // A bit impossible here, but let's ignore that throw new OutdatedDeobfuscatorException("Actor", "Space", "initializeActorSpawnPredicates"); } String actorSpawnPredicateClass = null; for (MethodNode method : spaceNode.methods) { if (method.name.equals(initializeActorSpawnPredicatesMethod) && method.desc.equals("()V")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.GETSTATIC) { continue; } insn = insn.getNext(); if (insn == null || insn.getOpcode() != Opcodes.NEW) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "*", "Unexpected opcode."); } actorSpawnPredicateClass = ((TypeInsnNode) insn).desc; break; } break; } } if (actorSpawnPredicateClass == null) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "*", "Unresolved"); } remapClass(mappingsStream, actorSpawnPredicateClass, ACTOR_SPAWNING_PREDICATE_CLASS); ClassNode actorSpawnPredicateNode = name2Node.get(actorSpawnPredicateClass); if (actorSpawnPredicateNode == null) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "*", "Node unresolved"); } // Line number nodes point to ActorSpawningPredicate being a local class of the Space class // This explains the access modifiers of the class so we are going to replicate this relationship { InnerClassNode icn = new InnerClassNode(actorSpawnPredicateClass, SPACE_CLASS, "ActorSpawningPredicate", Opcodes.ACC_STATIC); String actorSpawningPredicateClassFinal = actorSpawnPredicateClass; // Lambdas are nice! spaceNode.innerClasses.removeIf(inner -> inner.name.equals(actorSpawningPredicateClassFinal)); spaceNode.innerClasses.add(icn); actorSpawnPredicateNode.innerClasses.removeIf(inner -> inner.name.equals(actorSpawningPredicateClassFinal)); actorSpawnPredicateNode.innerClasses.add(icn); actorSpawnPredicateNode.outerClass = SPACE_CLASS; boolean religionRequirement = false; boolean actorFactory = false; boolean spawningChance = false; boolean specialRequirements = false; for (FieldNode field : actorSpawnPredicateNode.fields) { // We are going to cheat and just base our guess on the descriptor of the field. // In case this does not work due to some future change the deobfuscator internals are going to crash anyways if (field.desc.equals("F")) { remapField(mappingsStream, actorSpawnPredicateClass, field.name, "spawningChance", "F"); if (spawningChance) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "spawningChance", "Collision"); } spawningChance = true; } else if (field.desc.equals("L" + RELIGION_CLASS + ";")) { remapField(mappingsStream, actorSpawnPredicateClass, field.name, "religionRequirement", "L" + RELIGION_CLASS + ";"); if (religionRequirement) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "religionRequirement", "Collision"); } religionRequirement = true; } else if (field.desc.equals("L" + ACTOR_CREATOR_CLASS + ";")) { remapField(mappingsStream, actorSpawnPredicateClass, field.name, "actorFactory", "L" + ACTOR_CREATOR_CLASS + ";"); if (actorFactory) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "actorFactory", "Collision"); } actorFactory = true; } else if (field.desc.equals("Ljava/util/List;")) { remapField(mappingsStream, actorSpawnPredicateClass, field.name, "specialRequirements", "Ljava/util/List;"); if (specialRequirements) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "specialRequirements", "Collision"); } specialRequirements = true; field.signature = "Ljava/util/List;"; } else { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, field.name, "Unmatched field descriptor: " + field.desc); } } if (!religionRequirement || !actorFactory || !spawningChance || !specialRequirements) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "?", "At least one field is missing"); } boolean testMethodFound = false; for (MethodNode method : actorSpawnPredicateNode.methods) { if (method.desc.equals("(L" + STAR_CLASS + ";)Z")) { if (testMethodFound) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "test", "Collision"); } remapMethod(mappingsStream, actorSpawnPredicateClass, method.name, "test", "(L" + STAR_CLASS + ";)Z"); testMethodFound = true; } else if (method.name.equals("") && method.desc.equals("(L" + ACTOR_CREATOR_CLASS + ";FLjava/util/List;L" + RELIGION_CLASS + ";)V")) { method.signature = "(L" + ACTOR_CREATOR_CLASS + ";FLjava/util/List;L" + RELIGION_CLASS + ";)V"; } } if (!testMethodFound) { throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "test", "Method not found"); } } String landmarkManagerClass = null; String regenerateLandmarksMethod = null; nodeLoop: for (ClassNode node : this.nodes) { if (node.name.startsWith(GUIDES_PACKAGE)) { for (MethodNode method : node.methods) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Picking landmarks")) { landmarkManagerClass = node.name; regenerateLandmarksMethod = method.name; if (!method.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Guides", LANDMARK_MANAGER_CLASS, "regenerateLandmarks", "Unexpected descriptor"); } continue nodeLoop; } } } } } if (landmarkManagerClass == null) { throw new OutdatedDeobfuscatorException("Guides", LANDMARK_MANAGER_CLASS, "*", "Unresolved"); } remapClass(mappingsStream, landmarkManagerClass, LANDMARK_MANAGER_CLASS); remapMethod(mappingsStream, landmarkManagerClass, regenerateLandmarksMethod, "regenerateLandmarks", "()V"); } private void remapClass(Writer mappingsOut, @NotNull String oldName, @NotNull String newName) throws IOException { remapper.remapClassName(oldName, newName); mappingsOut.write("CLASS "); mappingsOut.write(oldName); mappingsOut.write(' '); mappingsOut.write(newName); mappingsOut.write('\n'); } protected void remapDialogClasses(Writer mappingsStream, final @NotNull String settingsDialogClass) throws IOException { if (settingsTypeMemberNames.isEmpty()) { resolveEnumMemberNames(SETTINGS_TYPE_CLASS, settingsTypeMemberNames); } remapClass(mappingsStream, settingsDialogClass, SETTINGS_DIALOG_CLASS); ClassNode settingsDialog = name2Node.get(settingsDialogClass); if (settingsDialog == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CLASS, "*"); } if (settingsDialog.interfaces.size() != 1) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CLASS, "*", "unexpected amount of interfaces"); } remapClass(mappingsStream, settingsDialog.interfaces.get(0), DIALOG_INTERFACE); String blacklistButtonClass = null; String dialogButtonClass = null; String dialogButtonOnTouchMethod = null; String dialogComponentInterface = null; String dialogPackage = null; String labeledCheckboxComponent = null; String labeledStringChooserComponent = null; for (MethodNode method : settingsDialog.methods) { if (method.name.equals("getItems") && method.desc.equals("()Ljava/util/ArrayList;")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Simulation")) { AbstractInsnNode nextInsn = getNext(ldcInsn); if (nextInsn.getOpcode() != Opcodes.INVOKESPECIAL) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON_CLASS, "*", "Unexpected opcode"); } MethodInsnNode methodInsn = (MethodInsnNode) nextInsn; if (!methodInsn.name.equals("")) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON_CLASS, "*", "Logic error"); } blacklistButtonClass = methodInsn.owner; remapClass(mappingsStream, blacklistButtonClass, SETTINGS_DIALOG_BLACKLIST_BUTTON_CLASS); ClassNode blacklistButton = name2Node.get(blacklistButtonClass); if (blacklistButton == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON_CLASS, "*", "Unresolved node"); } assignAsAnonymousClass(settingsDialog, method, blacklistButtonClass); dialogButtonClass = blacklistButton.superName; remapClass(mappingsStream, dialogButtonClass, DIALOG_BUTTON_CLASS); ClassNode dialogButton = name2Node.get(dialogButtonClass); if (dialogButton == null) { throw new OutdatedDeobfuscatorException("Dialog", DIALOG_BUTTON_CLASS, "*", "Unresolved node"); } if (dialogButton.interfaces.size() != 1) { throw new OutdatedDeobfuscatorException("Dialog", DIALOG_BUTTON_CLASS, "*", "Unexpected amount of interfaces"); } dialogComponentInterface = dialogButton.interfaces.get(0); remapClass(mappingsStream, dialogComponentInterface, DIALOG_COMPONENT_INTERFACE); int packageNameLength = dialogButtonClass.lastIndexOf('/') + 1; dialogPackage = dialogButtonClass.substring(0, packageNameLength); String itemsSignature = "Ljava/util/ArrayList;"; String getItemsSignature = "()" + itemsSignature; method.signature = getItemsSignature; ClassNode dialogNode = name2Node.get(settingsDialog.interfaces.get(0)); boolean foundDialogGetItemsMethod = false; for (MethodNode method2 : dialogNode.methods) { if (method2.name.equals("getItems") && method2.desc.equals("()Ljava/util/ArrayList;")) { method2.signature = getItemsSignature; foundDialogGetItemsMethod = true; break; } } if (!foundDialogGetItemsMethod) { throw new OutdatedDeobfuscatorException("Dialog", DIALOG_INTERFACE, "getItems", "methods exhausted"); } AbstractInsnNode lastInsn = method.instructions.getLast(); while (lastInsn.getOpcode() == -1 || lastInsn.getOpcode() == Opcodes.ARETURN) { lastInsn = lastInsn.getPrevious(); } if (lastInsn.getOpcode() != Opcodes.ALOAD) { throw new OutdatedDeobfuscatorException("Dialog", "Unepexcted opcode"); } VarInsnNode loadItemsInsn = (VarInsnNode) lastInsn; if (method.localVariables.isEmpty()) { // CFR and Procyon are a bit strange and guess the signature of the items variable completely wrong // That is why we are going to aid them there // Problem is that CFR will now have serious issues with void declarations, but CFR is pretty bad // with dealing with out remapping shenanigans anyways, so not a big issue. // Link to related bug: https://github.com/leibnitz27/cfr/issues/150 String lvnDesc = "Ljava/java/ArrayList;"; AbstractInsnNode startLabelInsn = method.instructions.getFirst(); while (!(startLabelInsn instanceof LabelNode)) { startLabelInsn = startLabelInsn.getNext(); } AbstractInsnNode endLabelInsn = method.instructions.getLast(); while (!(endLabelInsn instanceof LabelNode)) { endLabelInsn = endLabelInsn.getPrevious(); } LabelNode startLabel = (LabelNode) startLabelInsn; LabelNode endLabel = (LabelNode) endLabelInsn; LocalVariableNode lvn = new LocalVariableNode("items", lvnDesc, itemsSignature, startLabel, endLabel, loadItemsInsn.var); method.localVariables.add(lvn); } for (MethodNode method2 : dialogButton.methods) { if (method2.desc.equals("()V") && (method2.access & Opcodes.ACC_ABSTRACT) != 0) { if (dialogButtonOnTouchMethod != null) { throw new OutdatedDeobfuscatorException("Dialog", DIALOG_BUTTON_CLASS, "onTouch", "Collision"); } dialogButtonOnTouchMethod = method2.name; } } } } else if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(SETTINGS_TYPE_CLASS) && settingsTypeMemberNames.getOrDefault(fieldInsn.name, "").equals("BOOLEAN")) { if (labeledCheckboxComponent != null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CHECKBOX_CLASS, "*", "Collision"); } AbstractInsnNode previousInsn = fieldInsn.getPrevious(); if (previousInsn.getOpcode() != Opcodes.GETFIELD) { throw new OutdatedDeobfuscatorException("Dialog", USER_SETTING_ENTRY_CLASS, "*", "Unexpected opcode"); } FieldInsnNode getTypeInsn = (FieldInsnNode) previousInsn; if (!getTypeInsn.desc.equals("L" + SETTINGS_TYPE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Dialog", USER_SETTING_ENTRY_CLASS, "type", "Unexpected descriptor"); } remapField(mappingsStream, getTypeInsn.owner, getTypeInsn.name, "type", "L" + SETTINGS_TYPE_CLASS + ";"); remapClass(mappingsStream, getTypeInsn.owner, USER_SETTING_ENTRY_CLASS); AbstractInsnNode nextInsn = fieldInsn.getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.NEW) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CHECKBOX_CLASS, "*", "Instructions exhausted"); } TypeInsnNode newInsn = (TypeInsnNode) nextInsn; String settingsCheckbox = newInsn.desc; remapClass(mappingsStream, settingsCheckbox, SETTINGS_DIALOG_CHECKBOX_CLASS); ClassNode settingsCheckboxNode = name2Node.get(settingsCheckbox); if (settingsCheckboxNode == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CHECKBOX_CLASS, "*", "Unresolved node"); } labeledCheckboxComponent = Objects.requireNonNull(settingsCheckboxNode.superName, settingsCheckboxNode.name + " without superclass"); remapClass(mappingsStream, labeledCheckboxComponent, LABELED_CHECKBOX_COMPONENT); assignAsAnonymousClass(settingsDialog, method, settingsCheckbox); } else if (fieldInsn.owner.equals(SETTINGS_TYPE_CLASS) && settingsTypeMemberNames.getOrDefault(fieldInsn.name, "").equals("STRING")) { AbstractInsnNode nextInsn = fieldInsn.getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.NEW) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_STRING_CHOOSER_CLASS, "*", "Instructions exhausted"); } TypeInsnNode newInsn = (TypeInsnNode) nextInsn; ClassNode stringChooserSubclassNode = name2Node.get(newInsn.desc); if (stringChooserSubclassNode == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_STRING_CHOOSER_CLASS, "*", "Unresolved node"); } labeledStringChooserComponent = stringChooserSubclassNode.superName; if (labeledStringChooserComponent == null) { throw new IllegalStateException(stringChooserSubclassNode.name + " without superclass"); } remapClass(mappingsStream, labeledStringChooserComponent, LABELED_STRING_CHOOSER_COMPONENT); remapClass(mappingsStream, newInsn.desc, SETTINGS_DIALOG_STRING_CHOOSER_CLASS); assignAsAnonymousClass(settingsDialog, method, newInsn.desc); } } } break; } } if (blacklistButtonClass == null) { throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON_CLASS, "*", "Unresolved"); } if (dialogButtonOnTouchMethod == null) { throw new OutdatedDeobfuscatorException("Dialog", DIALOG_BUTTON_CLASS, "onTouch", "Unresolved"); } if (dialogPackage == null) { throw new OutdatedDeobfuscatorException("Dialog", "Package \"" + DIALOG_PACKAGE + "\" not resolved."); } if (labeledCheckboxComponent == null) { throw new OutdatedDeobfuscatorException("Dialog", LABELED_CHECKBOX_COMPONENT, "*", "Unresolved"); } if (labeledStringChooserComponent == null) { throw new OutdatedDeobfuscatorException("Dialog", LABELED_STRING_CHOOSER_COMPONENT, "*", "Unresolved"); } Set forbiddenClasses = new HashSet<>(); forbiddenClasses.add(dialogButtonClass); forbiddenClasses.add(dialogComponentInterface); forbiddenClasses.add(labeledCheckboxComponent); forbiddenClasses.add(labeledStringChooserComponent); mappingsStream.write("# Begin dialog package relocation\n"); int packageNameLength = dialogPackage.length(); for (ClassNode node : nodes) { if (node.name.startsWith(dialogPackage) && !forbiddenClasses.contains(node.name)) { remapClass(mappingsStream, node.name, DIALOG_PACKAGE + node.name.substring(packageNameLength)); } } mappingsStream.write("# End dialog package relocation\n"); for (ClassNode node : nodes) { if (isInstanceofClass(node, dialogButtonClass)) { remapMethod(mappingsStream, node.name, dialogButtonOnTouchMethod, "onTouch", "()V"); } } } /** * Remaps the methods and fields in the Empire class. * It does not need to be run after slIntermediary. * * @param mappingsStream The stream to write the mapping to * @throws IOException If some generic IO Exception occurred (most likely because it failed writing to the mappings stream) */ public void remapEmpireClass(Writer mappingsStream) throws IOException { if (enumSettingsMemberNames.isEmpty()) { resolveEnumMemberNames(ENUM_SETTINGS_CLASS, enumSettingsMemberNames); } String shipCapacityModifierEnum = enumSettingsMemberNames.get("SHIP_NUMBER_MOD"); if (shipCapacityModifierEnum == null) { throw new OutdatedDeobfuscatorException("Empire", "EnumSettings", "SHIP_NUMBER_MOD"); } ClassNode empireNode = name2Node.get(EMPIRE_CLASS); if (empireNode == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "*"); } String getFlagItemsMethod = null; String getShipCapacityMethod = null; String tickEmpireMethod = null; String recentlyLostStarsField = null; String beaconsField = null; String allianceField = null; String getAllianceMethod = null; String setAllianceMethod = null; String getRelationsMethod = null; String vassalizeMethod = null; for (MethodNode method : empireNode.methods) { if (method.desc.equals("()Ljava/util/Vector;")) { AbstractInsnNode insn = getNext(method.instructions.getFirst()); if (insn.getOpcode() != Opcodes.ALOAD) { continue; } VarInsnNode aloadThis = (VarInsnNode) insn; insn = getNext(insn); if (insn.getOpcode() != Opcodes.GETFIELD || aloadThis.var != 0) { continue; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; insn = getNext(insn); if (insn.getOpcode() != Opcodes.IFNONNULL || !fieldInsn.owner.equals(EMPIRE_CLASS) || !fieldInsn.desc.equals("Ljava/util/Vector;") || !fieldInsn.name.equals("flagItems")) { continue; } if (getFlagItemsMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getFlagItems", "Multiple candidates"); } getFlagItemsMethod = method.name; } else if (method.desc.equals("()D")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.name.equals(shipCapacityModifierEnum) && fieldInsn.owner.equals(ENUM_SETTINGS_CLASS) && fieldInsn.desc.equals("L" + ENUM_SETTINGS_CLASS + ";")) { if (getShipCapacityMethod != null && !getShipCapacityMethod.equals(method.name)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getCurrentShipCapacity", "Multiple candidates"); } getShipCapacityMethod = method.name; } } } } else if (method.desc.equals("()V")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Have now exterminated the heathens!")) { if (tickEmpireMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "tickEmpire", "Collision"); } tickEmpireMethod = method.name; break; } } } } else if (method.desc.equals("(Ljava/io/ObjectInputStream;)V")) { if (method.name.equals("readObject")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.name.equals("")) { insn = insn.getNext(); continue; } if (methodInsn.owner.equals("java/util/ArrayDeque")) { if (recentlyLostStarsField != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "recentlyLostStars", "Collision"); } FieldInsnNode fieldInsn = (FieldInsnNode) insn.getNext(); if (!fieldInsn.desc.equals("Ljava/util/Deque;")) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "recentlyLostStars", "Invalid descriptor"); } if (!fieldInsn.owner.equals(EMPIRE_CLASS)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "recentlyLostStars", "Invalid owner class"); } recentlyLostStarsField = fieldInsn.name; } else if (methodInsn.owner.equals("java/util/ArrayList")) { if (beaconsField != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "beacons", "Collision"); } FieldInsnNode fieldInsn = (FieldInsnNode) insn.getNext(); if (!fieldInsn.desc.equals("Ljava/util/ArrayList;")) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "beacons", "Invalid descriptor"); } if (!fieldInsn.owner.equals(EMPIRE_CLASS)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "beacons", "Invalid owner class"); } beaconsField = fieldInsn.name; } } insn = insn.getNext(); } } } else if (method.desc.equals("()L" + ALLIANCE_CLASS + ";")) { AbstractInsnNode insn = method.instructions.getFirst(); insn = getNext(insn); if (insn.getOpcode() != Opcodes.ALOAD || ((VarInsnNode) insn).var != 0) { continue; } insn = getNext(insn); if (insn.getOpcode() != Opcodes.GETFIELD) { continue; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (getNext(insn).getOpcode() != Opcodes.ARETURN) { continue; } if (!fieldInsn.desc.equals("L" + ALLIANCE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "alliance", "Nonsensical descriptor"); } if (!fieldInsn.owner.equals(EMPIRE_CLASS)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "alliance", "Invalid owner"); } if (allianceField == null) { allianceField = fieldInsn.name; } else if (!allianceField.equals(fieldInsn.name)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "alliance", "Clash"); } if (getAllianceMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getAlliance", "Collision"); } getAllianceMethod = method.name; } else if (method.desc.equals("(L" + ALLIANCE_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.PUTFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.desc.equals("L" + ALLIANCE_CLASS + ";") && fieldInsn.owner.equals(EMPIRE_CLASS)) { if (allianceField == null) { allianceField = fieldInsn.name; } else if (!allianceField.equals(fieldInsn.name)) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "alliance", "Clash"); } if (setAllianceMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "setAlliance", "Collision"); } setAllianceMethod = method.name; break; } } insn = insn.getNext(); } } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";)Ljava/lang/String;")) { AbstractInsnNode insn = method.instructions.getFirst(); LdcInsnNode ldcInsn = getNextOrNull(insn, Opcodes.LDC); if (ldcInsn != null && ldcInsn.cst.equals("NO CONTACT")) { if (getRelationsMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getRelations", "Collision"); } getRelationsMethod = method.name; MethodInsnNode methodInsn = getPreviousOrNull(ldcInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "hasContact", "Invalid owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "hasContact", "(L" + EMPIRE_CLASS + ";)Z"); methodInsn = getNext(ldcInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()L" + EMPIRE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "getMaster", "Invalid owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "getMaster", "()L" + EMPIRE_CLASS + ";"); do { ldcInsn = getNext(ldcInsn, Opcodes.LDC); } while (!ldcInsn.cst.equals("ALLIED")); methodInsn = getPreviousOrNull(ldcInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "isAllied", "Invalid owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "isAllied", "(L" + EMPIRE_CLASS + ";)Z"); methodInsn = getNext(ldcInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "isAtPeace", "Invalid owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "isAtPeace", "(L" + EMPIRE_CLASS + ";)Z"); } } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); LdcInsnNode ldcInsn = getNextOrNull(insn, Opcodes.LDC); if (ldcInsn != null && ldcInsn.cst.equals("Has been vassalized by ")) { if (vassalizeMethod != null) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "vassalize", "Collision"); } vassalizeMethod = method.name; } } } if (getFlagItemsMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getFlagItems"); } if (getShipCapacityMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getCurrentShipCapacity"); } if (tickEmpireMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "tickEmpire", "Unresolved"); } if (recentlyLostStarsField == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "recentlyLostStars", "Unresolved"); } if (beaconsField == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "beacons", "Unresolved"); } if (allianceField == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "alliance", "Unresolved"); } if (getAllianceMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getAlliance", "Unresolved"); } if (setAllianceMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "setAlliance", "Unresolved"); } if (getRelationsMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "getRelations", "Unresolved"); } if (vassalizeMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "vassalize", "Unresolved"); } remapField(mappingsStream, EMPIRE_CLASS, recentlyLostStarsField, "recentlyLostStars", "Ljava/util/Deque;"); remapField(mappingsStream, EMPIRE_CLASS, beaconsField, "beacons", "Ljava/util/ArrayList;"); remapField(mappingsStream, EMPIRE_CLASS, allianceField, "alliance", "L" + ALLIANCE_CLASS + ";"); remapMethod(mappingsStream, EMPIRE_CLASS, getAllianceMethod, "getAlliance", "()L" + ALLIANCE_CLASS + ";"); remapMethod(mappingsStream, EMPIRE_CLASS, setAllianceMethod, "setAlliance", "(L" + ALLIANCE_CLASS + ";)V"); remapMethod(mappingsStream, EMPIRE_CLASS, getRelationsMethod, "getRelations", "(L" + EMPIRE_CLASS + ";)Ljava/lang/String;"); remapMethod(mappingsStream, EMPIRE_CLASS, vassalizeMethod, "vassalize", "(L" + EMPIRE_CLASS + ";)V"); String flagOwnerInterface = null; for (String itf : empireNode.interfaces) { ClassNode node = name2Node.get(itf); if (node == null) { continue; } for (MethodNode method : node.methods) { if (method.name.equals(getFlagItemsMethod) && method.desc.equals("()Ljava/util/Vector;")) { if (flagOwnerInterface != null) { throw new OutdatedDeobfuscatorException(FLAG_OWNER_INTERFACE, "Collision"); } flagOwnerInterface = node.name; break; } } } if (flagOwnerInterface == null) { throw new OutdatedDeobfuscatorException(FLAG_OWNER_INTERFACE, "Unresolved"); } remapClass(mappingsStream, flagOwnerInterface, FLAG_OWNER_INTERFACE); for (ClassNode node : nodes) { if (node.interfaces.contains(flagOwnerInterface)) { remapMethod(mappingsStream, node.name, getFlagItemsMethod, "getFlagItems", "()Ljava/util/Vector;"); } } remapMethod(mappingsStream, flagOwnerInterface, getFlagItemsMethod, "getFlagItems", "()Ljava/util/Vector;"); remapMethod(mappingsStream, EMPIRE_CLASS, getShipCapacityMethod, "getCurrentShipCapacity", "()D"); remapMethod(mappingsStream, EMPIRE_CLASS, tickEmpireMethod, "tickEmpire", "()V"); String internalSessionRandomField = null; for (FieldNode field : empireNode.fields) { if ((field.access & Opcodes.ACC_TRANSIENT) != 0 && field.desc.equals("Ljava/util/Random;")) { if (internalSessionRandomField != null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "internalSessionRandom", "Collision"); } internalSessionRandomField = field.name; } } if (internalSessionRandomField == null) { throw new OutdatedDeobfuscatorException("Empire", "Empire", "internalSessionRandom", "Unresolved"); } remapField(mappingsStream, EMPIRE_CLASS, internalSessionRandomField, "internalSessionRandom", "Ljava/util/Random;"); String starTickMethod = null; String canBeVassalizedByMethod = null; String canVassalizeMethod = null; ClassNode starNode = name2Node.get(STAR_CLASS); for (MethodNode method : starNode.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); LdcInsnNode ldcInsn = getNextOrNull(insn, Opcodes.LDC); while (ldcInsn != null && !ldcInsn.cst.equals("Diplomatic master stroke")) { ldcInsn = getNextOrNull(ldcInsn, Opcodes.LDC); } if (ldcInsn != null) { if (starTickMethod != null) { throw new OutdatedDeobfuscatorException("Empire", "Star", "tick", "Collision"); } starTickMethod = method.name; MethodInsnNode methodInsn = getNext(ldcInsn, Opcodes.INVOKESPECIAL); if (!methodInsn.owner.equals("snoddasmannen/galimulator/relationships/LinearDropRelMod")) { throw new OutdatedDeobfuscatorException("Empire", "DiploymacyActor", "*", "Unexpected owner"); } remapClass(mappingsStream, methodInsn.desc.substring(2, methodInsn.desc.indexOf(';')), BASE_PACKAGE + "relationships/DiploymacyActor"); methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals("snoddasmannen/galimulator/relationships/RelManager")) { throw new OutdatedDeobfuscatorException("Empire", "DiploymacyActor", "addModifier", "Unexpected owner"); } remapMethod(mappingsStream, "snoddasmannen/galimulator/relationships/RelManager", methodInsn.name, "addModifier", methodInsn.desc); methodInsn = getNext(getNext(methodInsn, Opcodes.INVOKEVIRTUAL), Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "canBeVassalizedBy", "Unexpected owner or descriptor"); } canBeVassalizedByMethod = methodInsn.name; remapMethod(mappingsStream, EMPIRE_CLASS, canBeVassalizedByMethod, "canBeVassalizedBy", "(L" + EMPIRE_CLASS + ";)Z"); methodInsn = getNext(getNext(methodInsn, Opcodes.INVOKEVIRTUAL), Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "canVassalize", "Unexpected owner or descriptor"); } canVassalizeMethod = methodInsn.name; remapMethod(mappingsStream, EMPIRE_CLASS, canVassalizeMethod, "canVassalize", "(L" + EMPIRE_CLASS + ";)Z"); } } } if (starTickMethod == null) { throw new OutdatedDeobfuscatorException("Empire", "Star", "tick", "Unresolved"); } for (MethodNode method : empireNode.methods) { if (method.desc.equals("(L" + EMPIRE_CLASS + ";)Z")) { if (method.name.equals(canVassalizeMethod)) { MethodInsnNode methodInsn = getNext(method.instructions.getFirst(), Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "isNotDegenerating", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "isNotDegenerating", "()Z"); methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Empire", EMPIRE_CLASS, "isAvoidingAlliances", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "isAvoidingAlliances", "()Z"); } } } String mottoGeneratorClass = null; nodeloop: for (ClassNode node : nodes) { for (MethodNode method : node.methods) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("data/mottopreps.txt")) { if (mottoGeneratorClass != null) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "*", "Collision"); } mottoGeneratorClass = node.name; remapClass(mappingsStream, node.name, MOTTO_GENERATOR_CLASS); remapMethod(mappingsStream, node.name, method.name, "initialize", method.desc); FieldInsnNode finsn = getNext(insn, Opcodes.PUTSTATIC); if (!finsn.owner.equals(node.name) || !finsn.desc.equals("L" + WORDLIST_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "prepositions", "Unexpected owner or descriptor"); } remapField(mappingsStream, node.name, finsn.name, "prepositions", "L" + WORDLIST_CLASS + ";"); finsn = getNext(finsn, Opcodes.PUTSTATIC); if (!finsn.owner.equals(node.name) || !finsn.desc.equals("L" + WORDLIST_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "nouns", "Unexpected owner or descriptor"); } remapField(mappingsStream, node.name, finsn.name, "nouns", "L" + WORDLIST_CLASS + ";"); if (getNextOrNull(finsn, Opcodes.PUTSTATIC) != null) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "nouns", "Unexpected trailing PUTSTATIC call"); } MethodNode candidate = null; for (MethodNode method2 : node.methods) { if (method2.desc.equals("()Ljava/lang/String;")) { if (candidate != null) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "generateMotto", "Collision"); } candidate = method2; } } if (candidate == null) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "generateMotto", "Not found"); } remapMethod(mappingsStream, node.name, candidate.name, "generateMotto", "()Ljava/lang/String;"); insn = candidate.instructions.getFirst(); MethodInsnNode minsn = getNext(insn, Opcodes.INVOKEVIRTUAL); if (!minsn.owner.equals(VANITY_HOLDER_CLASS) || !minsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Empire", VANITY_HOLDER_CLASS, "hasMotto", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, VANITY_HOLDER_CLASS, minsn.name, "hasMotto", "()Z"); minsn = getNext(minsn, Opcodes.INVOKEVIRTUAL); if (!minsn.owner.equals(VANITY_HOLDER_CLASS) || !minsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Empire", VANITY_HOLDER_CLASS, "getMotto", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, VANITY_HOLDER_CLASS, minsn.name, "getMotto", "()Ljava/lang/String;"); minsn = getNext(minsn, Opcodes.INVOKEVIRTUAL); if (!minsn.owner.equals(WORDLIST_CLASS) || !minsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Empire", WORDLIST_CLASS, "getRandomWord", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, WORDLIST_CLASS, minsn.name, "getRandomWord", "()Ljava/lang/String;"); continue nodeloop; } } } } if (mottoGeneratorClass == null) { throw new OutdatedDeobfuscatorException("Empire", MOTTO_GENERATOR_CLASS, "*", "Not found"); } } public void remapEmploymentAgency(Writer mappingsStream) throws IOException { ClassNode employmentAgency = name2Node.get(EMPLOYMENT_AGENCY_CLASS); ClassNode jobNode = name2Node.get(JOB_CLASS); if (employmentAgency == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "*", "Node not found"); } if (jobNode == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "*", "Node not found"); } String employmentInstance = null; for (FieldNode field : employmentAgency.fields) { if (field.desc.equals("L" + EMPLOYMENT_AGENCY_CLASS + ";") && (field.access & Opcodes.ACC_STATIC) != 0) { if (employmentInstance != null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "instance", "Collision"); } employmentInstance = field.name; } } if (employmentInstance == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "instance", "Not found"); } remapField(mappingsStream, EMPLOYMENT_AGENCY_CLASS, employmentInstance, "instance", "L" + EMPLOYMENT_AGENCY_CLASS + ";"); String setInstanceMethod = null; String getInstanceMethod = null; for (MethodNode method : employmentAgency.methods) { if ((method.access & Opcodes.ACC_STATIC) != 0) { if (method.desc.equals("(L" + EMPLOYMENT_AGENCY_CLASS + ";)V")) { if (setInstanceMethod != null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "setInstance", "Collision"); } setInstanceMethod = method.name; } else if (method.desc.equals("()L" + EMPLOYMENT_AGENCY_CLASS + ";")) { if (getInstanceMethod != null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "getInstance", "Collision"); } getInstanceMethod = method.name; } } } if (setInstanceMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "setInstance", "Not found"); } if (getInstanceMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "getInstance", "Not found"); } remapMethod(mappingsStream, EMPLOYMENT_AGENCY_CLASS, setInstanceMethod, "setInstance", "(L" + EMPLOYMENT_AGENCY_CLASS + ";)V"); remapMethod(mappingsStream, EMPLOYMENT_AGENCY_CLASS, getInstanceMethod, "getInstance", "()L" + EMPLOYMENT_AGENCY_CLASS + ";"); String tickJobMethod = null; String vacateJobMethod = null; String isVacatedMethod = null; for (MethodNode method : jobNode.methods) { if (!method.desc.equals("()V") || (method.access & Opcodes.ACC_PUBLIC) == 0) { continue; } AbstractInsnNode insn = method.instructions.getLast(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Job hazard")) { break; } } insn = insn.getPrevious(); } if (insn == null) { continue; } if (tickJobMethod != null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "tick", "Collision"); } tickJobMethod = method.name; insn = getNext(insn); if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "vacate", "Unexpected opcode"); } MethodInsnNode vacateInsn = (MethodInsnNode) insn; if (!vacateInsn.desc.equals("(Ljava/lang/String;)V") || !vacateInsn.owner.equals(JOB_CLASS)) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "vacate", "Unexpected call specifics"); } vacateJobMethod = vacateInsn.name; insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.desc.equals("()Z") && methodInsn.owner.equals(JOB_CLASS)) { isVacatedMethod = methodInsn.name; break; } } insn = insn.getNext(); } } if (tickJobMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "tick", "Not found"); } if (vacateJobMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "vacate", "Not found"); } if (isVacatedMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "isVacated", "Not found"); } remapMethod(mappingsStream, JOB_CLASS, tickJobMethod, "tick", "()V"); remapMethod(mappingsStream, JOB_CLASS, vacateJobMethod, "vacate", "(Ljava/lang/String;)V"); remapMethod(mappingsStream, JOB_CLASS, isVacatedMethod, "isVacated", "()Z"); String getCurrentHolder = null; for (MethodNode method : jobNode.methods) { if (method.name.equals(isVacatedMethod) && method.desc.equals("()Z")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { insn = insn.getNext(); } if (insn == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "getCurrentHolder", "Instructions exhausted"); } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(JOB_CLASS) || !methodInsn.desc.equals("()L" + PERSON_CLASS + ";")) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "getCurrentHolder", "Unexpected call specifics"); } getCurrentHolder = methodInsn.name; break; } } if (getCurrentHolder == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "getCurrentHolder", "Not found"); } remapMethod(mappingsStream, JOB_CLASS, getCurrentHolder, "getCurrentHolder", "()L" + PERSON_CLASS + ";"); String currentHolder = null; String previousHolder = null; exteriorLoop: for (MethodNode method : jobNode.methods) { if (method.name.equals(getCurrentHolder) && method.desc.equals("()L" + PERSON_CLASS + ";")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && insn.getOpcode() != Opcodes.GETFIELD) { insn = insn.getNext(); } if (insn == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "currentHolder", "Instructions exhausted"); } final FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.desc.equals("L" + PERSON_CLASS + ";") || !fieldInsn.owner.equals(JOB_CLASS)) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "currentHolder", "Unexpected GETFIELD instruction"); } currentHolder = fieldInsn.name; while (insn != null) { if (insn.getOpcode() == Opcodes.PUTFIELD) { FieldInsnNode var10001 = (FieldInsnNode) insn; if (var10001.desc.equals("L" + PERSON_CLASS + ";") && !var10001.name.equals(fieldInsn.name)) { previousHolder = var10001.name; break exteriorLoop; } } insn = insn.getNext(); } throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "previousHolder", "Instructions exhausted"); } } if (currentHolder == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "currentHolder", "Not found"); } if (previousHolder == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "previousHolder", "Not found"); } remapField(mappingsStream, JOB_CLASS, currentHolder, "currentHolder", "L" + PERSON_CLASS + ";"); remapField(mappingsStream, JOB_CLASS, previousHolder, "previousHolder", "L" + PERSON_CLASS + ";"); String getPreviousHolder = null; for (MethodNode method : jobNode.methods) { if (!method.desc.equals("()L" + PERSON_CLASS + ";") || (method.access & Opcodes.ACC_PUBLIC) == 0) { continue; } if (isGetter(method, JOB_CLASS, previousHolder, "L" + PERSON_CLASS + ";", false)) { getPreviousHolder = method.name; break; } } if (getPreviousHolder == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", JOB_CLASS, "getPreviousHolder", "Not found"); } remapMethod(mappingsStream, JOB_CLASS, getPreviousHolder, "getPreviousHolder", "()L" + PERSON_CLASS + ";"); String tickAgencyMethod = null; exteriorLoop2: for (MethodNode method : employmentAgency.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(JOB_CLASS) && methodInsn.desc.equals("()V") && methodInsn.name.equals(tickJobMethod)) { tickAgencyMethod = method.name; break exteriorLoop2; } } insn = insn.getNext(); } } } if (tickAgencyMethod == null) { throw new OutdatedDeobfuscatorException("EmploymentAgency", EMPLOYMENT_AGENCY_CLASS, "tick", "Instructions exhausted"); } remapMethod(mappingsStream, EMPLOYMENT_AGENCY_CLASS, tickAgencyMethod, "tick", "()V"); } private void remapField(Writer mappingsOut, String owner, String oldName, String newName, String desc) throws IOException { remapper.remapField(owner, desc, oldName, newName); // Format: FIELD owner descriptor originalName newName mappingsOut.write("FIELD " + owner + " " + desc + " " + oldName + " " + newName + "\n"); } /** * Remap field, methods and Classes related to galaxy generation. * * @param mappingsStream Where to save the mapped values to. The stream will be in the tiny format * @throws IOException Exception that is raised during writes to the stream */ public void remapGalaxyGeneration(Writer mappingsStream) throws IOException { ClassNode space = name2Node.get(SPACE_CLASS); if (space == null) { throw new IllegalStateException("Class not present: " + SPACE_CLASS); } String generateGalaxyMethod = null; String setBackgroundTaskDescriptionMethod = null; for (MethodNode method : space.methods) { if (method.desc.equals("(IL" + MAPDATA_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && insn.getOpcode() != Opcodes.LDC) { insn = insn.getNext(); } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn == null || !ldcInsn.cst.equals("Generating galaxy")) { continue; } if (generateGalaxyMethod != null) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "generateGalaxy", "Collision"); } generateGalaxyMethod = method.name; insn = ldcInsn.getNext(); if (insn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("GalaxyGen", DEBUG_CLASS, "startDebuggingSection", "Unexpected opcode"); } MethodInsnNode startDebuggingSectionInsn = (MethodInsnNode) insn; if (!startDebuggingSectionInsn.owner.equals(DEBUG_CLASS) || !startDebuggingSectionInsn.desc.equals("(Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("GalaxyGen", DEBUG_CLASS, "startDebuggingSection", "Unexpected specifics about method call"); } remapMethod(mappingsStream, DEBUG_CLASS, startDebuggingSectionInsn.name, "startDebuggingSection", "(Ljava/lang/String;)V"); while (insn != null && insn.getOpcode() != Opcodes.LDC) { insn = insn.getNext(); } if (insn == null || (insn = insn.getNext()).getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "setBackgroundTaskDescription", "Unexpected opcode"); } MethodInsnNode setBackgroundTaskDescInsn = (MethodInsnNode) insn; if (!setBackgroundTaskDescInsn.desc.equals("(Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "setBackgroundTaskDescription", "Unexpected descriptor"); } remapMethod(mappingsStream, SPACE_CLASS, setBackgroundTaskDescInsn.name, "setBackgroundTaskDescription", "(Ljava/lang/String;)V"); setBackgroundTaskDescriptionMethod = setBackgroundTaskDescInsn.name; } } if (setBackgroundTaskDescriptionMethod == null) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "generateGalaxy", "Unresolved"); } remapMethod(mappingsStream, SPACE_CLASS, generateGalaxyMethod, "generateGalaxy", "(IL" + MAPDATA_CLASS + ";)V"); String backgroundTaskDescription = null; for (MethodNode method : space.methods) { if (method.desc.equals("(Ljava/lang/String;)V") && method.name.equals(setBackgroundTaskDescriptionMethod)) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() != Opcodes.PUTSTATIC) { insn = insn.getNext(); } AbstractInsnNode prev = insn.getPrevious(); if (prev.getOpcode() != Opcodes.ALOAD) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "backgroundTaskDescription", "Unexpected opcode (prev)"); } VarInsnNode varInsn = (VarInsnNode) prev; if (varInsn.var != 0) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "backgroundTaskDescription", "Unexpected operand"); } if (insn.getOpcode() != Opcodes.PUTSTATIC) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "backgroundTaskDescription", "Unexpected opcode (insn)"); } FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "backgroundTaskDescription", "Unexpected owner"); } backgroundTaskDescription = fieldInsn.name; } } if (backgroundTaskDescription == null) { throw new OutdatedDeobfuscatorException("GalaxyGen", SPACE_CLASS, "backgroundTaskDescription", "Unresolved"); } remapField(mappingsStream, SPACE_CLASS, backgroundTaskDescription, "backgroundTaskDescription", "Ljava/lang/String;"); } public void remapGenerators(Writer mappingsStream) throws IOException { ClassNode bitmapGenClass = name2Node.get(BITMAP_STAR_GENERATOR_CLASS); if (bitmapGenClass == null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "*", "Not found"); } if (bitmapGenClass.interfaces.size() != 2) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "*", "Not found (1240)"); } String starGeneratorClass = bitmapGenClass.interfaces.get(0); if (!starGeneratorClass.startsWith(BASE_PACKAGE)) { starGeneratorClass = bitmapGenClass.interfaces.get(1); if (!starGeneratorClass.startsWith(BASE_PACKAGE)) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "*", "Not found (1246)"); } } String generateStarMethod = null; String getResourceListMethod = null; String getMaxYMethod = null; String setupSettingsMethod = null; for (MethodNode method : bitmapGenClass.methods) { if (method.desc.equals("()L" + STAR_CLASS + ";")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Invalid bits file for map: ")) { if (generateStarMethod != null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "generateStar", "Collision"); } generateStarMethod = method.name; } } } } else if (method.desc.equals("()Ljava/util/List;")) { if (contentsEqual(method, BITMAP_STAR_GENERATOR_GET_RESOURCES_LIST_METHOD_CONTENTS)) { if (getResourceListMethod != null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getResources", "Collision"); } getResourceListMethod = method.name; } } else if (method.desc.equals("()F")) { AbstractInsnNode insn = method.instructions.getLast(); while (insn.getOpcode() == -1) { insn = insn.getPrevious(); } insn = insn.getPrevious(); while (insn.getOpcode() == -1) { insn = insn.getPrevious(); } if (insn.getOpcode() == Opcodes.GETFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(BITMAP_STAR_GENERATOR_CLASS) && fieldInsn.desc.equals("F") && fieldInsn.name.equals("yMaxCache")) { if (getMaxYMethod != null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getMaxY", "Collision"); } getMaxYMethod = method.name; continue; } } } else if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(ENUM_SETTINGS_CLASS)) { if (setupSettingsMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "setupSettings", "Collision"); } setupSettingsMethod = method.name; } } } } if (generateStarMethod == null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "generateStar", "Unresolved"); } if (getResourceListMethod == null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getResources", "Not resolved"); } if (getMaxYMethod == null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getMaxY", "Not resolved"); } if (setupSettingsMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "setupSettings", "Unresolved"); } String getMaxXMethod = null; for (MethodNode method : bitmapGenClass.methods) { if (method.desc.equals("()F")) { if (contentsEqual(method, new VarInsnNode(Opcodes.ALOAD, 0), new MethodInsnNode(Opcodes.INVOKEVIRTUAL, BITMAP_STAR_GENERATOR_CLASS, getMaxYMethod, "()F"), new FieldInsnNode(Opcodes.GETSTATIC, SPACE_CLASS, "*", "F"), new InsnNode(Opcodes.FMUL), new InsnNode(Opcodes.FRETURN))) { if (getMaxXMethod != null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getMaxX", "Collision"); } getMaxXMethod = method.name; } } } if (getMaxXMethod == null) { throw new OutdatedDeobfuscatorException("Generator", BITMAP_STAR_GENERATOR_CLASS, "getMaxX", "Not resolved"); } ClassNode spaceNode = name2Node.get(SPACE_CLASS); if (spaceNode == null) { throw new AssertionError(); } String spaceGetMaxXMethod = null; String spaceGetMaxYMethod = null; for (MethodNode method : spaceNode.methods) { if (method.desc.equals("()F")) { AbstractInsnNode firstNode = method.instructions.getFirst(); while (firstNode.getOpcode() == -1) { firstNode = firstNode.getNext(); } if (firstNode.getOpcode() != Opcodes.GETSTATIC) { continue; } FieldInsnNode fieldInsn = (FieldInsnNode) firstNode; if (!fieldInsn.owner.equals(SPACE_CLASS) || !fieldInsn.desc.equals("L" + MAPDATA_CLASS + ";")) { continue; } AbstractInsnNode insn = fieldInsn.getNext(); if (insn.getOpcode() != Opcodes.IFNULL) { continue; } insn = getNext(insn); if (insn.getOpcode() != Opcodes.GETSTATIC) { continue; } fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(SPACE_CLASS) || !fieldInsn.desc.equals("L" + MAPDATA_CLASS + ";")) { continue; } insn = getNext(insn); if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { continue; } MethodInsnNode getGenInsn = (MethodInsnNode) insn; if (!getGenInsn.owner.equals(MAPDATA_CLASS)) { continue; } insn = getNext(insn); if (insn.getOpcode() != Opcodes.INVOKEINTERFACE) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.desc.equals("()F")) { continue; } if (methodInsn.name.equals(getMaxXMethod)) { if (spaceGetMaxXMethod != null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "getMaxX", "Collision"); } spaceGetMaxXMethod = method.name; } else if (methodInsn.name.equals(getMaxYMethod)) { if (spaceGetMaxYMethod != null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "getMaxY", "Collision"); } spaceGetMaxYMethod = method.name; } else { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "getMaxX", "Logic error"); } } } if (spaceGetMaxXMethod == null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "getMaxX", "Unresolved"); } if (spaceGetMaxYMethod == null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "getMaxY", "Unresolved"); } String spaceMaxXCacheField = null; String spaceMaxYCacheField = null; String prepareGeneratorMethod = null; for (MethodNode method : spaceNode.methods) { if (method.desc.equals("(IL" + MAPDATA_CLASS + ";)V")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.INVOKESTATIC) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.desc.equals("()F")) { continue; } if (insn.getNext().getOpcode() != Opcodes.PUTSTATIC) { continue; } FieldInsnNode field = (FieldInsnNode) insn.getNext(); if (!field.owner.equals(SPACE_CLASS)) { continue; } if (methodInsn.name.equals(spaceGetMaxXMethod)) { if (spaceMaxXCacheField != null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "maxXCache", "Collision"); } spaceMaxXCacheField = field.name; AbstractInsnNode previous = methodInsn.getPrevious(); while (previous.getOpcode() == -1) { previous = previous.getPrevious(); } if (previous.getOpcode() != Opcodes.INVOKEINTERFACE) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "prepareGenerator", "Unexpected opcode"); } MethodInsnNode previousMethodInsn = (MethodInsnNode) previous; if (!previousMethodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "prepareGenerator", "Unexpected opcode"); } if (prepareGeneratorMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "prepareGenerator", "Collision"); } prepareGeneratorMethod = previousMethodInsn.name; } else if (methodInsn.name.equals(spaceGetMaxYMethod)) { if (spaceMaxYCacheField != null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "maxYCache", "Collision"); } spaceMaxYCacheField = field.name; } } } } if (spaceMaxXCacheField == null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "maxXCache", "Unresolved"); } if (spaceMaxYCacheField == null) { throw new OutdatedDeobfuscatorException("Generator", SPACE_CLASS, "maxYCache", "Unresolved"); } if (prepareGeneratorMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "prepareGenerator", "Unresolved"); } remapMethod(mappingsStream, SPACE_CLASS, spaceGetMaxXMethod, "getMaxX", "()F"); remapMethod(mappingsStream, SPACE_CLASS, spaceGetMaxYMethod, "getMaxY", "()F"); remapField(mappingsStream, SPACE_CLASS, spaceMaxXCacheField, "maxXCache", "F"); remapField(mappingsStream, SPACE_CLASS, spaceMaxYCacheField, "maxYCache", "F"); ClassNode fractalStarGenerator = name2Node.get(FRACTAL_STAR_GENERATOR_CLASS); if (fractalStarGenerator == null) { throw new OutdatedDeobfuscatorException("Generator", FRACTAL_STAR_GENERATOR_CLASS, "*", "Missing"); } String getEngravingTextMethod = null; String onLoadMethod = null; String getBackgroundTextureMethod = null; for (MethodNode method : fractalStarGenerator.methods) { if (method.desc.equals("()Ljava/lang/String;")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Seed: ")) { if (getEngravingTextMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getEngravingText", "Collision"); } getEngravingTextMethod = method.name; break; } } } } else if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } insn = getNext(insn); if (insn.getOpcode() == Opcodes.NEW) { TypeInsnNode typeInsn = (TypeInsnNode) insn; if (typeInsn.desc.equals("java/util/BitSet")) { if (onLoadMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "onLoad", "Collision (1520)"); } for (MethodNode method2 : fractalStarGenerator.methods) { if (!method2.desc.equals("()V")) { continue; } if (contentsEqual(method2, new VarInsnNode(Opcodes.ALOAD, 0), new MethodInsnNode(Opcodes.INVOKEVIRTUAL, FRACTAL_STAR_GENERATOR_CLASS, method.name, "()V"), new InsnNode(Opcodes.RETURN))) { if (onLoadMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "onLoad", "Collision (1530)"); } onLoadMethod = method2.name; } } } } } else if (method.desc.equals("()Lcom/badlogic/gdx/graphics/Texture;")) { if ((method.access & Opcodes.ACC_PUBLIC) == 0) { continue; } if (getBackgroundTextureMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getBackgroundTexture", "Collision"); } getBackgroundTextureMethod = method.name; } } if (getEngravingTextMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getEngravingText", "Not found"); } if (onLoadMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "onLoad", "Not found"); } if (getBackgroundTextureMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getBackgroundTexture", "Not found"); } String getSettingsDialogMethod = null; String getSettingsDialogDesc = null; String hasMovingStarsMethod = null; for (ClassNode node : nodes) { if (node.name.startsWith(PROCEDURAL_STAR_GENERATOR_CLASS + "$")) { boolean movingSpiral = false; for (FieldNode field : node.fields) { if (field.desc.equals("F") && field.name.equals("undulation")) { movingSpiral = true; break; } } for (MethodNode method : node.methods) { if (method.desc.startsWith("()L")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Planet count")) { if (getSettingsDialogMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getSettingsDialog", "Collision"); } getSettingsDialogMethod = method.name; getSettingsDialogDesc = method.desc; } } } } else if (movingSpiral && method.desc.equals("()Z")) { if (hasMovingStarsMethod != null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "hasMovingStars", "Collision"); } hasMovingStarsMethod = method.name; } } } } if (getSettingsDialogMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "getSettingsDialog", "Not found"); } if (hasMovingStarsMethod == null) { throw new OutdatedDeobfuscatorException("Generator", STAR_GENERATOR_INTERFACE, "hasMovingStars", "Not found"); } for (ClassNode node : nodes) { if (isInstanceofInterface(node, starGeneratorClass)) { remapMethod(mappingsStream, node.name, generateStarMethod, "generateStar", "()L" + STAR_CLASS + ";"); remapMethod(mappingsStream, node.name, getResourceListMethod, "getResources", "()Ljava/util/List;"); remapMethod(mappingsStream, node.name, getMaxXMethod, "getMaxX", "()F"); remapMethod(mappingsStream, node.name, getMaxYMethod, "getMaxY", "()F"); remapMethod(mappingsStream, node.name, prepareGeneratorMethod, "prepareGenerator", "()V"); remapMethod(mappingsStream, node.name, getEngravingTextMethod, "getEngravingText", "()Ljava/lang/String;"); remapMethod(mappingsStream, node.name, getSettingsDialogMethod, "getSettingsDialog", getSettingsDialogDesc); remapMethod(mappingsStream, node.name, hasMovingStarsMethod, "hasMovingStars", "()Z"); remapMethod(mappingsStream, node.name, setupSettingsMethod, "setupSettings", "()V"); remapMethod(mappingsStream, node.name, onLoadMethod, "onLoad", "()V"); remapMethod(mappingsStream, node.name, getBackgroundTextureMethod, "getBackgroundTexture", "()Lcom/badlogic/gdx/graphics/Texture;"); } } } /* * Remaps Hotkey/Keybind-related classes. * This method does not need to be run after intermediary. */ public void remapHotkeys(Writer mappingsStream) throws IOException { ClassNode mainClass = this.name2Node.get(MAIN_ENTRYPOINT_CLASS); if (mainClass == null) { throw new OutdatedDeobfuscatorException("Hotkey", "Cannot find " + MAIN_ENTRYPOINT_CLASS); } String aShortcutClass = null; scope10001: for (MethodNode method : mainClass.methods) { if (method.desc.equals("()V") && method.name.equals("setupShortcuts")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.NEW) { TypeInsnNode newInsn = (TypeInsnNode) insn; aShortcutClass = newInsn.desc; break scope10001; } } throw new OutdatedDeobfuscatorException("Hotkey", "Hotkey", "*"); } } if (aShortcutClass == null) { throw new OutdatedDeobfuscatorException("Hotkey", MAIN_ENTRYPOINT_CLASS, "setupShortcuts"); } ClassNode extendsShortcut = this.name2Node.get(aShortcutClass); if (extendsShortcut == null) { throw new OutdatedDeobfuscatorException("Hotkey", "Cannot find " + aShortcutClass); } remapClass(mappingsStream, extendsShortcut.superName, BASE_PACKAGE + "Shortcut"); String aboutWidgetClass = null; String showShortcutListButtonClass = null; for (ClassNode node : this.nodes) { if (node.name.startsWith(UI_PACKAGE)) { for (MethodNode method : node.methods) { if (!method.name.equals("")) { continue; } boolean flag = false; for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.LDC) { continue; } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("about")) { flag = true; } else if (flag && ldcInsn.cst.equals("{$1}")) { aboutWidgetClass = node.name; while (insn != null) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Keyboard shortcuts")) { break; } insn = insn.getNext(); } TypeInsnNode typeInsn = getPreviousOrNull(insn, Opcodes.NEW); if (typeInsn == null) { throw new OutdatedDeobfuscatorException("Hotkey", SHOW_SHORTCUT_LIST_BUTTON_CLASS, "*", insn == null ? "Prerequisite missing" : "No such NEW insn"); } showShortcutListButtonClass = typeInsn.desc; break; } else { break; } } } } else if (node.name.startsWith(MAIN_ENTRYPOINT_CLASS + "$") && node.superName.equals(extendsShortcut.superName)) { } } if (aboutWidgetClass == null) { throw new OutdatedDeobfuscatorException("Hotkey", ABOUT_WIDGET_CLASS, "*", "Unresolved"); } if (showShortcutListButtonClass == null) { throw new OutdatedDeobfuscatorException("Hotkey", SHOW_SHORTCUT_LIST_BUTTON_CLASS, "*", "Unresolved"); } remapClass(mappingsStream, aboutWidgetClass, ABOUT_WIDGET_CLASS); remapClass(mappingsStream, showShortcutListButtonClass, SHOW_SHORTCUT_LIST_BUTTON_CLASS); ClassNode showShortcutListButtonNode = this.name2Node.get(showShortcutListButtonClass); this.assignAsInnerClass(this.name2Node.get(aboutWidgetClass), showShortcutListButtonNode, SHOW_SHORTCUT_LIST_BUTTON_CLASS.substring(ABOUT_WIDGET_CLASS.length() + 1)); MethodNode showShortcutListMethod = null; for (MethodNode method : showShortcutListButtonNode.methods) { if (method.name.codePointAt(0) == '<') { continue; } if (showShortcutListMethod != null) { throw new OutdatedDeobfuscatorException("Hotkey", SHOW_SHORTCUT_LIST_BUTTON_CLASS, "onMouseDown", "Collision"); } showShortcutListMethod = method; } if (showShortcutListMethod == null) { throw new OutdatedDeobfuscatorException("Hotkey", SHOW_SHORTCUT_LIST_BUTTON_CLASS, "onMouseDown", "Not found"); } ClassNode shortcutListWidgetClass = this.name2Node.get(((TypeInsnNode) getNext(showShortcutListMethod.instructions.getFirst())).desc); remapClass(mappingsStream, shortcutListWidgetClass.name, SHORTCUT_LIST_WIDGET_CLASS); } public void remapMapModes(Writer mappingsOut) throws IOException { ClassNode mapModeNode = name2Node.get(MAP_MODE_CLASS); if (mapModeNode == null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "*", "Cannot resolve node"); } String mapModeField = null; for (FieldNode field : mapModeNode.fields) { if (field.desc.equals("L" + MAP_MODE_ENUM_CLASS + ";")) { if (mapModeField != null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "currentMode", "Collision"); } mapModeField = field.name; remapField(mappingsOut, MAP_MODE_CLASS, mapModeField, "currentMode", "L" + MAP_MODE_ENUM_CLASS + ";"); } } if (mapModeField == null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "currentMode", "Undefined"); } String rotateMapModeMethod = null; String setMapModeMethod = null; String getMapModeMethod = null; for (MethodNode method : mapModeNode.methods) { if (method.desc.equals("()L" + MAP_MODE_ENUM_CLASS + ";")) { if (isGetter(method, MAP_MODE_CLASS, mapModeField, "L" + MAP_MODE_ENUM_CLASS + ";", true)) { if (getMapModeMethod != null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "getCurrentMode", "Collision"); } getMapModeMethod = method.name; remapMethod(mappingsOut, MAP_MODE_CLASS, getMapModeMethod, "getCurrentMode", "()L" + MAP_MODE_ENUM_CLASS + ";"); } } else if (method.desc.equals("(L" + MAP_MODE_ENUM_CLASS + ";)V")) { if (setMapModeMethod != null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "setCurrentMode", "Collision"); } setMapModeMethod = method.name; remapMethod(mappingsOut, MAP_MODE_CLASS, setMapModeMethod, "setCurrentMode", "(L" + MAP_MODE_ENUM_CLASS + ";)V"); } else if (method.desc.equals("()V") && method.name.codePointAt(0) != '<') { if (rotateMapModeMethod != null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "rotateCurrentMode", "Collision"); } rotateMapModeMethod = method.name; remapMethod(mappingsOut, MAP_MODE_CLASS, rotateMapModeMethod, "rotateCurrentMode", "()V"); } } if (getMapModeMethod == null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "getCurrentMode", "Unresolved"); } if (rotateMapModeMethod == null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "rotateCurrentMode", "Unresolved"); } if (setMapModeMethod == null) { throw new OutdatedDeobfuscatorException("MapMode", MAP_MODE_CLASS, "setCurrentMode", "Unresolved"); } ClassNode starNode = name2Node.get(STAR_CLASS); if (starNode == null) { throw new OutdatedDeobfuscatorException("MapMode", STAR_CLASS, "*", "Cannot resolve node"); } MethodNode renderRegionsMethod = null; for (MethodNode method : starNode.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(GDX_POLYGON_SPRITE) && methodInsn.desc.equals("(L" + GDX_COLOR_CLASS + ";)V") && methodInsn.name.equals("setColor")) { if (renderRegionsMethod != null) { throw new OutdatedDeobfuscatorException("MapMode", STAR_CLASS, "renderRegion", "Collision"); } renderRegionsMethod = method; remapMethod(mappingsOut, STAR_CLASS, method.name, "renderRegion", "()V"); break; } } insn = insn.getNext(); } } } if (renderRegionsMethod == null) { throw new OutdatedDeobfuscatorException("MapMode", STAR_CLASS, "renderRegion", "Unresolved"); } String starRenderingRegionField; { String[] fieldName = new String[1]; StackWalker.walkStack(starNode, renderRegionsMethod, new StackWalkerConsumer() { private boolean search = true; @Override public void preCalculation(AbstractInsnNode instruction, LIFOQueue stack) { if (search && instruction.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) instruction; if (methodInsn.owner.equals(GDX_POLYGON_SPRITE) && methodInsn.desc.equals("(L" + GDX_COLOR_CLASS + ";)V") && methodInsn.name.equals("setColor")) { AbstractSource src = stack.getDelegateList().get(1).source; if (!(src instanceof FieldSource)) { throw new IllegalStateException("Stack walker was unable to capture source of stack element"); } FieldInsnNode source = ((FieldSource) src).getInsn(); if (!source.owner.equals(STAR_CLASS)) { throw new OutdatedDeobfuscatorException("MapMode", "Source of stack element is not the expected class"); } if (!source.desc.equals("L" + GDX_POLYGON_SPRITE + ";")) { throw new OutdatedDeobfuscatorException("MapMode", "Source of stack element does not have the expected signature"); } fieldName[0] = source.name; search = false; } } } @Override public void postCalculation(AbstractInsnNode instruction, LIFOQueue stack) { // Unneeded } }); starRenderingRegionField = fieldName[0]; if (starRenderingRegionField == null) { throw new OutdatedDeobfuscatorException("MapMode", STAR_CLASS, "starRenderingRegion", "Unresolved"); } remapField(mappingsOut, STAR_CLASS, starRenderingRegionField, "starRenderingRegion", "L" + GDX_POLYGON_SPRITE + ";"); } } private void remapMethod(Writer mappingsOut, String owner, String oldName, String newName, String desc) throws IOException { if (oldName.equals(newName)) { throw new IllegalStateException("old name is equal to new name. Operation is a bit nonsensical"); } if (owner == null) { throw new NullPointerException("owner is null"); } if (desc == null) { throw new NullPointerException("desc is null"); } try { this.remapper.remapMethod(owner, desc, oldName, newName); // Format (for valid tiny files): METHOD owner desc srcName dstName mappingsOut.write("METHOD " + owner + " " + desc + " " + oldName + " " + newName + "\n"); } catch (ConflicitingMappingException e) { try { throw new RuntimeException("Old mapping: " + this.remapper.getRemappedClassName(owner) + "." + this.remapper.getRemappedMethodName(owner, oldName, desc) + this.remapper.getRemappedMethodDescriptor(desc, new StringBuilder()) + ". Proposed: " + this.remapper.getRemappedClassName(owner) + "." + newName + desc, e); } catch (RuntimeException e2) { e2.printStackTrace(); } } } public void remapNoiseGenerators(Writer mappingsString) throws IOException { ClassNode fractalStarGenerator = name2Node.get(FRACTAL_STAR_GENERATOR_CLASS); if (fractalStarGenerator == null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "*", "Node not present"); } String simplexMethod = null; String perlinMethod = null; String offsetMethod = null; String generateMapMethod = null; String perlinGeneratorClass = null; String getPerlinNoiseMethod = null; for (MethodNode method : fractalStarGenerator.methods) { if ((method.access & Opcodes.ACC_PRIVATE) != 0 && method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && insn.getOpcode() != Opcodes.LDC) { insn = insn.getNext(); } if (insn == null) { continue; } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("FSG: generateSimplexBits")) { if (simplexMethod != null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateSimplex", "Collision"); } simplexMethod = method.name; } else if (ldcInsn.cst.equals("FSG: generateOffsetBits")) { if (offsetMethod != null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateOffset", "Collision"); } offsetMethod = method.name; } else if (ldcInsn.cst.equals("FSG: generatePerlinBits")) { if (perlinMethod != null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generatePerlin", "Collision"); } perlinMethod = method.name; while (insn != null && insn.getOpcode() != Opcodes.BIPUSH) { insn = insn.getNext(); } if (insn == null) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "Instructions exhausted (cannot find BIPUSH)"); } IntInsnNode biPushInsn = (IntInsnNode) insn; if (biPushInsn.operand != 10) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "BIPUSH has unexpected operand"); } insn = insn.getNext(); if (insn == null || insn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "Unexpected insn after BIPUSH"); } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.desc.equals("(III)[[F")) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "Unexpected "); } getPerlinNoiseMethod = methodInsn.name; perlinGeneratorClass = methodInsn.owner; } } else if ((method.access & Opcodes.ACC_PUBLIC) != 0 && method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && insn.getOpcode() != Opcodes.LDC) { insn = insn.getNext(); } if (insn == null) { continue; } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("FSG: generateMap called")) { if (generateMapMethod != null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateMap", "Collision"); } generateMapMethod = method.name; } } } if (simplexMethod == null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateSimplex", "Unresolved"); } if (perlinMethod == null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generatePerlin", "Unresolved"); } if (offsetMethod == null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateOffset", "Unresolved"); } if (generateMapMethod == null) { throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateMap", "Unresolved"); } if (perlinGeneratorClass == null) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "Unresolved"); } remapMethod(mappingsString, FRACTAL_STAR_GENERATOR_CLASS, simplexMethod, "generateSimplex", "()V"); remapMethod(mappingsString, FRACTAL_STAR_GENERATOR_CLASS, perlinMethod, "generatePerlin", "()V"); remapMethod(mappingsString, FRACTAL_STAR_GENERATOR_CLASS, offsetMethod, "generateOffset", "()V"); remapMethod(mappingsString, FRACTAL_STAR_GENERATOR_CLASS, generateMapMethod, "generateMap", "()V"); remapClass(mappingsString, perlinGeneratorClass, PERLIN_NOISE_GENERATOR_CLASS); remapMethod(mappingsString, perlinGeneratorClass, getPerlinNoiseMethod, "generatePerlinNoise", "(III)[[F"); ClassNode perlinGeneratorNode = name2Node.get(perlinGeneratorClass); if (perlinGeneratorNode == null) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "*", "node missing"); } for (MethodNode method : perlinGeneratorNode.methods) { if (method.desc.equals("(III)[[F") && method.name.equals(getPerlinNoiseMethod)) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null && !(insn instanceof LabelNode)) { insn = insn.getNext(); } LabelNode firstLabel = (LabelNode) insn; while (insn != null && insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getNext(); } MethodInsnNode firstInsn = (MethodInsnNode) insn; if (firstInsn == null) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "generateWhiteNoise", "Instructions exhausted"); } if (!firstInsn.desc.equals("(II)[[F")) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "generateWhiteNoise", "Unexpected descriptor"); } remapMethod(mappingsString, perlinGeneratorClass, firstInsn.name, "generateWhiteNoise", "(II)[[F"); insn = firstInsn.getNext(); while (insn != null && insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getNext(); } MethodInsnNode secondInsn = (MethodInsnNode) insn; if (secondInsn == null) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "generatePerlinNoise", "Instructions exhausted"); } if (!secondInsn.desc.equals("([[FI)[[F")) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "generatePerlinNoise", "Unexpected descriptor"); } remapMethod(mappingsString, perlinGeneratorClass, secondInsn.name, "generatePerlinNoise", "([[FI)[[F"); method.parameters.clear(); method.parameters.add(new ParameterNode("width", Opcodes.ACC_FINAL)); method.parameters.add(new ParameterNode("height", Opcodes.ACC_FINAL)); method.parameters.add(new ParameterNode("octaveCount", Opcodes.ACC_FINAL)); insn = secondInsn.getNext(); if (insn == null || insn.getOpcode() != Opcodes.ARETURN) { throw new OutdatedDeobfuscatorException("Noise", PERLIN_NOISE_GENERATOR_CLASS, "generatePerlinNoise", "Unexpected opcode"); } LabelNode lastLabel = new LabelNode(); method.instructions.add(lastLabel); method.localVariables.clear(); method.localVariables.add(new LocalVariableNode("width", "I", null, firstLabel, lastLabel, 0)); method.localVariables.add(new LocalVariableNode("height", "I", null, firstLabel, lastLabel, 1)); method.localVariables.add(new LocalVariableNode("octaveCount", "I", null, firstLabel, lastLabel, 2)); method.localVariables.add(new LocalVariableNode("baseNoise", "[[F", null, firstLabel, lastLabel, 3)); break; } } for (ClassNode node : nodes) { if (node.superName.equals(FRACTAL_STAR_GENERATOR_CLASS)) { // This is faster performance-wise than actually propagating. // Should we need to propagate, we will know throw new OutdatedDeobfuscatorException("Noise", FRACTAL_STAR_GENERATOR_CLASS, "generateMap", node.name + " has unexpected superclass. Propagation might not happen"); } } } public void remapPlayerMethods(Writer mappingsString) throws IOException { ClassNode playerClass = name2Node.get(PLAYER_CLASS); if (playerClass == null) { throw new IllegalStateException("The player class was not defined!"); } boolean selectedFlagshipMethod = false; boolean selectedGetScoreMethod = false; for (MethodNode method : playerClass.methods) { if (method.desc.equals("()Lsnoddasmannen/galimulator/actors/Flagship;")) { if (selectedFlagshipMethod) { throw new IllegalStateException("Found two Player#getFlagship candidates."); } selectedFlagshipMethod = true; remapMethod(mappingsString, PLAYER_CLASS, method.name, "getFlagship", "()Lsnoddasmannen/galimulator/actors/Flagship;"); } else if (method.desc.equals("()I")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.GETFIELD) { FieldInsnNode node = (FieldInsnNode) insn; if (!node.owner.equals(PLAYER_CLASS) || !node.name.equals("extraPoints")) { break; } AbstractInsnNode next = insn.getNext(); if (next.getOpcode() != Opcodes.ALOAD) { break; } next = next.getNext(); if (next instanceof FieldInsnNode) { next = next.getNext(); } else { break; } if (next instanceof MethodInsnNode) { next = next.getNext(); } else { break; } if (next instanceof VarInsnNode) { next = next.getNext(); } else { break; } if (next instanceof FieldInsnNode) { FieldInsnNode finsn = (FieldInsnNode) next; if (finsn.owner.equals(PLAYER_CLASS) && finsn.name.equals("startStarCount")) { next = next.getNext(); } else { break; } } else { break; } if (next.getOpcode() != Opcodes.ISUB) { break; } next = next.getNext(); if (next.getOpcode() != Opcodes.IADD) { break; } next = next.getNext(); if (next.getOpcode() != Opcodes.I2F) { break; } next = next.getNext(); if (next instanceof VarInsnNode) { next = next.getNext(); } else { break; } if (next instanceof FieldInsnNode) { FieldInsnNode finsn = (FieldInsnNode) next; if (finsn.owner.equals(PLAYER_CLASS) && finsn.name.equals("scoreModifier")) { next = next.getNext(); } else { break; } } else { break; } if (next.getOpcode() != Opcodes.FMUL) { break; } next = next.getNext(); if (next.getOpcode() != Opcodes.F2I) { break; } next = next.getNext(); if (next.getOpcode() != Opcodes.IRETURN) { break; } if (selectedGetScoreMethod) { throw new IllegalStateException("Guessed two Player#getScore methods, which is nonsensical"); } selectedGetScoreMethod = true; remapMethod(mappingsString, PLAYER_CLASS, method.name, "getScore", "()I"); break; } insn = insn.getNext(); } } } } public void remapRendersystem(Writer mappingsStream) throws IOException { ClassNode renderCacheClass = null; Set renderItemDescriptors = new HashSet<>(); for (ClassNode node : nodes) { if (!node.name.startsWith(RENDERSYSTEM_PACKAGE)) { continue; } if (node.superName.equals(RENDER_ITEM_CLASS)) { MethodNode ctor = null; for (MethodNode method : node.methods) { if (method.name.equals("")) { if (ctor != null) { throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "", "Multiple present"); } ctor = method; } } if (ctor == null) { throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "", "Not present"); } if (!renderItemDescriptors.add(ctor.desc)) { throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "*", "Another class has it's constructor descriptor"); } String deobfuscatedName; switch (ctor.desc) { case "(Lcom/badlogic/gdx/graphics/Camera;)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "CameraRenderItem"; // I have 0 clue what it really does break; case "(Lcom/badlogic/gdx/graphics/g2d/PolygonSprite;)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "PolygonRenderItem"; break; case "([F)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "VertexRenderItem"; break; case "(FFFLcom/badlogic/gdx/math/Vector3;Ljava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;FLcom/badlogic/gdx/graphics/Camera;)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "TextRenderItem"; break; case "(Lcom/badlogic/gdx/graphics/g2d/TextureRegion;DDDDDLsnoddasmannen/galimulator/GalColor;ZLcom/badlogic/gdx/graphics/Camera;)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "TextureRenderItem"; break; case "(Ljava/lang/String;F)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "ShaderUniformRenderItem"; break; case "(Lcom/badlogic/gdx/graphics/glutils/ShaderProgram;)V": deobfuscatedName = RENDERSYSTEM_PACKAGE + "ShaderRenderItem"; break; default: throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "*", "Unmapped descriptor: " + ctor.desc); } remapClass(mappingsStream, node.name, deobfuscatedName); } else { for (MethodNode method : node.methods) { if ((method.access & Opcodes.ACC_SYNCHRONIZED) == 0) { continue; } for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Who is this thread??")) { if (renderCacheClass != null) { throw new OutdatedDeobfuscatorException("RenderSystem", RENDER_CACHE_CLASS, "*", "Duplicates resolved"); } renderCacheClass = node; remapMethod(mappingsStream, node.name, method.name, "pushItem", "(L" + RENDER_ITEM_CLASS + ";)V"); break; } } } } } if (renderCacheClass == null) { throw new OutdatedDeobfuscatorException("RenderSystem", RENDER_CACHE_CLASS, "*", "Not found"); } remapClass(mappingsStream, renderCacheClass.name, RENDER_CACHE_CLASS); ClassNode spaceClass = name2Node.get(SPACE_CLASS); String drawToCacheMethod = null; String drawToCacheMethodDesc = "()L" + renderCacheClass.name + ";"; for (MethodNode method : spaceClass.methods) { if (method.desc.equals(drawToCacheMethodDesc)) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(renderCacheClass.name) && methodInsn.name.equals("")) { if (drawToCacheMethod != null) { throw new OutdatedDeobfuscatorException("RenderSystem", SPACE_CLASS, "drawToCache", "Duplication"); } remapMethod(mappingsStream, SPACE_CLASS, method.name, "drawToCache", drawToCacheMethodDesc); drawToCacheMethod = method.name; break; } } } } } if (drawToCacheMethod == null) { throw new OutdatedDeobfuscatorException("RenderSystem", SPACE_CLASS, "drawToCache", "Not found"); } ClassNode renderCacheCollectorClass = null; for (ClassNode node : nodes) { if (node.interfaces.size() != 1 || !node.interfaces.get(0).equals("java/lang/Runnable")) { continue; } methodLoop: for (MethodNode method : node.methods) { if (!method.name.equals("run") || !method.desc.equals("()V")) { continue; } for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.INVOKESTATIC) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.name.equals(drawToCacheMethod) || !methodInsn.desc.equals(drawToCacheMethodDesc)) { continue; } remapClass(mappingsStream, node.name, RENDER_CACHE_COLLECTOR_CLASS); ClassNode galemulatorClass = null; for (ClassNode node2 : nodes) { if (node2.interfaces.size() != 1 || !node2.interfaces.get(0).equals("com/badlogic/gdx/ApplicationListener") || !node2.name.startsWith(BASE_PACKAGE)) { continue; } if (galemulatorClass != null) { throw new OutdatedDeobfuscatorException("RenderSystem", "Two galemulator classes found"); } galemulatorClass = node2; } if (galemulatorClass == null) { throw new OutdatedDeobfuscatorException("RenderSystem", "No galemulator class found"); } node.outerClass = galemulatorClass.name; node.innerClasses.removeIf(icn -> icn.name.equals(node.name)); galemulatorClass.innerClasses.removeIf(icn -> icn.name.equals(node.name)); InnerClassNode icn = new InnerClassNode(node.name, galemulatorClass.name, "RenderCacheCollector", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); node.innerClasses.add(icn); galemulatorClass.innerClasses.add(icn); if (renderCacheCollectorClass != null) { throw new OutdatedDeobfuscatorException("RenderSystem", RENDER_CACHE_COLLECTOR_CLASS, "*", "Collision"); } renderCacheCollectorClass = node; break methodLoop; } } } if (renderCacheCollectorClass == null) { throw new OutdatedDeobfuscatorException("RenderSystem", RENDER_CACHE_COLLECTOR_CLASS, "*", "Not found"); } } /** * Remap the fields of the snoddasmannen/galimulator/Space class. * * @param mappingsStream Suggested remapper mappings are written to the writer in the tiny v1 format */ public void remapSpaceFields(Writer mappingsStream) throws IOException { ClassNode space = name2Node.get(SPACE_CLASS); if (space == null) { throw new IllegalStateException("Class not present: " + SPACE_CLASS); } MethodNode loadMethod = null; Map fieldRemaps = new HashMap<>(); String showToastMethod = null; String showScreenSizeResetHintMethod = null; String openInputDialogMethod = null; String addUnbufferedWidgetMethod = null; String galimulatorTextInputDialogClass = null; String saveAsyncMethod = null; String saveSyncMethod = null; MethodNode logicalTickMethod = null; String getStateActorCreatorsMethod = null; String setupBackgroundEffectsMethod = null; spaceLogicalTickMethodName = null; for (MethodNode method : space.methods) { if (method.desc.equals("()V")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("[BLACK]Hi![] If you are having issues with screen size, press 'q' to reset display settings!")) { if (showToastMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "showToast", "Collision"); } MethodInsnNode methodInsn = (MethodInsnNode) insn.getNext(); if (!methodInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Space", "Space", "showToast", "Unexpected owner"); } if (!methodInsn.desc.equals("(Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("Space", "Space", "showToast", "Unexpected descriptor"); } showToastMethod = methodInsn.name; showScreenSizeResetHintMethod = method.name; remapMethod(mappingsStream, SPACE_CLASS, showToastMethod, "showToast", "(Ljava/lang/String;)V"); remapMethod(mappingsStream, SPACE_CLASS, method.name, "showScreenSizeResetHint", "()V"); } } else if (insn.getOpcode() == Opcodes.NEW) { TypeInsnNode typeInsn = (TypeInsnNode) insn; if (typeInsn.desc.equals(AMBIENT_SNOWFLAKE_EFFECT_CLASS)) { boolean likelyValid = false; while (insn != null) { if (insn.getOpcode() == Opcodes.NEW && ((TypeInsnNode) insn).desc.equals(AMBIENT_STAR_EFFECT_CLASS)) { likelyValid = true; } insn = insn.getNext(); } if (!likelyValid) { throw new OutdatedDeobfuscatorException("Space", "Space", "setupBackgroundEffects", "Did not find reference to " + AMBIENT_STAR_EFFECT_CLASS + " within method " + method.name + ":" + method.desc + ", which was almost selected."); } setupBackgroundEffectsMethod = method.name; break; } } } } else if (method.desc.equals("()I")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.LDC) { continue; } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Autosave (possible to disable in settings)")) { AbstractInsnNode nextInsn = ldcInsn.getNext(); if (nextInsn.getOpcode() != Opcodes.LDC) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveAsync", "Unexpected opcode"); } nextInsn = nextInsn.getNext(); if (nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveAsync", "Unexpected opcode (2)"); } MethodInsnNode saveAsyncInsn = (MethodInsnNode) nextInsn; if (!saveAsyncInsn.owner.equals(SPACE_CLASS) || !saveAsyncInsn.desc.equals("(Ljava/lang/String;Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveAsync", "Unexpected owner or descriptor"); } if (saveAsyncMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveAsync", "Collision"); } saveAsyncMethod = saveAsyncInsn.name; if (logicalTickMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "tick", "Collision"); } logicalTickMethod = method; } } } else if (method.desc.equals("(Ljava/lang/String;)Z")) { AbstractInsnNode insn = method.instructions.getFirst(); int spaceStateIndex = -1; while (insn != null) { if (insn.getOpcode() == Opcodes.CHECKCAST) { TypeInsnNode typeInsn = (TypeInsnNode) insn; if (typeInsn.desc.equals("snoddasmannen/galimulator/SpaceState")) { AbstractInsnNode next = typeInsn.getNext(); if (next.getOpcode() == Opcodes.ASTORE) { VarInsnNode vnext = (VarInsnNode) next; if (vnext.var != spaceStateIndex && spaceStateIndex != -1) { throw new IllegalStateException("Cannot determine the Space#loadState method (var collision)"); } spaceStateIndex = vnext.var; } } } insn = insn.getNext(); } if (spaceStateIndex != -1 && loadMethod != null) { throw new IllegalStateException("Multiple Space#loadState methods theorised."); } else { loadMethod = method; } insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.GETFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals("snoddasmannen/galimulator/SpaceState") && fieldInsn.getNext().getOpcode() == Opcodes.PUTSTATIC) { FieldInsnNode destinationField = (FieldInsnNode) fieldInsn.getNext(); if (destinationField.owner.equals(SPACE_CLASS)) { fieldRemaps.put(destinationField.name + ' ' + destinationField.desc, fieldInsn.name); } remapField(mappingsStream, destinationField.owner, destinationField.name, fieldInsn.name, destinationField.desc); } } insn = insn.getNext(); } } else if (method.desc.equals(SPACE_OPEN_INPUT_DIALOG_DESCRIPTOR)) { boolean isMethod = false; String firstNewClass = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEINTERFACE) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(GDX_INPUT_CLASS) || !methodInsn.name.equals("getTextInput")) { continue; } else if (openInputDialogMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "openInputDialog", "collision"); } openInputDialogMethod = method.name; remapMethod(mappingsStream, SPACE_CLASS, openInputDialogMethod, "openInputDialog", SPACE_OPEN_INPUT_DIALOG_DESCRIPTOR); isMethod = true; } else if (insn.getOpcode() == Opcodes.NEW && firstNewClass == null) { firstNewClass = ((TypeInsnNode) insn).desc; } } if (isMethod) { AbstractInsnNode insn = method.instructions.getLast(); while (insn != null && insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getPrevious(); } if (insn == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "addUnbufferedWidget", "instructions exhausted"); } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Space", "Space", "addUnbufferedWidget", "unexpected owner"); } if (!methodInsn.desc.equals("(L" + WIDGET_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Space", "Space", "addUnbufferedWidget", "unexpected descriptor"); } addUnbufferedWidgetMethod = methodInsn.name; remapMethod(mappingsStream, SPACE_CLASS, method.name, "addUnbufferedWidget", "(L" + WIDGET_CLASS + ";)V"); if (firstNewClass == null) { throw new OutdatedDeobfuscatorException("Space", "TextInputDialogWidget", "*", "Missing instruction"); } if (!firstNewClass.startsWith(UI_PACKAGE)) { throw new OutdatedDeobfuscatorException("Space", "TextInputDialogWidget", "*", "Unexpected start of package"); } galimulatorTextInputDialogClass = firstNewClass; remapClass(mappingsStream, galimulatorTextInputDialogClass, UI_PACKAGE + "TextInputDialogWidget"); } } else if (method.desc.equals("(Ljava/lang/String;Ljava/lang/String;)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Saving galaxy: ")) { if (saveSyncMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveSync", "Collision"); } saveSyncMethod = method.name; break; } } insn = insn.getNext(); } } else if (method.desc.equals("()Ljava/util/List;")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.LDC) { continue; } LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Error while getting state actor creators")) { if (getStateActorCreatorsMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "getStateActorCreators", "Collision"); } getStateActorCreatorsMethod = method.name; break; } } } } String warsField = fieldRemaps.entrySet().stream().filter((e) -> e.getValue().equals("wars")).findFirst().map((e) -> { return e.getKey().substring(0, e.getKey().indexOf(' ')); }).get(); if (loadMethod == null) { throw new IllegalStateException("Unable to locate Space#loadState method."); } if (showToastMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "showToast", "Not resolved"); } if (showScreenSizeResetHintMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "showScreenSizeResetHint", "Not resolved"); } if (openInputDialogMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "openInputDialog", "Not resolved"); } if (addUnbufferedWidgetMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "addUnbufferedWidget", "Not resolved"); } if (galimulatorTextInputDialogClass == null) { throw new OutdatedDeobfuscatorException("Space", "TextInputDialogWidget", "*", "Not resolved"); } if (saveAsyncMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveAsync", "Not resolved"); } if (saveSyncMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "saveSync", "Not resolved"); } if (logicalTickMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "tick", "Not resolved"); } if (getStateActorCreatorsMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "getStateActorCreators", "Not resolved"); } if (setupBackgroundEffectsMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "setupBackgroundEffects", "Not resolved"); } remapMethod(mappingsStream, SPACE_CLASS, saveAsyncMethod, "saveAsync", "(Ljava/lang/String;Ljava/lang/String;)V"); remapMethod(mappingsStream, SPACE_CLASS, saveSyncMethod, "saveSync", "(Ljava/lang/String;Ljava/lang/String;)V"); remapMethod(mappingsStream, SPACE_CLASS, logicalTickMethod.name, "tick", "()I"); remapMethod(mappingsStream, SPACE_CLASS, getStateActorCreatorsMethod, "getStateActorCreators", "()Ljava/util/List;"); remapMethod(mappingsStream, SPACE_CLASS, setupBackgroundEffectsMethod, "setupBackgroundEffects", "()V"); spaceLogicalTickMethodName = logicalTickMethod.name; this.textInputDialogWidgetClass = galimulatorTextInputDialogClass; String spaceInitializeMethod = null; String aspectRatioField = null; Map> spstarmappedGetters = new HashMap<>(); methodLoop: for (MethodNode method : space.methods) { if (method.desc.equals("(F)V")) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.INVOKESTATIC) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.name.equals(showScreenSizeResetHintMethod) || !methodInsn.desc.equals("()V") || !methodInsn.owner.equals(SPACE_CLASS)) { continue; } if (spaceInitializeMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "initialize", "Collision"); } spaceInitializeMethod = method.name; remapMethod(mappingsStream, SPACE_CLASS, method.name, "initialize", "(F)V"); for (AbstractInsnNode insn2 = method.instructions.getFirst(); insn2 != null; insn2 = insn2.getNext()) { if (insn2.getOpcode() != Opcodes.FLOAD) { continue; } AbstractInsnNode next = insn2.getNext(); if (next.getOpcode() != Opcodes.PUTSTATIC) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "aspectRatio", "Unexpected opcode"); } FieldInsnNode fieldInsn = (FieldInsnNode) next; if (!fieldInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "aspectRatio", "Unexpected owner"); } if (aspectRatioField != null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "aspectRatio", "Collision"); } aspectRatioField = fieldInsn.name; remapField(mappingsStream, SPACE_CLASS, aspectRatioField, "aspectRatio", "F"); } } } else if (method.desc.startsWith("()") && !method.desc.equals("()V")) { // Follows the getter pattern AbstractInsnNode returnInsn = null; for (AbstractInsnNode insn : method.instructions) { if (isReturn(insn.getOpcode())) { if (returnInsn != null) { continue methodLoop; } returnInsn = insn; } } if (returnInsn == null) { throw new RuntimeException("Method does not return"); } AbstractInsnNode before = returnInsn.getPrevious(); if (before.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) before; if (!fieldInsn.owner.equals(SPACE_CLASS)) { continue; } String spstarmapName = fieldRemaps.remove(fieldInsn.name + ' ' + fieldInsn.desc); if (spstarmapName != null) { spstarmapName = "get" + Character.toUpperCase(spstarmapName.charAt(0)) + spstarmapName.substring(1); // Prevent collisions if (!spstarmappedGetters.containsKey(method.name)) { spstarmappedGetters.put(method.name, new AbstractMap.SimpleImmutableEntry<>(spstarmapName, method.desc)); } else { spstarmappedGetters.put(method.name, null); } } } } else if (method.name.equals(addUnbufferedWidgetMethod) && method.desc.equals("(L" + WIDGET_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() != Opcodes.GETSTATIC) { insn = insn.getNext(); } FieldInsnNode fieldInsn = (FieldInsnNode) insn; remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, SPACE_OPENED_WIDGETS_FIELD, "Ljava/util/Vector;"); } } if (spaceInitializeMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "initialize", "Not resolved"); } if (aspectRatioField == null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "aspectRatio", "Not resolved"); } for (Map.Entry> entry : spstarmappedGetters.entrySet()) { Map.Entry signature = entry.getValue(); if (signature == null) { continue; } remapMethod(mappingsStream, SPACE_CLASS, entry.getKey(), signature.getKey(), signature.getValue()); } remapMethod(mappingsStream, SPACE_CLASS, loadMethod.name, "loadState", "(Ljava/lang/String;)Z"); // We can do more - a lot more actually AbstractInsnNode insn = loadMethod.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.name.equals("useXStream") && methodInsn.desc.equals("()Z")) { remapClass(mappingsStream, methodInsn.owner, "snoddasmannen/galimulator/DeviceConfiguration"); break; } } insn = insn.getNext(); } if (insn == null) { throw new IllegalStateException("Space#loadState lacks DeviceConfiguration#useXStream call."); } String pauseMethod = null; while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEINTERFACE) { MethodInsnNode postRunnableCall = (MethodInsnNode) insn; if (postRunnableCall.owner.equals("com/badlogic/gdx/Application") && postRunnableCall.name.equals("postRunnable") && postRunnableCall.desc.equals("(Ljava/lang/Runnable;)V")) { AbstractInsnNode before = postRunnableCall.getPrevious(); if (before.getOpcode() != Opcodes.INVOKESPECIAL) { throw new IllegalStateException("Unexpected opcode"); } MethodInsnNode constructorInvokation = (MethodInsnNode) before; if (!constructorInvokation.name.equals("")) { throw new IllegalStateException("Unexpected opcode"); } ClassNode runnableClass = name2Node.get(constructorInvokation.owner); if (runnableClass == null) { throw new IllegalStateException(); } runnableClass.outerMethod = loadMethod.name; runnableClass.outerMethodDesc = loadMethod.desc; runnableClass.outerClass = SPACE_CLASS; if (runnableClass.innerClasses.isEmpty()) { runnableClass.innerClasses.add(new InnerClassNode(runnableClass.name, SPACE_CLASS, null, 0)); } boolean hasInnerClassNode = false; for (InnerClassNode icn : space.innerClasses) { if (icn.name.equals(runnableClass.name)) { hasInnerClassNode = true; } } if (!hasInnerClassNode) { space.innerClasses.add(new InnerClassNode(runnableClass.name, SPACE_CLASS, null, 0)); } for (MethodNode method : runnableClass.methods) { if (method.name.equals("run") && method.desc.equals("()V")) { AbstractInsnNode runnableInsn = method.instructions.getFirst(); while (runnableInsn != null) { if (runnableInsn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode pauseInsn = (MethodInsnNode) runnableInsn; if (!pauseInsn.owner.equals(SPACE_CLASS) || !pauseInsn.desc.equals("(Z)V")) { throw new IllegalStateException("Unexpected opcode"); } remapMethod(mappingsStream, SPACE_CLASS, pauseInsn.name, "setPaused", "(Z)V"); pauseMethod = pauseInsn.name; break; } runnableInsn = runnableInsn.getNext(); } } } break; } } insn = insn.getNext(); } if (pauseMethod == null) { throw new IllegalStateException("Unable to identify Space#setPaused."); } String pausedField = null; String signPeaceMethod = null; for (MethodNode method : space.methods) { if (method.name.equals(pauseMethod) && method.desc.equals("(Z)V")) { AbstractInsnNode pauseMethodInsn = method.instructions.getFirst(); boolean didPutstatic = false; boolean didInvokestatic = false; while (pauseMethodInsn != null) { if (pauseMethodInsn.getOpcode() == Opcodes.PUTSTATIC) { if (didPutstatic) { throw new IllegalStateException("Unexpected bytecode (putstatic happened twice)"); } didPutstatic = true; FieldInsnNode fieldInsn = (FieldInsnNode) pauseMethodInsn; if (!fieldInsn.desc.equals("Z")) { throw new IllegalStateException("The estimated Space#paused field is not a boolean."); } if (!fieldInsn.owner.equals(SPACE_CLASS)) { throw new IllegalStateException("The esitmated Space#paused field does not belong to the space class."); } pausedField = fieldInsn.name; remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, "paused", "Z"); } else if (pauseMethodInsn.getOpcode() == Opcodes.INVOKESTATIC) { if (didInvokestatic) { throw new IllegalStateException("Unexpected bytecode (invokestatic happened twice)"); } didInvokestatic = true; MethodInsnNode methodInsn = (MethodInsnNode) pauseMethodInsn; if (!methodInsn.desc.equals("()V") || !methodInsn.owner.equals(SPACE_CLASS)) { throw new IllegalStateException("Asseration error: Space#displayStepButton not what was expected"); } remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, "displayStepButton", "()V"); } pauseMethodInsn = pauseMethodInsn.getNext(); } } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)V")) { insn = method.instructions.getFirst(); LdcInsnNode ldcInsn = getNextOrNull(insn, Opcodes.LDC); if (ldcInsn == null || !ldcInsn.cst.equals("Signed PEACE with ")) { continue; } if (signPeaceMethod != null) { throw new OutdatedDeobfuscatorException("Space", "Space", "signPeace", "Collision"); } signPeaceMethod = method.name; MethodInsnNode methodInsn = getNext(insn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Space", EMPIRE_CLASS, "addPeaceAgreement", "Unexpected desc or owner"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "addPeaceAgreement", "(L" + EMPIRE_CLASS + ";)V"); methodInsn = Objects.requireNonNull(getPreviousOrNull(ldcInsn, Opcodes.INVOKEVIRTUAL), "Null insn"); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Space", EMPIRE_CLASS, "isNotable", "Unexpected desc or owner"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "isNotable", "()Z"); methodInsn = (MethodInsnNode) getNext(ldcInsn, Opcodes.ALOAD).getNext(); if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Space", EMPIRE_CLASS, "getDisplayName", "Unexpected desc or owner"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "getDisplayName", "()Ljava/lang/String;"); methodInsn = getNext(methodInsn, Opcodes.INVOKESTATIC); if (!methodInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "postBulletin", "Unexpected owner"); } remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, "postBulletin", methodInsn.desc); } } if (pausedField == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "paused"); } if (signPeaceMethod == null) { throw new OutdatedDeobfuscatorException("Space", "Space", "signPeace", "Unresolved"); } remapMethod(mappingsStream, SPACE_CLASS, signPeaceMethod, "signPeace", "(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)V"); boolean foundIsPaused = false; for (MethodNode method : space.methods) { if (!foundIsPaused && (method.access & Opcodes.ACC_PUBLIC) != 0 && method.desc.equals("()Z") && isGetter(method, SPACE_CLASS, pausedField, "Z", true)) { remapMethod(mappingsStream, SPACE_CLASS, method.name, "isPaused", "()Z"); foundIsPaused = true; } } if (!foundIsPaused) { throw new OutdatedDeobfuscatorException("Space", "Space", "isPaused"); } boolean foundSaveStackdepth = false; for (ClassNode node : nodes) { if (node.interfaces.size() == 1 && node.interfaces.get(0).equals("java/lang/Runnable")) { for (MethodNode method : node.methods) { if (method.name.equals("run") && method.desc.equals("()V")) { insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Restored from disk, stack depth was: ")) { break; } insn = insn.getNext(); } if (insn == null) { break; } insn = insn.getNext().getNext(); if (insn.getOpcode() != Opcodes.GETSTATIC) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "saveStackdepth", "Follow-up instruction has wrong opcode"); } FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(SPACE_CLASS) || !fieldInsn.desc.equals("I")) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "saveStackdepth", "Follow-up instruction has wrong owner class or descriptor"); } remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, "saveStackdepth", "I"); if (foundSaveStackdepth) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "saveStackdepth", "Collision"); } foundSaveStackdepth = true; break; } } } } if (!foundSaveStackdepth) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "saveStackdepth", "outright missing"); } ClassNode warListWidgetNode = null; String warListEntryClass = null; String warListWidgetPopulateMethod = null; String paginatedWidgetClass = null; String getWarNameMethod = null; String getWarDisplayScoreMethod = null; String getWarDisplayAgeMethod = null; String widgetGetInnerWidthMethod = null; nodeLoop: for (ClassNode node : nodes) { if (!node.name.startsWith(UI_PACKAGE)) { continue; } for (MethodNode method : node.methods) { if (!method.desc.equals("()V")) { continue; } for (insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.GETSTATIC) { continue; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.name.equals(warsField) || !fieldInsn.owner.equals(SPACE_CLASS) || !fieldInsn.desc.equals("Ljava/util/Vector;")) { continue; } if (warListWidgetNode != null) { throw new OutdatedDeobfuscatorException("Space", WAR_LIST_WIDGET_CLASS, "*", "Collision"); } warListWidgetNode = node; warListWidgetPopulateMethod = method.name; while ((insn = insn.getNext()) != null) { if (insn.getOpcode() != Opcodes.NEW) { continue; } warListEntryClass = ((TypeInsnNode) insn).desc; insn = insn.getNext(); break; } while (insn != null) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { insn = insn.getNext(); continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "getWarName", "Invalid owner or descriptor"); } getWarNameMethod = methodInsn.name; insn = insn.getNext(); break; } while (insn != null) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL || ((MethodInsnNode) insn).owner.equals("java/lang/StringBuilder")) { insn = insn.getNext(); continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "getDisplayScore", "Invalid owner or descriptor"); } getWarDisplayScoreMethod = methodInsn.name; insn = insn.getNext(); break; } while (insn != null) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL || ((MethodInsnNode) insn).owner.equals("java/lang/StringBuilder")) { insn = insn.getNext(); continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("()Ljava/lang/String;")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "getDisplayAge", "Invalid owner or descriptor"); } getWarDisplayAgeMethod = methodInsn.name; insn = insn.getNext(); break; } while (insn != null) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL || !((MethodInsnNode) insn).owner.equals(node.name)) { insn = insn.getNext(); continue; } if (!((MethodInsnNode) insn).desc.equals("()I")) { throw new OutdatedDeobfuscatorException("Space", WIDGET_CLASS, "getInnerWidth", "Invalid descriptor"); } widgetGetInnerWidthMethod = ((MethodInsnNode) insn).name; insn = insn.getNext(); break; } while (insn != null) { if (insn.getOpcode() != Opcodes.NEW) { insn = insn.getNext(); continue; } paginatedWidgetClass = ((TypeInsnNode) insn).desc; break; } for (MethodNode method2 : node.methods) { if (method2.name.equals("") && method2.desc.equals("()V")) { insn = method2.instructions.getFirst(); insn = getNext(insn, Opcodes.ICONST_0).getNext(); if (insn.getOpcode() != Opcodes.PUTFIELD) { throw new OutdatedDeobfuscatorException("Space", WAR_LIST_WIDGET_CLASS, "__unused0", "Unexpected ocpode"); } fieldInsn = (FieldInsnNode) insn; remapField(mappingsStream, node.name, fieldInsn.name, "__unused0", "()I"); insn = getNext(insn, Opcodes.ICONST_3).getNext(); if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Space", "WidgetLayout", "setHorizontalMargin", "Unexpected ocpode"); } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.desc.equals("(I)V")) { throw new OutdatedDeobfuscatorException("Space", "WidgetLayout", "setHorizontalMargin", "Unexpected descriptor"); } for (ClassNode node2 : nodes) { if (isInstanceofClass(node2, methodInsn.owner)) { remapMethod(mappingsStream, node2.name, methodInsn.name, "setHorizontalMargin", "(I)V"); } } } } continue nodeLoop; } } } if (warListWidgetNode == null) { throw new OutdatedDeobfuscatorException("Space", WAR_LIST_WIDGET_CLASS, "*", "Cannot be resolved"); } if (paginatedWidgetClass == null) { throw new OutdatedDeobfuscatorException("Space", PAGINATED_WIDGET_CLASS, "*", "Cannot be resolved"); } remapClass(mappingsStream, warListWidgetNode.name, WAR_LIST_WIDGET_CLASS); remapClass(mappingsStream, warListEntryClass, WAR_LIST_ENTRY_CLASS); remapClass(mappingsStream, paginatedWidgetClass, PAGINATED_WIDGET_CLASS); remapMethod(mappingsStream, warListWidgetNode.name, warListWidgetPopulateMethod, "populate", "()V"); remapMethod(mappingsStream, WAR_CLASS, getWarNameMethod, "getWarName", "()Ljava/lang/String;"); remapMethod(mappingsStream, WAR_CLASS, getWarDisplayScoreMethod, "getDisplayScore", "()Ljava/lang/String;"); remapMethod(mappingsStream, WAR_CLASS, getWarDisplayAgeMethod, "getDisplayAge", "()Ljava/lang/String;"); ClassNode warListEntryNode = name2Node.get(warListEntryClass); String flowButtonClass = name2Node.get(warListEntryNode.superName).superName; remapClass(mappingsStream, flowButtonClass, FLOW_BUTTON_CLASS); assignAsInnerClass(warListWidgetNode, warListEntryNode, WAR_LIST_ENTRY_CLASS.substring(WAR_LIST_ENTRY_CLASS.lastIndexOf('$') + 1)); String getLazyMethodName = null; String getLazyMethodDesc = null; String getOrCreateWarMethod = null; for (MethodNode method : space.methods) { if (method.desc.equals("()V")) { TypeInsnNode typeInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.NEW); if (typeInsn != null && typeInsn.desc.equals(warListWidgetNode.name)) { remapMethod(mappingsStream, SPACE_CLASS, method.name, "openActiveWarList", "()V"); } } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)L" + WAR_CLASS + ";")) { TypeInsnNode typeInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.NEW); if (typeInsn != null && typeInsn.desc.equals(WAR_CLASS)) { remapMethod(mappingsStream, SPACE_CLASS, method.name, "getOrCreateWar", "(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)L" + WAR_CLASS + ";"); MethodInsnNode methodInsn = getPreviousOrNull(typeInsn, Opcodes.INVOKEVIRTUAL); if (methodInsn == null) { throw new OutdatedDeobfuscatorException("Space", "Lazy", "get", "Not found"); } getLazyMethodName = methodInsn.name; getLazyMethodDesc = methodInsn.desc; remapClass(mappingsStream, getLazyMethodDesc.substring(3, getLazyMethodDesc.length() - 1), BASE_PACKAGE + "Identifiable"); getOrCreateWarMethod = method.name; } } } if (getOrCreateWarMethod == null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "getOrCreateWar", "Not found"); } String onTakeStarFromMethod = null; for (MethodNode method : space.methods) { if (method.desc.equals("(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)V")) { MethodInsnNode methodInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.INVOKESTATIC); if (methodInsn == null || !methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.name.equals(getOrCreateWarMethod) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)L" + WAR_CLASS + ";")) { continue; } if (onTakeStarFromMethod != null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "onTakeStarFrom", "Collision"); } onTakeStarFromMethod = method.name; methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "incrementScore", "Invalid owner or desc"); } remapMethod(mappingsStream, WAR_CLASS, methodInsn.name, "incrementScore", "(L" + EMPIRE_CLASS + ";)V"); } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";)Ljava/util/List;")) { FieldInsnNode fInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.GETSTATIC); if (fInsn != null && fInsn.owner.equals(SPACE_CLASS) && fInsn.name.equals(warsField) && fInsn.desc.equals("Ljava/util/Vector;")) { MethodInsnNode methodInsn = getNextOrNull(fInsn, Opcodes.INVOKEVIRTUAL); if (methodInsn != null && methodInsn.owner.equals("java/util/Vector") && methodInsn.name.equals("stream")) { remapMethod(mappingsStream, SPACE_CLASS, method.name, "getParticipatingWars", "(L" + EMPIRE_CLASS + ";)Ljava/util/List;"); } } } } if (onTakeStarFromMethod == null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "onTakeStarFrom", "Not found"); } remapMethod(mappingsStream, SPACE_CLASS, onTakeStarFromMethod, "onTakeStarFrom", "(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)V"); ClassNode starNode = name2Node.get(STAR_CLASS); String onHostileTakeoverMethod = null; for (MethodNode method : starNode.methods) { if (method.desc.equals("(L" + EMPIRE_CLASS + ";)V")) { MethodInsnNode methodInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.INVOKESTATIC); if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.name.equals(onTakeStarFromMethod) || !methodInsn.desc.equals("(L" + EMPIRE_CLASS + ";L" + EMPIRE_CLASS + ";)V")) { continue; } if (onHostileTakeoverMethod != null) { throw new OutdatedDeobfuscatorException("Space", STAR_CLASS, "onHostileTakeover", "Collision"); } onHostileTakeoverMethod = method.name; methodInsn = getNextOrNull(methodInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(STAR_CLASS) || !methodInsn.desc.equals("(I)V")) { throw new OutdatedDeobfuscatorException("Space", STAR_CLASS, "setDevelopment", "Invalid owner or desc"); } remapMethod(mappingsStream, STAR_CLASS, methodInsn.name, "setDevelopment", "(I)V"); LdcInsnNode ldcInsn = getNext(methodInsn, Opcodes.LDC); while (!(ldcInsn.cst instanceof Number) || (Math.abs(((Number) ldcInsn.cst).floatValue() - 0.02F) > 0.001F)) { ldcInsn = getNext(ldcInsn, Opcodes.LDC); } methodInsn = (MethodInsnNode) ldcInsn.getNext(); if (!methodInsn.owner.equals(STAR_CLASS) || !methodInsn.desc.equals("(F)V")) { throw new OutdatedDeobfuscatorException("Space", STAR_CLASS, "reduceWealthFactor", "Invalid owner or desc"); } remapMethod(mappingsStream, STAR_CLASS, methodInsn.name, "reduceWealthFactor", "(F)V"); } } if (onHostileTakeoverMethod == null) { throw new OutdatedDeobfuscatorException("Space", STAR_CLASS, "onHostileTakeover", "Not found"); } remapMethod(mappingsStream, STAR_CLASS, onHostileTakeoverMethod, "onHostileTakeover", "(L" + EMPIRE_CLASS + ";)V"); ClassNode warNode = name2Node.get(WAR_CLASS); String getScoreMethod = null; String isActiveMethod = null; for (MethodNode method : warNode.methods) { if (method.desc.equals("(L" + EMPIRE_CLASS + ";)I")) { LdcInsnNode ldcInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.LDC); if (ldcInsn != null && ldcInsn.cst.equals("Score fetched for empire not in the war, that's weird huh")) { if (getScoreMethod != null) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "getScore", "Collision"); } getScoreMethod = method.name; } } else if (method.name.equals("getItems") && method.desc.equals("()Ljava/util/ArrayList;")) { insn = method.instructions.getFirst(); while (insn != null && !(insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Active"))) { insn = insn.getNext(); } if (insn == null) { continue; } MethodInsnNode methodInsn = getNext(insn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "isActive", "Invalid owner or desc"); } isActiveMethod = methodInsn.name; } } if (getScoreMethod == null) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "getScore", "Not found"); } if (isActiveMethod == null) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "isActive", "Not found"); } for (insn = logicalTickMethod.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.name.equals(isActiveMethod) || !methodInsn.desc.equals("()Z")) { continue; } methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(WAR_CLASS) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Space", WAR_CLASS, "tick", "Invalid owner or desc"); } remapMethod(mappingsStream, WAR_CLASS, methodInsn.name, "tick", "()V"); break; } remapMethod(mappingsStream, WAR_CLASS, getScoreMethod, "getScore", "(L" + EMPIRE_CLASS + ";)I"); remapMethod(mappingsStream, WAR_CLASS, isActiveMethod, "isActive", "()Z"); for (ClassNode node : nodes) { if (isInstanceofWidget(node)) { remapMethod(mappingsStream, node.name, widgetGetInnerWidthMethod, "getInnerWidth", "()I"); } else if (isInstanceofClass(node, BASE_PACKAGE + "Lazy")) { remapMethod(mappingsStream, node.name, getLazyMethodName, "get", getLazyMethodDesc); } } } public void remapStarMethods(Writer mappingsStream) throws IOException { ClassNode starNode = name2Node.get(STAR_CLASS); MethodNode tickMethod = null; String connectMethod = null; String disconnectMethod = null; String setOwnerEmpireMethod = null; String ownerEmpireField = null; methodLoop0: for (MethodNode method : starNode.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst instanceof Double) { Double d = (Double) ldcInsn.cst; // Yeah, it is beyond me why a double is used there but I couldn't care less if (d.floatValue() == 0.05F) { if (tickMethod != null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "tick", "Collision"); } tickMethod = method; continue methodLoop0; } } } insn = insn.getNext(); } } else if (method.desc.equals("(L" + STAR_CLASS + ";)V")) { FieldInsnNode lastGetIntLanes = null; for (AbstractInsnNode insn = method.instructions.getLast(); insn != null; insn = insn.getPrevious()) { if (insn.getOpcode() == Opcodes.GETFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(STAR_CLASS) && fieldInsn.name.equals("intLanes")) { lastGetIntLanes = fieldInsn; break; } } } if (lastGetIntLanes == null) { continue; } MethodInsnNode operation = null; for (AbstractInsnNode insn = lastGetIntLanes; insn != null; insn = insn.getNext()) { if (insn instanceof MethodInsnNode) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.desc.equals("(Ljava/lang/Object;)Z")) { operation = methodInsn; break; } } } if (operation == null) { continue; } else if (operation.name.equals("add")) { if (connectMethod != null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "connect", "Collison"); } connectMethod = method.name; } else if (operation.name.equals("remove")) { if (disconnectMethod != null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "disconnect", "Collison"); } disconnectMethod = method.name; } } else if (method.desc.equals("(L" + EMPIRE_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (insn.getOpcode() != Opcodes.ALOAD) { continue; } VarInsnNode aLoadThis = (VarInsnNode) insn; if (aLoadThis.var != 0 || (insn = insn.getNext()).getOpcode() != Opcodes.ALOAD) { continue; } VarInsnNode aLoadEmpire = (VarInsnNode) insn; if (aLoadEmpire.var != 1 || (insn = insn.getNext()).getOpcode() != Opcodes.PUTFIELD) { continue; } FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(STAR_CLASS) || !fieldInsn.desc.equals("L" + EMPIRE_CLASS + ";")) { continue; } if (ownerEmpireField != null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "ownerEmpire", "Collision"); } ownerEmpireField = fieldInsn.name; setOwnerEmpireMethod = method.name; } } if (tickMethod == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "tick", "Not resolved"); } if (connectMethod == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "connect", "Not resolved"); } if (disconnectMethod == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "disconnect", "Not resolved"); } if (ownerEmpireField == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "ownerEmpire", "Not resolved"); } remapMethod(mappingsStream, STAR_CLASS, tickMethod.name, "tick", "()V"); remapMethod(mappingsStream, STAR_CLASS, connectMethod, "connect", "(L" + STAR_CLASS + ";)V"); remapMethod(mappingsStream, STAR_CLASS, disconnectMethod, "disconnect", "(L" + STAR_CLASS + ";)V"); remapField(mappingsStream, STAR_CLASS, ownerEmpireField, "ownerEmpire", "L" + EMPIRE_CLASS + ";"); remapMethod(mappingsStream, STAR_CLASS, setOwnerEmpireMethod, "setOwnerEmpire", "(L" + EMPIRE_CLASS + ";)V"); { AbstractInsnNode insn = tickMethod.instructions.getFirst(); FieldInsnNode getWealth = getNext(insn, Opcodes.GETFIELD); insn = getWealth.getNext(); if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "addTransientWealth", "Opcode mismatch"); } MethodInsnNode addTransientWealth = (MethodInsnNode) insn; if (!addTransientWealth.owner.equals(EMPIRE_CLASS) || !addTransientWealth.desc.equals("(F)V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "addTransientWealth", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, addTransientWealth.name, "addTransientWealth", "(F)V"); MethodInsnNode random = getNext(addTransientWealth, Opcodes.INVOKESTATIC); if (!random.name.equals("random")) { throw new OutdatedDeobfuscatorException("Star", "expected random call where there isn't"); } random = getNext(random, Opcodes.INVOKESTATIC); if (!random.owner.equals(MATH_UTILS_CLASS) || !random.name.equals("randomBoolean") || !random.desc.equals("(F)Z")) { throw new OutdatedDeobfuscatorException("Star", "expected randomBoolean call where there isn't"); } if (((Float) ((LdcInsnNode) random.getPrevious()).cst).floatValue() != 0.1F) { throw new OutdatedDeobfuscatorException("Star", "randomBoolean chance mismatch"); } MethodInsnNode refreshBeacon = getNext(random, Opcodes.INVOKEVIRTUAL); if (!refreshBeacon.owner.equals(STAR_CLASS) || !refreshBeacon.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "refreshBeaconState", "Owner or descriptor mismatch"); } String refreshBeaconMethod = refreshBeacon.name; remapMethod(mappingsStream, STAR_CLASS, refreshBeaconMethod, "refreshBeaconState", "()V"); insn = getNext(refreshBeacon, Opcodes.INVOKEVIRTUAL); MethodInsnNode getDevelopmentGrowthRate = (MethodInsnNode) insn.getNext(); insn = getDevelopmentGrowthRate.getNext(); if (insn.getOpcode() != Opcodes.IMUL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getDevelopmentGrowthRate", "Expected an IMUL op after instruction"); } if (!getDevelopmentGrowthRate.owner.equals(EMPIRE_CLASS) || !getDevelopmentGrowthRate.desc.equals("()I")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getDevelopmentGrowthRate", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, getDevelopmentGrowthRate.name, "getDevelopmentGrowthRate", "()I"); insn = insn.getNext(); if (insn.getOpcode() != Opcodes.INVOKESPECIAL) { // Don't ask why this is INVOKESPECIAL - probably has something to do with that it is private throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addDevelopment", "Unexpected Opcode (Invokevirtual: " + (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) + ")"); } MethodInsnNode addDevelopment = (MethodInsnNode) insn; if (!addDevelopment.owner.equals(STAR_CLASS) || !addDevelopment.desc.equals("(I)V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addDevelopment", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, STAR_CLASS, addDevelopment.name, "addDevelopment", "(I)V"); FieldInsnNode getHeat = getNext(insn, Opcodes.GETFIELD); if (!getHeat.desc.equals("F")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "heat", "Invalid descriptor"); } insn = getHeat.getNext(); if (((Float) ((LdcInsnNode) insn).cst).floatValue() != 0.99F) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "heat", "Unexpected following instruction"); } remapField(mappingsStream, STAR_CLASS, getHeat.name, "heat", "F"); insn = getNext(insn, Opcodes.INVOKEVIRTUAL).getNext(); if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getWealthDecayFactor", "Unexpected Opcode for instruction"); } MethodInsnNode getWealthDecayFactor = (MethodInsnNode) insn; if (!getWealthDecayFactor.owner.equals(EMPIRE_CLASS) || !getWealthDecayFactor.desc.equals("()F")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getWealthDecayFactor", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, getWealthDecayFactor.name, "getWealthDecayFactor", "()F"); if ((insn = insn.getNext()).getOpcode() != Opcodes.FMUL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getWealthDecayFactor", "Unexpected Opcode after instruction"); } insn = getNext(insn, Opcodes.GETFIELD); FieldInsnNode getNeighbours = getNext(insn, Opcodes.GETFIELD); if (!getNeighbours.owner.equals(STAR_CLASS) || !getNeighbours.desc.equals("Ljava/util/Vector;")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "neighbours", "Owner or descriptor mismatch"); } remapField(mappingsStream, STAR_CLASS, getNeighbours.name, "neighbours", "Ljava/util/Vector;"); TypeInsnNode checkcast = getNext(getNeighbours, Opcodes.CHECKCAST); if (!checkcast.desc.equals(STAR_CLASS)) { throw new OutdatedDeobfuscatorException("Star", "Checkcast not casting to expected type. Expected Star, current casting to " + checkcast.desc + " instead."); } checkcast = getNext(checkcast, Opcodes.CHECKCAST); if (!checkcast.desc.equals("java/lang/Boolean")) { throw new OutdatedDeobfuscatorException("Star", ENUM_SETTINGS_CLASS, "getValue", "Casting to invalid type. Instead casting to " + checkcast.desc); } AbstractInsnNode prev = checkcast.getPrevious(); if (prev.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Star", ENUM_SETTINGS_CLASS, "getValue", "Unexpected opcode for instruction"); } MethodInsnNode getSettingValue = (MethodInsnNode) prev; if (!getSettingValue.owner.equals(ENUM_SETTINGS_CLASS) || !getSettingValue.desc.equals("()Ljava/lang/Object;")) { throw new OutdatedDeobfuscatorException("Star", ENUM_SETTINGS_CLASS, "getValue", "Unexpected owner or descriptor"); } ClassNode enumSettingsNode = name2Node.get(ENUM_SETTINGS_CLASS); if (enumSettingsNode.interfaces.size() != 1) { throw new OutdatedDeobfuscatorException("Star", CONFIGURABLE_PREFERNCE_INTERFACE, "*", "Not an exactly ideal number of interfaces present"); } String configurablePreferenceClass = enumSettingsNode.interfaces.get(0); remapClass(mappingsStream, configurablePreferenceClass, CONFIGURABLE_PREFERNCE_INTERFACE); for (ClassNode node : nodes) { if (isInstanceofInterface(node, configurablePreferenceClass)) { remapMethod(mappingsStream, node.name, getSettingValue.name, "getValue", "()Ljava/lang/Object;"); } } MethodInsnNode isCapital = getNext(checkcast.getNext(), Opcodes.INVOKEVIRTUAL); if (!isCapital.owner.equals(STAR_CLASS) || !isCapital.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "isCapital", "Unexpected owner or descriptor"); } insn = isCapital.getNext(); if (insn.getOpcode() != Opcodes.IFEQ) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "isCapital", "Unexpected opcode after instruction"); } remapMethod(mappingsStream, STAR_CLASS, isCapital.name, "isCapital", "()Z"); String isCapitalMethod = isCapital.name; LdcInsnNode magicConstant = null; while (insn != null) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (((Float) ldcInsn.cst).floatValue() == 500F) { magicConstant = ldcInsn; break; } } insn = insn.getNext(); } if (magicConstant == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "getArtifact", "Unable to find magic constant"); } MethodInsnNode getArtifact = getNext(magicConstant, Opcodes.INVOKEVIRTUAL); if (!getArtifact.owner.equals(STAR_CLASS) || !getArtifact.desc.equals("()L" + ARTIFACT_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "getArtifact", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, STAR_CLASS, getArtifact.name, "getArtifact", "()L" + ARTIFACT_CLASS + ";"); MethodInsnNode methodInsn = getNext(getArtifact, Opcodes.INVOKESPECIAL); if (!methodInsn.owner.equals("java/util/ArrayList")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addSprawl", "Unexpected owner of guidance instruction"); } MethodInsnNode addSprawl = getNext(methodInsn, Opcodes.INVOKESPECIAL); if (!addSprawl.owner.equals(STAR_CLASS) || !addSprawl.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addSprawl", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, STAR_CLASS, addSprawl.name, "addSprawl", "()V"); MethodInsnNode removeSprawl = getNext(addSprawl, Opcodes.INVOKESPECIAL); if (!removeSprawl.owner.equals(STAR_CLASS) || !removeSprawl.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "removeSprawl", "Unexpected owner or descriptor"); } String removeSprawlMethod = removeSprawl.name; remapMethod(mappingsStream, STAR_CLASS, removeSprawlMethod, "removeSprawl", "()V"); methodInsn = getNext(removeSprawl, Opcodes.INVOKEVIRTUAL); while (!methodInsn.owner.equals(SPRAWL_CLASS) || !methodInsn.name.equals("activity")) { methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); } FieldInsnNode getOrbitingActors = getNext(methodInsn, Opcodes.GETFIELD); if (!getOrbitingActors.owner.equals(STAR_CLASS) || !getOrbitingActors.desc.equals("Ljava/util/Set;")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "orbitingActors", "Unexpected owner or descriptor"); } String orbitingActorsField = getOrbitingActors.name; remapField(mappingsStream, STAR_CLASS, orbitingActorsField, "orbitingActors", "Ljava/util/Set;"); methodInsn = getNext(getOrbitingActors, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(STAR_CLASS) || !methodInsn.desc.equals("()L" + STAR_NATIVES_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", STAR_NATIVES_CLASS, "spawnNativeActor", "Unexpected owner or descriptor of anchor instruction"); } methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); while (!methodInsn.owner.equals(STAR_CLASS) || !methodInsn.desc.equals("()L" + STAR_NATIVES_CLASS + ";")) { methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); } MethodInsnNode spawnNativeActor = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!spawnNativeActor.owner.equals(STAR_NATIVES_CLASS) || !spawnNativeActor.desc.equals("(L" + STAR_CLASS + ";)L" + STATE_ACTOR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", STAR_NATIVES_CLASS, "spawnNativeActor", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, STAR_NATIVES_CLASS, spawnNativeActor.name, "spawnNativeActor", "(L" + STAR_CLASS + ";)L" + STATE_ACTOR_CLASS + ";"); insn = spawnNativeActor.getNext(); if (insn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "addActor", "Unexpected opcode"); } MethodInsnNode addActor = (MethodInsnNode) insn; if (!addActor.owner.equals(SPACE_CLASS) || !addActor.desc.equals("(L" + ACTOR_CLASS + ";)L" + ACTOR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "addActor", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, SPACE_CLASS, addActor.name, "addActor", "(L" + ACTOR_CLASS + ";)L" + ACTOR_CLASS + ";"); methodInsn = getNext(addActor, Opcodes.INVOKEVIRTUAL); while (!methodInsn.owner.equals(EMPIRE_CLASS)) { methodInsn = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); } if (!methodInsn.desc.equals("(L" + STAR_CLASS + ";L" + STAR_CLASS + ";)Z")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "canAttack", "Unexpected descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, methodInsn.name, "canAttack", "(L" + STAR_CLASS + ";L" + STAR_CLASS + ";)Z"); insn = methodInsn.getNext(); if (insn.getOpcode() != Opcodes.IFEQ) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "canAttack", "Following opcode should be IFEQ, but is something different"); } MethodInsnNode incrementHeat = getNext(insn, Opcodes.INVOKESPECIAL); if (!incrementHeat.owner.equals(STAR_CLASS) || !incrementHeat.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "incrementHeat", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, STAR_CLASS, incrementHeat.name, "incrementHeat", "()V"); TypeInsnNode typeInsn = getNext(incrementHeat, Opcodes.NEW); if (!typeInsn.desc.contains("snoddasmannen/galimulator/Debris")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getColor", "Unexpected NEW instruction for anchor instruction"); } MethodInsnNode getColor = getNext(typeInsn, Opcodes.INVOKEVIRTUAL); if (!getColor.owner.equals(EMPIRE_CLASS) || !getColor.desc.equals("()L" + GALCOLOR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getColor", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, getColor.name, "getColor", "()L" + GALCOLOR_CLASS + ";"); methodInsn = getNext(getColor, Opcodes.INVOKESPECIAL); while (!methodInsn.owner.equals("snoddasmannen/galimulator/effects/AuraEffect")) { methodInsn = getNext(methodInsn, Opcodes.INVOKESPECIAL); } if (!methodInsn.name.equals("") || !methodInsn.desc.equals("(DDDZDILsnoddasmannen/galimulator/GalColor;)V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getCombatPower", "Unexpected name or descriptor of anchor instruction"); } MethodInsnNode getCombatPower = getNext(getNext(methodInsn, Opcodes.INVOKEVIRTUAL), Opcodes.INVOKEVIRTUAL); if (!getCombatPower.owner.equals(EMPIRE_CLASS) || !getCombatPower.desc.equals("(L" + STAR_CLASS + ";L" + STAR_CLASS + ";Z)I")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getCombatPower", "Unexpected owner or descriptor"); } remapMethod(mappingsStream, EMPIRE_CLASS, getCombatPower.name, "getCombatPower", "(L" + STAR_CLASS + ";L" + STAR_CLASS + ";Z)I"); String getForeignConnectionsMethod = null; String setBeaconMethod = null; String addOrbitingActorMethod = null; for (MethodNode method : starNode.methods) { if (method.desc.equals("()V")) { if (method.name.equals(refreshBeaconMethod)) { MethodInsnNode getForeignConnections = getNext(method.instructions.getFirst(), Opcodes.INVOKEVIRTUAL); if (!getForeignConnections.owner.equals(STAR_CLASS) || !getForeignConnections.desc.equals("(Z)Ljava/util/Vector;")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "getForeignConnections", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, STAR_CLASS, getForeignConnections.name, "getForeignConnections", "(Z)Ljava/util/Vector;"); getForeignConnectionsMethod = getForeignConnections.name; MethodInsnNode setBeacon = getNext(getForeignConnections.getNext(), Opcodes.INVOKEVIRTUAL); if (!setBeacon.owner.equals(STAR_CLASS) || !setBeacon.desc.equals("(Z)V")) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "setBeacon", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, STAR_CLASS, setBeacon.name, "setBeacon", "(Z)V"); setBeaconMethod = setBeacon.name; } else if (method.name.equals(removeSprawlMethod)) { TypeInsnNode var10001 = getNext(method.instructions.getFirst(), Opcodes.CHECKCAST); if (!var10001.desc.equals(SPRAWL_CLASS)) { throw new OutdatedDeobfuscatorException("Star", SPRAWL_CLASS, "setAlive", "Checkcast type unexpected"); } MethodInsnNode setAlive = getNext(var10001, Opcodes.INVOKEVIRTUAL); if (!setAlive.owner.equals(SPRAWL_CLASS) || !setAlive.desc.equals("(Z)V")) { throw new OutdatedDeobfuscatorException("Star", SPRAWL_CLASS, "setAlive", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, SPRAWL_CLASS, setAlive.name, "setAlive", "(Z)V"); } } else if (method.desc.equals("()Z")) { if (method.name.equals(isCapitalMethod)) { MethodInsnNode getCapital = getNext(getNext(method.instructions.getFirst(), Opcodes.INVOKEVIRTUAL), Opcodes.INVOKEVIRTUAL); if (!getCapital.owner.equals(EMPIRE_CLASS) || !getCapital.desc.equals("()L" + STAR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "getCapital", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, getCapital.name, "getCapital", "()L" + STAR_CLASS + ";"); } } else if (method.desc.equals("(L" + STATE_ACTOR_CLASS + ";)V")) { AbstractInsnNode var10001 = getNext(method.instructions.getFirst()); if (var10001.getOpcode() != Opcodes.ALOAD || ((VarInsnNode) var10001).var != 0) { continue; } var10001 = var10001.getNext(); if (var10001.getOpcode() != Opcodes.GETFIELD) { continue; } FieldInsnNode var10002 = (FieldInsnNode) var10001; if (!var10002.owner.equals(STAR_CLASS) || !var10002.desc.equals("Ljava/util/Set;") || !var10002.name.equals(orbitingActorsField)) { continue; } if (addOrbitingActorMethod != null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addOrbitingActor", "Collision"); } addOrbitingActorMethod = method.name; remapMethod(mappingsStream, STAR_CLASS, addOrbitingActorMethod, "addOrbitingActor", "(L" + STATE_ACTOR_CLASS + ";)V"); } } if (getForeignConnectionsMethod == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "getForeignConnections", "Unresolved"); } if (addOrbitingActorMethod == null) { throw new OutdatedDeobfuscatorException("Star", STAR_CLASS, "addOrbitingActor", "Unresolved"); } for (MethodNode method : starNode.methods) { if (method.desc.equals("(Z)Ljava/util/Vector;")) { if (method.name.equals(getForeignConnectionsMethod)) { method.signature = "(Z)Ljava/util/Vector;"; } } else if (method.desc.equals("(Z)V")) { if (method.name.equals(setBeaconMethod)) { VarInsnNode var10001 = getNext(method.instructions.getFirst(), Opcodes.ILOAD); if (var10001.getNext().getOpcode() != Opcodes.IFEQ) { throw new OutdatedDeobfuscatorException("Star", "If-block order mismatch in the Star#setBeacon method"); } MethodInsnNode var10002 = getNext(var10001, Opcodes.INVOKEVIRTUAL); if (!var10002.owner.equals(STAR_CLASS) || !var10002.desc.equals("()L" + EMPIRE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "addBeacon", "Preceding instruction has issues"); } var10002 = getNext(var10002, Opcodes.INVOKEVIRTUAL); if (!var10002.owner.equals(EMPIRE_CLASS) || !var10002.desc.equals("(L" + STAR_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "addBeacon", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, var10002.name, "addBeacon", "(L" + STAR_CLASS + ";)V"); var10002 = getNext(var10002, Opcodes.INVOKEVIRTUAL); if (!var10002.owner.equals(STAR_CLASS) || !var10002.desc.equals("()L" + EMPIRE_CLASS + ";")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "removeBeacon", "Preceding instruction has issues"); } var10002 = getNext(var10002, Opcodes.INVOKEVIRTUAL); if (!var10002.owner.equals(EMPIRE_CLASS) || !var10002.desc.equals("(L" + STAR_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "removeBeacon", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, EMPIRE_CLASS, var10002.name, "removeBeacon", "(L" + STAR_CLASS + ";)V"); } } } } ClassNode quadTreeClass = null; classLoop: for (ClassNode node : nodes) { for (MethodNode method : node.methods) { if (method.desc.equals("()Z")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Unable to insert star into quad tree!")) { if (quadTreeClass != null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "*", "Collision"); } quadTreeClass = node; continue classLoop; } } } } } } if (quadTreeClass == null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "*", "Unresolved"); } String quadtreeInsert = null; String quadtreeX1 = null; String quadtreeX2 = null; String quadtreeY1 = null; String quadtreeY2 = null; methodLoop: for (MethodNode method : quadTreeClass.methods) { if (method.desc.equals("(L" + STAR_CLASS + ";)Z")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.PUTFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(quadTreeClass.name) && fieldInsn.desc.equals("L" + STAR_CLASS + ";")) { if (quadtreeInsert != null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "insert", "Collision"); } quadtreeInsert = method.name; continue methodLoop; } } } } else if (method.desc.equals("(FFFF)V") && method.name.equals("")) { if (method.parameters == null) { method.parameters = new ArrayList<>(); } if (method.parameters.isEmpty()) { method.parameters.add(new ParameterNode("x1", Opcodes.ACC_FINAL)); method.parameters.add(new ParameterNode("y1", Opcodes.ACC_FINAL)); method.parameters.add(new ParameterNode("x2", Opcodes.ACC_FINAL)); method.parameters.add(new ParameterNode("y2", Opcodes.ACC_FINAL)); } String[] fieldNames = new String[method.maxLocals]; for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.FLOAD) { VarInsnNode varInsn = (VarInsnNode) insn; if (insn.getNext().getOpcode() == Opcodes.PUTFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn.getNext(); if (fieldInsn.owner.equals(quadTreeClass.name)) { fieldNames[varInsn.var] = fieldInsn.name; } } } } quadtreeX1 = fieldNames[1]; quadtreeY1 = fieldNames[2]; quadtreeX2 = fieldNames[3]; quadtreeY2 = fieldNames[4]; } } if (quadtreeInsert == null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "insert", "Unresolved"); } if (quadtreeX1 == null || quadtreeX2 == null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "x1/x2", "Unresolved"); } if (quadtreeY1 == null || quadtreeY2 == null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "y1/y2", "Unresolved"); } remapClass(mappingsStream, quadTreeClass.name, QUAD_TREE_CLASS); remapMethod(mappingsStream, quadTreeClass.name, quadtreeInsert, "insert", "(L" + STAR_CLASS + ";)Z"); remapField(mappingsStream, quadTreeClass.name, quadtreeX1, "x1", "F"); remapField(mappingsStream, quadTreeClass.name, quadtreeY1, "y1", "F"); remapField(mappingsStream, quadTreeClass.name, quadtreeX2, "x2", "F"); remapField(mappingsStream, quadTreeClass.name, quadtreeY2, "y2", "F"); ClassNode spaceNode = name2Node.get(SPACE_CLASS); if (spaceNode == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "*", "Unresolved"); } String buildPairsMethod = null; String regenerateVoronoiCellsMethod = null; String setMottoMethod = null; String resetSpecialsMethod = null; String registerEmployerMethod = null; String quadTreeField = null; String spaceConnectStarsMethod = null; String spaceDisconnectStarsMethod = null; String restoreQuadTreeMethod = null; for (MethodNode method : spaceNode.methods) { if (method.desc.equals("(IL" + MAPDATA_CLASS + ";)V")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Generating galaxy: Building pairs")) { MethodInsnNode methodInsn = getNext(ldcInsn, Opcodes.INVOKEVIRTUAL); if (!methodInsn.owner.equals(quadTreeClass.name) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "buildPairs", "Unexpected owner or descriptor"); } if (buildPairsMethod != null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "buildPairs", "Collision"); } buildPairsMethod = methodInsn.name; } else if (ldcInsn.cst.equals("Generating galaxy: Generating star regions")) { AbstractInsnNode target = getNext(ldcInsn.getNext()); if (target.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "regenerateVoronoiCells", "Unexpected opcode"); } MethodInsnNode methodInsn = (MethodInsnNode) target; if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "regenerateVoronoiCells", "Unexpected owner or descriptor"); } if (regenerateVoronoiCellsMethod != null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "regenerateVoronoiCells", "Collision"); } regenerateVoronoiCellsMethod = methodInsn.name; } else if (ldcInsn.cst.equals("Leave us alone")) { AbstractInsnNode target = ldcInsn.getNext(); if (target.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "setMotto", "Unexpected opcode"); } MethodInsnNode methodInsn = (MethodInsnNode) target; if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("(Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "setMotto", "Unexpected owner or descriptor"); } if (setMottoMethod != null) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "setMotto", "Collision"); } setMottoMethod = methodInsn.name; target = ldcInsn.getPrevious().getPrevious(); while (target.getOpcode() == -1) { target = target.getPrevious(); } if (target.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "resetSpecials", "Unexpected opcode"); } methodInsn = (MethodInsnNode) target; if (!methodInsn.owner.equals(EMPIRE_CLASS) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "resetSpecials", "Unexpected owner or descriptor (it is: " + new MethodReference(methodInsn) + ')'); } if (resetSpecialsMethod != null) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "resetSpecials", "Collision"); } resetSpecialsMethod = methodInsn.name; } } else if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.name.equals(quadtreeInsert) && methodInsn.desc.equals("(L" + STAR_CLASS + ";)Z") && methodInsn.owner.equals(quadTreeClass.name)) { MethodInsnNode target = getNext(methodInsn, Opcodes.INVOKEVIRTUAL); if (!target.owner.equals(EMPLOYMENT_AGENCY_CLASS)) { throw new OutdatedDeobfuscatorException("Star", EMPLOYMENT_AGENCY_CLASS, "registerEmployer", "Unexpected owner"); } else if (!target.desc.equals("(L" + EMPLOYER_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Star", EMPLOYMENT_AGENCY_CLASS, "registerEmployer", "Unexpected descriptor"); } else if (registerEmployerMethod != null) { throw new OutdatedDeobfuscatorException("Star", EMPLOYMENT_AGENCY_CLASS, "registerEmployer", "Collision"); } else { registerEmployerMethod = method.name; } AbstractInsnNode previous = methodInsn.getPrevious().getPrevious(); if (previous.getOpcode() != Opcodes.GETSTATIC) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "starsQuadTree", "Unexpected opcode"); } FieldInsnNode fieldInsn = (FieldInsnNode) previous; if (!fieldInsn.owner.equals(SPACE_CLASS) || !fieldInsn.desc.equals("L" + quadTreeClass.name + ";")) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "starsQuadTree", "Unexpected owner or descriptor"); } if (quadTreeField != null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "starsQuadTree", "Collision"); } quadTreeField = fieldInsn.name; } } } } else if (method.desc.equals("(L" + STAR_CLASS + ";L" + STAR_CLASS + ";)V")) { boolean disconnect = false; boolean connect = false; for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(STAR_CLASS) && methodInsn.desc.equals("(L" + STAR_CLASS + ";)V")) { if (methodInsn.name.equals(connectMethod)) { connect = true; } else if (methodInsn.name.equals(disconnectMethod)) { disconnect = true; } } } } if (connect && !disconnect) { if (spaceConnectStarsMethod != null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "connectStars", "Collision"); } spaceConnectStarsMethod = method.name; } else if (!connect && disconnect) { if (spaceDisconnectStarsMethod != null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "disconnectStars", "Collision"); } spaceDisconnectStarsMethod = method.name; } } else if (method.desc.equals("()V")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("-> Edges")) { AbstractInsnNode previous = ldcInsn.getPrevious(); while (previous.getOpcode() == -1) { previous = previous.getPrevious(); } if (previous.getOpcode() != Opcodes.INVOKESTATIC) { dumpMethod(method); throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "restoreQuadtree", "Unexpected opcode - it is a " + previous.getClass().getName() + " Opcode " + previous.getOpcode()); } MethodInsnNode methodInsn = (MethodInsnNode) previous; if (!methodInsn.owner.equals(SPACE_CLASS) || !methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "restoreQuadtree", "Unexpected owner or descriptor"); } boolean isValidTarget = false; for (MethodNode method2 : spaceNode.methods) { if (!method2.desc.equals("()V") || !method2.name.equals(methodInsn.name)) { continue; } for (AbstractInsnNode insn2 = method2.instructions.getFirst(); insn2 != null; insn2 = insn2.getNext()) { if (insn2.getOpcode() == Opcodes.NEW) { TypeInsnNode typeInsn = (TypeInsnNode) insn2; if (typeInsn.desc.equals(quadTreeClass.name)) { isValidTarget = true; break; } } } break; } if (!isValidTarget) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "restoreQuadtree", "Nonsensical contents"); } if (restoreQuadTreeMethod != null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "restoreQuadtree", "Collision"); } restoreQuadTreeMethod = methodInsn.name; break; } } } } } if (buildPairsMethod == null) { throw new OutdatedDeobfuscatorException("Star", QUAD_TREE_CLASS, "buildPairs", "Unresolved"); } if (regenerateVoronoiCellsMethod == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "regenerateVoronoiCells", "Unresolved"); } if (setMottoMethod == null) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "setMotto", "Unresolved"); } if (resetSpecialsMethod == null) { throw new OutdatedDeobfuscatorException("Star", EMPIRE_CLASS, "resetSpecials", "Unresolved"); } if (registerEmployerMethod == null) { throw new OutdatedDeobfuscatorException("Star", EMPLOYMENT_AGENCY_CLASS, "registerEmployer", "Unresolved"); } if (quadTreeField == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "starsQuadTree", "Unresolved"); } if (spaceConnectStarsMethod == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "connectStars", "Unresolved"); } if (spaceDisconnectStarsMethod == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "disconnectStars", "Unresolved"); } if (restoreQuadTreeMethod == null) { throw new OutdatedDeobfuscatorException("Star", SPACE_CLASS, "restoreQuadtree", "Unresolved"); } remapMethod(mappingsStream, quadTreeClass.name, buildPairsMethod, "buildPairs", "()V"); remapMethod(mappingsStream, SPACE_CLASS, regenerateVoronoiCellsMethod, "regenerateVoronoiCells", "()V"); remapMethod(mappingsStream, EMPIRE_CLASS, setMottoMethod, "setMotto", "(Ljava/lang/String;)V"); remapMethod(mappingsStream, EMPIRE_CLASS, resetSpecialsMethod, "resetSpecials", "()V"); remapMethod(mappingsStream, EMPLOYMENT_AGENCY_CLASS, registerEmployerMethod, "registerEmployer", "(L" + EMPLOYER_CLASS + ";)V"); remapField(mappingsStream, SPACE_CLASS, quadTreeField, "starsQuadTree", "L" + quadTreeClass.name + ";"); remapMethod(mappingsStream, SPACE_CLASS, spaceConnectStarsMethod, "connectStars", "(L" + STAR_CLASS + ";L" + STAR_CLASS + ";)V"); remapMethod(mappingsStream, SPACE_CLASS, spaceDisconnectStarsMethod, "disconnectStars", "(L" + STAR_CLASS + ";L" + STAR_CLASS + ";)V"); remapMethod(mappingsStream, SPACE_CLASS, restoreQuadTreeMethod, "restoreQuadtree", "()V"); String naiveRestoreQuadTreeMethod = null; methodLoop: for (MethodNode method : spaceNode.methods) { if (method.desc.equals("()V")) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() == Opcodes.PUTSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(SPACE_CLASS) && fieldInsn.name.equals(quadTreeField) && fieldInsn.desc.equals("L" + quadTreeClass.name +";")) { if (method.name.equals(restoreQuadTreeMethod)) { // -> "paranoid" variant, not of relevance // For those wondering: The difference between the naive and the paranoid variant is that the // paranoid variant infers the map size from the star count and the aspect ratio, where as // the naive variant obtains the size from the X/Y size cache. continue methodLoop; } else if (naiveRestoreQuadTreeMethod != null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "naiveRestoreQuadtree", "Collision"); } else { naiveRestoreQuadTreeMethod = method.name; } } } } } } if (naiveRestoreQuadTreeMethod == null) { throw new OutdatedDeobfuscatorException("Space", SPACE_CLASS, "naiveRestoreQuadtree", "Unresolved"); } remapMethod(mappingsStream, SPACE_CLASS, naiveRestoreQuadTreeMethod, "naiveRestoreQuadtree", "()V"); } /** * Remaps the characteristic "this$0" field that can be found in anonymous inner classes. * Also remaps anonymous classes to the more characteristic naming scheme of anonymous classes * * @param remapper The remapper to use for remapping * @deprecated Not used, only dumped here for future use. Does work however */ @Deprecated public void remapThisZero(Remapper remapper) { Map innerClasses = new HashMap<>(); for (ClassNode node : nodes) { for (InnerClassNode icn : node.innerClasses) { if (icn.innerName == null && icn.name.equals(node.name)) { if (innerClasses.containsKey(icn.name)) { innerClasses.put(icn.name, null); } else { innerClasses.put(icn.name, icn.outerName); } break; } } } for (ClassNode node : nodes) { String outerName = innerClasses.get(node.name); if (outerName == null) { continue; } int lastSlash = node.name.lastIndexOf('/'); int lastDollar = node.name.lastIndexOf('$', lastSlash); if (lastDollar == -1) { int indexOfUnderscore = node.name.indexOf('_', lastSlash); if (node.name.indexOf('_', indexOfUnderscore) != -1) { remapper.remapClassName(node.name, outerName + '$' + node.name.substring(indexOfUnderscore + 1)); } } FieldNode fieldNode = null; boolean imminentCollision = false; for (FieldNode field : node.fields) { if (field.name.equals("this$0")) { imminentCollision = true; } if ((field.access & Opcodes.ACC_SYNTHETIC) == 0) { continue; } if ((field.desc.length() == outerName.length() + 2) && field.desc.startsWith(outerName, 1)) { if (fieldNode != null) { throw new IllegalStateException("Two similar nodes found"); } fieldNode = field; } } if (fieldNode == null) { continue; // Should we remove the InnerClassNode? } if (imminentCollision) { if (!fieldNode.name.equals("this$0")) { throw new IllegalStateException("Field node has strange name: " + node.name + " " + fieldNode.name + fieldNode.desc); } } else { remapper.remapField(node.name, fieldNode.desc, fieldNode.name, "this$0"); } } } /** * Needs to be called after {@link #remapSpaceFields(Writer)}. */ public void remapUIClasses(Writer mappingsStream) throws IOException { String textInputDialogWidget = this.textInputDialogWidgetClass; if (textInputDialogWidget == null) { throw new IllegalStateException("This method (remapUIClasses) has to be invoked after remapSpaceFields as some deobfuscation subroutines " + "depend on the output of the remapSpaceFields deobfuscation run."); } String setTimelapseModifierMethod = null; String galemulatorClass = null; for (ClassNode node : nodes) { if (!node.name.startsWith("com/example/Main$")) { continue; } for (MethodNode method : node.methods) { if (!method.name.equals("checkAndDoStuff")) { continue; } boolean isTimelapseModifierHotkey = false; for (AbstractInsnNode insn : method.instructions) { if (insn instanceof LdcInsnNode) { LdcInsnNode ldc = (LdcInsnNode) insn; if (ldc.cst.equals("New speed is: ")) { isTimelapseModifierHotkey = true; } } else if (isTimelapseModifierHotkey && insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode minsn = (MethodInsnNode) insn; if (!minsn.desc.equals("(I)V")) { continue; } if (setTimelapseModifierMethod != null) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "setTimelapseModifier", "Collision"); } setTimelapseModifierMethod = minsn.name; galemulatorClass = minsn.owner; remapClass(mappingsStream, galemulatorClass, "snoddasmannen/galimulator/Galemulator"); remapMethod(mappingsStream, galemulatorClass, setTimelapseModifierMethod, "setTimelapseModifier", "(I)V"); } } } } if (setTimelapseModifierMethod == null) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "setTimelapseModifier"); } if (galemulatorClass == null) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "*"); } String widgetDrawMethod = null; String widgetRefreshLayoutMethod = null; String bufferedWidgetWrapperClass = null; String sidebarClass = null; String newDynastyWidgetClass = null; String galimulatorGestureListener = null; String settingsDialogClass = null; String selectActorMethod = null; String galaxyPreviewClass = null; String oddityBulletinClass = null; String bulletinDrawAtMethod = null; String textBulletinClass = null; String abstractBulletinClass = null; MethodNode sidebarInitializeMethod = null; for (ClassNode node : nodes) { boolean isGalemulator = false; boolean isSidebar = false; boolean isNewDynastyWidget = false; boolean isGestureListener = false; if (node.interfaces.size() == 1 && node.interfaces.get(0).equals("com/badlogic/gdx/ApplicationListener") && node.name.startsWith(BASE_PACKAGE)) { // I know that some class is called "Galemulator" (sic) as snoddasmannen disclosed that on the discord. // I am pretty sure that the application listener is that class. // If the Space class weren't deobfuscated, I would think that the Space class would be called like that - // in old Starmap Space was actually called "Galimulator". So as there is precedence of me guessing names wrongly, // which might also apply here. // really, it would be an interesting experience should galimulator drop all obfuscation. // At this point we use the deobfuscated names too much that adapting to the actual names // may be a bit hard to do isGalemulator = true; if (!galemulatorClass.equals(node.name)) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "*", "Collision. Previous analysis: " + galemulatorClass + ". This method: " + node.name); } } else if (node.interfaces.size() == 1 && node.interfaces.get(0).equals(GDX_GESTURE_LISTENER_CLASS) && node.name.startsWith(BASE_PACKAGE)) { if (galimulatorGestureListener != null) { throw new OutdatedDeobfuscatorException("GestureListener", "GalimulatorGestureListener", "*", "Multiple candidates found"); } galimulatorGestureListener = node.name; remapClass(mappingsStream, node.name, BASE_PACKAGE + "GalimulatorGestureListener"); isGestureListener = true; } else if (node.superName.equals(WIDGET_CLASS)) { boolean doubleClearBuffer = false; boolean openWindow = false; for (MethodNode method : node.methods) { if (!method.desc.equals("()V")) { continue; } for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Attempted double clear of buffer: ")) { doubleClearBuffer = true; break; } else if (ldcInsn.cst.equals("Attempted double clear of buffer in draw(): ")) { widgetDrawMethod = method.name; openWindow = true; break; } else if (ldcInsn.cst.equals("settingsbutton.png") && node.name.startsWith(UI_PACKAGE)) { sidebarInitializeMethod = method; isSidebar = true; break; } else if (ldcInsn.cst.equals("Create a new Dynasty") && method.name.equals("") && node.name.startsWith(UI_PACKAGE)) { if (newDynastyWidgetClass != null) { throw new OutdatedDeobfuscatorException("Widget", "Multiple candidates for NewDynastyWidget found!"); } newDynastyWidgetClass = node.name; isNewDynastyWidget = true; break; } else if (ldcInsn.cst.equals("Preview not supported for this galaxy type :(")) { if (galaxyPreviewClass != null) { throw new OutdatedDeobfuscatorException("UI", GALAXY_PREVIEW_WIDGET_CLASS, "*", "Collision"); } galaxyPreviewClass = node.name; remapClass(mappingsStream, galaxyPreviewClass, GALAXY_PREVIEW_WIDGET_CLASS); break; } } } } if (doubleClearBuffer != openWindow) { throw new IllegalStateException("Found insufficent non-zero evidence of BufferedWidgetWrapper instance."); } if (doubleClearBuffer && openWindow) { if (bufferedWidgetWrapperClass != null) { throw new IllegalStateException("The deobfuscator for the BufferedWidgetWrapper class must be updated (suspecting multiple classes to be BufferedWidgetWrapper)"); } remapClass(mappingsStream, node.name, "snoddasmannen/galimulator/ui/BufferedWidgetWrapper"); bufferedWidgetWrapperClass = node.name; continue; } } else if ((node.access & Opcodes.ACC_INTERFACE) == 0 && node.interfaces.size() == 1) { for (MethodNode method : node.methods) { if (method.name.equals("getTitle")) { AbstractInsnNode insn = getNext(method.instructions.getFirst()); if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Settings")) { if (settingsDialogClass != null) { throw new OutdatedDeobfuscatorException("Dialog", "SettingsDialog", "*", "Collision"); } settingsDialogClass = node.name; } } } } continue; } else if (node.name.equals(LOCATION_SELECTED_EFFECT_CLASS)) { for (MethodNode method : node.methods) { if (method.name.equals("draw") && method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); String projectMethod = null; while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(GALFX_CLASS)) { if (methodInsn.desc.equals("(Lcom/badlogic/gdx/math/Vector3;)V")) { if (projectMethod != null) { throw new OutdatedDeobfuscatorException("GalFX", GALFX_CLASS, "projectBoardToScreen", "Collision"); } projectMethod = methodInsn.name; } } } insn = insn.getNext(); } if (projectMethod == null) { throw new OutdatedDeobfuscatorException("GalFX", GALFX_CLASS, "projectBoardToScreen", "Not found"); } remapMethod(mappingsStream, GALFX_CLASS, projectMethod, "projectBoardToScreen", "(Lcom/badlogic/gdx/math/Vector3;)V"); break; } } continue; } else if (node.methods.size() == 2 && node.fields.size() == 3) { for (MethodNode method : node.methods) { if (!method.desc.equals("(FF)V")) { continue; } AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Space oddity!")) { break; } insn = insn.getNext(); } if (insn == null) { break; } if (oddityBulletinClass != null) { throw new OutdatedDeobfuscatorException("GalFX", ODDITY_BULLETIN_CLASS, "*", "Collision"); } oddityBulletinClass = node.name; textBulletinClass = node.superName; abstractBulletinClass = name2Node.get(textBulletinClass).superName; bulletinDrawAtMethod = method.name; insn = insn.getNext().getNext().getNext(); if (insn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("GalFX", GALFX_CLASS, "drawText", "Invalid follow-up instruction"); } MethodInsnNode drawTextInsn = (MethodInsnNode) insn; if (!drawTextInsn.owner.equals(GALFX_CLASS) || !drawTextInsn.desc.equals("(FFLjava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;)F")) { throw new OutdatedDeobfuscatorException("GalFX", GALFX_CLASS, "drawText", "Owner or descriptor mismatch"); } remapMethod(mappingsStream, GALFX_CLASS, drawTextInsn.name, "drawText", "(FFLjava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;)F"); } continue; } else { continue; } if (isGalemulator) { MethodNode renderMethod = null; MethodNode setTimelapseModifierMethodNode = null; for (MethodNode method : node.methods) { if (method.name.equals("render") && method.desc.equals("()V")) { renderMethod = method; } else if (method.name.equals(setTimelapseModifierMethod) && method.desc.equals("(I)V")) { setTimelapseModifierMethodNode = method; } } if (renderMethod == null) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (render method not found)"); } if (setTimelapseModifierMethodNode == null) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "setTimelapseSpeed", "Node cannot be resolved"); } String timelapseModifierField = null; for (AbstractInsnNode insn : setTimelapseModifierMethodNode.instructions) { if (insn.getOpcode() != Opcodes.PUTSTATIC) { continue; } if (timelapseModifierField != null) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "timelapseModifier", "Collison"); } FieldInsnNode fieldInsn = (FieldInsnNode) insn; timelapseModifierField = fieldInsn.name; if (!fieldInsn.desc.equals("I")) { throw new OutdatedDeobfuscatorException("UI", "Galemulator", "timelapseModifer", "Descriptor mismatch"); } } if (timelapseModifierField == null) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (No timelapseModifier field found)"); } boolean firstLdcDrawPassed = false; boolean secondLdcDrawPassed = false; boolean foundSpaceDrawMethod = false; boolean foundSpaceRecomputeVisibleWidgets = false; for (AbstractInsnNode insn : renderMethod.instructions) { if (insn instanceof LdcInsnNode) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Draw")) { if (firstLdcDrawPassed) { if (secondLdcDrawPassed) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (three or more ldc draw present)"); } AbstractInsnNode iteratedInsn = insn.getNext(); while (iteratedInsn.getOpcode() != Opcodes.INVOKESTATIC) { iteratedInsn = iteratedInsn.getNext(); } MethodInsnNode methodInsn = (MethodInsnNode) iteratedInsn; if (!methodInsn.owner.equals(DEBUG_CLASS) || !methodInsn.desc.equals("(Ljava/lang/String;)I")) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (debug end call not present)"); } remapMethod(mappingsStream, methodInsn.owner, methodInsn.name, "endDebuggingSection", methodInsn.desc); secondLdcDrawPassed = true; } else { AbstractInsnNode iteratedInsn = insn.getNext(); while (iteratedInsn.getOpcode() != Opcodes.INVOKESTATIC) { iteratedInsn = iteratedInsn.getNext(); } MethodInsnNode methodInsn = (MethodInsnNode) iteratedInsn; if (!methodInsn.owner.equals(DEBUG_CLASS) || !methodInsn.desc.equals("(Ljava/lang/String;Z)V")) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (debug start call not present)"); } remapMethod(mappingsStream, methodInsn.owner, methodInsn.name, "startDebuggingSection", methodInsn.desc); firstLdcDrawPassed = true; } } } else if (insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (firstLdcDrawPassed && !secondLdcDrawPassed && methodInsn.owner.equals(SPACE_CLASS)) { if (methodInsn.desc.equals("()V")) { if (foundSpaceRecomputeVisibleWidgets) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (multiple Space#recomputeVisibleWidget methods found). New one: " + methodInsn.name + methodInsn.desc); } remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, "recomputeVisibleWidgets", methodInsn.desc); foundSpaceRecomputeVisibleWidgets = true; } else { // Descriptor should be something along the lines of (Lsnoddasmannen/galimulator/rendersystem/class_3;)V if (foundSpaceDrawMethod) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (multiple Space#draw methods found). New one: " + methodInsn.name + methodInsn.desc); } foundSpaceDrawMethod = true; remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, "draw", methodInsn.desc); } } } } if (!firstLdcDrawPassed) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (ldc draw not found)"); } if (!foundSpaceDrawMethod || !foundSpaceRecomputeVisibleWidgets) { throw new OutdatedDeobfuscatorException("UI", "One of the draw method block methods couldn't vbe resolved."); } boolean foundTimelapseSpeedGetter = false; boolean foundTimelapseSpeedSetter = false; for (MethodNode method : node.methods) { if (method.desc.equals("()I")) { AbstractInsnNode insn = method.instructions.getLast().getPrevious(); while (insn.getOpcode() == -1) { insn = insn.getPrevious(); } if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode getStaticInsn = (FieldInsnNode) insn; if (getStaticInsn.desc.equals("I") && getStaticInsn.owner.equals(node.name) && getStaticInsn.name.equals(timelapseModifierField)) { if (foundTimelapseSpeedGetter) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (Found multiple getTimelapseModifier methods)"); } foundTimelapseSpeedGetter = true; remapMethod(mappingsStream, node.name, method.name, "getTimelapseModifier", "()I"); } } } else if (method.desc.equals("(I)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() == -1) { // Filter out pseudo-opcodes insn = insn.getNext(); } if (insn.getOpcode() != Opcodes.ILOAD) { continue; } insn = insn.getNext(); while (insn.getOpcode() == -1) { insn = insn.getNext(); } if (insn.getOpcode() == Opcodes.PUTSTATIC) { FieldInsnNode getStaticInsn = (FieldInsnNode) insn; if (getStaticInsn.desc.equals("I") && getStaticInsn.owner.equals(node.name) && getStaticInsn.name.equals(timelapseModifierField)) { if (foundTimelapseSpeedSetter) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (Found multiple setTimelapseModifier methods)"); } foundTimelapseSpeedSetter = true; remapMethod(mappingsStream, node.name, method.name, "setTimelapseModifier", "(I)V"); } } } } if (!foundTimelapseSpeedGetter) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (Found no getTimelapseModifier method)"); } if (!foundTimelapseSpeedSetter) { throw new IllegalStateException("The deobfuscator for the Galemulator class is out of date (Found no setTimelapseModifier method)"); } } else if (isSidebar) { if (sidebarClass != null) { throw new OutdatedDeobfuscatorException("Sidebar", "Multiple potential Sidebar classes detected."); } sidebarClass = node.name; remapClass(mappingsStream, sidebarClass, UI_PACKAGE + "SidebarWidget"); String openGameControlMethod = null; for (MethodNode method : node.methods) { if ((method.access & Opcodes.ACC_STATIC) == 0 || !method.desc.equals("()V")) { continue; } AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Game control")) { if (openGameControlMethod != null) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget", "openGameControl", "Collision"); } insn = insn.getNext(); if (insn.getOpcode() != Opcodes.LDC || !((LdcInsnNode) insn).cst.equals("Game control")) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget", "openGameControl", "Duplicate LDC instruction not duplicated."); } openGameControlMethod = method.name; MethodInsnNode invokestatic = getNext(insn, Opcodes.INVOKESTATIC); if (!invokestatic.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "openOptionChooser", "Wrong class"); } String returnType = invokestatic.desc.substring(invokestatic.desc.lastIndexOf(')') + 1); if (returnType.codePointAt(0) != 'L') { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "openOptionChooser", "Wrong return type"); } remapMethod(mappingsStream, SPACE_CLASS, invokestatic.name, "openOptionChooser", invokestatic.desc); String optionChooserWidgetClass = returnType.substring(1, returnType.length() - 1); remapClass(mappingsStream, optionChooserWidgetClass, UI_PACKAGE + "OptionChooserWidget"); TypeInsnNode typeInsn = getNext(invokestatic, Opcodes.NEW); if (!typeInsn.desc.startsWith(UI_PACKAGE)) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget$", "*", "Package mismatch"); } insn = typeInsn.getNext() // DUP .getNext() // INVOKESPECIAL .getNext(); // INVOKEVIRTUAL if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget$", "*", "Opcode mismatch"); } MethodInsnNode registerListenerInsn = (MethodInsnNode) insn; if (!registerListenerInsn.owner.equals(optionChooserWidgetClass)) { throw new OutdatedDeobfuscatorException("Sidebar", "OptionChooserWidget", "registerListener", "Owner mismatch"); } remapMethod(mappingsStream, optionChooserWidgetClass, registerListenerInsn.name, "registerSelectionListener", registerListenerInsn.desc); String listenerInterface = registerListenerInsn.desc.substring(2, registerListenerInsn.desc.length() - 3); remapClass(mappingsStream, listenerInterface, UI_PACKAGE + "OptionSelectionListener"); ClassNode listenerImplNode = name2Node.get(typeInsn.desc); if (listenerImplNode == null) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget$", "*", "Missing as a node"); } if (listenerImplNode.methods.size() != 2) { throw new OutdatedDeobfuscatorException("Sidebare", "SidebarWidget$", "*", "Too many (or too few) methods"); } MethodNode listenerImplMethod = listenerImplNode.methods.get(0); if (listenerImplMethod.name.equals("")) { listenerImplMethod = listenerImplNode.methods.get(1); } int hitCount = 0; insn = getNext(listenerImplMethod.instructions.getFirst(), Opcodes.LDC); while (insn != null) { Object cst = ((LdcInsnNode) insn).cst; if (!(cst instanceof String)) { insn = getNextOrNull(insn, Opcodes.LDC); continue; } switch ((String) cst) { case "Create a new galaxy": { hitCount++; MethodInsnNode createGalaxyInsn = getNext(insn, Opcodes.INVOKESTATIC); if (!createGalaxyInsn.owner.equals(SPACE_CLASS) || !createGalaxyInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "showGalaxyCreationScreen", "Mismatching owner or descriptor."); } insn = createGalaxyInsn; remapMethod(mappingsStream, SPACE_CLASS, createGalaxyInsn.name, "showGalaxyCreationScreen", "()V"); break; } case "Load autosave": { hitCount++; MethodInsnNode loadSaveMethod = getNext(insn, Opcodes.INVOKESTATIC); if (!loadSaveMethod.owner.equals(SPACE_CLASS) || !loadSaveMethod.desc.equals("(Ljava/lang/String;)V")) { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "loadAsync", "Mismatching owner or descriptor."); } insn = loadSaveMethod; remapMethod(mappingsStream, SPACE_CLASS, loadSaveMethod.name, "loadAsync", "(Ljava/lang/String;)V"); break; } case "Edit scenario": { hitCount++; MethodInsnNode openDialogMethod = getNext(getNext(insn, Opcodes.INVOKESTATIC), Opcodes.INVOKESTATIC); if (!openDialogMethod.owner.equals(SPACE_CLASS) || openDialogMethod.desc.codePointBefore(openDialogMethod.desc.length()) != 'V') { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "showDialog", "Mismatching owner or descriptor."); } insn = openDialogMethod; remapMethod(mappingsStream, SPACE_CLASS, openDialogMethod.name, "showDialog", openDialogMethod.desc); break; } case "Online scenarios": { hitCount++; MethodInsnNode showScenarioBrowserInsn = getNext(insn, Opcodes.INVOKESTATIC); if (!showScenarioBrowserInsn.owner.equals(SPACE_CLASS) || !showScenarioBrowserInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "showOnlineScenarioBrowser", "Mismatching owner or descriptor."); } insn = showScenarioBrowserInsn; remapMethod(mappingsStream, SPACE_CLASS, showScenarioBrowserInsn.name, "showOnlineScenarioBrowser", "()V"); break; } case "Share a mod": { hitCount++; MethodInsnNode showWidgetInsn = getNext(insn, Opcodes.INVOKESTATIC); if (!showWidgetInsn.owner.equals(SPACE_CLASS) || !showWidgetInsn.desc.equals("(Ljava/lang/Class;)V")) { throw new OutdatedDeobfuscatorException("Sidebar", "Space", "showWidget", "Mismatching owner or descriptor."); } insn = showWidgetInsn; remapMethod(mappingsStream, SPACE_CLASS, showWidgetInsn.name, "showWidget", "(Ljava/lang/Class;)V"); AbstractInsnNode previousInsn = showWidgetInsn.getPrevious(); if (previousInsn.getOpcode() != Opcodes.LDC) { throw new OutdatedDeobfuscatorException("Sidebar", "ModUploadWidget", "*", "Opcode mismatch"); } LdcInsnNode previousLdc = (LdcInsnNode) previousInsn; if (!(previousLdc.cst instanceof Type)) { throw new OutdatedDeobfuscatorException("Sidebar", "ModUploadWidget", "*", "Expected LDC TYPE, got " + previousLdc.cst.getClass().toString()); } remapClass(mappingsStream, ((Type) previousLdc.cst).getInternalName(), UI_PACKAGE + "ModUploadWidget"); break; } } insn = getNextOrNull(insn, Opcodes.LDC); } if (hitCount != 5) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget$", "N/A", "hit count mismatch"); } break; } insn = insn.getNext(); } } if (openGameControlMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "SidebarWidget", "openGameControl", "Absent"); } remapMethod(mappingsStream, sidebarClass, openGameControlMethod, "openGameControl", "()V"); } else if (isNewDynastyWidget) { String refreshLayoutImplMethod = null; for (MethodNode method : node.methods) { if ((method.access & Opcodes.ACC_PRIVATE) == 0 || !method.desc.equals("()V")) { continue; } for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC && ((LdcInsnNode) insn).cst.equals("Name: ")) { if (refreshLayoutImplMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Multiple candidates for NewDynastyWidget#refreshLayout0"); } remapMethod(mappingsStream, node.name, method.name, "refreshLayout0", "()V"); refreshLayoutImplMethod = method.name; break; } } } if (refreshLayoutImplMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "NewDynastyWidget", "refreshLayout0"); } for (MethodNode method : node.methods) { if ((method.access & Opcodes.ACC_PUBLIC) == 0 || !method.desc.equals("()V") || method.name.equals("")) { continue; } for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.name.equals(refreshLayoutImplMethod) && methodInsn.desc.equals("()V")) { if (widgetRefreshLayoutMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Multiple candidates for Widget#refreshLayout"); } widgetRefreshLayoutMethod = method.name; break; } } } } if (widgetRefreshLayoutMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "refreshLayout"); } } else if (isGestureListener) { MethodNode tapMethod = null; for (MethodNode method : node.methods) { if (method.name.equals("tap") && method.desc.equals("(FFII)Z")) { tapMethod = method; break; } } if (tapMethod == null) { throw new OutdatedDeobfuscatorException("GestureListener", "GestureListener", "tap"); } MethodInsnNode getTickLoopLockInsn = null; for (AbstractInsnNode insn : tapMethod.instructions) { if (insn.getOpcode() == Opcodes.INVOKESTATIC) { getTickLoopLockInsn = (MethodInsnNode) insn; break; } } if (getTickLoopLockInsn == null || !getTickLoopLockInsn.desc.equals("()Ljava/util/concurrent/Semaphore;") || !getTickLoopLockInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "getMainTickLoopLock"); } String mainTickLoopLockFieldName = null; ClassNode spaceNode = name2Node.get(SPACE_CLASS); for (MethodNode method : spaceNode.methods) { if (method.name.equals(getTickLoopLockInsn.name) && method.desc.equals("()Ljava/util/concurrent/Semaphore;")) { FieldInsnNode finsn = null; AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.GETSTATIC) { if (finsn != null) { throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "MAIN_TICK_LOOP_LOCK", "Collision"); } finsn = (FieldInsnNode) insn; } insn = insn.getNext(); } if (finsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "MAIN_TICK_LOOP_LOCK", "GETSTATIC missing"); } if (!finsn.desc.equals("Ljava/util/concurrent/Semaphore;")) { throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "MAIN_TICK_LOOP_LOCK", "Invalid descriptor"); } mainTickLoopLockFieldName = finsn.name; break; } } if (mainTickLoopLockFieldName == null) { throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "MAIN_TICK_LOOP_LOCK", "Entirely missing"); } remapMethod(mappingsStream, SPACE_CLASS, getTickLoopLockInsn.name, "getMainTickLoopLock", "()Ljava/util/concurrent/Semaphore;"); remapField(mappingsStream, SPACE_CLASS, mainTickLoopLockFieldName, "MAIN_TICK_LOOP_LOCK", "Ljava/util/concurrent/Semaphore;"); MethodInsnNode isShowingActorsMethod = null; for (AbstractInsnNode insn : tapMethod.instructions) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.desc.equals("()Z") && methodInsn.owner.equals(MAP_MODE_ENUM_CLASS)) { if (isShowingActorsMethod != null) { throw new OutdatedDeobfuscatorException("GestureListener", MAP_MODE_ENUM_CLASS, "getShowsActors", "Collision"); } isShowingActorsMethod = methodInsn; } } if (isShowingActorsMethod == null) { throw new OutdatedDeobfuscatorException("GestureListener", MAP_MODE_ENUM_CLASS, "getShowsActors", "Unresolved"); } if (isShowingActorsMethod.getNext().getOpcode() != Opcodes.IFEQ) { throw new OutdatedDeobfuscatorException("GestureListener", "Unexpected next opcode"); } AbstractInsnNode nextInsn = isShowingActorsMethod.getNext().getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "getActorNear", "Missing instruction"); } MethodInsnNode getActorNearMethodInsn = (MethodInsnNode) nextInsn; if (!getActorNearMethodInsn.owner.equals(SPACE_CLASS) || !getActorNearMethodInsn.desc.equals("(FFL" + EMPIRE_CLASS + ";F)L" + ACTOR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "findNearestActor", "Unexpected nature of the instruction"); } remapMethod(mappingsStream, SPACE_CLASS, getActorNearMethodInsn.name, "findNearestActor", "(FFL" + EMPIRE_CLASS + ";F)L" + ACTOR_CLASS + ";"); nextInsn = nextInsn.getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "selectActor", "Missing instruction"); } MethodInsnNode actorSelectionInsn = (MethodInsnNode) nextInsn; if (!actorSelectionInsn.owner.equals(SPACE_CLASS) || !actorSelectionInsn.desc.equals("(L" + ACTOR_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "selectActor", "Unexpected nature of the instruction"); } selectActorMethod = actorSelectionInsn.name; remapMethod(mappingsStream, SPACE_CLASS, selectActorMethod, "selectActor", "(L" + ACTOR_CLASS + ";)V"); nextInsn = nextInsn.getNext(); while (nextInsn != null && (nextInsn.getOpcode() != Opcodes.GETSTATIC || !((FieldInsnNode) nextInsn).owner.equals(STAR_CLASS))) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Star", "globalSizeFactor", "Missing instruction"); } FieldInsnNode getSizeFactorInsn = (FieldInsnNode) nextInsn; if (!getSizeFactorInsn.desc.equals("F")) { throw new OutdatedDeobfuscatorException("GestureListener", "Star", "globalSizeFactor", "Unexpected descriptor of field"); } remapField(mappingsStream, STAR_CLASS, getSizeFactorInsn.name, "globalSizeFactor", "F"); nextInsn = nextInsn.getNext(); while (nextInsn != null && nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "findStarNear", "Instructions exhausted"); } MethodInsnNode findStarNearInsn = (MethodInsnNode) nextInsn; if (!findStarNearInsn.owner.equals(SPACE_CLASS) || !findStarNearInsn.desc.equals("(FFDL" + EMPIRE_CLASS + ";)L" + STAR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("GestureListener", "Space", "findStarNear", "Unexpected nature of the instruction"); } remapMethod(mappingsStream, SPACE_CLASS, findStarNearInsn.name, "findStarNear", "(FFDL" + EMPIRE_CLASS + ";)L" + STAR_CLASS + ";"); } } if (bufferedWidgetWrapperClass == null) { throw new IllegalStateException("Unable to find the BufferedWidgetWrapper class."); } if (newDynastyWidgetClass == null) { throw new OutdatedDeobfuscatorException("Widget", "NewDynastyWidget", "*"); } if (galimulatorGestureListener == null) { throw new OutdatedDeobfuscatorException("GestureListener", "GalimulatorGestureListener", "*", "No candidates found"); } if (settingsDialogClass == null) { throw new OutdatedDeobfuscatorException("Dialog", "SettingsDialog", "*", "No candidates found"); } if (oddityBulletinClass == null) { throw new OutdatedDeobfuscatorException("UI", ODDITY_BULLETIN_CLASS, "*", "Not found"); } if (textBulletinClass == null) { throw new OutdatedDeobfuscatorException("UI", TEXT_BULLETIN_CLASS, "*", "Not found"); } if (abstractBulletinClass == null) { throw new OutdatedDeobfuscatorException("UI", ABSTRACT_BULLETIN_CLASS, "*", "Not found"); } remapClass(mappingsStream, newDynastyWidgetClass, UI_PACKAGE + "NewDynastyWidget"); remapClass(mappingsStream, oddityBulletinClass, ODDITY_BULLETIN_CLASS); remapClass(mappingsStream, textBulletinClass, TEXT_BULLETIN_CLASS); remapClass(mappingsStream, abstractBulletinClass, ABSTRACT_BULLETIN_CLASS); remapMethod(mappingsStream, oddityBulletinClass, bulletinDrawAtMethod, "drawAt", "(FF)V"); remapMethod(mappingsStream, textBulletinClass, bulletinDrawAtMethod, "drawAt", "(FF)V"); remapMethod(mappingsStream, abstractBulletinClass, bulletinDrawAtMethod, "drawAt", "(FF)V"); String getWidgetWidthMethod = null; String widgetDrawHeaderMethod = null; String widgetHeaderColorField = null; String widgetHeaderTitleField = null; String fxDrawWindowMethod = null; String fxDrawTextMethod = null; String fxFullDrawTextureMethod = null; String widgetCameraField = null; String widgetOnDisposeMethod = null; String widgetHoverMethod = null; String closeNonPersistentWidgetsMethod = null; String ninepatchButtonClass = null; String shipConstructionWidgetClass = null; String basicButtonSetTextMethod = null; String basicButtonSetColorMethod = null; String empireBulletinClass = null; for (ClassNode node : nodes) { if (node.name.equals(SPACE_CLASS)) { boolean foundShowWidgetMethod = false; boolean foundTickMethod = false; for (MethodNode method : node.methods) { if (method.desc.equals("()I")) { if (method.name.equals(spaceLogicalTickMethodName)) { AbstractInsnNode firstInsn = getNext(method.instructions.getFirst()); if (firstInsn.getOpcode() != Opcodes.GETSTATIC) { throw new OutdatedDeobfuscatorException("UI", "Space", "tickCount", "Unexpected opcode"); } FieldInsnNode fieldInsn = (FieldInsnNode) firstInsn; if (!fieldInsn.desc.equals("I")) { throw new OutdatedDeobfuscatorException("UI", "Space", "tickCount", "Unexpected descriptor"); } remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, "tickCount", "I"); foundTickMethod = true; } continue; } if (!method.desc.equals("(L" + WIDGET_CLASS + ";)V")) { continue; } boolean wasShowWidgetMethod = false; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.name.equals("") && methodInsn.owner.equals(bufferedWidgetWrapperClass)) { if (foundShowWidgetMethod) { throw new IllegalStateException("The deobfuscator for the Space class is out of date (Suspecting multiple Space#showWidget methods!)"); } foundShowWidgetMethod = true; wasShowWidgetMethod = true; remapMethod(mappingsStream, SPACE_CLASS, method.name, "showWidget", method.desc); break; } } } if (wasShowWidgetMethod) { AbstractInsnNode insn = method.instructions.getFirst(); MethodInsnNode invokeGetScreenWidth; MethodInsnNode invokeGetWidthWidth; while (insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getNext(); } closeNonPersistentWidgetsMethod = ((MethodInsnNode) insn).name; insn = insn.getNext(); while (insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getNext(); } invokeGetScreenWidth = (MethodInsnNode) insn; while (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { insn = insn.getNext(); } invokeGetWidthWidth = (MethodInsnNode) insn; if (invokeGetWidthWidth.desc.equals("()I") && invokeGetWidthWidth.owner.equals(WIDGET_CLASS)) { if (getWidgetWidthMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Unable to surely say which method is Widget#getWidth) (TRAP 1)"); } getWidgetWidthMethod = invokeGetWidthWidth.name; } else { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Unable to surely say which method is Widget#getWidth) (TRAP 2)"); } remapMethod(mappingsStream, GALFX_CLASS, invokeGetScreenWidth.name, "getScreenWidth", "()F"); } } if (!foundShowWidgetMethod) { throw new IllegalStateException("The deobfuscator for the Space class is out of date (Found no Space#showWidget method)"); } if (!foundTickMethod) { throw new IllegalStateException("The deobfuscator for the Space class is out of date (Found no Space#tick method)"); } } else if (node.name.equals(WIDGET_CLASS)) { for (MethodNode method : node.methods) { if (method.desc.equals("()V")) { boolean isDrawheaderMethod = false; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("xbutton.png")) { if (widgetDrawHeaderMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Found multiple void-returning methods that make use of xbutton.png)"); } isDrawheaderMethod = true; widgetDrawHeaderMethod = method.name; AbstractInsnNode nextInsn = ldcInsn.getNext(); if (nextInsn.getOpcode() != Opcodes.INVOKESTATIC) { throw new IllegalStateException("The deobfuscator for the GalFX class is out of date (Unable to find GalFX#getTextureRegion)"); } MethodInsnNode methodInsn = (MethodInsnNode) nextInsn; if (!methodInsn.owner.equals(GALFX_CLASS) || !methodInsn.desc.equals("(Ljava/lang/String;)Lcom/badlogic/gdx/graphics/g2d/TextureRegion;")) { throw new IllegalStateException("The deobfuscator for the GalFX class is out of date (Unable to find GalFX#getTextureRegion)"); } remapMethod(mappingsStream, GALFX_CLASS, methodInsn.name, "getTextureRegion", "(Ljava/lang/String;)Lcom/badlogic/gdx/graphics/g2d/TextureRegion;"); break; } } } if (!isDrawheaderMethod) { continue; } String cameraField = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.GETFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(WIDGET_CLASS)) { continue; } if (fieldInsn.desc.equals("Ljava/lang/String;")) { if (widgetHeaderTitleField != null) { throw new IllegalStateException("The deobfuscator for the widget class is out of date (multiple headerTitle fields suspected)"); } widgetHeaderTitleField = fieldInsn.name; } else if (fieldInsn.desc.equals("Lcom/badlogic/gdx/graphics/Camera;")) { if (cameraField != null && !cameraField.equals(fieldInsn.name)) { throw new IllegalStateException("The deobfuscator for the widget class is out of date (multiple internalCamera fields suspected)"); } cameraField = fieldInsn.name; } else if (fieldInsn.desc.equals("Lsnoddasmannen/galimulator/GalColor;")) { if (widgetHeaderColorField != null) { throw new IllegalStateException("The deobfuscator for the widget class is out of date (multiple headerColor fields suspected)"); } widgetHeaderColorField = fieldInsn.name; } } else if (insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (fxDrawWindowMethod == null) { if (!methodInsn.owner.equals(GALFX_CLASS)) { continue; } if (!methodInsn.desc.equals("(FFFFLsnoddasmannen/galimulator/GalColor;Lcom/badlogic/gdx/graphics/Camera;)V")) { continue; } fxDrawWindowMethod = methodInsn.name; } else if (fxDrawTextMethod == null) { if (!methodInsn.owner.equals(GALFX_CLASS) || !methodInsn.desc.equals(GALFX_DRAW_TEXT_DESCRIPTOR)) { continue; } fxDrawTextMethod = methodInsn.name; } else if (fxFullDrawTextureMethod == null) { if (!methodInsn.owner.equals(GALFX_CLASS) || !methodInsn.desc.equals(GALFX_DRAW_TEXTURE_DESCRIPTOR)) { continue; } fxFullDrawTextureMethod = methodInsn.name; } } } widgetCameraField = cameraField; remapField(mappingsStream, WIDGET_CLASS, cameraField, "internalCamera", "Lcom/badlogic/gdx/graphics/Camera;"); remapField(mappingsStream, WIDGET_CLASS, widgetHeaderColorField, "headerColor", "Lsnoddasmannen/galimulator/GalColor;"); remapField(mappingsStream, WIDGET_CLASS, widgetHeaderTitleField, "headerTitle", "Ljava/lang/String;"); if (fxDrawWindowMethod == null) { throw new IllegalStateException("The deobfuscator for the GalFX class is out of date! (Cannot resolve GalFX#drawWindow)"); } if (fxDrawTextMethod == null) { throw new OutdatedDeobfuscatorException("GalFX", "GalFX", "drawText"); } if (fxFullDrawTextureMethod == null) { throw new OutdatedDeobfuscatorException("GalFX", "GalFX", "drawTexture"); } remapMethod(mappingsStream, GALFX_CLASS, fxDrawWindowMethod, "drawWindow", "(FFFFLsnoddasmannen/galimulator/GalColor;Lcom/badlogic/gdx/graphics/Camera;)V"); remapMethod(mappingsStream, GALFX_CLASS, fxDrawTextMethod, "drawText", GALFX_DRAW_TEXT_DESCRIPTOR); remapMethod(mappingsStream, GALFX_CLASS, fxFullDrawTextureMethod, "drawTexture", GALFX_DRAW_TEXTURE_DESCRIPTOR); } } } else if (node.name.equals(bufferedWidgetWrapperClass)) { methodLoop: for (MethodNode method : node.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn instanceof LdcInsnNode) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (!ldcInsn.cst.equals("Attempted double clear of buffer: ")) { continue methodLoop; } if (widgetOnDisposeMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "onDispose", "Collision"); } widgetOnDisposeMethod = method.name; } insn = insn.getNext(); } } } } else if (node.interfaces.size() == 1 && node.interfaces.get(0).equals(GDX_INPUT_PROCESSOR_CLASS)) { boolean isAnonymousInputProcessor = false; for (MethodNode ctor : node.methods) { if (ctor.name.equals("") && ctor.desc.equals("(L" + galemulatorClass + ";)V")) { isAnonymousInputProcessor = true; break; // Short-circuit } } if (isAnonymousInputProcessor) { // Make class actually anonymous remapClass(mappingsStream, node.name, GALEMULATOR_INPUT_PROCESSOR_CLASS); node.outerClass = galemulatorClass; node.outerMethod = "create"; node.outerMethodDesc = "()V"; boolean hasIcn = false; for (InnerClassNode icn : node.innerClasses) { if (icn.name.equals(node.name)) { hasIcn = true; break; } } if (!hasIcn) { InnerClassNode icn = new InnerClassNode(node.name, galemulatorClass, null, Opcodes.ACC_FINAL); node.innerClasses.add(icn); // We assume that the inner class nodes are valid both ways, so we do not bother to check the icns for // duplicates in the galemulator class node name2Node.get(galemulatorClass).innerClasses.add(icn); } // Decompilers act a bit strange if we do not remap that field { final String expectedDesc = 'L' + galemulatorClass + ';'; for (FieldNode field : node.fields) { if (field.desc.equals(expectedDesc)) { remapField(mappingsStream, node.name, field.name, "this$0", expectedDesc); break; } } } // Actually deobf the contents of the methods for (MethodNode method : node.methods) { if (method.desc.equals("(II)Z") && method.name.equals("mouseMoved")) { AbstractInsnNode insn = method.instructions.getFirst(); insn = getNext(insn, Opcodes.GETFIELD).getNext(); if (insn.getOpcode() != Opcodes.INVOKESTATIC) { throw new OutdatedDeobfuscatorException("Widget", "Galemulator", "access$000", "Unexpected opcode"); } MethodInsnNode invokeAccess000 = (MethodInsnNode) insn; if (!invokeAccess000.owner.equals(galemulatorClass)) { throw new OutdatedDeobfuscatorException("Widget", "Galemulator", "access$000", "Wrong owner"); } remapMethod(mappingsStream, galemulatorClass, invokeAccess000.name, "access$000", invokeAccess000.desc); FieldInsnNode getMouseMoveIdInsn = getNext(insn, Opcodes.GETSTATIC); if (!getMouseMoveIdInsn.desc.equals("I")) { throw new OutdatedDeobfuscatorException("Widget", "Galemulator", "access$000", "Wrong descriptor"); } remapField(mappingsStream, galemulatorClass, getMouseMoveIdInsn.name, "mouseMoveId", "I"); insn = getMouseMoveIdInsn.getNext(); MethodInsnNode unprojectCoordsInsn = getNext(insn, Opcodes.INVOKESTATIC); if (!unprojectCoordsInsn.desc.equals("(Lcom/badlogic/gdx/math/Vector3;)V")) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "unprojectScreenToWidget", "Wrong descriptor"); } if (!unprojectCoordsInsn.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "unprojectScreenToWidget", "Wrong owner"); } remapMethod(mappingsStream, GALFX_CLASS, unprojectCoordsInsn.name, "unprojectScreenToWidget", "(Lcom/badlogic/gdx/math/Vector3;)V"); MethodInsnNode getScreenHeightInsn = getNext(unprojectCoordsInsn, Opcodes.INVOKESTATIC); if (!getScreenHeightInsn.desc.equals("()I")) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "getScreenHeight", "Wrong descriptor"); } if (!getScreenHeightInsn.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "getScreenHeight", "Wrong owner"); } remapMethod(mappingsStream, GALFX_CLASS, getScreenHeightInsn.name, "getScreenHeight", "()I"); insn = getScreenHeightInsn.getNext(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(WIDGET_CLASS) && methodInsn.desc.equals("(FFZ)V")) { widgetHoverMethod = methodInsn.name; break; } } insn = insn.getNext(); } } } } } else if (node.superName.equals(abstractBulletinClass)) { if (node.name.equals(textBulletinClass)) { continue; } remapMethod(mappingsStream, node.name, bulletinDrawAtMethod, "drawAt", "(FF)V"); if (empireBulletinClass != null) { throw new OutdatedDeobfuscatorException("UI", EMPIRE_BULLETIN_CLASS, "*", "Collision"); } empireBulletinClass = node.name; } else { methodLoop: for (MethodNode method : node.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn = (LdcInsnNode) insn; if (ldcInsn.cst.equals("Tap to select location")) { ninepatchButtonClass = node.superName; for (FieldNode field : node.fields) { if (field.desc.startsWith("L" + UI_PACKAGE)) { if (shipConstructionWidgetClass != null) { throw new OutdatedDeobfuscatorException("ShipConstruction", SHIP_CONSTRUCTION_WIDGET_CLASS, "*", "Collision"); } shipConstructionWidgetClass = field.desc.substring(1, field.desc.length() - 1); remapField(mappingsStream, node.name, field.name, "this$0", field.desc); ClassNode outerClassNode = name2Node.get(shipConstructionWidgetClass); if (outerClassNode == null) { throw new OutdatedDeobfuscatorException("ShipConstruction", SHIP_CONSTRUCTION_WIDGET_CLASS, "*", "Node not found"); } assignAsAnonymousClass(outerClassNode, node, "", "()V"); remapClass(mappingsStream, node.name, SHIP_CONSTRUCTION_WIDGET_LOCATION_SELECTOR_CLASS); } } AbstractInsnNode nextInsn = insn.getNext(); if (nextInsn.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("ShipConstruction", BASIC_BUTTON_CLASS, "setButtonText", "Unexpected opcode"); } basicButtonSetTextMethod = ((MethodInsnNode) nextInsn).name; methodLoop2: for (MethodNode method2 : node.methods) { AbstractInsnNode instruction = method2.instructions.getFirst(); while (instruction != null) { if (instruction.getOpcode() == Opcodes.LDC) { LdcInsnNode ldcInsn2 = (LdcInsnNode) instruction; if (ldcInsn2.cst.equals("Placing ...")) { AbstractInsnNode prevInsn = instruction.getPrevious(); while (prevInsn.getOpcode() != Opcodes.INVOKEVIRTUAL) { prevInsn = prevInsn.getPrevious(); } MethodInsnNode prevMethod = (MethodInsnNode) prevInsn; if (!prevMethod.desc.equals("(L" + GALCOLOR_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("ShipConstruction", BASIC_BUTTON_CLASS, "setButtonColor", "Unexpected descriptor"); } basicButtonSetColorMethod = prevMethod.name; while (instruction != null) { if (instruction.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) instruction; if (methodInsn.owner.equals(SPACE_CLASS)) { remapMethod(mappingsStream, SPACE_CLASS, methodInsn.name, SPACE_ADD_AUXILIARY_LISTENER, methodInsn.desc); String auxListenerClassName = methodInsn.desc.substring(2, methodInsn.desc.length() - 3); if (!name2Node.containsKey(auxListenerClassName)) { throw new OutdatedDeobfuscatorException("ShipConstruction", AUXILIARY_LISTENER_CLASS, "*", "Node not found (searched for " + auxListenerClassName + ")"); } remapClass(mappingsStream, auxListenerClassName, AUXILIARY_LISTENER_CLASS); ClassNode auxListenerImpl = name2Node.get(((MethodInsnNode) methodInsn.getPrevious()).owner); if (auxListenerImpl.interfaces.size() != 1 || !auxListenerImpl.interfaces.get(0).equals(auxListenerClassName)) { throw new OutdatedDeobfuscatorException("ShipConstruction", "Unable to find the anonymous class that implements " + AUXILIARY_LISTENER_CLASS); } for (MethodNode method3: auxListenerImpl.methods) { if (!method3.desc.equals("(FF)Z") || !method3.name.equals("globalTap")) { continue; } AbstractInsnNode insn3 = method3.instructions.getFirst(); MethodInsnNode unprojectToBoard = getNext(insn3, Opcodes.INVOKESTATIC); if (!unprojectToBoard.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("ShipConstruction", GALFX_CLASS, "unprojectScreenToBoard", "Wrong owner class"); } MethodInsnNode findStarNear = getNext(unprojectToBoard, Opcodes.INVOKESTATIC); if (!findStarNear.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("ShipConstruction", SPACE_CLASS, "findStarNear(FF)", "Wrong owner class"); } remapMethod(mappingsStream, SPACE_CLASS, findStarNear.name, "findStarNear", "(FF)L" + STAR_CLASS + ";"); remapMethod(mappingsStream, GALFX_CLASS, unprojectToBoard.name, "unprojectScreenToBoard", "(FF)Lcom/badlogic/gdx/math/Vector3;"); MethodInsnNode getOwningEmpire = getNext(findStarNear, Opcodes.INVOKEVIRTUAL); if (!getOwningEmpire.owner.equals(STAR_CLASS)) { throw new OutdatedDeobfuscatorException("ShipConstruction", STAR_CLASS, "getOwningEmpire", "Assertion failed"); } MethodInsnNode getEmpireColor = getNext(getOwningEmpire, Opcodes.INVOKEVIRTUAL); if (!getEmpireColor.desc.equals("()L" + GALCOLOR_CLASS + ";")) { throw new OutdatedDeobfuscatorException("ShipConstruction", EMPIRE_CLASS, "getDarkerColor", "Wrong desc (Method actually is " + new MethodReference(getEmpireColor) + ")"); } if (!getEmpireColor.owner.equals(EMPIRE_CLASS)) { throw new OutdatedDeobfuscatorException("ShipConstruction", EMPIRE_CLASS, "getDarkerColor", "Wrong class"); } MethodInsnNode setEffectColor = (MethodInsnNode) getEmpireColor.getNext(); if (!setEffectColor.desc.equals("(L" + GALCOLOR_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("ShipConstruction", LOCATION_SELECTED_EFFECT_CLASS, "setColor", "Wrong desc"); } remapMethod(mappingsStream, EMPIRE_CLASS, getEmpireColor.name, "getDarkerColor", "()L" + GALCOLOR_CLASS + ";"); remapMethod(mappingsStream, LOCATION_SELECTED_EFFECT_CLASS, setEffectColor.name, "setColor", "(L" + GALCOLOR_CLASS + ";)V"); insn3 = setEffectColor.getNext(); while (insn3 != null) { if (insn3.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn3 = (MethodInsnNode) insn3; if (methodInsn3.owner.equals(SPACE_CLASS)) { if (!methodInsn3.desc.equals("(L" + ITEM_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("ShipCosntruction", SPACE_CLASS, "showItem", "Unexpected desc"); } remapMethod(mappingsStream, SPACE_CLASS, methodInsn3.name, "showItem", "(L" + ITEM_CLASS + ";)V"); break; } } insn3 = insn3.getNext(); } MethodInsnNode playSample = getNext(insn3, Opcodes.INVOKEVIRTUAL); if (!playSample.owner.equals(AUDIO_SAMPLE_CLASS) || !playSample.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("ShipConstruction", AUDIO_SAMPLE_CLASS, "play", "Invalid descriptor or owner class"); } remapMethod(mappingsStream, AUDIO_SAMPLE_CLASS, playSample.name, "play", "()V"); MethodInsnNode removeThisInsn = getNext(playSample, Opcodes.INVOKESTATIC); if (!removeThisInsn.owner.equals(SPACE_CLASS)) { throw new OutdatedDeobfuscatorException("ShipConstruction", "Space", "removeAuxiliaryListener", "Wrong owner"); } remapMethod(mappingsStream, SPACE_CLASS, removeThisInsn.name, "removeAuxiliaryListener", removeThisInsn.desc); break; } break; } } instruction = instruction.getNext(); } break methodLoop2; } } instruction = instruction.getNext(); } } break methodLoop; } } insn = insn.getNext(); } } } } } if (sidebarInitializeMethod == null) { throw new IllegalStateException("logic error"); } if (widgetCameraField == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "internalCamera"); } if (widgetOnDisposeMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "tick", "Not found"); } if (widgetHoverMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "hover"); } if (closeNonPersistentWidgetsMethod == null) { throw new OutdatedDeobfuscatorException("Space", "closeNonPersistentWidgets", "Not found"); } if (ninepatchButtonClass == null) { throw new OutdatedDeobfuscatorException("ShipConstruction", NINEPATCH_BUTTON, "*", "Not found"); } if (shipConstructionWidgetClass == null) { throw new OutdatedDeobfuscatorException("ShipConstruction", SHIP_CONSTRUCTION_WIDGET_CLASS, "*", "Not found"); } if (galaxyPreviewClass == null) { throw new OutdatedDeobfuscatorException("UI", GALAXY_PREVIEW_WIDGET_CLASS, "*", "Not found"); } if (empireBulletinClass == null) { throw new OutdatedDeobfuscatorException("UI", EMPIRE_BULLETIN_CLASS, "*", "Not found"); } if (fxFullDrawTextureMethod == null) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, GALFX_DRAW_TEXTURE_DESCRIPTOR, "Not found"); } ClassNode ninepatchButtonNode = name2Node.get(ninepatchButtonClass); String baseButtonClass = ninepatchButtonNode.superName; ClassNode baseButtonNode = name2Node.get(baseButtonClass); remapMethod(mappingsStream, SPACE_CLASS, closeNonPersistentWidgetsMethod, "closeNonPersistentWidgets", "()V"); remapMethod(mappingsStream, sidebarClass, sidebarInitializeMethod.name, "reinitElements", "()V"); remapClass(mappingsStream, ninepatchButtonClass, NINEPATCH_BUTTON); remapClass(mappingsStream, baseButtonClass, BASIC_BUTTON_CLASS); remapClass(mappingsStream, shipConstructionWidgetClass, SHIP_CONSTRUCTION_WIDGET_CLASS); remapClass(mappingsStream, empireBulletinClass, EMPIRE_BULLETIN_CLASS); String widgetClearChildrenMethod = null; String widgetAddChildMethod = null; String widgetDrawDefaultBackgroundMethod = null; final String widgetAddChildMethodDescriptor = "(L" + WIDGET_CLASS + ";)L" + WIDGET_CLASS + ";"; String starGeneratorInterface = null; String fxDrawNinepatchMethod = null; String baseButtonRenderBackgroundMethod = null; String fxDrawTextMethod2 = null; String fxRendercacheThreadlocal = null; int fxAltDrawTexturesRemapCount = 0; String fxDrawRectMethod = null; ClassNode widgetClass = this.name2Node.get(WIDGET_CLASS); { // Let's not pollute the method with even more local variables AbstractInsnNode first = sidebarInitializeMethod.instructions.getFirst(); while (first.getOpcode() == -1) { first = first.getNext(); } if (first.getOpcode() != Opcodes.ALOAD) { throw new OutdatedDeobfuscatorException("Widget", "Widget#clearChildren cannot be resolved (first opcode not ALOAD)"); } first = getNext(first); if (first.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Widget", "Widget#clearChildren cannto be resolved (unexpected bytecode)"); } MethodInsnNode clearChildren = (MethodInsnNode) first; widgetClearChildrenMethod = clearChildren.name; if (!clearChildren.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "clearChildren", "unexpected Descriptor"); } first = first.getNext(); while (first.getOpcode() != Opcodes.INVOKESPECIAL) { first = first.getNext(); } first = getNext(first); if (first.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "addChild", "Unexpected opcode"); } MethodInsnNode addChild = (MethodInsnNode) first; if (!addChild.desc.equals(widgetAddChildMethodDescriptor)) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "addChild", "unexpected Descriptor"); } widgetAddChildMethod = addChild.name; ClassNode galaxyPreview = name2Node.get(galaxyPreviewClass); ClassNode galFXNode = name2Node.get(GALFX_CLASS); for (MethodNode method : galaxyPreview.methods) { if (method.name.equals("")) { if (starGeneratorInterface != null) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "*", "Collision"); } starGeneratorInterface = method.desc.substring(2, method.desc.length() - 3); if (!starGeneratorInterface.startsWith(BASE_PACKAGE)) { throw new AssertionError("Programmer error:" + starGeneratorInterface); } String generatorField = null; String previewLocationsField = null; for (FieldNode field : galaxyPreview.fields) { if (field.desc.equals('L' + starGeneratorInterface + ';')) { if (generatorField != null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "generator", "Collision"); } generatorField = field.name; } else if (field.desc.equals("Ljava/util/ArrayList;")) { if (previewLocationsField != null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "previewLocations", "Collision"); } previewLocationsField = field.name; } } if (generatorField == null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "generator", "Not present"); } if (previewLocationsField == null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "previewLocations", "Not present"); } remapField(mappingsStream, galaxyPreviewClass, generatorField, "generator", 'L' + starGeneratorInterface + ';'); remapField(mappingsStream, galaxyPreviewClass, previewLocationsField, "previewLocations", "Ljava/util/ArrayList;"); String allowPreviewMethod = null; String setPreviewGeneratorMethod = null; for (MethodNode method2 : galaxyPreview.methods) { if (method2.name.equals(widgetRefreshLayoutMethod) && method2.desc.equals("()V")) { for (AbstractInsnNode insn = method2.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.GETFIELD) { continue; } FieldInsnNode fInsn = (FieldInsnNode) insn; if (!fInsn.name.equals(generatorField) || !fInsn.desc.equals('L' + starGeneratorInterface + ';')) { continue; } if (allowPreviewMethod != null) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "allowPreview", "Collision"); } fInsn = getNext(fInsn, Opcodes.GETFIELD); if (!fInsn.name.equals(generatorField) || !fInsn.desc.equals('L' + starGeneratorInterface + ';')) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "allowPreview", "Expected preceeding null check potentially missing"); } AbstractInsnNode nextInsn = getNext(fInsn); if (nextInsn.getOpcode() != Opcodes.INVOKEINTERFACE) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "allowPreview", "Unexpected opcode; type " + nextInsn.getClass()); } MethodInsnNode methodInsn = (MethodInsnNode) nextInsn; if (!methodInsn.owner.equals(starGeneratorInterface) || !methodInsn.desc.equals("()Z")) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "allowPreview", "Unexpected owner or descriptor"); } allowPreviewMethod = methodInsn.name; break; } } else if (method2.desc.equals("(L" + starGeneratorInterface + ";)V") && !method2.name.equals("")) { LdcInsnNode ldcInsn = getNextOrNull(method2.instructions.getFirst(), Opcodes.LDC); while (ldcInsn != null && !ldcInsn.cst.equals("Setting generator to: ")) { ldcInsn = getNextOrNull(ldcInsn, Opcodes.LDC); } if (ldcInsn == null) { continue; } if (setPreviewGeneratorMethod != null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "setGenerator", "Collision"); } setPreviewGeneratorMethod = method2.name; MethodInsnNode mInsn = getNextOrNull(ldcInsn, Opcodes.INVOKEVIRTUAL); while (mInsn != null && (mInsn.owner.equals("java/lang/StringBuilder") || mInsn.owner.equals("java/io/PrintStream"))) { mInsn = getNextOrNull(mInsn, Opcodes.INVOKEVIRTUAL); } if (mInsn == null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "reconfigureGenerator", "Instructions exhausted"); } if (!mInsn.owner.equals(galaxyPreviewClass)) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "reconfigureGenerator", "Unexpected owner: " + mInsn.owner + "." + mInsn.name + mInsn.desc); } if (!mInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "reconfigureGenerator", "Unexpected descriptor: " + mInsn.owner + "." + mInsn.name + mInsn.desc); } remapMethod(mappingsStream, galaxyPreviewClass, mInsn.name, "reconfigureGenerator", "()V"); while ((mInsn = getNextOrNull(mInsn, Opcodes.INVOKEVIRTUAL)) != null) { if (mInsn.owner.equals(galaxyPreviewClass) && mInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "reconfigureGenerator", "Collision"); } } } } if (allowPreviewMethod == null) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "allowPreview", "Missing/Absent reference"); } if (setPreviewGeneratorMethod == null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "setGenerator", "Not present"); } remapMethod(mappingsStream, starGeneratorInterface, allowPreviewMethod, "allowPreview", "()Z"); remapMethod(mappingsStream, galaxyPreviewClass, setPreviewGeneratorMethod, "setGenerator", "(L" + starGeneratorInterface + ";)V"); } else if (method.name.equals(widgetRefreshLayoutMethod) && method.desc.equals("()V")) { String targettedMethod = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(galaxyPreviewClass)) { if (targettedMethod != null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "addGeneratedStar", "Collision"); } targettedMethod = methodInsn.name; remapMethod(mappingsStream, galaxyPreviewClass, methodInsn.name, "addGeneratedStar", "()V"); } } } if (targettedMethod == null) { throw new OutdatedDeobfuscatorException("Widget", GALAXY_PREVIEW_WIDGET_CLASS, "addGeneratedStar", "Not found"); } String starCountField = null; for (MethodNode method2 : galaxyPreview.methods) { if (method2.name.equals(targettedMethod) && method2.desc.equals("()V")) { for (AbstractInsnNode insn : method2.instructions) { if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (fieldInsn.owner.equals(SPACE_CLASS) && fieldInsn.desc.equals("F")) { if (starCountField != null) { throw new OutdatedDeobfuscatorException("Widget", SPACE_CLASS, "starCount", "Collision"); } // Why would the star count - which should be an integer - be a float? starCountField = fieldInsn.name; } } } break; } } if (starCountField == null) { throw new OutdatedDeobfuscatorException("Widget", SPACE_CLASS, "starCount", "Not found"); } remapField(mappingsStream, SPACE_CLASS, starCountField, "starCount", "F"); } else if (method.name.equals(widgetDrawMethod) && method.desc.equals("()V")) { MethodInsnNode mInsn = getNextOrNull(method.instructions.getFirst(), Opcodes.INVOKEVIRTUAL); MethodInsnNode drawDefaultBackgroundInsn = null; while (mInsn != null) { insnCheck: if (mInsn.desc.equals("()V") && mInsn.getPrevious().getOpcode() == Opcodes.ALOAD && ((VarInsnNode) mInsn.getPrevious()).var == 0) { for (MethodNode method2 : widgetClass.methods) { if (method2.name.equals(mInsn.name) && method2.desc.equals("()V")) { FieldInsnNode fInsn = getNextOrNull(method2.instructions.getFirst(), Opcodes.GETSTATIC); if (fInsn == null || !fInsn.owner.equals(GALCOLOR_CLASS) || !fInsn.name.equals("NEAR_SOLID") || !fInsn.desc.equals("L" + GALCOLOR_CLASS + ";")) { break insnCheck; } if (widgetDrawDefaultBackgroundMethod != null) { throw new OutdatedDeobfuscatorException("Widget", WIDGET_CLASS, "drawDefaultBackground", "Collision"); } widgetDrawDefaultBackgroundMethod = mInsn.name; drawDefaultBackgroundInsn = mInsn; } } } mInsn = getNextOrNull(mInsn, Opcodes.INVOKEVIRTUAL); } if (drawDefaultBackgroundInsn == null) { throw new OutdatedDeobfuscatorException("Widget", WIDGET_CLASS, "drawDefaultBackground", "Absent/Missing instruction"); } mInsn = getNextOrNull(drawDefaultBackgroundInsn, Opcodes.INVOKESTATIC); if (!mInsn.owner.equals(GALFX_CLASS) || !mInsn.desc.equals("(FFFFL" + GALCOLOR_CLASS + ";ZL" + GDX_CAMERA_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawRectangle", "Invalid owner or descriptor"); } fxDrawRectMethod = mInsn.name; remapMethod(mappingsStream, GALFX_CLASS, fxDrawRectMethod, "drawRectangle", "(FFFFL" + GALCOLOR_CLASS + ";ZL" + GDX_CAMERA_CLASS + ";)V"); } } // Resolve more GalFX#drawTexture methods methodLoop: for (MethodNode method : galFXNode.methods) { if (method.name.equals(fxDrawRectMethod) && method.desc.equals("(FFFFL" + GALCOLOR_CLASS + ";ZL" + GDX_CAMERA_CLASS + ";)V")) { MethodInsnNode minsn = getNextOrNull(method.instructions.getFirst(), Opcodes.INVOKESTATIC); if (getNextOrNull(minsn, Opcodes.INVOKESTATIC) != null) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawRectangle", "Multiple delegate candidates"); } if (!minsn.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawRectangle", "Invalid delegate owner"); } remapMethod(mappingsStream, GALFX_CLASS, fxDrawRectMethod, "drawRectangle", minsn.desc); methodLoop2: for (MethodNode method2 : galFXNode.methods) { if (method2.name.equals(minsn.name) && method2.desc.equals(minsn.desc)) { MethodInsnNode mInsn2 = getNext(method2.instructions.getFirst(), Opcodes.INVOKESTATIC); mInsn2 = getNext(mInsn2, Opcodes.INVOKESTATIC); final MethodInsnNode witnessInsn = mInsn2; if (!witnessInsn.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawLine", "Invalid owner"); } for (int i = 0; i < 3; i++) { mInsn2 = getNextOrNull(mInsn2, Opcodes.INVOKESTATIC); if (mInsn2 == null) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawLine", "Repeating insn not repeating - exhausted"); } if (!mInsn2.owner.equals(GALFX_CLASS) || !witnessInsn.name.equals(mInsn2.name) || !witnessInsn.desc.equals(mInsn2.desc)) { throw new OutdatedDeobfuscatorException("Widget", GALFX_CLASS, "drawLine", "Repeating insn not repeating - owner, name or desc mismatch"); } } remapMethod(mappingsStream, GALFX_CLASS, mInsn2.name, "drawLine", mInsn2.desc); break methodLoop2; } } continue; } for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != -1 && insn.getOpcode() != Opcodes.ALOAD && insn.getOpcode() != Opcodes.DLOAD && insn.getOpcode() != Opcodes.ILOAD && insn.getOpcode() != Opcodes.ICONST_1 && insn.getOpcode() != Opcodes.INVOKESTATIC && insn.getOpcode() != Opcodes.GETSTATIC && insn.getOpcode() != Opcodes.RETURN) { // The method is not matching the delegator pattern we are scanning for continue methodLoop; } } MethodInsnNode minsn = getNextOrNull(method.instructions.getFirst(), Opcodes.INVOKESTATIC); if (minsn == null || getNextOrNull(getNext(minsn)) != null || !minsn.owner.equals(GALFX_CLASS) || !minsn.name.equals(fxFullDrawTextureMethod) || !minsn.desc.equals(GALFX_DRAW_TEXTURE_DESCRIPTOR)) { continue; } remapMethod(mappingsStream, GALFX_CLASS, method.name, "drawTexture", method.desc); fxAltDrawTexturesRemapCount++; } for (MethodNode method : ninepatchButtonNode.methods) { if (method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); FieldInsnNode finsn = getNextOrNull(insn, Opcodes.GETFIELD); if (finsn != null && finsn.desc.equals("L" + NINEPATCH_CLASS + ";") && finsn.owner.equals(ninepatchButtonClass)) { if (fxDrawNinepatchMethod != null) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawNinepatch", "Collision"); } baseButtonRenderBackgroundMethod = method.name; MethodInsnNode minsn = getNext(finsn, Opcodes.INVOKESTATIC); if (minsn == null || getNextOrNull(minsn, Opcodes.INVOKESTATIC) != null || !minsn.owner.equals(GALFX_CLASS)) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawNinepatch", "Unexpected method structure"); } if (!minsn.desc.equals(GALFX_DRAW_NINEPATCH_DESCRIPTOR)) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawNinepatch", "Unexpected method descriptor"); } fxDrawNinepatchMethod = minsn.name; remapMethod(mappingsStream, GALFX_CLASS, fxDrawNinepatchMethod, "drawNinepatch", GALFX_DRAW_NINEPATCH_DESCRIPTOR); continue; } } } for (MethodNode method : baseButtonNode.methods) { if (method.desc.equals("()V") && method.name.equals(widgetDrawMethod)) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() != Opcodes.INVOKESTATIC) { insn = insn.getNext(); } if (getNext(insn).getOpcode() != Opcodes.RETURN) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawText (with align)", "Unexpected opcode after matched opcode"); } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(GALFX_CLASS) || !methodInsn.desc.equals(GALFX_DRAW_TEXT_DESCRIPTOR_2)) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawText (with align)", "Matched instruction invalid"); } fxDrawTextMethod2 = methodInsn.name; remapMethod(mappingsStream, GALFX_CLASS, fxDrawTextMethod2, "drawText", GALFX_DRAW_TEXT_DESCRIPTOR_2); break; } } for (MethodNode method : galFXNode.methods) { if (method.name.equals(fxDrawTextMethod) && method.desc.equals(GALFX_DRAW_TEXT_DESCRIPTOR)) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode minsn = (MethodInsnNode) insn; if (minsn.owner.equals("java/lang/ThreadLocal") && minsn.name.equals("get") && minsn.desc.equals("()Ljava/lang/Object;")) { insn = insn.getNext(); if (insn.getOpcode() == Opcodes.CHECKCAST) { TypeInsnNode castInsn = (TypeInsnNode) insn; if (castInsn.desc.startsWith(RENDERSYSTEM_PACKAGE)) { AbstractInsnNode gestaticInsn = minsn.getPrevious(); if (gestaticInsn.getOpcode() != Opcodes.GETSTATIC) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "RENDERCACHE_LOCAL", "Unexpected opcode; Expected GETSTATIC"); } FieldInsnNode finsn = (FieldInsnNode) gestaticInsn; if (!finsn.owner.equals(GALFX_CLASS) || !finsn.desc.equals("Ljava/lang/ThreadLocal;")) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "RENDERCACHE_LOCAL", "Instruction invalid"); } fxRendercacheThreadlocal = finsn.name; } } } } insn = insn.getNext(); } break; } } } if (widgetDrawDefaultBackgroundMethod == null) { throw new OutdatedDeobfuscatorException("Widget", WIDGET_CLASS, "drawDefaultBackground", "Not found"); } if (widgetClearChildrenMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "clearChildren"); } if (widgetAddChildMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "addChild"); } if (getWidgetWidthMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#getWidth)"); } if (widgetDrawHeaderMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#drawHeader)"); } if (starGeneratorInterface == null) { throw new OutdatedDeobfuscatorException("Widget", STAR_GENERATOR_INTERFACE, "*", "Not found"); } if (fxAltDrawTexturesRemapCount != 2) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawTexture (alternative)", "Hit " + fxAltDrawTexturesRemapCount + ", expected 2."); } if (fxDrawNinepatchMethod == null) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawNinepatch", "Not found"); } if (fxDrawTextMethod2 == null) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "drawText (with align)", "Not found"); } if (fxRendercacheThreadlocal == null) { throw new OutdatedDeobfuscatorException("UI", GALFX_CLASS, "RENDERCACHE_LOCAL", "Not found"); } remapClass(mappingsStream, starGeneratorInterface, STAR_GENERATOR_INTERFACE); remapField(mappingsStream, GALFX_CLASS, fxRendercacheThreadlocal, "RENDERCACHE_LOCAL", "Ljava/lang/ThreadLocal;"); String activeWidgetsField = null; String widgetIsPersistentMethod = null; String closeWidgetMethod = null; ClassNode spaceClassNode = name2Node.get(SPACE_CLASS); for (MethodNode method : spaceClassNode.methods) { // Woo! Loops! if (method.name.equals(closeNonPersistentWidgetsMethod) && method.desc.equals("()V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() != Opcodes.GETSTATIC) { insn = insn.getNext(); } activeWidgetsField = ((FieldInsnNode) insn).name; while (insn != null) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(WIDGET_CLASS) && methodInsn.desc.equals("()Z")) { if (widgetIsPersistentMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "isPersistent", "Collision"); } widgetIsPersistentMethod = methodInsn.name; } } else if (insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(SPACE_CLASS) && methodInsn.desc.equals("(L" + WIDGET_CLASS + ";)V")) { if (closeWidgetMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "closeWidget", "Collision"); } closeWidgetMethod = methodInsn.name; } } insn = insn.getNext(); } break; } } if (activeWidgetsField == null) { throw new OutdatedDeobfuscatorException("Widget", "Space", SPACE_ACTIVE_WIDGETS_FIELD, "not resolved"); } if (widgetIsPersistentMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "isPersistent", "not resolved"); } if (closeWidgetMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "closeWidget", "not resolved"); } remapField(mappingsStream, SPACE_CLASS, activeWidgetsField, SPACE_ACTIVE_WIDGETS_FIELD, "Ljava/util/Vector;"); remapMethod(mappingsStream, SPACE_CLASS, closeWidgetMethod, "closeWidget", "(L" + WIDGET_CLASS + ";)V"); for (MethodNode method : spaceClassNode.methods) { if (method.name.equals(closeWidgetMethod) && method.desc.equals("(L" + WIDGET_CLASS + ";)V")) { AbstractInsnNode insn = method.instructions.getFirst(); while (insn.getOpcode() != Opcodes.GETSTATIC) { insn = insn.getNext(); } FieldInsnNode fieldInsn = (FieldInsnNode) insn; remapField(mappingsStream, SPACE_CLASS, fieldInsn.name, "closedWidgets", "Ljava/util/Vector;"); break; } } if (widgetClass.interfaces.size() != 1) { throw new OutdatedDeobfuscatorException("Widget", "Widget implements more/fewer classes than expected."); } String widgetGetHeightMethod = null; String getXMethod = null; String getYMethod = null; String containsPointMethod = null; String widgetSetHeaderColorMethod = null; String widgetSetHeaderTitleMethod = null; String widgetGetHeaderTitleMethod = null; String widgetDrawBackgroundMethod = null; String widgetLayoutClass = null; String widgetLayoutNewlineMethod = null; String widgetLayoutRecomputeMethod = null; String widgetChildrenField = null; String widgetPositioningField = null; String widgetGetPositioningMethod = null; String widgetSetPositioningMethod = null; String widgetPropagateMessageLocally = null; String widgetMessageRecieverClass = widgetClass.interfaces.get(0); if (!widgetMessageRecieverClass.startsWith(BASE_PACKAGE) || widgetMessageRecieverClass.startsWith(UI_PACKAGE)) { throw new OutdatedDeobfuscatorException("Widget", "Widget implements an unexpected interface."); } remapClass(mappingsStream, widgetMessageRecieverClass, BASE_PACKAGE + "WidgetMessageReciever"); for (FieldNode field : widgetClass.fields) { if (field.desc.equals("L" + WIDGET_POSITIONING_CLASS + ";")) { if (widgetPositioningField != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "positioning", "Collision"); } widgetPositioningField = field.name; remapField(mappingsStream, WIDGET_CLASS, field.name, "positioning", field.desc); } } if (widgetPositioningField == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "positioning"); } String widgetRecieveMessageMethod = null; ClassNode widgetMessageRecieverClassNode = name2Node.get(widgetMessageRecieverClass); if (widgetMessageRecieverClassNode.methods.size() != 1) { throw new OutdatedDeobfuscatorException("Widget", "WidgetMessageReciever", "recieveMessage", "Unexpected amounts of methods in the WidgetMessageReciever class."); } if (!widgetMessageRecieverClassNode.methods.get(0).desc.equals("(L" + WIDGET_MESSAGE_CLASS + ";)V")) { throw new OutdatedDeobfuscatorException("Widget", "WidgetMessageReciever", "recieveMessage", "Unexpected descriptor."); } widgetRecieveMessageMethod = widgetMessageRecieverClassNode.methods.get(0).name; for (MethodNode method : widgetClass.methods) { if (method.desc.equals("(Lcom/badlogic/gdx/math/Vector2;)Z")) { if (containsPointMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Detected multiple containsPoint methods)"); } containsPointMethod = method.name; boolean loadedWidth = false; for (AbstractInsnNode insn : method.instructions) { if (insn instanceof MethodInsnNode) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.name.equals("")) { break; } if (methodInsn.owner.equals(WIDGET_CLASS) && methodInsn.desc.startsWith("()")) { // parameter of the constructor for new Rectangle(): x, y, width, height if (getXMethod == null) { getXMethod = methodInsn.name; } else if (getYMethod == null) { getYMethod = methodInsn.name; } else if (!loadedWidth) { loadedWidth = true; if (!getWidgetWidthMethod.equals(methodInsn.name)) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Conflicting information about Widget#getWidth)"); } } else if (widgetGetHeightMethod == null) { widgetGetHeightMethod = methodInsn.name; } else { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Unexpected method call in Widget#containsPoint method)"); } } } } } else if (method.desc.equals("(Lsnoddasmannen/galimulator/GalColor;)V")) { if (isSetter(method, WIDGET_CLASS, widgetHeaderColorField, "Lsnoddasmannen/galimulator/GalColor;")) { if (widgetSetHeaderColorMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Multiple Widget#setHeaderColor methods detected)"); } widgetSetHeaderColorMethod = method.name; } else { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKESTATIC) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(GALFX_CLASS) && methodInsn.name.equals(fxDrawWindowMethod) && methodInsn.desc.equals("(FFFFLsnoddasmannen/galimulator/GalColor;Lcom/badlogic/gdx/graphics/Camera;)V")) { if (widgetDrawBackgroundMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Multiple Widget#drawBackground methods detected)"); } widgetDrawBackgroundMethod = method.name; break; } } } } } else if (method.desc.equals("(Ljava/lang/String;)V")) { if (isSetter(method, WIDGET_CLASS, widgetHeaderTitleField, "Ljava/lang/String;")) { if (widgetSetHeaderTitleMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Multiple Widget#setHeaderTitle methods detected)"); } widgetSetHeaderTitleMethod = method.name; } } else if (method.desc.equals("()Ljava/lang/String;")) { if (isGetter(method, WIDGET_CLASS, widgetHeaderTitleField, "Ljava/lang/String;", false)) { if (widgetGetHeaderTitleMethod != null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Multiple Widget#getHeaderTitle methods detected)"); } widgetGetHeaderTitleMethod = method.name; } } else if (method.name.equals(widgetAddChildMethod) && method.desc.equals(widgetAddChildMethodDescriptor)) { FieldInsnNode firstField = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.PUTFIELD) { FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (!fieldInsn.owner.equals(WIDGET_CLASS) || fieldInsn.getPrevious().getOpcode() != Opcodes.INVOKESPECIAL) { continue; } MethodInsnNode ctor = (MethodInsnNode) fieldInsn.getPrevious(); if (!ctor.owner.equals(FLOW_LAYOUT_CLASS)) { continue; } if (widgetLayoutClass != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "layout", "Multiple candidates assumed"); } widgetLayoutClass = fieldInsn.desc.substring(1, fieldInsn.desc.length() - 1); remapClass(mappingsStream, widgetLayoutClass, UI_PACKAGE + "WidgetLayout"); remapField(mappingsStream, WIDGET_CLASS, fieldInsn.name, "layout", fieldInsn.desc); } else if (insn.getOpcode() == Opcodes.GETFIELD && firstField == null) { firstField = (FieldInsnNode) insn; } } if (widgetLayoutClass == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "layout", "No candidates assumed"); } if (firstField == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "children", "No getfield instructions"); } if (!firstField.owner.equals(WIDGET_CLASS) || !firstField.desc.equals("Ljava/util/Vector;")) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "children", "Descriptor or owner mismatch"); } widgetChildrenField = firstField.name; remapField(mappingsStream, WIDGET_CLASS, firstField.name, "children", firstField.desc); } else if ((method.access & Opcodes.ACC_PUBLIC) != 0 && method.desc.equals("()L" + WIDGET_POSITIONING_CLASS + ";") && isGetter(method, WIDGET_CLASS, widgetPositioningField, "L" + WIDGET_POSITIONING_CLASS + ";", false)) { if (widgetGetPositioningMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getPositioning", "Collision"); } widgetGetPositioningMethod = method.name; } else if ((method.access & Opcodes.ACC_PUBLIC) != 0 && method.desc.equals("(L" + WIDGET_POSITIONING_CLASS + ";)V") && isSetter(method, WIDGET_CLASS, widgetPositioningField, "L" + WIDGET_POSITIONING_CLASS + ";")) { if (widgetSetPositioningMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "setPositioning", "Collision"); } widgetSetPositioningMethod = method.name; } else if ((method.access & Opcodes.ACC_PUBLIC) != 0 && method.desc.equals("(L" + WIDGET_MESSAGE_CLASS + ";)V") && method.name.equals(widgetRecieveMessageMethod)) { for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) { continue; } MethodInsnNode methodInsn = (MethodInsnNode) insn; if (!methodInsn.owner.equals(WIDGET_CLASS) || !methodInsn.desc.equals("(L" + WIDGET_MESSAGE_CLASS + ";)V")) { continue; } if (widgetPropagateMessageLocally != null && !widgetPropagateMessageLocally.equals(methodInsn.name)) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "propagateMessageLocally", "Name mismatch"); } widgetPropagateMessageLocally = methodInsn.name; } if (widgetPropagateMessageLocally == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "propagateMessageLocally", "Instructions exhausted"); } } else if (method.name.equals(widgetHoverMethod) && method.desc.equals("(FFZ)V")) { FieldInsnNode fieldInsn = getNext(method.instructions.getFirst(), Opcodes.GETFIELD); if (!fieldInsn.desc.equals("J")) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "lastRegisteredMouseMevement", "Descriptor mismatch"); } remapField(mappingsStream, WIDGET_CLASS, fieldInsn.name, "lastRegisteredMouseMevement", "J"); } } if (containsPointMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#containsPoint)"); } if (widgetGetHeightMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#getHeight)"); } if (widgetSetHeaderColorMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#setHeaderColor)"); } if (widgetSetHeaderTitleMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#setHeaderTitle)"); } if (widgetGetHeaderTitleMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#getHeaderTitle)"); } if (widgetDrawBackgroundMethod == null) { throw new IllegalStateException("The deobfuscator for the Widget class is out of date (Did not detect Widget#drawBackground)"); } if (widgetChildrenField == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "children", "Not matched"); } if (widgetGetPositioningMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getPositioning", "Not matched"); } if (widgetSetPositioningMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "setPositioning", "Not matched"); } if (widgetPropagateMessageLocally == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "propagateMessageLocally", "Widget#recieveMessage not found"); } if (widgetPropagateMessageLocally.equals(widgetRecieveMessageMethod)) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "propagateMessageLocally", "Assertion failed: propagateMessageLocally is recieveMessage"); } String widgetLayoutGetHeightMethod = null; String widgetLayoutGetWidthMethod = null; String widgetDrawChildrenMethod = null; { AbstractInsnNode last = sidebarInitializeMethod.instructions.getLast(); while (last.getOpcode() == -1 || last.getOpcode() == Opcodes.RETURN) { last = last.getPrevious(); } if (last.getOpcode() != Opcodes.INVOKEVIRTUAL) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "recompute", "Unexpected opcode in sidebar initalize method"); } MethodInsnNode recomputeLayout = (MethodInsnNode) last; if (!recomputeLayout.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "recompute", "Unexpected method descriptor"); } widgetLayoutRecomputeMethod = recomputeLayout.name; last = last.getPrevious(); while (last != null) { if (last.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) last; if (methodInsn.owner.equals(widgetLayoutClass)) { if (!methodInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "newline", "Unexpected method descriptor"); } if (widgetLayoutNewlineMethod != null && !widgetLayoutNewlineMethod.equals(methodInsn.name)) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "newline", "Conflicts found"); } widgetLayoutNewlineMethod = methodInsn.name; } } last = last.getPrevious(); } ClassNode sidebarNode = name2Node.get(sidebarClass); for (MethodNode method : sidebarNode.methods) { if (method.name.equals(widgetGetHeightMethod) && method.desc.equals("()I")) { MethodInsnNode delegateInsn = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { if (delegateInsn != null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getHeight", "Name collision"); } delegateInsn = (MethodInsnNode) insn; if (!delegateInsn.owner.equals(widgetLayoutClass)) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getHeight", "Wrong owner class"); } if (!delegateInsn.desc.equals("()I")) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getHeight", "Invalid descriptor"); } } } if (delegateInsn == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getHeight", "Not referenced"); } widgetLayoutGetHeightMethod = delegateInsn.name; } else if (method.name.equals(getWidgetWidthMethod) && method.desc.equals("()I")) { MethodInsnNode delegateInsn = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { if (delegateInsn != null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getWidth", "Name collision"); } delegateInsn = (MethodInsnNode) insn; if (!delegateInsn.owner.equals(widgetLayoutClass)) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getWidth", "Wrong owner class"); } if (!delegateInsn.desc.equals("()I")) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getWidth", "Invalid descriptor"); } } } if (delegateInsn == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getWidth", "Not referenced"); } widgetLayoutGetWidthMethod = delegateInsn.name; } else if (method.name.equals(widgetDrawMethod) && method.desc.equals("()V")) { MethodInsnNode delegateInsn = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { if (delegateInsn != null) { throw new OutdatedDeobfuscatorException("Sidebar", "Widget", "drawChildren", "Name collision"); } delegateInsn = (MethodInsnNode) insn; if (!delegateInsn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("Sidebar", "Widget", "drawChildren", "Invalid descriptor"); } } } if (delegateInsn == null) { throw new OutdatedDeobfuscatorException("Sidebar", "Widget", "drawChildren", "Not referenced"); } widgetDrawChildrenMethod = delegateInsn.name; } } } if (widgetLayoutRecomputeMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "recompute"); } if (widgetLayoutNewlineMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "newline"); } if (widgetLayoutGetWidthMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getWidth"); } if (widgetLayoutGetHeightMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "WidgetLayout", "getHeight"); } if (widgetDrawChildrenMethod == null) { throw new OutdatedDeobfuscatorException("Sidebar", "Widget", "fillDefaultBackground"); } String widgetGetChildrenMethod = null; String widgetGetCameraMethod = null; for (MethodNode method : widgetClass.methods) { if ((method.access & Opcodes.ACC_PUBLIC) == 0) { continue; } if (method.desc.equals("()Ljava/util/Vector;")) { if (isGetter(method, WIDGET_CLASS, widgetChildrenField, "Ljava/util/Vector;", false)) { if (widgetGetChildrenMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getChildren", "collision"); } widgetGetChildrenMethod = method.name; } } else if (method.desc.equals("()L" + GDX_CAMERA_CLASS + ";")) { if (isGetter(method, WIDGET_CLASS, widgetCameraField, "L" + GDX_CAMERA_CLASS + ";", false)) { if (widgetGetCameraMethod != null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getCamera", "collision"); } widgetGetCameraMethod = method.name; } } } if (widgetGetChildrenMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getChildren", "not resolved"); } if (widgetGetCameraMethod == null) { throw new OutdatedDeobfuscatorException("Widget", "Widget", "getCamera", "not resolved"); } String widgetInterceptMouseDownMethod = null; String widgetMouseDownMethod = null; String widgetConsiderRelayoutMethod = null; { ClassNode gestureListener = name2Node.get(galimulatorGestureListener); for (MethodNode method : gestureListener.methods) { if (method.name.equals("touchDown") && method.desc.equals("(FFII)Z")) { MethodInsnNode containsPointInsn = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(WIDGET_CLASS) && methodInsn.desc.equals("(Lcom/badlogic/gdx/math/Vector2;)Z") && methodInsn.name.equals(containsPointMethod)) { if (containsPointInsn != null) { throw new OutdatedDeobfuscatorException("GestureListener", "Multiple Widget#containsPoint instructions found (touchDown)."); } containsPointInsn = methodInsn; } } } if (containsPointInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "No Widget#containsPoint instructions found (touchDown)."); } AbstractInsnNode nextInsn = containsPointInsn.getNext(); while (nextInsn != null) { if (nextInsn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode insn = (MethodInsnNode) nextInsn; if (insn.desc.equals("(FF)Z") && insn.owner.equals(WIDGET_CLASS)) { widgetInterceptMouseDownMethod = insn.name; break; } } nextInsn = nextInsn.getNext(); } if (widgetInterceptMouseDownMethod == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "interceptMouseDown", "Instructions exhausted"); } while (nextInsn != null) { if (nextInsn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode insn = (MethodInsnNode) nextInsn; if (insn.desc.equals("(DD)V") && insn.owner.equals(WIDGET_CLASS)) { widgetMouseDownMethod = insn.name; insn = getNext(insn, Opcodes.INVOKEVIRTUAL); if (!insn.owner.equals(WIDGET_CLASS) || !insn.desc.equals("()V")) { throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "mouseDown", "Invalid owner or descriptor"); } widgetConsiderRelayoutMethod = insn.name; break; } } nextInsn = nextInsn.getNext(); } if (widgetMouseDownMethod == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "mouseDown", "Instructions exhausted"); } } else if (method.name.equals("tap") && method.desc.equals("(FFII)Z")) { // TODO slated for removal I guess, but does it have another probable use in the (near) future? MethodInsnNode containsPointInsn = null; for (AbstractInsnNode insn : method.instructions) { if (insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { MethodInsnNode methodInsn = (MethodInsnNode) insn; if (methodInsn.owner.equals(WIDGET_CLASS) && methodInsn.desc.equals("(Lcom/badlogic/gdx/math/Vector2;)Z") && methodInsn.name.equals(containsPointMethod)) { if (containsPointInsn != null) { throw new OutdatedDeobfuscatorException("GestureListener", "Multiple Widget#containsPoint instructions found (tap)."); } containsPointInsn = methodInsn; } } } if (containsPointInsn == null) { throw new OutdatedDeobfuscatorException("GestureListener", "No Widget#containsPoint instructions found (tap)."); } } } } if (widgetInterceptMouseDownMethod == null) { throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "onMouseDown"); } mappingsStream.append("#START Remap UI-related methods across hierarchy\n"); for (ClassNode node : nodes) { if (isInstanceofInterface(node, widgetMessageRecieverClass)) { if (isInstanceofWidget(node)) { if (isInstanceofClass(node, baseButtonClass)) { remapMethod(mappingsStream, node.name, basicButtonSetTextMethod, "setButtonText", "(Ljava/lang/String;)V"); remapMethod(mappingsStream, node.name, basicButtonSetColorMethod, "setButtonColor", "(L" + GALCOLOR_CLASS + ";)V"); remapMethod(mappingsStream, node.name, baseButtonRenderBackgroundMethod, "renderButtonBackground", "()V"); } remapMethod(mappingsStream, node.name, widgetDrawMethod, "draw", "()V"); remapMethod(mappingsStream, node.name, widgetDrawHeaderMethod, "drawHeader", "()V"); remapMethod(mappingsStream, node.name, getWidgetWidthMethod, "getWidth", "()I"); remapMethod(mappingsStream, node.name, widgetGetHeightMethod, "getHeight", "()I"); remapMethod(mappingsStream, node.name, getXMethod, "getX", "()D"); remapMethod(mappingsStream, node.name, getYMethod, "getY", "()D"); remapMethod(mappingsStream, node.name, containsPointMethod, "containsPoint", "(Lcom/badlogic/gdx/math/Vector2;)Z"); remapMethod(mappingsStream, node.name, widgetSetHeaderColorMethod, "setHeaderColor", "(Lsnoddasmannen/galimulator/GalColor;)V"); remapMethod(mappingsStream, node.name, widgetSetHeaderTitleMethod, "setHeaderTitle", "(Ljava/lang/String;)V"); remapMethod(mappingsStream, node.name, widgetGetHeaderTitleMethod, "getHeaderTitle", "()Ljava/lang/String;"); remapMethod(mappingsStream, node.name, widgetDrawBackgroundMethod, "drawBackground", "(Lsnoddasmannen/galimulator/GalColor;)V"); remapMethod(mappingsStream, node.name, widgetClearChildrenMethod, "clearChildren", "()V"); remapMethod(mappingsStream, node.name, widgetAddChildMethod, "addChild", widgetAddChildMethodDescriptor); remapMethod(mappingsStream, node.name, widgetRefreshLayoutMethod, "refreshLayout", "()V"); remapMethod(mappingsStream, node.name, widgetDrawChildrenMethod, "drawChildren", "()V"); remapMethod(mappingsStream, node.name, widgetInterceptMouseDownMethod, "interceptMouseDown", "(FF)Z"); remapMethod(mappingsStream, node.name, widgetGetChildrenMethod, "getChildWidgets", "()Ljava/util/Vector;"); remapMethod(mappingsStream, node.name, widgetGetCameraMethod, "getCamera", "()L" + GDX_CAMERA_CLASS + ";"); remapMethod(mappingsStream, node.name, widgetGetPositioningMethod, "getPositioning", "()L" + WIDGET_POSITIONING_CLASS + ";"); remapMethod(mappingsStream, node.name, widgetSetPositioningMethod, "setPositioning", "(L" + WIDGET_POSITIONING_CLASS + ";)V"); remapMethod(mappingsStream, node.name, widgetPropagateMessageLocally, "propagateMessageLocally", "(L" + WIDGET_MESSAGE_CLASS + ";)V"); remapMethod(mappingsStream, node.name, widgetOnDisposeMethod, "onDispose", "()V"); remapMethod(mappingsStream, node.name, widgetIsPersistentMethod, "isPersistent", "()Z"); remapMethod(mappingsStream, node.name, widgetHoverMethod, "hover", "(FFZ)V"); remapMethod(mappingsStream, node.name, widgetConsiderRelayoutMethod, "considerRelayout", "()V"); remapMethod(mappingsStream, node.name, widgetMouseDownMethod, "mouseDown", "(DD)V"); remapMethod(mappingsStream, node.name, widgetDrawDefaultBackgroundMethod, "drawDefaultBackground", "()V"); } remapMethod(mappingsStream, node.name, widgetRecieveMessageMethod, "recieveMessage", "(L" + WIDGET_MESSAGE_CLASS + ";)V"); } else if (isInstanceofClass(node, widgetLayoutClass)) { remapMethod(mappingsStream, node.name, widgetLayoutNewlineMethod, "newline", "()V"); remapMethod(mappingsStream, node.name, widgetLayoutRecomputeMethod, "recompute", "()V"); remapMethod(mappingsStream, node.name, widgetLayoutGetHeightMethod, "getHeight", "()I"); remapMethod(mappingsStream, node.name, widgetLayoutGetWidthMethod, "getWidth", "()I"); } } mappingsStream.append("#END Remap UI-related methods across hierarchy\n"); remapDialogClasses(mappingsStream, settingsDialogClass); } private void resolveEnumMemberNames(String className, Map memberMappings) { ClassNode node = name2Node.get(className); if (node == null) { throw new OutdatedDeobfuscatorException("Unknown", "Class node for " + className + " could not be resolved"); } memberMappings.clear(); for (MethodNode method : node.methods) { if (!method.desc.equals("()V") || !method.name.equals("")) { continue; } // It's an overkill solution, but probably will not break anytime soon StackWalker.walkStack(node, method, new StackWalkerConsumer() { private boolean awaitPutstatic; private String lastEnumName; @Override public void postCalculation(AbstractInsnNode insn, LIFOQueue elem) { if (lastEnumName != null) { if (awaitPutstatic && insn.getOpcode() != -1) { awaitPutstatic = false; lastEnumName = null; } else { awaitPutstatic = true; } } } @Override public void preCalculation(AbstractInsnNode insn, LIFOQueue stack) { if (insn.getOpcode() == -1) { // Filter out pseudo-instructions return; } if (insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode invoked = (MethodInsnNode) insn; if (stack.getSize() < 4 || !invoked.name.equals("")) { // I'm sure that INVOKESPECIAL is only used for the constructor, but let's be on the safe side return; } lastEnumName = ((LdcInsnNode) ((AbstractSource) stack.getDelegateList().get(stack.getSize() - 3).source).getInsn()).cst.toString(); } else if (insn.getOpcode() == Opcodes.PUTSTATIC && awaitPutstatic) { FieldInsnNode fieldInsn = ((FieldInsnNode)insn); if (fieldInsn.owner.equals(className) && fieldInsn.desc.equals("L" + className + ";") && memberMappings.put(lastEnumName, fieldInsn.name) != null) { throw new OutdatedDeobfuscatorException("Unknown", "Just overwrote a mapping?"); } } } }); } if (memberMappings.isEmpty()) { throw new OutdatedDeobfuscatorException("Unknown", "Cannot find any member fields of the enum. Is it really an enum?"); } } private void dumpMethod(MethodNode method) { Textifier textifier = new Textifier(); TraceMethodVisitor visitor = new TraceMethodVisitor(textifier); method.accept(visitor); textifier.print(new PrintWriter(new OutputStreamWriter(System.err))); } /** * Runs all remapping tasks, however does NOT run the remapper itself. * * @param mappingsStream Suggested remapper mappings are written to the writer in the tiny v1 format. It appeands, so the header is not written */ public void runAll(@NotNull Writer mappingsStream) throws IOException { remapSpaceFields(mappingsStream); remapPlayerMethods(mappingsStream); remapHotkeys(mappingsStream); remapEmpireClass(mappingsStream); remapUIClasses(mappingsStream); remapActorClasses(mappingsStream); remapMapModes(mappingsStream); remapNoiseGenerators(mappingsStream); remapGalaxyGeneration(mappingsStream); remapEmploymentAgency(mappingsStream); remapStarMethods(mappingsStream); remapRendersystem(mappingsStream); remapGenerators(mappingsStream); } }