diff --git a/build.gradle b/build.gradle index 49ba740..a296930 100644 --- a/build.gradle +++ b/build.gradle @@ -144,6 +144,9 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } + + implementation 'org.ow2.asm:asm:5.2' + implementation 'org.ow2.asm:asm-commons:5.2' } apply from: 'gradle/scripts/dependencies.gradle' diff --git a/gradle.properties b/gradle.properties index 449f7d6..9dfe3bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -115,9 +115,9 @@ mixin_refmap = mixins.${mod_id}.refmap.json # Only make a coremod if you are absolutely sure of what you are doing # Change the property `coremod_includes_mod` to false if your coremod doesn't have a @Mod annotation # You MUST state a class name for `coremod_plugin_class_name` if you are making a coremod, the class should implement `IFMLLoadingPlugin` -is_coremod = false +is_coremod = true coremod_includes_mod = true -coremod_plugin_class_name = +coremod_plugin_class_name = world.maryt.rawinput.RawInputCorePlugin # AssetMover # Convenient way to allow downloading of assets from official vanilla Minecraft servers, CurseForge, or any direct links diff --git a/src/main/java/world/maryt/rawinput/RawInputCorePlugin.java b/src/main/java/world/maryt/rawinput/RawInputCorePlugin.java new file mode 100644 index 0000000..9947a87 --- /dev/null +++ b/src/main/java/world/maryt/rawinput/RawInputCorePlugin.java @@ -0,0 +1,38 @@ +package world.maryt.rawinput; + +import java.util.Map; + +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +/** + * Coremod plugin that transforms jinput classes with ASM + * This ensures our safety check is applied before problematic input devices can crash the game + */ +@IFMLLoadingPlugin.MCVersion("1.12.2") +@IFMLLoadingPlugin.Name("RawInput Coremod") +public class RawInputCorePlugin implements IFMLLoadingPlugin { + @Override + public String[] getASMTransformerClass() { + return new String[]{"world.maryt.rawinput.RawInputCoreTransformer"}; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + // No additional data needed + } + + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/java/world/maryt/rawinput/RawInputCoreTransformer.java b/src/main/java/world/maryt/rawinput/RawInputCoreTransformer.java new file mode 100644 index 0000000..c48eb84 --- /dev/null +++ b/src/main/java/world/maryt/rawinput/RawInputCoreTransformer.java @@ -0,0 +1,104 @@ +package world.maryt.rawinput; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * ASM transformer that prevents crashes from problematic input devices + * by rejecting devices with num_buttons >= 32 before they can cause issues + */ +public class RawInputCoreTransformer implements net.minecraft.launchwrapper.IClassTransformer { + + private static final Logger LOGGER = LogManager.getLogger("RawInputCoreTransformer"); + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + // Only transform jinput classes + if (!name.startsWith("net.java.games.input")) { + return basicClass; + } + + // Target RawMouseInfo for the safety check + if (name.equals("net.java.games.input.RawMouseInfo")) { + LOGGER.info("Applying safety check to RawMouseInfo for problematic input devices"); + return transformRawMouseInfo(basicClass); + } + + return basicClass; + } + + private byte[] transformRawMouseInfo(byte[] basicClass) { + try { + ClassReader reader = new ClassReader(basicClass); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0); + + // Find the createControllerFromDevice method + for (Object methodObj : classNode.methods) { + MethodNode method = (MethodNode) methodObj; + if (method.name.equals("createControllerFromDevice") && + method.desc.equals("(Lnet/java/games/input/RawDevice;Lnet/java/games/input/SetupAPIDevice;)Lnet/java/games/input/Controller;")) { + + LOGGER.info("Injecting safety check: rejecting devices with num_buttons >= 32"); + + // Create the safety check: if (num_buttons >= 32) return null; + InsnList safetyCheck = new InsnList(); + + // Create a label for the jump target + LabelNode skipReturnLabel = new LabelNode(); + + // Load "this" (aload_0) + safetyCheck.add(new VarInsnNode(Opcodes.ALOAD, 0)); + + // Get the num_buttons field value + safetyCheck.add(new FieldInsnNode( + Opcodes.GETFIELD, + "net/java/games/input/RawMouseInfo", + "num_buttons", + "I" + )); + + // Load the constant 32 + safetyCheck.add(new LdcInsnNode(32)); + + // Compare num_buttons >= 32 (if_icmplt means if num_buttons < 32, skip the return) + safetyCheck.add(new JumpInsnNode(Opcodes.IF_ICMPLT, skipReturnLabel)); + + // If num_buttons >= 32, return null + safetyCheck.add(new InsnNode(Opcodes.ACONST_NULL)); + safetyCheck.add(new InsnNode(Opcodes.ARETURN)); + + // Add the label where execution should continue if num_buttons < 32 + safetyCheck.add(skipReturnLabel); + + // Insert the safety check at the start of the method + method.instructions.insertBefore(method.instructions.getFirst(), safetyCheck); + + LOGGER.info("Safety check successfully injected"); + break; + } + } + + // Write the transformed class + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + return writer.toByteArray(); + + } catch (Exception e) { + LOGGER.error("Failed to transform RawMouseInfo class", e); + return basicClass; + } + } +}