Skip to content

Instantly share code, notes, and snippets.

@Geolykt
Created June 16, 2021 12:02
Show Gist options
  • Save Geolykt/d2af470e97ee5575de5187077495ea71 to your computer and use it in GitHub Desktop.
Save Geolykt/d2af470e97ee5575de5187077495ea71 to your computer and use it in GitHub Desktop.
Starloader ASM mod that enables the use of .ogg and .wav files. Tested with Galimulator 4.8 to Galimulator 4.9-beta.7
package de.geolykt.moremusic;
import java.lang.reflect.Method;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
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.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import de.geolykt.starloader.mod.Extension;
import net.minestom.server.extras.selfmodification.CodeModifier;
public class MMFCodeModifier extends CodeModifier {
private final @NotNull Object logger;
public MMFCodeModifier(@NotNull Extension ext) {
try {
Method method = Extension.class.getMethod("getLogger");
logger = method.invoke(ext);
} catch (Exception e1) {
throw new RuntimeException(e1);
}
}
@Override
public @Nullable String getNamespace() {
return "snoddasmannen";
}
protected boolean isInjectionTarget(MethodNode node) {
InsnList instructions = node.instructions;
for (AbstractInsnNode instruction : instructions) {
if (instruction instanceof LdcInsnNode) {
LdcInsnNode ldcInsnNode = (LdcInsnNode) instruction;
if (ldcInsnNode.cst.equals("data/music")) {
return true;
}
}
}
return false;
}
protected void logError(String message) {
try {
logger.getClass().getMethod("error", String.class).invoke(logger, message);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean transform(ClassNode source) {
if (!source.name.equals("snoddasmannen/galimulator/AudioManager")) {
return false;
}
MethodNode foundNode = null;
for (MethodNode node : source.methods) {
if (isInjectionTarget(node)) {
if (foundNode != null) {
logError("Found multiple candidates for modification!");
}
foundNode = node;
}
}
if (foundNode == null) {
logError("Unable to find correct method node!");
return false;
}
try {
transformMethod(foundNode);
} catch (Throwable e) {
e.printStackTrace();
return false;
}
return true;
}
protected void transformMethod(@NotNull MethodNode foundNode) {
AbstractInsnNode currentInstruction = foundNode.instructions.getFirst();
InsnList instructions = foundNode.instructions;
// obtain the loop entrypoint
while (true) {
if (currentInstruction instanceof InsnNode && currentInstruction.getOpcode() == Opcodes.ARRAYLENGTH) {
break;
}
currentInstruction = currentInstruction.getNext();
if (currentInstruction == null) {
logError("Unable to find loop start.");
return;
}
}
// obtain loop start label
LabelNode loopStart = null;
while (true) {
if (currentInstruction instanceof LabelNode) {
loopStart = (LabelNode) currentInstruction;
break;
}
currentInstruction = currentInstruction.getNext();
if (currentInstruction == null) {
logError("Unable to find loop start label.");
return;
}
}
// Obtain the end of the interesting section
VarInsnNode aloadInsn = null;
LabelNode endInterestingLabel = new LabelNode();
currentInstruction = loopStart; // reset pointer as the instructions after the loop end do not interest us
// Obtain ALOAD 0; NEW class instructions
while (true) {
if (currentInstruction == null) {
throw new NullPointerException("Unable to transform method. Issue not expected to actually happen.");
}
// obtain ALOAD instruction
if (currentInstruction instanceof VarInsnNode && currentInstruction.getOpcode() == Opcodes.ALOAD &&
currentInstruction.getNext() != null && currentInstruction.getNext() instanceof TypeInsnNode
&& currentInstruction.getNext().getOpcode() == Opcodes.NEW) {
aloadInsn = (VarInsnNode) currentInstruction;
break;
}
currentInstruction = currentInstruction.getNext();
if (currentInstruction == instructions.getLast()) {
logError("Unable to find end of interesting section. (Instructions exhausted)");
return;
}
}
instructions.insertBefore(aloadInsn, endInterestingLabel);
currentInstruction = loopStart;
LdcInsnNode ldcMp3; // The LDC "mp3" instruction
while (true) {
if (currentInstruction == null) {
throw new InternalError(); // impossible to happen
}
if (currentInstruction instanceof LdcInsnNode && ((LdcInsnNode) currentInstruction).cst.equals("mp3")) {
ldcMp3 = (LdcInsnNode) currentInstruction;
break;
}
currentInstruction = currentInstruction.getNext();
if (currentInstruction == instructions.getLast()) {
logError("Unable to find ldcMp3 instruction. (Instructions exhausted)");
return;
}
}
// The actual modifications happen now
AbstractInsnNode getExtension = ldcMp3.getPrevious();
AbstractInsnNode loadFile = getExtension.getPrevious();
AbstractInsnNode compare = ldcMp3.getNext();
JumpInsnNode ifEq = (JumpInsnNode) compare.getNext();
// Replace the jump operator
AbstractInsnNode currentHead = new JumpInsnNode(Opcodes.IFNE, endInterestingLabel);
instructions.insert(compare, currentHead);
instructions.insert(currentHead, new JumpInsnNode(Opcodes.GOTO, ifEq.label));
instructions.remove(ifEq);
var loadFileIsn = loadFile.clone(null);
instructions.insert(currentHead, loadFileIsn);
var getExtIsn = getExtension.clone(null);
instructions.insert(loadFileIsn, getExtIsn);
var ldcisn = new LdcInsnNode("ogg");
instructions.insert(getExtIsn, ldcisn);
var cmpIsn = compare.clone(null);
instructions.insert(ldcisn, cmpIsn);
var ifneIsn = new JumpInsnNode(Opcodes.IFNE, endInterestingLabel);
instructions.insert(cmpIsn, ifneIsn);
currentHead = ifneIsn;
loadFileIsn = loadFile.clone(null);
instructions.insert(currentHead, loadFileIsn);
getExtIsn = getExtension.clone(null);
instructions.insert(loadFileIsn, getExtIsn);
ldcisn = new LdcInsnNode("wav");
instructions.insert(getExtIsn, ldcisn);
cmpIsn = compare.clone(null);
instructions.insert(ldcisn, cmpIsn);
ifneIsn = new JumpInsnNode(Opcodes.IFNE, endInterestingLabel);
instructions.insert(cmpIsn, ifneIsn);
currentHead = ifneIsn;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment