Skip to content

Commit

Permalink
Implemented mouse fix for deAWT
Browse files Browse the repository at this point in the history
  • Loading branch information
Moresteck committed Aug 19, 2024
1 parent d735d7f commit dc9ed21
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 37 deletions.
3 changes: 2 additions & 1 deletion src/main/java/uk/betacraft/legacyfix/LegacyFixAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public static void premain(String agentArgs, final Instrumentation inst) {
new ModloaderPatch(),
new BetaForgePatch(),
new DeAwtPatch(),
new CloudPatch()
new CloudPatch(),
new MousePatch()
));

List<String> patchStates = new ArrayList<String>();
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/uk/betacraft/legacyfix/patch/PatchHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package uk.betacraft.legacyfix.patch;

import java.lang.reflect.Modifier;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.NotFoundException;
import uk.betacraft.legacyfix.LFLogger;

public class PatchHelper {

private static CtClass minecraftAppletClass = null;
private static CtClass mouseHelperClass = null;
private static CtClass minecraftClass = null;

private static CtField appletModeField = null;

public static CtClass findMinecraftAppletClass(ClassPool pool) {
if (minecraftAppletClass != null)
return minecraftAppletClass;

String[] typicalPaths = new String[] {"net.minecraft.client.MinecraftApplet", "com.mojang.minecraft.MinecraftApplet"};

for (String path : typicalPaths) {
minecraftAppletClass = pool.getOrNull(path);

if (minecraftAppletClass != null)
break;
}

return minecraftAppletClass;
}

public static CtClass findMinecraftClass(ClassPool pool) throws NotFoundException {
if (minecraftClass != null)
return minecraftClass;

if (minecraftAppletClass == null)
findMinecraftAppletClass(pool);

minecraftClass = pool.getOrNull("net.minecraft.client.Minecraft");

if (minecraftClass == null) {
for (CtField field : minecraftAppletClass.getDeclaredFields()) {
String className = field.getType().getName();

if (!className.equals("java.awt.Canvas") &&
!className.equals("java.lang.Thread") &&
!className.equals("long")) {

minecraftClass = field.getType();
LFLogger.info("Found Minecraft class: " + minecraftClass.getName());
}
}
}

return minecraftClass;
}

public static CtField findAppletModeField(ClassPool pool) throws NotFoundException {
if (appletModeField != null)
return appletModeField;

if (minecraftClass == null)
findMinecraftClass(pool);

for (CtField field : minecraftClass.getDeclaredFields()) {
String className = field.getType().getName();

if (className.equals("boolean") && Modifier.isPublic(field.getModifiers())) {
appletModeField = field;

LFLogger.info("Found appletMode field: " + appletModeField.getName());
break;
}
}

return appletModeField;
}

public static CtClass findMouseHelperClass(ClassPool pool) throws NotFoundException {
if (mouseHelperClass != null)
return mouseHelperClass;

if (minecraftClass == null)
findMinecraftClass(pool);

CtField[] minecraftFields = minecraftClass.getDeclaredFields();

for (CtField field : minecraftFields) {
CtConstructor[] constructors = field.getType().getConstructors();

for (CtConstructor constr : constructors) {
CtClass[] constrParams = constr.getParameterTypes();

if (constrParams.length >= 1 && constrParams[0].getName().equals("java.awt.Component")) {
mouseHelperClass = field.getType();

LFLogger.info("Found match for MouseHelper class: " + mouseHelperClass.getName());
break;
}
}
}

return mouseHelperClass;
}
}
41 changes: 5 additions & 36 deletions src/main/java/uk/betacraft/legacyfix/patch/impl/DeAwtPatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Modifier;

import javassist.CannotCompileException;
import javassist.CtClass;
Expand All @@ -16,6 +15,7 @@
import uk.betacraft.legacyfix.LegacyFixAgent;
import uk.betacraft.legacyfix.patch.Patch;
import uk.betacraft.legacyfix.patch.PatchException;
import uk.betacraft.legacyfix.patch.PatchHelper;
import uk.betacraft.legacyfix.util.IconUtils;

