modernize ASM

This commit is contained in:
FalsePattern committed 2024-06-07 19:56:58 +02:00
1 parent a4868e8de8
commit 07474f33f9
15 files changed
+475 -394

No files matched your search

@@ -1,66 +0,0 @@
package com.falsepattern.endlessids;
import lombok.val;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
public class Unsafer {
public static final Unsafe UNSAFE;
static {
Field unsafeField = null;
Unsafe unsafe = null;
for (val field : Unsafe.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) &&
field.getType().equals(Unsafe.class)) {
unsafeField = field;
break;
}
}
if (unsafeField != null) {
unsafeField.setAccessible(true);
try {
unsafe = (Unsafe) unsafeField.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (unsafe == null) {
UNSAFE = null;
new IllegalStateException("Could not retrieve Unsafe! Falling back to bytebuffers!").printStackTrace();
} else {
UNSAFE = unsafe;
}
}
public static void arraycopy(short[] source, int sourceOffset, byte[] target, int targetOffset, int shorts) {
if (UNSAFE != null) {
val sourceOffsetBytes = sourceOffset * 2;
val arrayOffsetSource = UNSAFE.arrayBaseOffset(source.getClass());
val arrayOffsetTarget = UNSAFE.arrayBaseOffset(target.getClass());
UNSAFE.copyMemory(source, arrayOffsetSource + sourceOffsetBytes, target, arrayOffsetTarget + targetOffset,
(long) shorts << 1);
} else {
ByteBuffer.wrap(target, targetOffset, target.length - targetOffset)
.asShortBuffer()
.put(source, sourceOffset, shorts);
}
}
public static void arraycopy(byte[] source, int sourceOffset, short[] target, int targetOffset, int shorts) {
if (UNSAFE != null) {
val targetOffsetBytes = targetOffset * 2;
val arrayOffsetSource = UNSAFE.arrayBaseOffset(source.getClass());
val arrayOffsetTarget = UNSAFE.arrayBaseOffset(target.getClass());
UNSAFE.copyMemory(source, arrayOffsetSource + sourceOffset, target, arrayOffsetTarget + targetOffsetBytes,
(long) shorts << 1);
} else {
ShortBuffer.wrap(target, targetOffset, target.length - targetOffset)
.put(ByteBuffer.wrap(source, sourceOffset, shorts * 2).asShortBuffer());
}
}
}
@@ -1,71 +0,0 @@
package com.falsepattern.endlessids.asm;
import com.falsepattern.endlessids.asm.transformer.ChunkProviderSuperPatcher;
import com.falsepattern.endlessids.asm.transformer.FmlRegistry;
import com.falsepattern.endlessids.asm.transformer.GameDataAccelerator;
import com.falsepattern.endlessids.asm.transformer.LOTRFieldExposer;
import com.falsepattern.endlessids.asm.transformer.SpaceCoreModInfoGenerator;
import com.falsepattern.endlessids.config.GeneralConfig;
import lombok.val;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
public enum ClassEdit {
ChunkProviderSuperPatcher(new ChunkProviderSuperPatcher(), () -> GeneralConfig.extendBiome),
FmlRegistry(new FmlRegistry(),
() -> GeneralConfig.extendBlockItem,
"cpw.mods.fml.common.registry.GameData",
"cpw.mods.fml.common.registry.FMLControlledNamespacedRegistry"),
LOTRFieldExposer(new LOTRFieldExposer(),
() -> GeneralConfig.extendBiome,
"lotr.common.network.LOTRPacketBiomeVariantsWatch"),
SpaceCoreModInfoGenerator(new SpaceCoreModInfoGenerator(), () -> true,
"com.spacechase0.minecraft.spacecore.mcp.ModInfoGenerator"),
GameDataAccelerator(new GameDataAccelerator(), () -> GeneralConfig.enableRegistryPerformanceTweak, "cpw.mods.fml.common.registry.GameData"),
;
private static final Map<String, List<ClassEdit>> editMap;
static {
editMap = new HashMap<>();
for (final ClassEdit edit : values()) {
for (final String name : edit.classNames) {
ClassEdit.editMap.computeIfAbsent(name, (ignored) -> new ArrayList<>()).add(edit);
}
}
}
private final IClassNodeTransformer transformer;
private final BooleanSupplier cond;
private final String[] classNames;
ClassEdit(final IClassNodeTransformer transformer, BooleanSupplier cond, final String... classNames) {
this.transformer = transformer;
this.cond = cond;
this.classNames = classNames;
}
public static List<ClassEdit> get(final String className) {
val edit = ClassEdit.editMap.get(className);
if (edit == null) {
return Collections.emptyList();
}
return edit.stream()
.filter(r -> r.cond.getAsBoolean())
.collect(Collectors.toList());
}
public String getName() {
return this.name();
}
public IClassNodeTransformer getTransformer() {
return this.transformer;
}
}
@@ -1,6 +1,6 @@
package com.falsepattern.endlessids;
package com.falsepattern.endlessids.asm;
import com.falsepattern.endlessids.asm.IETransformer;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.config.GeneralConfig;
import lombok.SneakyThrows;
import lombok.val;
@@ -19,29 +19,30 @@ import static cpw.mods.fml.relauncher.IFMLLoadingPlugin.Name;
import static cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
@MCVersion("1.7.10")
@Name(Tags.MODID)
@Name(Tags.MODID + "_core")
@TransformerExclusions({"com.falsepattern.endlessids.asm", "com.falsepattern.endlessids.config.GeneralConfig"})
@DependsOn("falsepatternlib")
public class IEPlugin implements IFMLLoadingPlugin {
public IEPlugin() {
public class EndlessIDsCore implements IFMLLoadingPlugin {
public static boolean deobfuscated;
public EndlessIDsCore() {
super();
}
@SneakyThrows
public String[] getASMTransformerClass() {
deobfuscated = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
val cl = Launch.classLoader;
val field = LaunchClassLoader.class.getDeclaredField("transformerExceptions");
field.setAccessible(true);
val exceptions = (Set<String>) field.get(cl);
if (exceptions.contains("code.elix_x.coremods")) {
IETransformer.logger.info("AntiIDConflict detected!");
IETransformer.logger.info("Removing ASM protections so that we can fix its code.");
EndlessIDsTransformer.logger.info("AntiIDConflict detected!");
EndlessIDsTransformer.logger.info("Removing ASM protections so that we can fix its code.");
exceptions.remove("code.elix_x.coremods");
exceptions.add("code.elix_x.coremods.antiidconflict.core");
exceptions.add("code.elix_x.coremods.antiidconflict.ByteCodeTester");
}
return new String[]{IETransformer.class.getName()};
return new String[]{EndlessIDsTransformer.class.getName()};
}
public String getModContainerClass() {
@@ -68,7 +69,6 @@ public class IEPlugin implements IFMLLoadingPlugin {
}
public void injectData(final Map<String, Object> data) {
IETransformer.isObfuscated = (Boolean) data.get("runtimeDeobfuscationEnabled");
}
public String getAccessTransformerClass() {
@@ -0,0 +1,45 @@
package com.falsepattern.endlessids.asm;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.transformer.DevFixer;
import com.falsepattern.endlessids.asm.transformer.FmlRegistry;
import com.falsepattern.endlessids.asm.transformer.GameDataAccelerator;
import com.falsepattern.endlessids.asm.transformer.SpaceCoreModInfoGenerator;
import com.falsepattern.endlessids.asm.transformer.chunk.ChunkProviderSuperPatcher;
import com.falsepattern.endlessids.asm.transformer.chunk.LOTRFieldExposer;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.falsepattern.lib.turboasm.MergeableTurboTransformer;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
public class EndlessIDsTransformer extends MergeableTurboTransformer {
public static final Logger logger = LogManager.getLogger(Tags.MODNAME + " ASM");
private static List<TurboClassTransformer> transformers() {
val result = new ArrayList<TurboClassTransformer>();
if (GeneralConfig.extendBiome) {
result.add(new ChunkProviderSuperPatcher());
result.add(new LOTRFieldExposer());
}
if (GeneralConfig.extendBlockItem) {
result.add(new FmlRegistry());
}
if (GeneralConfig.enableRegistryPerformanceTweak) {
new GameDataAccelerator();
}
if (EndlessIDsCore.deobfuscated) {
result.add(new SpaceCoreModInfoGenerator());
result.add(new DevFixer());
}
return result;
}
public EndlessIDsTransformer() {
super(transformers());
}
}
@@ -12,6 +12,8 @@ import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.WeakHashMap;
//Used by ASM
@SuppressWarnings("unused")
public class GameDataHooks {
private static WeakIdentityHashMap<Block, WeakReference<ItemBlock>> forGameData(GameData gameData) {
if (gameData.endlessIDsItemBlockCache == null) {
@@ -20,14 +22,10 @@ public class GameDataHooks {
return gameData.endlessIDsItemBlockCache;
}
//Called by ASM
@SuppressWarnings("unused")
public static void cacheItemBlock(ItemBlock itemBlock, GameData gameData) {
forGameData(gameData).put(itemBlock.field_150939_a, new WeakReference<>(itemBlock));
}
//Called by ASM
@SuppressWarnings("unused")
public static ItemBlock fetchCachedItemBlock(Block block, GameData gameData) {
var weakItem = forGameData(gameData).get(block);
if (weakItem != null) {
@@ -36,8 +34,6 @@ public class GameDataHooks {
return null;
}
//Called by ASM
@SuppressWarnings("unused")
public static Iterable<Item> fakeIterable() {
return Collections.emptyList();
}
@@ -1,92 +0,0 @@
package com.falsepattern.endlessids.asm;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.transformer.ChunkProviderSuperPatcher;
import com.falsepattern.endlessids.asm.transformer.DevFixer;
import lombok.val;
import lombok.var;
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 net.minecraft.launchwrapper.IClassTransformer;
import java.util.ArrayList;
import java.util.List;
public class IETransformer implements IClassTransformer {
public static final Logger logger;
private static final List<String> blacklist = new ArrayList<>();
public static boolean isObfuscated;
static {
logger = LogManager.getLogger(Tags.MODNAME + " ASM");
blacklist.add("net.minecraft.world.chunk.storage.AnvilChunkLoader");
}
public IETransformer() {
super();
}
public byte[] transform(final String name, final String transformedName, final byte[] bytes) {
if (bytes == null) {
return null;
}
if (blacklist.contains(transformedName)) {
return bytes;
}
var edits = new ArrayList<ClassEdit>(ClassEdit.get(transformedName));
ClassNode cn = new ClassNode(Opcodes.ASM5);
{
val reader = new ClassReader(bytes);
reader.accept(cn, 0);
}
boolean expanded = false;
if (ChunkProviderSuperPatcher.shouldPatch(cn)) {
expanded = true;
{
cn = new ClassNode(Opcodes.ASM5);
val reader = new ClassReader(bytes);
reader.accept(cn, ClassReader.EXPAND_FRAMES);
}
edits.add(ClassEdit.ChunkProviderSuperPatcher);
}
if (edits.size() == 0) {
return isObfuscated ? bytes : DevFixer.fixDev(bytes);
}
if (!isObfuscated) {
DevFixer.transform(cn);
}
for (final ClassEdit edit : edits) {
if (edit == null) {
continue;
}
IETransformer.logger.debug("Patching {} with {}...", transformedName, edit.getName());
try {
edit.getTransformer().transform(cn, IETransformer.isObfuscated);
} catch (AsmTransformException t) {
IETransformer.logger.error("Error transforming {} with {}: {}", transformedName, edit.getName(),
t.getMessage());
throw t;
} catch (Throwable t2) {
IETransformer.logger.error("Error transforming {} with {}: {}", transformedName, edit.getName(),
t2.getMessage());
throw new RuntimeException(t2);
}
}
final ClassWriter writer = new ClassWriter(expanded ? ClassWriter.COMPUTE_FRAMES : 0);
try {
cn.accept(writer);
} catch (Exception e) {
for (int i = 0; i < 100; i++) {
IETransformer.logger.fatal("FAILED TO TRANSFORM " + transformedName + ", WORLD MAY GET CORRUPTED!", e);
}
return bytes;
}
IETransformer.logger.debug("Patched {} successfully.", transformedName);
return writer.toByteArray();
}
}
@@ -1,27 +1,25 @@
package com.falsepattern.endlessids.asm.transformer;
import com.falsepattern.endlessids.asm.IETransformer;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.EndlessIDsCore;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import org.jetbrains.annotations.NotNull;
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.util.CheckClassAdapter;
import net.minecraft.launchwrapper.Launch;
import java.io.IOException;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DevFixer {
public class DevFixer implements TurboClassTransformer {
private static final ClassNode blocks;
static {
if (IETransformer.isObfuscated) {
if (!EndlessIDsCore.deobfuscated) {
blocks = null;
} else {
try {
@@ -35,41 +33,20 @@ public final class DevFixer {
}
}
public static byte[] fixDev(byte[] classBytes) {
if (IETransformer.isObfuscated) {
return classBytes;
}
val cn = new ClassNode(Opcodes.ASM5);
val reader = new ClassReader(classBytes);
reader.accept(cn, 0);
transform(cn);
val writer = new ClassWriter(0);
val check = new CheckClassAdapter(writer);
cn.accept(check);
return writer.toByteArray();
}
@SneakyThrows
public static void transform(final ClassNode cn) {
if (IETransformer.isObfuscated) {
return;
}
remapBlocks(cn);
if (cn.name.equals("net/minecraft/world/biome/BiomeGenBase")) {
biomeTweakerCompat(cn);
}
}
private static void biomeTweakerCompat(final ClassNode cn) {
private static boolean biomeTweakerCompat(final ClassNode cn) {
boolean modified = false;
for (val field : cn.fields) {
if (field.name.startsWith("spawnable")) {
field.access &= ~Opcodes.ACC_PROTECTED;
field.access |= Opcodes.ACC_PUBLIC;
modified = true;
}
}
return modified;
}
private static void remapBlocks(final ClassNode cn) {
private static boolean remapBlocks(final ClassNode cn) {
boolean modified = false;
for (val method : cn.methods) {
val instructions = method.instructions;
int insnCount = instructions.size();
@@ -81,11 +58,41 @@ public final class DevFixer {
for (val blockField : blocks.fields) {
if (blockField.name.equals(field.name) && !blockField.desc.equals(field.desc)) {
field.desc = blockField.desc;
modified = true;
}
}
}
}
}
}
return modified;
}
@Override
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "DevFixer";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
return EndlessIDsCore.deobfuscated && !"net.minecraft.world.chunk.storage.AnvilChunkLoader".equals(className);
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
boolean modified = false;
modified |= remapBlocks(cn);
if (className.equals("net.minecraft.world.biome.BiomeGenBase")) {
modified |= biomeTweakerCompat(cn);
}
return modified;
}
}
@@ -1,21 +1,46 @@
package com.falsepattern.endlessids.asm.transformer;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.AsmTransformException;
import com.falsepattern.endlessids.asm.AsmUtil;
import com.falsepattern.endlessids.asm.IClassNodeTransformer;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.falsepattern.endlessids.constants.ExtendedConstants;
import com.falsepattern.endlessids.constants.VanillaConstants;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import lombok.var;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
public class FmlRegistry implements IClassNodeTransformer {
public class FmlRegistry implements TurboClassTransformer {
public FmlRegistry() {
super();
}
@Override
public void transform(final ClassNode cn, final boolean obfuscated) {
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "FMLRegistry";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
return GeneralConfig.extendBlockItem && ("cpw.mods.fml.common.registry.GameData".equals(className) ||
"cpw.mods.fml.common.registry.FMLControlledNamespacedRegistry".equals(className));
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
var field = AsmUtil.findField(cn, "MAX_BLOCK_ID", true);
if (field != null) {
field.value = ExtendedConstants.maxBlockID;
@@ -35,5 +60,6 @@ public class FmlRegistry implements IClassNodeTransformer {
if (!found) {
throw new AsmTransformException("can't find 4095 constant in any method");
}
return true;
}
}
@@ -1,12 +1,15 @@
package com.falsepattern.endlessids.asm.transformer;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.AsmTransformException;
import com.falsepattern.endlessids.asm.IClassNodeTransformer;
import com.falsepattern.endlessids.asm.IETransformer;
import com.falsepattern.endlessids.asm.EndlessIDsTransformer;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import lombok.var;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnNode;
@@ -14,35 +17,9 @@ import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
public class GameDataAccelerator implements IClassNodeTransformer {
public class GameDataAccelerator implements TurboClassTransformer {
private static final int MASK_REGISTER_BLOCK = 0b1;
private static final int MASK_REGISTER_ITEM = 0b10;
@Override
public void transform(ClassNode classNode, boolean obfuscated) {
var cacheField = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "endlessIDsItemBlockCache", "Lcom/falsepattern/endlessids/util/WeakIdentityHashMap;", null, null);
classNode.fields.add(cacheField);
int tweaksLeft = MASK_REGISTER_BLOCK | MASK_REGISTER_ITEM;
for (val method: classNode.methods) {
if ((tweaksLeft & MASK_REGISTER_BLOCK) != 0 &&
method.name.equals("registerBlock") &&
method.desc.equals("(Lnet/minecraft/block/Block;Ljava/lang/String;I)I")) {
transformRegisterBlock(method);
tweaksLeft &= ~MASK_REGISTER_BLOCK;
}
if ((tweaksLeft & MASK_REGISTER_ITEM) != 0 &&
method.name.equals("registerItem") &&
method.desc.equals("(Lnet/minecraft/item/Item;Ljava/lang/String;I)I")) {
transformRegisterItem(method);
tweaksLeft &= ~MASK_REGISTER_ITEM;
}
if (tweaksLeft == 0) {
break;
}
}
if (tweaksLeft != 0) {
IETransformer.logger.error("Could not accelerate block registration!");
}
}
private void transformRegisterItem(MethodNode method) {
var iter = method.instructions.iterator();
@@ -65,7 +42,7 @@ public class GameDataAccelerator implements IClassNodeTransformer {
}
private static void transformError(MethodNode method) {
IETransformer.logger.error("Could not add caching code to GameData." + method.name + method.desc);
EndlessIDsTransformer.logger.error("Could not add caching code to GameData." + method.name + method.desc);
}
private void transformRegisterBlock(MethodNode method) {
@@ -106,4 +83,46 @@ public class GameDataAccelerator implements IClassNodeTransformer {
}
transformError(method);
}
@Override
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "GameDataAccelerator";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
return GeneralConfig.enableRegistryPerformanceTweak && "cpw.mods.fml.common.registry.GameData".equals(className);
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
var cacheField = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "endlessIDsItemBlockCache", "Lcom/falsepattern/endlessids/util/WeakIdentityHashMap;", null, null);
cn.fields.add(cacheField);
int tweaksLeft = MASK_REGISTER_BLOCK | MASK_REGISTER_ITEM;
for (val method: cn.methods) {
if ((tweaksLeft & MASK_REGISTER_BLOCK) != 0 &&
method.name.equals("registerBlock") &&
method.desc.equals("(Lnet/minecraft/block/Block;Ljava/lang/String;I)I")) {
transformRegisterBlock(method);
tweaksLeft &= ~MASK_REGISTER_BLOCK;
} else if ((tweaksLeft & MASK_REGISTER_ITEM) != 0 &&
method.name.equals("registerItem") &&
method.desc.equals("(Lnet/minecraft/item/Item;Ljava/lang/String;I)I")) {
transformRegisterItem(method);
tweaksLeft &= ~MASK_REGISTER_ITEM;
}
if (tweaksLeft == 0) {
return true;
}
}
throw new AsmTransformException("Failed to accelerate block registration! Disable enableRegistryPerformanceTweak in the EndlessIDs config!");
}
}
@@ -1,20 +0,0 @@
package com.falsepattern.endlessids.asm.transformer;
import com.falsepattern.endlessids.asm.IClassNodeTransformer;
import lombok.val;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
public class LOTRFieldExposer implements IClassNodeTransformer {
@Override
public void transform(ClassNode classNode, boolean obfuscated) {
for (val field: classNode.fields) {
if ("chunkX".equals(field.name) ||
"chunkZ".equals(field.name) ||
"variants".equals(field.name)) {
field.access &= ~Opcodes.ACC_PRIVATE;
field.access |= Opcodes.ACC_PUBLIC;
}
}
}
}
@@ -1,22 +1,43 @@
package com.falsepattern.endlessids.asm.transformer;
import com.falsepattern.endlessids.asm.IClassNodeTransformer;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.EndlessIDsCore;
import com.falsepattern.endlessids.asm.EndlessIDsTransformer;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
public class SpaceCoreModInfoGenerator implements IClassNodeTransformer {
public class SpaceCoreModInfoGenerator implements TurboClassTransformer {
@Override
public void transform(final ClassNode cn, final boolean obfuscated) {
if (obfuscated) {
return;
}
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "SpaceCoreModInfoGenerator";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
return EndlessIDsCore.deobfuscated && "com.spacechase0.minecraft.spacecore.mcp.ModInfoGenerator".equals(className);
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
for (val method : cn.methods) {
if (method.name.equals("generate")) {
val iterator = method.instructions.iterator();
iterator.add(new InsnNode(Opcodes.RETURN));
return true;
}
}
return false;
}
}
@@ -1,11 +1,14 @@
package com.falsepattern.endlessids.asm.transformer;
package com.falsepattern.endlessids.asm.transformer.chunk;
import com.falsepattern.endlessids.asm.IClassNodeTransformer;
import com.falsepattern.endlessids.asm.IETransformer;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.endlessids.asm.EndlessIDsCore;
import com.falsepattern.endlessids.asm.EndlessIDsTransformer;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.Data;
import lombok.val;
import lombok.var;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
@@ -21,6 +24,7 @@ import org.objectweb.asm.tree.VarInsnNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -116,7 +120,7 @@ import java.util.function.Supplier;
* L6
* </pre>
*/
public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
public class ChunkProviderSuperPatcher implements TurboClassTransformer {
public static final String[] CLASS_BiomeGenBase;
public static final String[] CLASS_IChunkProvider;
public static final String[] CLASS_ChunkProviderGenerate;
@@ -149,20 +153,20 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
private static State<AbstractInsnNode> STATE_FINAL = null;
static {
if (IETransformer.isObfuscated) {
CLASS_BiomeGenBase = new String[]{"ahu", "net/minecraft/world/biome/BiomeGenBase"};
CLASS_IChunkProvider = new String[]{"apu", "net/minecraft/world/chunk/IChunkProvider"};
CLASS_ChunkProviderGenerate = new String[]{"aqz", "net/minecraft/world/gen/ChunkProviderGenerate"};
CLASS_Chunk = new String[]{"apx", "net/minecraft/world/chunk/Chunk"};
FIELD_biomeID = new String[]{"ay", "field_76756_M"};
METHOD_getBiomeArray = new String[]{"m", "func_76605_m"};
} else {
if (EndlessIDsCore.deobfuscated) {
CLASS_BiomeGenBase = new String[]{"net/minecraft/world/biome/BiomeGenBase"};
CLASS_IChunkProvider = new String[]{"net/minecraft/world/chunk/IChunkProvider"};
CLASS_ChunkProviderGenerate = new String[]{"net/minecraft/world/gen/ChunkProviderGenerate"};
CLASS_Chunk = new String[]{"net/minecraft/world/chunk/Chunk"};
FIELD_biomeID = new String[]{"biomeID"};
METHOD_getBiomeArray = new String[]{"getBiomeArray"};
} else {
CLASS_BiomeGenBase = new String[]{"ahu", "net/minecraft/world/biome/BiomeGenBase"};
CLASS_IChunkProvider = new String[]{"apu", "net/minecraft/world/chunk/IChunkProvider"};
CLASS_ChunkProviderGenerate = new String[]{"aqz", "net/minecraft/world/gen/ChunkProviderGenerate"};
CLASS_Chunk = new String[]{"apx", "net/minecraft/world/chunk/Chunk"};
FIELD_biomeID = new String[]{"ay", "field_76756_M"};
METHOD_getBiomeArray = new String[]{"m", "func_76605_m"};
}
}
@@ -170,9 +174,10 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
STATE_FINAL = (insn, memory) -> STATE_FINAL;
STATE0 = casting(VarInsnNode.class, (insn, memory) -> {
memory.reset();
if (insn.getOpcode() == Opcodes.ALOAD) {
memory.chunkIndex = insn.var;
memory.startingInsn = memory.currentInsn;
memory.startingInsn = insn;
return STATE1;
}
return STATE0;
@@ -183,7 +188,7 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
anyMatch(insn.owner, CLASS_Chunk) &&
anyMatch(insn.name, METHOD_getBiomeArray) &&
insn.desc.equals("()[B")) {
memory.getBiomeArrayInsn = memory.currentInsn;
memory.getBiomeArrayInsn = insn;
return STATE2;
}
return STATE0;
@@ -192,6 +197,7 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
STATE2 = casting(VarInsnNode.class, (insn, memory) -> {
if (insn.getOpcode() == Opcodes.ASTORE) {
memory.biomeArrayIndex = insn.var;
memory.startPatchingFrames();
return STATE3;
}
return STATE0;
@@ -294,7 +300,7 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
STATE16 = casting(InsnNode.class, (insn, memory) -> {
if (insn.getOpcode() == Opcodes.I2B) {
memory.castInsn = memory.currentInsn;
memory.castInsn = insn;
return STATE17;
}
return STATE0;
@@ -302,25 +308,13 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
STATE17 = casting(InsnNode.class, (insn, memory) -> {
if (insn.getOpcode() == Opcodes.BASTORE) {
memory.arrayStoreInsn = memory.currentInsn;
memory.arrayStoreInsn = insn;
return STATE_FINAL;
}
return STATE0;
});
}
public static boolean shouldPatch(final ClassNode cn) {
if (!GeneralConfig.extendBiome) {
return false;
}
for (MethodNode method : cn.methods) {
if (scanForBrokenCall(method)) {
return true;
}
}
return false;
}
private static boolean scanForBrokenCall(MethodNode method) {
val instructions = method.instructions;
val insnCount = instructions.size();
@@ -388,23 +382,28 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
};
}
private static Memory findTheTarget(MethodNode method) {
private static Memory findTheTarget(MethodNode method, ClassNode cn) {
val insnList = method.instructions;
var currentState = STATE0;
val memory = new Memory();
for (int insnIndex = 0; insnIndex < insnList.size(); insnIndex++) {
memory.currentInsn = insnIndex;
val insn = insnList.get(insnIndex);
memory.initLocals(method, cn);
val iter = insnList.iterator();
while (iter.hasNext()) {
val insn = iter.next();
if (insn instanceof FrameNode) {
val frame = (FrameNode) insn;
if (frame.type == Opcodes.F_NEW) {
memory.locals.clear();
memory.locals.addAll(frame.local);
}
memory.recordFrame(frame);
memory.updateLocalsState(frame);
}
val newState = currentState.apply(insn, memory);
if (newState != null) {
if (newState == STATE_FINAL) {
while (iter.hasNext()) {
val insn2 = iter.next();
if (insn2 instanceof FrameNode) {
memory.recordFrame((FrameNode) insn2);
}
}
return memory;
}
currentState = newState;
@@ -414,30 +413,93 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
}
@Override
public void transform(final ClassNode cn, final boolean obfuscated) {
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "ChunkProviderSuperPatcher";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
if ("net.minecraft.world.chunk.storage.AnvilChunkLoader".equals(className))
return false;
val cn = classNode.getNode();
if (cn == null)
return false;
for (MethodNode method : cn.methods) {
if (scanForBrokenCall(method)) {
return true;
}
}
return false;
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
boolean patched = false;
for (val method : cn.methods) {
val memory = findTheTarget(method);
val memory = findTheTarget(method, cn);
if (memory != null) {
patched = true;
IETransformer.logger.debug("[ChunkProvider patcher]: Match found in method " + method.name);
EndlessIDsTransformer.logger.debug("[ChunkProvider patcher]: Match found in method " + method.name);
val insnList = method.instructions;
for (val node : method.localVariables) {
if (node.desc.equals("[B") && node.index == memory.biomeArrayIndex) {
node.desc = "[S";
}
}
val getBiomeArray = (MethodInsnNode) insnList.get(memory.getBiomeArrayInsn);
val getBiomeArray = memory.getBiomeArrayInsn;
insnList.set(getBiomeArray,
new MethodInsnNode(Opcodes.INVOKEVIRTUAL, getBiomeArray.owner, "getBiomeShortArray",
"()[S", false));
insnList.set(insnList.get(memory.castInsn), new InsnNode(Opcodes.I2S));
insnList.set(insnList.get(memory.arrayStoreInsn), new InsnNode(Opcodes.SASTORE));
insnList.set(memory.castInsn, new InsnNode(Opcodes.I2S));
insnList.set(memory.arrayStoreInsn, new InsnNode(Opcodes.SASTORE));
patchStackFrames(memory, method, cn);
}
}
if (!patched) {
IETransformer.logger.debug("[ChunkProvider patcher]: No matches found in class! This is not explicitly " +
"an error, because the patch might be applied by a mixin instead!");
EndlessIDsTransformer.logger.debug("[ChunkProvider patcher]: No matches found in class! This is not explicitly " +
"an error, because the patch might be applied by a mixin instead!");
}
return patched;
}
private static void patchStackFrames(Memory memory, MethodNode method, ClassNode owner) {
memory.locals.clear();
memory.initLocals(method, owner);
for (val frame: memory.framesLeaveUnpatched)
memory.updateLocalsState(frame);
int localToPatch = memory.biomeArrayIndex;
for (val frame: memory.framesToPatch) {
int currentLocalIndex = memory.locals.size() - 1;
if (frame.local != null) {
switch (frame.type) {
case Opcodes.F_NEW:
case Opcodes.F_FULL:
if (frame.local.size() > localToPatch &&
"[B".equals(frame.local.get(localToPatch))) {
frame.local.set(localToPatch, "[S");
}
break;
case Opcodes.F_APPEND:
if (currentLocalIndex < localToPatch &&
(currentLocalIndex + frame.local.size()) >= localToPatch) {
int indexInFrame = localToPatch - currentLocalIndex - 1;
if ("[B".equals(frame.local.get(indexInFrame))) {
frame.local.set(indexInFrame, "[S");
}
}
}
}
memory.updateLocalsState(frame);
}
}
@@ -449,13 +511,125 @@ public class ChunkProviderSuperPatcher implements IClassNodeTransformer {
private static class Memory {
final List<Object> locals = new ArrayList<>();
int currentInsn;
int startingInsn;
int getBiomeArrayInsn;
int castInsn;
int arrayStoreInsn;
List<Object> localsAtPatchStart;
List<FrameNode> framesLeaveUnpatched = new ArrayList<>();
List<FrameNode> framesToPatch = new ArrayList<>();
boolean patchingFrames;
VarInsnNode startingInsn;
MethodInsnNode getBiomeArrayInsn;
InsnNode castInsn;
InsnNode arrayStoreInsn;
int chunkIndex;
int biomeArrayIndex;
int iteratorIndex;
public void startPatchingFrames() {
localsAtPatchStart = new ArrayList<>(locals);
patchingFrames = true;
}
public void recordFrame(FrameNode frame) {
if (patchingFrames)
framesToPatch.add(frame);
else
framesLeaveUnpatched.add(frame);
}
public void reset() {
localsAtPatchStart = null;
framesLeaveUnpatched.addAll(framesToPatch);
framesToPatch.clear();
patchingFrames = false;
startingInsn = null;
getBiomeArrayInsn = null;
castInsn = null;
arrayStoreInsn = null;
chunkIndex = 0;
biomeArrayIndex = 0;
iteratorIndex = 0;
}
private static int getDescriptorNameEnd(String desc, int index) {
val c = desc.charAt(index);
switch (c) {
case '[':
return getDescriptorNameEnd(desc, index + 1);
case 'L':
do {
index++;
} while (desc.charAt(index) != ';');
default:
return index + 1;
}
}
private static List<Object> parseDesc(String desc) {
val result = new ArrayList<Object>();
desc = desc.substring(1, desc.indexOf(')'));
for (int i = 0; i < desc.length(); i++) {
val end = getDescriptorNameEnd(desc, i);
val name = desc.substring(i, end);
switch (name) {
case "I":
case "S":
case "B":
case "Z":
result.add(Opcodes.INTEGER);
break;
case "F":
result.add(Opcodes.FLOAT);
break;
case "D":
result.add(Opcodes.DOUBLE);
result.add(Opcodes.DOUBLE);
break;
case "J":
result.add(Opcodes.LONG);
result.add(Opcodes.LONG);
break;
default:
result.add(name);
}
i = end - 1;
}
return result;
}
public void initLocals(MethodNode method, ClassNode owner) {
locals.clear();
if ((method.access & Opcodes.ACC_STATIC) == 0) {
locals.add("L" + owner.name + ";");
}
locals.addAll(parseDesc(method.desc));
}
public void updateLocalsState(FrameNode frame) {
switch (frame.type) {
case Opcodes.F_NEW:
case Opcodes.F_FULL:
locals.clear();
case Opcodes.F_APPEND:
if (frame.local != null) {
for (val local : frame.local) {
if (Objects.equals(local, Opcodes.DOUBLE) || Objects.equals(local, Opcodes.LONG))
locals.add(local);
locals.add(local);
}
}
break;
case Opcodes.F_CHOP: {
int removeCount = frame.local.size();
for (int i = removeCount - 1; i >= 0 && !locals.isEmpty(); i--) {
val removed = locals.remove(locals.size() - 1);
if (Objects.equals(removed, Opcodes.DOUBLE) || Objects.equals(removed, Opcodes.LONG))
locals.remove(locals.size() - 1);
}
break;
}
case Opcodes.F_SAME:
case Opcodes.F_SAME1:
break;
}
}
}
}
@@ -0,0 +1,41 @@
package com.falsepattern.endlessids.asm.transformer.chunk;
import com.falsepattern.endlessids.Tags;
import com.falsepattern.lib.turboasm.ClassNodeHandle;
import com.falsepattern.lib.turboasm.TurboClassTransformer;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;
public class LOTRFieldExposer implements TurboClassTransformer {
@Override
public String owner() {
return Tags.MODNAME;
}
@Override
public String name() {
return "LOTRFieldExposer";
}
@Override
public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
return "lotr.common.network.LOTRPacketBiomeVariantsWatch".equals(className);
}
@Override
public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
val cn = classNode.getNode();
if (cn == null)
return false;
for (val field: cn.fields) {
if ("chunkX".equals(field.name) ||
"chunkZ".equals(field.name) ||
"variants".equals(field.name)) {
field.access &= ~Opcodes.ACC_PRIVATE;
field.access |= Opcodes.ACC_PUBLIC;
}
}
return true;
}
}