Skip to content

Commit 57fd570

Browse files
committed
Improve code unpicking quality for dev.
1 parent 6f720c0 commit 57fd570

File tree

5 files changed

+211
-13
lines changed

5 files changed

+211
-13
lines changed

patching/src/main/java/com/fox2code/foxloader/patching/TransformerUtils.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.fox2code.foxloader.patching;
22

3+
import com.fox2code.foxloader.utils.EmptyArrays;
34
import org.jetbrains.annotations.Contract;
45
import org.jetbrains.annotations.NotNull;
56
import org.jetbrains.annotations.Nullable;
@@ -15,11 +16,12 @@
1516

1617
public final class TransformerUtils {
1718
public static final int ASM_BUILD = Opcodes.ASM9;
18-
private static final String[] STRING_ARRAY = new String[0];
19+
public static final AbstractInsnNode[] EMPTY_ABSTRACT_INSN_NODE_ARRAY = new AbstractInsnNode[0];
1920

2021
public static MethodNode copyMethodNode(MethodNode methodNode) {
2122
MethodNode methodNodeCopy = new MethodNode(ASM_BUILD, methodNode.access,
22-
methodNode.name, methodNode.desc, methodNode.signature, methodNode.exceptions.toArray(STRING_ARRAY));
23+
methodNode.name, methodNode.desc, methodNode.signature,
24+
methodNode.exceptions.toArray(EmptyArrays.EMPTY_STRING_ARRAY));
2325
Map<LabelNode, LabelNode> map = new IdentityHashMap<LabelNode, LabelNode>() {
2426
@Override
2527
public LabelNode get(Object key) {
@@ -911,4 +913,47 @@ public static void insertInDefaultCase(
911913
}
912914
classNode.methods.add(classNode.methods.indexOf(values) + 1, values$);
913915
}
916+
917+
public static InsnList compileStringAppendChain(AbstractInsnNode... stringConstants) {
918+
return compileStringAppendChain(Arrays.asList(stringConstants));
919+
}
920+
921+
public static InsnList compileStringAppendChain(Collection<AbstractInsnNode> stringConstants) {
922+
// This is to trick the decompiler to use + sign when concatenating strings or constants.
923+
InsnList insnList = new InsnList();
924+
if (stringConstants.isEmpty()) {
925+
insnList.add(new LdcInsnNode(""));
926+
return insnList;
927+
} else if (stringConstants.size() == 1) {
928+
insnList.add(stringConstants.iterator().next());
929+
return insnList;
930+
}
931+
insnList.add(new TypeInsnNode(Opcodes.NEW, "java/lang/StringBuilder"));
932+
insnList.add(new InsnNode(Opcodes.DUP));
933+
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
934+
"java/lang/StringBuilder", "<init>", "()V", false));
935+
appendStringsInAppendChain(insnList, stringConstants);
936+
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
937+
"java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false));
938+
return insnList;
939+
}
940+
941+
public static void appendStringsInAppendChain(InsnList insnList, AbstractInsnNode... stringConstants) {
942+
appendStringsInAppendChain(insnList, Arrays.asList(stringConstants));
943+
}
944+
945+
public static void appendStringsInAppendChain(InsnList insnList, Iterable<AbstractInsnNode> stringConstants) {
946+
for (AbstractInsnNode abstractInsnNode : stringConstants) {
947+
insnList.add(abstractInsnNode);
948+
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
949+
"java/lang/StringBuilder", "append",
950+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
951+
}
952+
}
953+
954+
public static String getFieldStringData(ClassNode classNode, String fieldName) {
955+
FieldNode fieldNode = getField(classNode, fieldName, "Ljava/lang/String;");
956+
if (fieldNode.value == null) throw new RuntimeException("Field isn't a final constant");
957+
return (String) fieldNode.value;
958+
}
914959
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.fox2code.foxloader.patching.dev;
2+
3+
import com.fox2code.foxloader.patching.TransformerUtils;
4+
import org.objectweb.asm.ClassReader;
5+
import org.objectweb.asm.Opcodes;
6+
import org.objectweb.asm.tree.*;
7+
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.util.ArrayList;
12+
import java.util.IdentityHashMap;
13+
import java.util.Objects;
14+
import java.util.jar.JarEntry;
15+
import java.util.jar.JarFile;
16+
17+
public class DevelopmentSourceConstantData {
18+
private static final int STATUS_DISABLED = -1;
19+
private static final int STATUS_DEFAULT = 0;
20+
private static final int STATUS_DISPLAY_FIRST = 1;
21+
public final String internalVersion, version, displayVersion;
22+
private final ConstantCheck[] regular, displayFirst;
23+
24+
private DevelopmentSourceConstantData(ClassNode classNode) {
25+
this.internalVersion = TransformerUtils.getFieldStringData(classNode, "INTERNAL_VERSION");
26+
this.version = TransformerUtils.getFieldStringData(classNode, "VERSION");
27+
this.displayVersion = TransformerUtils.getFieldStringData(classNode, "DISPLAY_VERSION");
28+
CoreConstantCheck internalVersionCheck = new CoreConstantCheck(this.internalVersion, "INTERNAL_VERSION");
29+
CoreConstantCheck versionCheck = new CoreConstantCheck(this.version, "VERSION");
30+
CoreConstantCheck displayVersionCheck = new CoreConstantCheck(this.displayVersion, "DISPLAY_VERSION");
31+
if (Objects.equals(this.version, this.displayVersion)) {
32+
this.regular = new ConstantCheck[]{versionCheck, internalVersionCheck};
33+
this.displayFirst = new ConstantCheck[]{displayVersionCheck, internalVersionCheck};
34+
} else {
35+
this.displayFirst = this.regular = new ConstantCheck[]{
36+
displayVersionCheck, versionCheck, internalVersionCheck};
37+
}
38+
}
39+
40+
public static DevelopmentSourceConstantData fromPatchedJar(File patchedJar) throws IOException {
41+
try (JarFile jarFile = new JarFile(patchedJar)) {
42+
JarEntry jarEntry = jarFile.getJarEntry("net/minecraft/common/CoreConstants.class");
43+
ClassNode coreConstantsClassNode = new ClassNode();
44+
try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
45+
new ClassReader(inputStream).accept(coreConstantsClassNode, ClassReader.SKIP_FRAMES);
46+
}
47+
return new DevelopmentSourceConstantData(coreConstantsClassNode);
48+
}
49+
}
50+
51+
public int methodStatus(ClassNode classNode, MethodNode methodNode) {
52+
switch (classNode.name) {
53+
case "net/minecraft/common/CoreConstants":
54+
return STATUS_DISABLED;
55+
case "net/minecraft/client/Minecraft":
56+
return "startGame".equals(methodNode.name) ? STATUS_DISPLAY_FIRST : STATUS_DEFAULT;
57+
case "net/minecraft/client/gui/GuiDebug":
58+
return "drawScreen".equals(methodNode.name) ? STATUS_DISPLAY_FIRST : STATUS_DEFAULT;
59+
}
60+
return STATUS_DEFAULT;
61+
}
62+
63+
public void patchStringConstant(IdentityHashMap<AbstractInsnNode, InsnList> constantPatching,
64+
int state, LdcInsnNode ldcInsnNode, ArrayList<AbstractInsnNode> list) {
65+
if (state == STATUS_DISABLED) return;
66+
if (!list.isEmpty()) {
67+
list.clear();
68+
}
69+
list.add(ldcInsnNode);
70+
ConstantCheck[] check = // Some method should prioritize display instead of version.
71+
state == STATUS_DISPLAY_FIRST ? this.displayFirst : this.regular;
72+
for (ConstantCheck constantCheck : check) {
73+
constantCheck.applyCheck(list);
74+
}
75+
if (list.size() != 1 || list.get(0).getOpcode() != Opcodes.LDC) {
76+
constantPatching.put(ldcInsnNode,
77+
TransformerUtils.compileStringAppendChain(list));
78+
}
79+
}
80+
81+
private static abstract class ConstantCheck {
82+
private final String value;
83+
84+
private ConstantCheck(String value) {
85+
this.value = value;
86+
}
87+
88+
public void applyCheck(ArrayList<AbstractInsnNode> list) {
89+
for (int listIndex = 0; listIndex < list.size(); listIndex++) {
90+
AbstractInsnNode abstractInsnNode = list.get(listIndex);
91+
if (abstractInsnNode.getOpcode() == Opcodes.LDC) {
92+
String data = (String) ((LdcInsnNode) abstractInsnNode).cst;
93+
int valIndex = data.indexOf(this.value);
94+
if (valIndex == -1) continue;
95+
if (valIndex == 0) {
96+
list.set(listIndex, this.makeFieldInsnNode());
97+
if (data.length() != this.value.length()) {
98+
list.add(listIndex + 1, new LdcInsnNode(data.substring(this.value.length())));
99+
}
100+
} else {
101+
if (list.size() == 1) {
102+
list.set(listIndex, new LdcInsnNode(data.substring(0, valIndex)));
103+
} else {
104+
((LdcInsnNode) abstractInsnNode).cst = data.substring(0, valIndex);
105+
}
106+
list.add(listIndex + 1, this.makeFieldInsnNode());
107+
if (data.length() != (valIndex + this.value.length())) {
108+
list.add(listIndex + 2, new LdcInsnNode(data.substring(valIndex + this.value.length())));
109+
}
110+
// skip next instruction as we know it is our field instruction
111+
listIndex++;
112+
}
113+
}
114+
}
115+
}
116+
117+
public abstract FieldInsnNode makeFieldInsnNode();
118+
}
119+
120+
private static class CoreConstantCheck extends ConstantCheck{
121+
private final String fieldName;
122+
123+
private CoreConstantCheck(String value, String fieldName) {
124+
super(value);
125+
this.fieldName = fieldName;
126+
}
127+
128+
public FieldInsnNode makeFieldInsnNode() {
129+
return new FieldInsnNode(Opcodes.GETSTATIC,
130+
"net/minecraft/common/CoreConstants", this.fieldName, "Ljava/lang/String;");
131+
}
132+
}
133+
}

patching/src/main/java/com/fox2code/foxloader/patching/dev/DevelopmentSourcePatcher.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414

1515
public final class DevelopmentSourcePatcher {
1616
public static void unpickPatchedJar(File patchedJar, File unpickedJar) throws IOException {
17-
/* WIP: try (JarFile jarFile = new JarFile(patchedJar)) {
18-
JarEntry jarEntry = jarFile.getJarEntry("");
19-
} / will be for better decomp later */
17+
DevelopmentSourceConstantData developmentSourceConstantData =
18+
DevelopmentSourceConstantData.fromPatchedJar(patchedJar);
2019

2120
try(ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(patchedJar.toPath()));
2221
ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(unpickedJar.toPath()))) {
@@ -32,7 +31,7 @@ public static void unpickPatchedJar(File patchedJar, File unpickedJar) throws IO
3231
ClassReader classReader = new ClassReader(byteArrayOutputStream.toByteArray());
3332
ClassNode classNode = new ClassNode();
3433
classReader.accept(classNode, ClassReader.SKIP_FRAMES);
35-
DevelopmentSourceTransformer.patchForDev(classNode);
34+
DevelopmentSourceTransformer.patchForDev(developmentSourceConstantData, classNode);
3635
ClassWriter classWriter = new ClassWriter(0);
3736
classNode.accept(classWriter);
3837
byte[] compiled = classWriter.toByteArray();

patching/src/main/java/com/fox2code/foxloader/patching/dev/DevelopmentSourceTransformer.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
package com.fox2code.foxloader.patching.dev;
22

33
import com.fox2code.foxloader.patching.TransformerUtils;
4-
import org.lwjgl.opengl.ARBVertexBufferObject;
54
import org.lwjgl.opengl.GL11;
65
import org.objectweb.asm.Opcodes;
76
import org.objectweb.asm.Type;
87
import org.objectweb.asm.tree.*;
98
import org.objectweb.asm.util.Textifier;
109

1110
import java.awt.*;
12-
import java.util.ArrayList;
13-
import java.util.HashMap;
14-
import java.util.LinkedHashMap;
15-
import java.util.LinkedList;
11+
import java.util.*;
1612

1713
public final class DevelopmentSourceTransformer implements Opcodes {
1814
private static final HashMap<String, ConstantUnpick> staticConstantUnpicks = new HashMap<>();
@@ -225,9 +221,15 @@ public String unpick(int value) {
225221

226222
private DevelopmentSourceTransformer() {}
227223

228-
public static ClassNode patchForDev(ClassNode classNode) {
224+
static void patchForDev(DevelopmentSourceConstantData developmentSourceConstantData, ClassNode classNode) {
225+
final IdentityHashMap<AbstractInsnNode, InsnList> constantPatching = new IdentityHashMap<>();
226+
final ArrayList<AbstractInsnNode> nodesCache = new ArrayList<>();
229227
for (MethodNode methodNode : classNode.methods) {
230228
final InsnList insnList = methodNode.instructions;
229+
final int state = developmentSourceConstantData.methodStatus(classNode, methodNode);
230+
if (!constantPatching.isEmpty()) {
231+
constantPatching.clear();
232+
}
231233
for (AbstractInsnNode abstractInsnNode : insnList) {
232234
final int opcode = abstractInsnNode.getOpcode();
233235
if (opcode == Opcodes.INVOKESTATIC) {
@@ -277,10 +279,21 @@ public static ClassNode patchForDev(ClassNode classNode) {
277279
if (constantUnpick != null) {
278280
constantUnpick.unpick(insnList, fieldInsnNode.getPrevious());
279281
}
282+
} else if (opcode == Opcodes.LDC) {
283+
LdcInsnNode ldcInsnNode = (LdcInsnNode) abstractInsnNode;
284+
if (ldcInsnNode.cst instanceof String) {
285+
developmentSourceConstantData.patchStringConstant(
286+
constantPatching, state, ldcInsnNode, nodesCache);
287+
}
280288
}
281289
}
290+
for (Map.Entry<AbstractInsnNode, InsnList> patching : constantPatching.entrySet()) {
291+
AbstractInsnNode oldConstant = patching.getKey();
292+
if (oldConstant.getNext() == null) continue;
293+
insnList.insert(oldConstant, patching.getValue());
294+
insnList.remove(oldConstant);
295+
}
282296
}
283-
return classNode;
284297
}
285298

286299
public static abstract class ConstantUnpick {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.fox2code.foxloader.utils;
2+
3+
public final class EmptyArrays {
4+
private EmptyArrays() { throw new AssertionError(); }
5+
6+
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
7+
public static final String[] EMPTY_STRING_ARRAY = new String[0];
8+
}

0 commit comments

Comments
 (0)