Skip to content

Commit

Permalink
feat: v3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Zxnii committed Jun 10, 2023
1 parent 2172d9e commit 7e66380
Show file tree
Hide file tree
Showing 21 changed files with 966 additions and 16 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ tasks.compileJava {
}

tasks.jar {
destinationDirectory.set(File("${System.getProperty("user.home")}/.lunarclient/mods"))
destinationDirectory.set(File("${System.getProperty("user.home")}/.weave/mods"))
}
46 changes: 46 additions & 0 deletions src/main/java/wtf/zani/vanillamenu/Util.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package wtf.zani.vanillamenu;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.function.Predicate;

public class Util {
public static MethodNode findMethod(ClassNode classNode, Predicate<MethodNode> predicate) {
return classNode.methods
.stream()
.filter(predicate)
.findFirst()
.orElseThrow();
}

public static InsnList asm(AbstractInsnNode... nodes) {
final InsnList list = new InsnList();

Arrays.stream(nodes).forEach(list::add);

return list;
}

public static void dumpClass(ClassNode node) {
try (final FileOutputStream outputStream = new FileOutputStream(node.name.replace('/', '.') + ".class")) {
final ClassWriter classWriter = new ClassWriter(0);

node.accept(classWriter);

outputStream.write(classWriter.toByteArray());
} catch (IOException ignored) {

}
}

public static String internalName(Class<?> clazz) {
return clazz.getName().replace('.', '/');
}
}
2 changes: 1 addition & 1 deletion src/main/java/wtf/zani/vanillamenu/VanillaMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