public class DeAwtPatch extends Patch {
Expand All @@ -36,52 +36,21 @@ public void apply(final Instrumentation inst) throws Exception {
}

// Find the applet class
String[] typicalPaths = new String[] {"net.minecraft.client.MinecraftApplet", "com.mojang.minecraft.MinecraftApplet"};

CtClass appletClass = null;
for (String path : typicalPaths) {
appletClass = pool.getOrNull(path);

if (appletClass != null)
break;
}
CtClass appletClass = PatchHelper.findMinecraftAppletClass(pool);

if (appletClass == null)
throw new PatchException("No applet class could be found");

final String appletClassName = appletClass.getName();

// Find the main Minecraft class
CtClass minecraftClass = pool.getOrNull("net.minecraft.client.Minecraft");

if (minecraftClass == null) {
for (CtField field : appletClass.getDeclaredFields()) {
String className = field.getType().getName();

if (!className.equals("java.awt.Canvas") &&
!className.equals("java.lang.Thread") &&
!className.equals("long")) {

minecraftClass = field.getType();
LFLogger.info("Found Minecraft class: " + minecraftClass.getName());
}
}
}
CtClass minecraftClass = PatchHelper.findMinecraftClass(pool);

if (minecraftClass == null)
throw new PatchException("No main Minecraft class could be found");

// Find the appletMode field
CtField appletmodeField = null;
for (CtField field : minecraftClass.getDeclaredFields()) {
String className = field.getType().getName();

if (className.equals("boolean") && Modifier.isPublic(field.getModifiers())) {
appletmodeField = field;
LFLogger.info("Found appletMode field: " + appletmodeField.getName());
break;
}
}
CtField appletModeField = PatchHelper.findAppletModeField(pool);

CtMethod initMethod = appletClass.getDeclaredMethod("init");

Expand All @@ -96,7 +65,7 @@ public void apply(final Instrumentation inst) throws Exception {
" parent = parent.getParent();" +
"}" +
// Set 'appletMode' to 'false' so the game handles LWJGL Display correctly
"$0." + minecraftClass.getName() + "." + appletmodeField.getName() + " = false;"
"$0." + minecraftClass.getName() + "." + appletModeField.getName() + " = false;"
);

// Take the canvas class name to later edit out its removeNotify() method
Expand Down
145 changes: 145 additions & 0 deletions src/main/java/uk/betacraft/legacyfix/patch/impl/MousePatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package uk.betacraft.legacyfix.patch.impl;

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import uk.betacraft.legacyfix.LFLogger;
import uk.betacraft.legacyfix.LegacyFixAgent;
import uk.betacraft.legacyfix.patch.Patch;
import uk.betacraft.legacyfix.patch.PatchException;
import uk.betacraft.legacyfix.patch.PatchHelper;

/**
* Fixes mouse on modern macOS, required for deAWT
*/
public class MousePatch extends Patch {
private boolean mouseDXYmatched;

public MousePatch() {
super("mouse", "Fixes mouse on modern macOS, also required for deAWT", true);
}

@Override
public void apply(final Instrumentation inst) throws PatchException, Exception {

final CtClass mouseHelperClass = PatchHelper.findMouseHelperClass(pool);

if (mouseHelperClass != null) {

CtMethod[] mouseHelperMethods = mouseHelperClass.getDeclaredMethods();
mouseHelperMethods[0].setBody(
"{" +
" org.lwjgl.input.Mouse.setGrabbed(true);" +
" $0.a = 0;" +
" $0.b = 0;" +
"}"
);

String body2 = (
"{" +
" $0.a = org.lwjgl.input.Mouse.getDX();" +
" $0.b = org.lwjgl.input.Mouse.getDY();" +
"}"
);

String body2invert = (
"{" +
" $0.a = org.lwjgl.input.Mouse.getDX();" +
" $0.b = -(org.lwjgl.input.Mouse.getDY());" +
"}"
);

// Mouse handling changed sometime during alpha
boolean invert = "invert".equals(LegacyFixAgent.getSettings().get("invertMouse"));

LFLogger.info("MOUSE Y INVERT: " + Boolean.toString(invert));

if (mouseHelperMethods.length == 2) {
mouseHelperMethods[1].setBody((invert ? body2invert : body2));
} else {
mouseHelperMethods[1].setBody(
"{" +
" org.lwjgl.input.Mouse.setCursorPosition(org.lwjgl.opengl.Display.getWidth() / 2, org.lwjgl.opengl.Display.getHeight() / 2);" +
" org.lwjgl.input.Mouse.setGrabbed(false);" +
"}"
);

mouseHelperMethods[2].setBody((invert ? body2invert : body2));
}

inst.redefineClasses(new ClassDefinition[] {new ClassDefinition(Class.forName(mouseHelperClass.getName()), mouseHelperClass.toBytecode())});
}

// Replace all calls to Mouse.getDX() and Mouse.getDY() with 0
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader,
String className,
Class<?> classRedefined,
ProtectionDomain domain,
byte[] classfileBuffer) {

CtClass clas = pool.getOrNull(className.replace("/", "."));
if (clas == null || clas.getName().startsWith("org.lwjgl") || clas.getName().equals(mouseHelperClass.getName()))
return null;

try {
clas.instrument(new ExprEditor() {
public void edit(MethodCall m) throws CannotCompileException {

if ("org.lwjgl.input.Mouse".equals(m.getClassName()) &&
"getDX".equals(m.getMethodName()) &&
"()I".equalsIgnoreCase(m.getSignature())) {

mouseDXYmatched = true;
m.replace("$_ = 0;");
LFLogger.info("Mouse.getDX() match!");

} else if ("org.lwjgl.input.Mouse".equals(m.getClassName()) &&
"getDY".equals(m.getMethodName()) &&
"()I".equalsIgnoreCase(m.getSignature())) {

mouseDXYmatched = true;
m.replace("$_ = 0;");
LFLogger.info("Mouse.getDY() match!");
}
}
});

if (mouseDXYmatched) {
mouseDXYmatched = false;
inst.removeTransformer(this); // job is done, don't fire any more classes
return clas.toBytecode();
}
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});

// Some versions refer to setNativeCursor within methods of the Minecraft class,
// we need to account for that too
CtClass mouseClass = pool.get("org.lwjgl.input.Mouse");
CtClass cursorClass = pool.get("org.lwjgl.input.Cursor");
CtMethod setNativeCursorMethod = mouseClass.getDeclaredMethod("setNativeCursor", new CtClass[] {cursorClass});

setNativeCursorMethod.setBody(
"{" +
" org.lwjgl.input.Mouse.setGrabbed($1 != null);" +
" if ($1 == null) {" +
" org.lwjgl.input.Mouse.setCursorPosition(org.lwjgl.opengl.Display.getWidth() / 2, org.lwjgl.opengl.Display.getHeight() / 2);" +
" }" +
" return null;" + // we don't need this to return anything
"}"
);

inst.redefineClasses(new ClassDefinition[] {new ClassDefinition(Class.forName("org.lwjgl.input.Mouse"), mouseClass.toBytecode())});
}
}

0 comments on commit dc9ed21

Please sign in to comment.