Created
August 28, 2022 19:03
-
-
Save Geolykt/9a13462a0d6029ed3fb629447fe6d834 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.geolykt.starplane; | |
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.Set; | |
import org.jetbrains.annotations.NotNull; | |
import org.jetbrains.annotations.Nullable; | |
import org.objectweb.asm.Opcodes; | |
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 Autodeobf implements StarmappedNames { | |
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 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_TEXT_DESCRIPTOR = "(FFFLcom/badlogic/gdx/math/Vector3;Ljava/lang/String;Lsnoddasmannen/galimulator/GalColor;Lsnoddasmannen/galimulator/GalFX$FONT_TYPE;Lcom/badlogic/gdx/graphics/Camera;)F"; | |
private static final String GALFX_DRAW_TEXTURE_DESCRIPTOR = "(Lcom/badlogic/gdx/graphics/g2d/TextureRegion;DDDDDLsnoddasmannen/galimulator/GalColor;Z)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 GDX_RECTANGLE_CLASS = "com/badlogic/gdx/math/Rectangle"; | |
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 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 STAR_CLASS = "snoddasmannen/galimulator/Star"; | |
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 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<String, String> enumSettingsMemberNames = new HashMap<>(); | |
@NotNull | |
private final Map<String, ClassNode> name2Node = new HashMap<>(); | |
private final List<ClassNode> nodes; | |
private final Remapper remapper; | |
@NotNull | |
private final Map<String, String> settingsTypeMemberNames = new HashMap<>(); | |
@Nullable | |
private String textInputDialogWidgetClass = null; | |
@Nullable | |
private String spaceLogicalTickMethodName = null; | |
public Autodeobf(List<ClassNode> nodes, Remapper remapper) { | |
this.nodes = nodes; | |
this.remapper = remapper; | |
for (ClassNode node : nodes) { | |
name2Node.put(node.name, node); | |
} | |
} | |
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; | |
} | |
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; | |
} | |
@SuppressWarnings("unchecked") | |
private <T extends AbstractInsnNode> T getNext(AbstractInsnNode insn, int matchOpcode) { | |
insn = insn.getNext(); | |
while (insn.getOpcode() != matchOpcode) { | |
insn = insn.getNext(); | |
} | |
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<L" + EMPIRE_SPECIAL_CLASS + ";>;"; | |
} 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("<init>") | |
&& method.desc.equals("(L" + ACTOR_CREATOR_CLASS + ";FLjava/util/List;L" + RELIGION_CLASS + ";)V")) { | |
method.signature = "(L" + ACTOR_CREATOR_CLASS + ";FLjava/util/List<L" + EMPIRE_SPECIAL_CLASS + ";>;L" + RELIGION_CLASS + ";)V"; | |
} | |
} | |
if (!testMethodFound) { | |
throw new OutdatedDeobfuscatorException("Actor", ACTOR_SPAWNING_PREDICATE_CLASS, "test", "Method not found"); | |
} | |
} | |
} | |
private void remapClass(Writer mappingsOut, String oldName, 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); | |
ClassNode settingsDialog = name2Node.get(settingsDialogClass); | |
if (settingsDialog == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG, "*"); | |
} | |
if (settingsDialog.interfaces.size() != 1) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG, "*", "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, "*", "Unexpected opcode"); | |
} | |
MethodInsnNode methodInsn = (MethodInsnNode) nextInsn; | |
if (!methodInsn.name.equals("<init>")) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON, "*", "Logic error"); | |
} | |
blacklistButtonClass = methodInsn.owner; | |
remapClass(mappingsStream, blacklistButtonClass, SETTINGS_DIALOG_BLACKLIST_BUTTON); | |
ClassNode blacklistButton = name2Node.get(blacklistButtonClass); | |
if (blacklistButton == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON, "*", "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<L" + dialogComponentInterface + ";>;"; | |
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, "*", "Collision"); | |
} | |
AbstractInsnNode previousInsn = fieldInsn.getPrevious(); | |
if (previousInsn.getOpcode() != Opcodes.GETFIELD) { | |
throw new OutdatedDeobfuscatorException("Dialog", USER_SETTING_ENTRY, "*", "Unexpected opcode"); | |
} | |
FieldInsnNode getTypeInsn = (FieldInsnNode) previousInsn; | |
if (!getTypeInsn.desc.equals("L" + SETTINGS_TYPE_CLASS + ";")) { | |
throw new OutdatedDeobfuscatorException("Dialog", USER_SETTING_ENTRY, "type", "Unexpected descriptor"); | |
} | |
remapField(mappingsStream, getTypeInsn.owner, getTypeInsn.name, "type", "L" + SETTINGS_TYPE_CLASS + ";"); | |
remapClass(mappingsStream, getTypeInsn.owner, USER_SETTING_ENTRY); | |
AbstractInsnNode nextInsn = fieldInsn.getNext(); | |
while (nextInsn != null && nextInsn.getOpcode() != Opcodes.NEW) { | |
nextInsn = nextInsn.getNext(); | |
} | |
if (nextInsn == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CHECKBOX, "*", "Instructions exhausted"); | |
} | |
TypeInsnNode newInsn = (TypeInsnNode) nextInsn; | |
String settingsCheckbox = newInsn.desc; | |
remapClass(mappingsStream, settingsCheckbox, SETTINGS_DIALOG_CHECKBOX); | |
ClassNode settingsCheckboxNode = name2Node.get(settingsCheckbox); | |
if (settingsCheckboxNode == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_CHECKBOX, "*", "Unresolved node"); | |
} | |
labeledCheckboxComponent = settingsCheckboxNode.superName; | |
if (labeledCheckboxComponent == null) { | |
throw new IllegalStateException(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, "*", "Instructions exhausted"); | |
} | |
TypeInsnNode newInsn = (TypeInsnNode) nextInsn; | |
ClassNode stringChooserSubclassNode = name2Node.get(newInsn.desc); | |
if (stringChooserSubclassNode == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_STRING_CHOOSER, "*", "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); | |
assignAsAnonymousClass(settingsDialog, method, newInsn.desc); | |
} | |
} | |
} | |
break; | |
} | |
} | |
if (blacklistButtonClass == null) { | |
throw new OutdatedDeobfuscatorException("Dialog", SETTINGS_DIALOG_BLACKLIST_BUTTON, "*", "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<String> 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; | |
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; | |
} | |
} | |
} | |
} | |
} | |
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"); | |
} | |
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;"); | |
} | |
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 = 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 = name2Node.get(aShortcutClass); | |
if (extendsShortcut == null) { | |
throw new OutdatedDeobfuscatorException("Hotkey", "Cannot find " + aShortcutClass); | |
} | |
remapClass(mappingsStream, extendsShortcut.superName, BASE_PACKAGE + "Shortcut"); | |
} | |
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<StackElement> 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<StackElement> 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"); | |
} | |
try { | |
remapper.remapMethod(owner, desc, oldName, newName); | |
// Format: METHOD owner originalName descriptor newName | |
mappingsOut.write("METHOD " + owner + " " + oldName + " " + desc + " " + newName + "\n"); | |
} catch (ConflicitingMappingException e) { | |
try { | |
throw new RuntimeException("Old mapping: " + remapper.remapReference(owner + "." + oldName + desc, new StringBuilder()) + ". Proposed: " + remapper.remapReference(owner, new StringBuilder()) + "." + 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"); | |
} | |
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<String> 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("<init>")) { | |
if (ctor != null) { | |
throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "<init>", "Multiple present"); | |
} | |
ctor = method; | |
} | |
} | |
if (ctor == null) { | |
throw new OutdatedDeobfuscatorException("RenderSystem", node.name, "<init>", "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;Lcom/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; | |
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("<init>")) { | |
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<String, String> fieldRemaps = new HashMap<>(); | |
String showToastMethod = null; | |
String showScreenSizeResetHintMethod = null; | |
String openInputDialogMethod = null; | |
String addUnbufferedWidgetMethod = null; | |
String galimulatorTextInputDialogClass = null; | |
String saveAsyncMethod = null; | |
String saveSyncMethod = null; | |
String logicalTickMethod = null; | |
String getStateActorCreatorsMethod = null; | |
spaceLogicalTickMethodName = null; | |
for (MethodNode method : space.methods) { | |
if (method.desc.equals("()V")) { | |
for (AbstractInsnNode insn : method.instructions) { | |
if (insn.getOpcode() != Opcodes.LDC) { | |
continue; | |
} | |
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 (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.name; | |
} | |
} | |
} 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; | |
} | |
} | |
} | |
} | |
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"); | |
} | |
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, "tick", "()I"); | |
remapMethod(mappingsStream, SPACE_CLASS, getStateActorCreatorsMethod, "getStateActorCreators", "()Ljava/util/List;"); | |
spaceLogicalTickMethodName = logicalTickMethod; | |
this.textInputDialogWidgetClass = galimulatorTextInputDialogClass; | |
String spaceInitializeMethod = null; | |
String aspectRatioField = null; | |
Map<String, Map.Entry<String, String>> 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<String, Map.Entry<String, String>> entry : spstarmappedGetters.entrySet()) { | |
Map.Entry<String, String> 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("<init>")) { | |
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; | |
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(); | |
} | |
} | |
} | |
if (pausedField == null) { | |
throw new OutdatedDeobfuscatorException("Space", "Space", "paused"); | |
} | |
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"); | |
} | |
} | |
public void remapStarMethods(Writer mappingsStream) throws IOException { | |
ClassNode starNode = name2Node.get(STAR_CLASS); | |
String 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.name; | |
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, "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"); | |
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("<init>")) { | |
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"); | |
} | |
/** | |
* 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<String, String> 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; | |
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("<init>") && 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 { | |
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"); | |
} 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("<init>")) { | |
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"); | |
} | |
FieldInsnNode firstGetStatic = null; | |
for (AbstractInsnNode insn : tapMethod.instructions) { | |
if (insn.getOpcode() == Opcodes.GETSTATIC) { | |
firstGetStatic = (FieldInsnNode) insn; | |
break; | |
} | |
} | |
if (firstGetStatic == null || !firstGetStatic.desc.equals("Ljava/util/concurrent/Semaphore;") || !firstGetStatic.owner.equals(SPACE_CLASS)) { | |
throw new OutdatedDeobfuscatorException("GestureListener", SPACE_CLASS, "MAIN_TICK_LOOP_LOCK"); | |
} | |
remapField(mappingsStream, SPACE_CLASS, firstGetStatic.name, "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"); | |
} | |
remapClass(mappingsStream, newDynastyWidgetClass, UI_PACKAGE + "NewDynastyWidget"); | |
String getWidgetWidthMethod = null; | |
String widgetDrawHeaderMethod = null; | |
String widgetHeaderColorField = null; | |
String widgetHeaderTitleField = null; | |
String fxDrawWindowMethod = null; | |
String fxDrawTextMethod = null; | |
String fxDrawTextureMethod = 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; | |
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("<init>") && 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 (fxDrawTextureMethod == null) { | |
if (!methodInsn.owner.equals(GALFX_CLASS) || !methodInsn.desc.equals(GALFX_DRAW_TEXTURE_DESCRIPTOR)) { | |
continue; | |
} | |
fxDrawTextureMethod = 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 (fxDrawTextureMethod == 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, fxDrawTextureMethod, "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("<init>") && 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 { | |
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, "<init>", "()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"); | |
} | |
String baseButtonClass = name2Node.get(ninepatchButtonClass).superName; | |
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); | |
String widgetClearChildrenMethod = null; | |
String widgetAddChildMethod = null; | |
final String widgetAddChildMethodDescriptor = "(L" + WIDGET_CLASS + ";)L" + WIDGET_CLASS + ";"; | |
String starGeneratorInterface = null; | |
{ // Let's not compute 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); | |
for (MethodNode method : galaxyPreview.methods) { | |
if (method.name.equals("<init>")) { | |
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); | |
} | |
} else if (method.name.equals(widgetRefreshLayoutMethod)) { | |
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"); | |
} | |
} | |
} | |
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"); | |
} | |
remapClass(mappingsStream, starGeneratorInterface, STAR_GENERATOR_INTERFACE); | |
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; | |
} | |
} | |
ClassNode widgetClass = name2Node.get(WIDGET_CLASS); | |
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("<init>")) { | |
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 widgetOnMouseDownMethod = null; | |
String widgetOnMouseUpMethod = 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)) { | |
widgetOnMouseDownMethod = insn.name; | |
break; | |
} | |
} | |
nextInsn = nextInsn.getNext(); | |
} | |
if (widgetOnMouseDownMethod == null) { | |
throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "onMouseDown", "Instructions exhausted"); | |
} | |
} else if (method.name.equals("tap") && 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 (tap)."); | |
} | |
containsPointInsn = methodInsn; | |
} | |
} | |
} | |
if (containsPointInsn == null) { | |
throw new OutdatedDeobfuscatorException("GestureListener", "No Widget#containsPoint instructions found (tap)."); | |
} | |
AbstractInsnNode nextInsn = containsPointInsn.getNext(); | |
while (nextInsn != null) { | |
if (nextInsn.getOpcode() == Opcodes.INVOKEVIRTUAL) { | |
MethodInsnNode insn = (MethodInsnNode) nextInsn; | |
if (insn.desc.equals("(DD)V") && insn.owner.equals(WIDGET_CLASS)) { | |
widgetOnMouseUpMethod = insn.name; | |
break; | |
} | |
} | |
nextInsn = nextInsn.getNext(); | |
} | |
if (widgetOnMouseUpMethod == null) { | |
throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "onMouseUp", "Instructions exhausted"); | |
} | |
} | |
} | |
} | |
if (widgetOnMouseDownMethod == null) { | |
throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "onMouseDown"); | |
} | |
if (widgetOnMouseUpMethod == null) { | |
throw new OutdatedDeobfuscatorException("GestureListener", "Widget", "onMouseUp"); | |
} | |
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, 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, widgetOnMouseDownMethod, "onMouseDown", "(FF)Z"); | |
remapMethod(mappingsStream, node.name, widgetOnMouseUpMethod, "onMouseUp", "(DD)V"); | |
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, 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<String, String> 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("<clinit>")) { | |
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<StackElement> elem) { | |
if (lastEnumName != null) { | |
if (awaitPutstatic && insn.getOpcode() != -1) { | |
awaitPutstatic = false; | |
lastEnumName = null; | |
} else { | |
awaitPutstatic = true; | |
} | |
} | |
} | |
@Override | |
public void preCalculation(AbstractInsnNode insn, LIFOQueue<StackElement> 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("<init>")) { | |
// 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(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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment