Skip to content

Commit 7e66380

Browse files
committed
feat: v3.0.0
1 parent 2172d9e commit 7e66380

21 files changed

+966
-16
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ tasks.compileJava {
2323
}
2424

2525
tasks.jar {
26-
destinationDirectory.set(File("${System.getProperty("user.home")}/.lunarclient/mods"))
26+
destinationDirectory.set(File("${System.getProperty("user.home")}/.weave/mods"))
2727
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package wtf.zani.vanillamenu;
2+
3+
import org.objectweb.asm.ClassWriter;
4+
import org.objectweb.asm.tree.AbstractInsnNode;
5+
import org.objectweb.asm.tree.ClassNode;
6+
import org.objectweb.asm.tree.InsnList;
7+
import org.objectweb.asm.tree.MethodNode;
8+
9+
import java.io.FileOutputStream;
10+
import java.io.IOException;
11+
import java.util.Arrays;
12+
import java.util.function.Predicate;
13+
14+
public class Util {
15+
public static MethodNode findMethod(ClassNode classNode, Predicate<MethodNode> predicate) {
16+
return classNode.methods
17+
.stream()
18+
.filter(predicate)
19+
.findFirst()
20+
.orElseThrow();
21+
}
22+
23+
public static InsnList asm(AbstractInsnNode... nodes) {
24+
final InsnList list = new InsnList();
25+
26+
Arrays.stream(nodes).forEach(list::add);
27+
28+
return list;
29+
}
30+
31+
public static void dumpClass(ClassNode node) {
32+
try (final FileOutputStream outputStream = new FileOutputStream(node.name.replace('/', '.') + ".class")) {
33+
final ClassWriter classWriter = new ClassWriter(0);
34+
35+
node.accept(classWriter);
36+
37+
outputStream.write(classWriter.toByteArray());
38+
} catch (IOException ignored) {
39+
40+
}
41+
}
42+
43+
public static String internalName(Class<?> clazz) {
44+
return clazz.getName().replace('.', '/');
45+
}
46+
}

src/main/java/wtf/zani/vanillamenu/VanillaMenu.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
public class VanillaMenu implements ModInitializer {
88
public static final Logger logger = LogManager.getLogger();
9-
9+
public static boolean launching = true;
1010
@Override
1111
public void preInit() {
1212

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package wtf.zani.vanillamenu.accessors;
2+
3+
import wtf.zani.vanillamenu.accessors.lunar.AccountManagerAccessor;
4+
import wtf.zani.vanillamenu.accessors.lunar.LoadingScreenRendererAccessor;
5+
6+
import java.lang.reflect.InvocationTargetException;
7+
import java.lang.reflect.Method;
8+
import java.util.Arrays;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
12+
public abstract class Accessor {
13+
public static AccountManagerAccessor accountAccessor;
14+
public static LoadingScreenRendererAccessor loadingScreenRendererAccessor;
15+
16+
protected final Map<String, Method> methodCache = new HashMap<>();
17+
protected final Object wrapped;
18+
19+
public Accessor(Object wrapped) {
20+
this.wrapped = wrapped;
21+
}
22+
23+
protected <T> T callSuperMethod(String name, Object... args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
24+
return this.callMethod(true, name, args);
25+
}
26+
27+
protected <T> T callMethod(String name, Object... args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
28+
return this.callMethod(false, name, args);
29+
}
30+
31+
@SuppressWarnings("unchecked")
32+
protected <T> T callMethod(boolean superMethod, String name, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
33+
if (this.methodCache.containsKey(name)) {
34+
return (T) this.methodCache.get(name).invoke(this.wrapped, args);
35+
} else {
36+
final Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
37+
final Method method = (superMethod ? this.wrapped.getClass().getSuperclass() : this.wrapped.getClass()).getDeclaredMethod(name, argTypes);
38+
39+
this.methodCache.put(name, method);
40+
41+
method.setAccessible(true);
42+
43+
return (T) method.invoke(this.wrapped, args);
44+
}
45+
}
46+
47+
public Object getWrapped() {
48+
return this.wrapped;
49+
}
50+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package wtf.zani.vanillamenu.accessors.lunar;
2+
3+
import wtf.zani.vanillamenu.accessors.Accessor;
4+
5+
import java.lang.reflect.InvocationTargetException;
6+
7+
public class AccountAccessor extends Accessor {
8+
public AccountAccessor(Object wrapped) {
9+
super(wrapped);
10+
}
11+
12+
public String getUsername() {
13+
try {
14+
return this.callSuperMethod("getUsername");
15+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
16+
throw new RuntimeException(e);
17+
}
18+
}
19+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package wtf.zani.vanillamenu.accessors.lunar;
2+
3+
import org.objectweb.asm.signature.SignatureReader;
4+
import org.objectweb.asm.signature.SignatureVisitor;
5+
import org.objectweb.asm.tree.ClassNode;
6+
import org.objectweb.asm.tree.MethodNode;
7+
import wtf.zani.vanillamenu.Util;
8+
import wtf.zani.vanillamenu.accessors.Accessor;
9+
import wtf.zani.vanillamenu.hooks.delegations.AccountManagerHook;
10+
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.lang.reflect.Method;
13+
import java.util.*;
14+
import java.util.concurrent.atomic.AtomicReference;
15+
16+
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
17+
import static org.objectweb.asm.Opcodes.ASM9;
18+
19+
public class AccountManagerAccessor extends Accessor {
20+
private final Method getAccountsMethod;
21+
private final Method getCurrentAccountMethod;
22+
private final Method setCurrentAccountMethod;
23+
24+
public AccountManagerAccessor(Object wrapped) throws NoSuchMethodException {
25+
super(wrapped);
26+
27+
final AtomicReference<String> accountClass = new AtomicReference<>();
28+
29+
final ClassNode managerNode = AccountManagerHook.getInstance().node;
30+
final MethodNode getAccountsMethodNode = Util.findMethod(managerNode, methodNode -> {
31+
if ((methodNode.access & ACC_PUBLIC) <= 0 || methodNode.signature == null) {
32+
return false;
33+
}
34+
35+
final List<String> types = new ArrayList<>();
36+
37+
final SignatureVisitor visitor = new SignatureVisitor(ASM9) {
38+
@Override
39+
public void visitClassType(String name) {
40+
types.add(name);
41+
}
42+
};
43+
44+
final SignatureReader reader = new SignatureReader(methodNode.signature);
45+
46+
reader.accept(visitor);
47+
visitor.visitReturnType();
48+
49+
accountClass.set(types.get(2));
50+
51+
return types.get(0).equals("java/util/Map");
52+
});
53+
54+
this.getAccountsMethod = wrapped.getClass().getMethod(getAccountsMethodNode.name);
55+
this.getCurrentAccountMethod = Arrays.stream(wrapped.getClass().getMethods())
56+
.filter(method ->
57+
method.getReturnType().getName().replace('.', '/').equals(accountClass.get()))
58+
.findFirst()
59+
.orElseThrow();
60+
this.setCurrentAccountMethod = Arrays.stream(wrapped.getClass().getMethods())
61+
.filter(method -> {
62+
if (method.getParameterCount() != 1) return false;
63+
64+
return method.getReturnType().getName().equals(Void.TYPE.getName()) && method.getParameterTypes()[0].getName().replace('.', '/').equals(accountClass.get());
65+
})
66+
.findFirst()
67+
.orElseThrow();
68+
}
69+
70+
@SuppressWarnings("unused")
71+
public static void create(Object accountManager) throws NoSuchMethodException {
72+
Accessor.accountAccessor = new AccountManagerAccessor(accountManager);
73+
}
74+
75+
@SuppressWarnings("unchecked")
76+
public Map<String, AccountAccessor> getAccounts() {
77+
try {
78+
final Map<String, Object> accounts = (Map<String, Object>) this.getAccountsMethod.invoke(this.wrapped);
79+
final Map<String, AccountAccessor> mappedAccounts = new HashMap<>();
80+
81+
accounts.forEach((uuid, account) -> mappedAccounts.put(uuid, new AccountAccessor(account)));
82+
83+
return mappedAccounts;
84+
} catch (InvocationTargetException | IllegalAccessException e) {
85+
throw new RuntimeException(e);
86+
}
87+
}
88+
89+
public AccountAccessor getCurrentAccount() {
90+
try {
91+
final Object account = this.getCurrentAccountMethod.invoke(this.wrapped);
92+
93+
return account != null ? new AccountAccessor(account) : null;
94+
} catch (InvocationTargetException | IllegalAccessException e) {
95+
throw new RuntimeException(e);
96+
}
97+
}
98+
99+
public void setAccount(AccountAccessor account) {
100+
try {
101+
this.setCurrentAccountMethod.invoke(this.wrapped, account.getWrapped());
102+
} catch (InvocationTargetException | IllegalAccessException e) {
103+
throw new RuntimeException(e);
104+
}
105+
}
106+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package wtf.zani.vanillamenu.accessors.lunar;
2+
3+
import wtf.zani.vanillamenu.accessors.Accessor;
4+
5+
import java.lang.reflect.InvocationTargetException;
6+
7+
public class LoadStageAccessor extends Accessor {
8+
public LoadStageAccessor(Object wrapped) {
9+
super(wrapped);
10+
}
11+
12+
public String getCategory() {
13+
try {
14+
return this.callMethod("getCategory");
15+
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
16+
throw new RuntimeException(e);
17+
}
18+
}
19+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package wtf.zani.vanillamenu.accessors.lunar;
2+
3+
import org.objectweb.asm.signature.SignatureReader;
4+
import org.objectweb.asm.signature.SignatureVisitor;
5+
import org.objectweb.asm.tree.ClassNode;
6+
import org.objectweb.asm.tree.FieldNode;
7+
import wtf.zani.vanillamenu.accessors.Accessor;
8+
import wtf.zani.vanillamenu.hooks.delegations.LoadingScreenRendererHook;
9+
10+
import java.lang.reflect.Field;
11+
import java.util.ArrayList;
12+
import java.util.Arrays;
13+
import java.util.List;
14+
15+
import static org.objectweb.asm.Opcodes.ASM9;
16+
17+
public class LoadingScreenRendererAccessor extends Accessor {
18+
private final Field statusField;
19+
private final Field loadStageField;
20+
21+
public LoadingScreenRendererAccessor(Object wrapped) throws NoSuchFieldException {
22+
super(wrapped);
23+
24+
final ClassNode classNode = LoadingScreenRendererHook.getInstance().node;
25+
26+
this.statusField = Arrays.stream(wrapped.getClass().getDeclaredFields())
27+
.filter(field -> field.getType().getName().equals(String.class.getName()))
28+
.findFirst()
29+
.orElseThrow();
30+
this.statusField.trySetAccessible();
31+
32+
final FieldNode stageList = classNode.fields.stream()
33+
.filter(fieldNode -> fieldNode.desc.equals("Ljava/util/List;") && fieldNode.signature != null)
34+
.findFirst()
35+
.orElseThrow();
36+
37+
final List<String> types = new ArrayList<>();
38+
39+
final SignatureVisitor visitor = new SignatureVisitor(ASM9) {
40+
@Override
41+
public void visitClassType(String name) {
42+
types.add(name);
43+
}
44+
};
45+
46+
final SignatureReader reader = new SignatureReader(stageList.signature);
47+
48+
reader.accept(visitor);
49+
visitor.visitEnd();
50+
51+
final FieldNode stageFieldNode = classNode.fields.stream()
52+
.filter(fieldNode -> fieldNode.desc.equals("L" + types.get(1) + ";"))
53+
.findFirst()
54+
.orElseThrow();
55+
56+
this.loadStageField = wrapped.getClass().getDeclaredField(stageFieldNode.name);
57+
this.loadStageField.trySetAccessible();
58+
}
59+
60+
@SuppressWarnings("unused")
61+
public static void create(Object renderer) throws NoSuchFieldException {
62+
if (Accessor.loadingScreenRendererAccessor == null) {
63+
Accessor.loadingScreenRendererAccessor = new LoadingScreenRendererAccessor(renderer);
64+
}
65+
}
66+
67+
public String getStatus() {
68+
try {
69+
return (String) this.statusField.get(this.wrapped);
70+
} catch (IllegalAccessException exception) {
71+
throw new RuntimeException(exception);
72+
}
73+
}
74+
75+
public LoadStageAccessor getLoadStage() {
76+
try {
77+
final Object value = this.loadStageField.get(this.wrapped);
78+
79+
return value != null ? new LoadStageAccessor(value) : null;
80+
} catch (IllegalAccessException exception) {
81+
throw new RuntimeException(exception);
82+
}
83+
}
84+
}

src/main/java/wtf/zani/vanillamenu/hooks/VanillaMenuHook.java renamed to src/main/java/wtf/zani/vanillamenu/hooks/GuiScreenHook.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,33 @@
66
import org.objectweb.asm.tree.InsnList;
77
import org.objectweb.asm.tree.MethodInsnNode;
88
import org.objectweb.asm.tree.MethodNode;
9+
import wtf.zani.vanillamenu.Util;
910

1011
import java.util.Arrays;
1112

1213
@SuppressWarnings("unused")
13-
public class VanillaMenuHook extends Hook {
14-
public VanillaMenuHook() {
15-
super("net/minecraft/client/Minecraft");
14+
public class GuiScreenHook extends Hook {
15+
public GuiScreenHook() {
16+
super("net/minecraft/client/gui/GuiScreen");
1617
}
1718

1819
@Override
1920
public void transform(@NotNull ClassNode classNode, @NotNull AssemblerConfig assemblerConfig) {
20-
final MethodNode displayGuiScreen = classNode.methods
21-
.stream()
22-
.filter(methodNode -> methodNode.name.equals("displayGuiScreen"))
23-
.findFirst()
24-
.orElseThrow();
21+
final MethodNode drawScreen = Util.findMethod(classNode, methodNode -> methodNode.name.equals("drawScreen"));
2522
final InsnList filteredInstructions = new InsnList();
2623

27-
Arrays.stream(displayGuiScreen.instructions.toArray())
24+
Arrays.stream(drawScreen.instructions.toArray())
2825
.filter(instruction -> {
2926
if (instruction instanceof final MethodInsnNode methodCall) {
30-
return !methodCall.name.endsWith("$impl$displayGuiScreen");
27+
return !methodCall.name.endsWith("$impl$renderLunarClientBrand");
3128
}
3229

3330
return true;
3431
})
3532
.forEach(filteredInstructions::add);
3633

34+
drawScreen.instructions = filteredInstructions;
35+
3736
assemblerConfig.computeFrames();
3837
}
3938
}

0 commit comments

Comments
 (0)