package openmods.core; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; import java.util.List; import java.util.Map; import java.util.Set; import net.minecraft.launchwrapper.IClassTransformer; import openmods.Log; import openmods.api.IResultListener; import openmods.asm.TransformerState; import openmods.asm.VisitorHelper; import openmods.asm.VisitorHelper.TransformProvider; import openmods.config.simple.ConfigProcessor; import openmods.config.simple.ConfigProcessor.UpdateListener; import openmods.entity.PlayerDamageEventInjector; import openmods.include.IncludingClassVisitor; import openmods.injector.InjectedClassesManager; import openmods.movement.MovementPatcher; import openmods.renderer.PlayerRendererHookVisitor; import openmods.stencil.CapabilitiesHookInjector; import openmods.stencil.FramebufferInjector; import openmods.utils.StateTracker; import openmods.utils.StateTracker.StateUpdater; import openmods.world.MapGenStructureVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; public class OpenModsClassTransformer implements IClassTransformer { private static OpenModsClassTransformer INSTANCE; private static final List<String> IGNORED_PREFIXES = ImmutableList.of( "cpw.mods.fml.", "net.minecraftforge.", "io.netty.", "gnu.trove.", "com.google.", "com.mojang.", "joptsimple.", "tv.twitch."); private final Map<String, TransformProvider> vanillaPatches = Maps.newHashMap(); private final StateTracker<TransformerState> states = StateTracker.create(TransformerState.DISABLED); private Set<String> includedClasses; private abstract class ConfigOption implements UpdateListener { private final StateUpdater<TransformerState> state; public ConfigOption(String name) { state = states.register(name); } @Override public void valueSet(String value) { if ("true".equalsIgnoreCase(value)) { state.update(TransformerState.ENABLED); onActivate(state); } } protected abstract void onActivate(StateUpdater<TransformerState> state); } private static IResultListener createResultListener(final StateUpdater<TransformerState> updater) { return new IResultListener() { @Override public void onSuccess() { updater.update(TransformerState.FINISHED); } @Override public void onFailure() { updater.update(TransformerState.FAILED); } }; } public OpenModsClassTransformer() { INSTANCE = this; } public static OpenModsClassTransformer instance() { return INSTANCE; } public void addConfigValues(ConfigProcessor config) { config.addEntry("activate_movement_callback", 0, "true", new ConfigOption("movement_callback") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.client.entity.EntityPlayerSP", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to apply movement callback (class: %s)", name); state.update(TransformerState.ACTIVATED); return new MovementPatcher(name, cv, createResultListener(state)); } }); } }, "Purpose: this transformer add hook to player movement controls", "Modified class: net.minecraft.client.entity.EntityPlayerSP", "Known users: OpenBlocks elevator", "When disabled: users usually have fallbacks (elevator will use less accurate algorithm)"); config.addEntry("activate_map_gen_fix", 0, "true", new ConfigOption("map_gen_fix") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.world.gen.structure.MapGenStructure", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to patch MapGenStructure (class: %s)", name); state.update(TransformerState.ACTIVATED); return new MapGenStructureVisitor(name, cv, createResultListener(state)); } }); } }, "Purpose: fix bug in vanilla code used to find nearby structures", "Modified class: net.minecraft.world.gen.structure.MapGenStructure", "Known users: OpenBlocks golden eye", "When disabled: features may not work (either silently fail or cause crash)"); config.addEntry("activate_player_render_hook", 0, "true", new ConfigOption("player_render_hook") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.client.renderer.entity.RenderPlayer", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to apply player render hook (class: %s)", name); state.update(TransformerState.ACTIVATED); return new PlayerRendererHookVisitor(name, cv, createResultListener(state)); } }); } }, "Purpose: add hook to player rendering code", "Modified class: net.minecraft.client.renderer.entity.RenderPlayer", "Known users: OpenBlocks hangglider", "When disabled: code may fallback to less compatible mechanism (like replacing renderer)"); config.addEntry("activate_stencil_patches", 0, "true", new ConfigOption("stencil_patches") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.client.shader.Framebuffer", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to patch Framebuffer (class: %s)", name); state.update(TransformerState.ACTIVATED); return new FramebufferInjector(name, cv, createResultListener(state)); } }); } }, "Purpose: to re-enable stencil buffer on FBO objects. This is was disabled due to problems on some configurations", "Modified class: net.minecraft.client.shader.Framebuffer", "Known users: OpenBlocks skyblocks", "When disabled: no stencil buffer available unless unlocked with Forge flag. Mods may not use some graphic features"); config.addEntry("activate_gl_capabilities_hook", 0, "true", new ConfigOption("gl_capabilities_hook") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.client.renderer.OpenGlHelper", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to patch OpenGlHelper (class: %s)", name); state.update(TransformerState.ACTIVATED); return new CapabilitiesHookInjector(name, cv, createResultListener(state)); } }); } }, "Purpose: hook to get check additional OpenGL capabilities (mostly stencil buffer related)", "Modified class: net.minecraft.client.renderer.OpenGlHelper", "Known users: OpenBlocks skyblocks", "When disabled: no stencil buffer available unless unlocked with Forge flag. Mods may not use some graphic features"); config.addEntry("activate_player_damage_hook", 0, "true", new ConfigOption("player_damage_hook") { @Override protected void onActivate(final StateUpdater<TransformerState> state) { vanillaPatches.put("net.minecraft.entity.player.EntityPlayer", new TransformProvider(ClassWriter.COMPUTE_FRAMES) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { Log.debug("Trying to patch EntityPlayer (class: %s)", name); state.update(TransformerState.ACTIVATED); return new PlayerDamageEventInjector(name, cv, createResultListener(state)); } }); } }, "Purpose: hook for capturing damage to player (after armor and potion calculations)", "Modified class: net.minecraft.entity.player.EntityPlayer", "Known users: Last Stand enchantment", "When disabled: Last Stand enchantment will not work"); } private final static TransformProvider INCLUDING_CV = new TransformProvider(0) { @Override public ClassVisitor createVisitor(String name, ClassVisitor cv) { return new IncludingClassVisitor(cv); } }; public void injectAsmData(ASMDataTable table) { ImmutableSet.Builder<String> includedClasses = ImmutableSet.builder(); for (ASMData data : table.getAll("openmods.include.IncludeInterface")) includedClasses.add(data.getClassName()); for (ASMData data : table.getAll("openmods.include.IncludeOverride")) includedClasses.add(data.getClassName()); this.includedClasses = includedClasses.build(); } private boolean shouldTryIncluding(String clsName) { if (includedClasses != null) return includedClasses.contains(clsName); for (String prefix : IGNORED_PREFIXES) if (clsName.startsWith(prefix)) return false; return true; } @Override public byte[] transform(String name, String transformedName, byte[] bytes) { if (bytes == null) { return InjectedClassesManager.instance.tryGetBytecode(name); } if (transformedName.startsWith("net.minecraft.")) { TransformProvider provider = vanillaPatches.get(transformedName); return (provider != null)? VisitorHelper.apply(bytes, name, provider) : bytes; } if (shouldTryIncluding(transformedName)) return applyIncludes(name, transformedName, bytes); return bytes; } protected byte[] applyIncludes(final String name, String transformedName, byte[] bytes) { try { return VisitorHelper.apply(bytes, name, INCLUDING_CV); } catch (Throwable t) { Log.severe(t, "Failed to apply including transformer on %s(%s)", name, transformedName); throw Throwables.propagate(t); } } public String listStates() { return Joiner.on(',').join(Iterables.transform(states.states(), Functions.toStringFunction())); } }