public class VanillaMenu implements ModInitializer {
public static final Logger logger = LogManager.getLogger();

public static boolean launching = true;
@Override
public void preInit() {

Expand Down
50 changes: 50 additions & 0 deletions src/main/java/wtf/zani/vanillamenu/accessors/Accessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package wtf.zani.vanillamenu.accessors;

import wtf.zani.vanillamenu.accessors.lunar.AccountManagerAccessor;
import wtf.zani.vanillamenu.accessors.lunar.LoadingScreenRendererAccessor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public abstract class Accessor {
public static AccountManagerAccessor accountAccessor;
public static LoadingScreenRendererAccessor loadingScreenRendererAccessor;

protected final Map<String, Method> methodCache = new HashMap<>();
protected final Object wrapped;

public Accessor(Object wrapped) {
this.wrapped = wrapped;
}

protected <T> T callSuperMethod(String name, Object... args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
return this.callMethod(true, name, args);
}

protected <T> T callMethod(String name, Object... args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
return this.callMethod(false, name, args);
}

@SuppressWarnings("unchecked")
protected <T> T callMethod(boolean superMethod, String name, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (this.methodCache.containsKey(name)) {
return (T) this.methodCache.get(name).invoke(this.wrapped, args);
} else {
final Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
final Method method = (superMethod ? this.wrapped.getClass().getSuperclass() : this.wrapped.getClass()).getDeclaredMethod(name, argTypes);

this.methodCache.put(name, method);

method.setAccessible(true);

return (T) method.invoke(this.wrapped, args);
}
}

public Object getWrapped() {
return this.wrapped;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package wtf.zani.vanillamenu.accessors.lunar;

import wtf.zani.vanillamenu.accessors.Accessor;

import java.lang.reflect.InvocationTargetException;

public class AccountAccessor extends Accessor {
public AccountAccessor(Object wrapped) {
super(wrapped);
}

public String getUsername() {
try {
return this.callSuperMethod("getUsername");
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package wtf.zani.vanillamenu.accessors.lunar;

import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import wtf.zani.vanillamenu.Util;
import wtf.zani.vanillamenu.accessors.Accessor;
import wtf.zani.vanillamenu.hooks.delegations.AccountManagerHook;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ASM9;

public class AccountManagerAccessor extends Accessor {
private final Method getAccountsMethod;
private final Method getCurrentAccountMethod;
private final Method setCurrentAccountMethod;

public AccountManagerAccessor(Object wrapped) throws NoSuchMethodException {
super(wrapped);

final AtomicReference<String> accountClass = new AtomicReference<>();

final ClassNode managerNode = AccountManagerHook.getInstance().node;
final MethodNode getAccountsMethodNode = Util.findMethod(managerNode, methodNode -> {
if ((methodNode.access & ACC_PUBLIC) <= 0 || methodNode.signature == null) {
return false;
}

final List<String> types = new ArrayList<>();

final SignatureVisitor visitor = new SignatureVisitor(ASM9) {
@Override
public void visitClassType(String name) {
types.add(name);
}
};

final SignatureReader reader = new SignatureReader(methodNode.signature);

reader.accept(visitor);
visitor.visitReturnType();

accountClass.set(types.get(2));

return types.get(0).equals("java/util/Map");
});

this.getAccountsMethod = wrapped.getClass().getMethod(getAccountsMethodNode.name);
this.getCurrentAccountMethod = Arrays.stream(wrapped.getClass().getMethods())
.filter(method ->
method.getReturnType().getName().replace('.', '/').equals(accountClass.get()))
.findFirst()
.orElseThrow();
this.setCurrentAccountMethod = Arrays.stream(wrapped.getClass().getMethods())
.filter(method -> {
if (method.getParameterCount() != 1) return false;

return method.getReturnType().getName().equals(Void.TYPE.getName()) && method.getParameterTypes()[0].getName().replace('.', '/').equals(accountClass.get());
})
.findFirst()
.orElseThrow();
}

@SuppressWarnings("unused")
public static void create(Object accountManager) throws NoSuchMethodException {
Accessor.accountAccessor = new AccountManagerAccessor(accountManager);
}

@SuppressWarnings("unchecked")
public Map<String, AccountAccessor> getAccounts() {
try {
final Map<String, Object> accounts = (Map<String, Object>) this.getAccountsMethod.invoke(this.wrapped);
final Map<String, AccountAccessor> mappedAccounts = new HashMap<>();

accounts.forEach((uuid, account) -> mappedAccounts.put(uuid, new AccountAccessor(account)));

return mappedAccounts;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public AccountAccessor getCurrentAccount() {
try {
final Object account = this.getCurrentAccountMethod.invoke(this.wrapped);

return account != null ? new AccountAccessor(account) : null;
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public void setAccount(AccountAccessor account) {
try {
this.setCurrentAccountMethod.invoke(this.wrapped, account.getWrapped());
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package wtf.zani.vanillamenu.accessors.lunar;

import wtf.zani.vanillamenu.accessors.Accessor;

import java.lang.reflect.InvocationTargetException;

public class LoadStageAccessor extends Accessor {
public LoadStageAccessor(Object wrapped) {
super(wrapped);
}

public String getCategory() {
try {
return this.callMethod("getCategory");
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package wtf.zani.vanillamenu.accessors.lunar;

import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import wtf.zani.vanillamenu.accessors.Accessor;
import wtf.zani.vanillamenu.hooks.delegations.LoadingScreenRendererHook;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.objectweb.asm.Opcodes.ASM9;

public class LoadingScreenRendererAccessor extends Accessor {
private final Field statusField;
private final Field loadStageField;

public LoadingScreenRendererAccessor(Object wrapped) throws NoSuchFieldException {
super(wrapped);

final ClassNode classNode = LoadingScreenRendererHook.getInstance().node;

this.statusField = Arrays.stream(wrapped.getClass().getDeclaredFields())
.filter(field -> field.getType().getName().equals(String.class.getName()))
.findFirst()
.orElseThrow();
this.statusField.trySetAccessible();

final FieldNode stageList = classNode.fields.stream()
.filter(fieldNode -> fieldNode.desc.equals("Ljava/util/List;") && fieldNode.signature != null)
.findFirst()
.orElseThrow();

final List<String> types = new ArrayList<>();

final SignatureVisitor visitor = new SignatureVisitor(ASM9) {
@Override
public void visitClassType(String name) {
types.add(name);
}
};

final SignatureReader reader = new SignatureReader(stageList.signature);

reader.accept(visitor);
visitor.visitEnd();

final FieldNode stageFieldNode = classNode.fields.stream()
.filter(fieldNode -> fieldNode.desc.equals("L" + types.get(1) + ";"))
.findFirst()
.orElseThrow();

this.loadStageField = wrapped.getClass().getDeclaredField(stageFieldNode.name);
this.loadStageField.trySetAccessible();
}

@SuppressWarnings("unused")
public static void create(Object renderer) throws NoSuchFieldException {
if (Accessor.loadingScreenRendererAccessor == null) {
Accessor.loadingScreenRendererAccessor = new LoadingScreenRendererAccessor(renderer);
}
}

public String getStatus() {
try {
return (String) this.statusField.get(this.wrapped);
} catch (IllegalAccessException exception) {
throw new RuntimeException(exception);
}
}

public LoadStageAccessor getLoadStage() {
try {
final Object value = this.loadStageField.get(this.wrapped);

return value != null ? new LoadStageAccessor(value) : null;
} catch (IllegalAccessException exception) {
throw new RuntimeException(exception);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,33 @@
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import wtf.zani.vanillamenu.Util;

import java.util.Arrays;

@SuppressWarnings("unused")
public class VanillaMenuHook extends Hook {
public VanillaMenuHook() {
super("net/minecraft/client/Minecraft");
public class GuiScreenHook extends Hook {
public GuiScreenHook() {
super("net/minecraft/client/gui/GuiScreen");
}

@Override
public void transform(@NotNull ClassNode classNode, @NotNull AssemblerConfig assemblerConfig) {
final MethodNode displayGuiScreen = classNode.methods
.stream()
.filter(methodNode -> methodNode.name.equals("displayGuiScreen"))
.findFirst()
.orElseThrow();
final MethodNode drawScreen = Util.findMethod(classNode, methodNode -> methodNode.name.equals("drawScreen"));
final InsnList filteredInstructions = new InsnList();

Arrays.stream(displayGuiScreen.instructions.toArray())
Arrays.stream(drawScreen.instructions.toArray())
.filter(instruction -> {
if (instruction instanceof final MethodInsnNode methodCall) {
return !methodCall.name.endsWith("$impl$displayGuiScreen");
return !methodCall.name.endsWith("$impl$renderLunarClientBrand");
}

return true;
})
.forEach(filteredInstructions::add);

drawScreen.instructions = filteredInstructions;

assemblerConfig.computeFrames();
}
}
Loading

0 comments on commit 7e66380

Please sign in to comment.